From c4da6fd07740c001da735d4fd09caaa2feb78b96 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Thu, 24 Oct 2024 10:02:12 +0200 Subject: [PATCH] ts for cookie, loginname to verification --- apps/login/locales/de.json | 1 + apps/login/locales/en.json | 1 + apps/login/locales/es.json | 1 + apps/login/locales/it.json | 1 + .../app/(login)/authenticator/set/page.tsx | 14 ++++--- apps/login/src/app/(login)/verify/page.tsx | 30 ++++++++++---- apps/login/src/components/verify-form.tsx | 18 ++++----- apps/login/src/lib/cookies.ts | 39 +++++++++++-------- apps/login/src/lib/server/cookie.ts | 30 +++++++------- apps/login/src/lib/server/email.ts | 4 +- apps/login/src/lib/server/loginname.ts | 24 ++++++++++++ packages/zitadel-client/src/index.ts | 2 +- 12 files changed, 107 insertions(+), 58 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index ad184962b2c..0a15315a6f9 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "Keine Benutzer-ID angegeben!", + "success": "Erfolgreich verifiziert", "verify": { "title": "Benutzer verifizieren", "description": "Geben Sie den Code ein, der in der Bestätigungs-E-Mail angegeben ist.", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 70844a8a1f8..f14a9835094 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "No userId provided!", + "success": "The user has been verified successfully.", "verify": { "title": "Verify user", "description": "Enter the Code provided in the verification email.", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index b5f67b40c3e..b1f50506fbf 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "¡No se proporcionó userId!", + "success": "¡Verificación exitosa!", "verify": { "title": "Verificar usuario", "description": "Introduce el código proporcionado en el correo electrónico de verificación.", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index b7436098093..31f80a1f637 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -155,6 +155,7 @@ }, "verify": { "userIdMissing": "Nessun userId fornito!", + "success": "Verifica effettuata con successo!", "verify": { "title": "Verifica utente", "description": "Inserisci il codice fornito nell'email di verifica.", diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index d7daaec05b5..aa66a19237f 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -1,6 +1,5 @@ import { Alert } from "@/components/alert"; import { BackButton } from "@/components/back-button"; -import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup"; import { DynamicTheme } from "@/components/dynamic-theme"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; @@ -41,13 +40,14 @@ export default async function Page({ const t = await getTranslations({ locale, namespace: "authenticator" }); const tError = await getTranslations({ locale, namespace: "error" }); - const { loginName, checkAfter, authRequestId, organization, sessionId } = - searchParams; + const { loginName, authRequestId, organization, sessionId } = searchParams; const sessionWithData = sessionId ? await loadSessionById(sessionId, organization) : await loadSessionByLoginname(loginName, organization); + console.log("sessionWithData", sessionWithData); + async function getAuthMethodsAndUser(session?: Session) { const userId = session?.factors?.user?.id; @@ -93,7 +93,9 @@ export default async function Page({ }); } - const branding = await getBrandingSettings(organization); + const branding = await getBrandingSettings( + sessionWithData.factors?.user?.organizationId, + ); const loginSettings = await getLoginSettings( sessionWithData.factors?.user?.organizationId, @@ -141,14 +143,14 @@ export default async function Page({ {!valid && {tError("sessionExpired")}} - {loginSettings && sessionWithData && ( + {/* {loginSettings && sessionWithData && ( - )} + )} */}
diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 1c6b629c4ca..368e9c1c5cd 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -1,5 +1,6 @@ -import { Alert } from "@/components/alert"; +import { Alert, AlertType } from "@/components/alert"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; @@ -47,6 +48,9 @@ export default async function Page({ searchParams }: { searchParams: any }) { return (
+

{t("verify.title")}

+

{t("verify.description")}

+ {!userId && ( <>

{t("verify.title")}

@@ -58,12 +62,24 @@ export default async function Page({ searchParams }: { searchParams: any }) { )} - + {user && ( + + )} + {human?.email?.isVerified ? ( + {t("success")} + ) : ( + // check if auth methods are set + + )}
); diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index b78c3674699..d974c1a949f 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -1,13 +1,10 @@ "use client"; import { Alert } from "@/components/alert"; -import { - resendVerification, - verifyUserAndCreateSession, -} from "@/lib/server/email"; +import { resendVerification, sendVerification } from "@/lib/server/email"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -41,6 +38,12 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { const router = useRouter(); + useEffect(() => { + if (code) { + submitCodeAndContinue({ code }); + } + }, []); + async function resendCode() { setLoading(true); @@ -60,7 +63,7 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { async function submitCodeAndContinue(value: Inputs): Promise { setLoading(true); - const verifyResponse = await verifyUserAndCreateSession({ + const verifyResponse = await sendVerification({ code: value.code, userId, isInvite: isInvite, @@ -83,9 +86,6 @@ export function VerifyForm({ userId, code, isInvite, params }: Props) { return ( <> -

{t("verify.title")}

-

{t("verify.description")}

-
( // TODO: improve cookie handling // this replaces the first session (oldest) with the new one currentSessions = [session].concat(currentSessions.slice(1)); + } else { + currentSessions = [session].concat(currentSessions); } } if (cleanup) { const now = new Date(); const filteredSessions = currentSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -99,7 +104,9 @@ export async function updateSessionCookie( if (cleanup) { const now = new Date(); const filteredSessions = sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -125,7 +132,9 @@ export async function removeSessionFromCookie( if (cleanup) { const now = new Date(); const filteredSessions = reducedSessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); return setSessionHttpOnlyCookie(filteredSessions); } else { @@ -141,10 +150,7 @@ export async function getMostRecentSessionCookie(): Promise { const sessions: SessionCookie[] = JSON.parse(stringifiedCookie?.value); const latest = sessions.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; + return prev.changeTs > current.changeTs ? prev : current; }); return latest; @@ -226,8 +232,8 @@ export async function getAllSessionCookieIds( const now = new Date(); return sessions .filter((session) => - session.expirationDate - ? new Date(session.expirationDate) > now + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true, ) .map((session) => session.id); @@ -256,7 +262,9 @@ export async function getAllSessions( if (cleanup) { const now = new Date(); return sessions.filter((session) => - session.expirationDate ? new Date(session.expirationDate) > now : true, + session.expirationTs + ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now + : true, ); } else { return sessions; @@ -297,10 +305,7 @@ export async function getMostRecentCookieWithLoginname({ const latest = filtered && filtered.length ? filtered.reduce((prev, current) => { - return new Date(prev.changeDate).getTime() > - new Date(current.changeDate).getTime() - ? prev - : current; + return prev.changeTs > current.changeTs ? prev : current; }) : undefined; diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 2b380bf3253..17be3844aa6 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -20,9 +20,9 @@ type CustomCookieData = { token: string; loginName: string; organization?: string; - creationDate: string; - expirationDate: string; - changeDate: string; + creationTs: string; + expirationTs: string; + changeTs: string; authRequestId?: string; // if its linked to an OIDC flow }; @@ -42,13 +42,13 @@ export async function createSessionAndUpdateCookie( const sessionCookie: CustomCookieData = { id: createdSession.sessionId, token: createdSession.sessionToken, - creationDate: response.session.creationDate + creationTs: response.session.creationDate ? `${timestampMs(response.session.creationDate)}` : "", - expirationDate: response.session.expirationDate + expirationTs: response.session.expirationDate ? `${timestampMs(response.session.expirationDate)}` : "", - changeDate: response.session.changeDate + changeTs: response.session.changeDate ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", @@ -97,13 +97,13 @@ export async function createSessionForIdpAndUpdateCookie( const sessionCookie: CustomCookieData = { id: createdSession.sessionId, token: createdSession.sessionToken, - creationDate: response.session.creationDate + creationTs: response.session.creationDate ? `${timestampMs(response.session.creationDate)}` : "", - expirationDate: response.session.expirationDate + expirationTs: response.session.expirationDate ? `${timestampMs(response.session.expirationDate)}` : "", - changeDate: response.session.changeDate + changeTs: response.session.changeDate ? `${timestampMs(response.session.changeDate)}` : "", loginName: response.session.factors.user.loginName ?? "", @@ -151,10 +151,10 @@ export async function setSessionAndUpdateCookie( const sessionCookie: CustomCookieData = { id: recentCookie.id, token: updatedSession.sessionToken, - creationDate: recentCookie.creationDate, - expirationDate: recentCookie.expirationDate, + creationTs: recentCookie.creationTs, + expirationTs: recentCookie.expirationTs, // just overwrite the changeDate with the new one - changeDate: updatedSession.details?.changeDate + changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: recentCookie.loginName, @@ -174,10 +174,10 @@ export async function setSessionAndUpdateCookie( const newCookie: CustomCookieData = { id: sessionCookie.id, token: updatedSession.sessionToken, - creationDate: sessionCookie.creationDate, - expirationDate: sessionCookie.expirationDate, + creationTs: sessionCookie.creationTs, + expirationTs: sessionCookie.expirationTs, // just overwrite the changeDate with the new one - changeDate: updatedSession.details?.changeDate + changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "", loginName: session.factors?.user?.loginName ?? "", diff --git a/apps/login/src/lib/server/email.ts b/apps/login/src/lib/server/email.ts index fce219715fe..ff08d3fa0a8 100644 --- a/apps/login/src/lib/server/email.ts +++ b/apps/login/src/lib/server/email.ts @@ -20,9 +20,7 @@ type VerifyUserByEmailCommand = { authRequestId?: string; }; -export async function verifyUserAndCreateSession( - command: VerifyUserByEmailCommand, -) { +export async function sendVerification(command: VerifyUserByEmailCommand) { const verifyResponse = command.isInvite ? await verifyInviteCode(command.userId, command.code).catch((error) => { return { error: "Could not verify invite" }; diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 2a115b8cd4c..fc5fd77c5c7 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -7,6 +7,7 @@ import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_se import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp"; + import { getActiveIdentityProviders, getIDPByID, @@ -161,6 +162,29 @@ export async function sendLoginname(command: SendLoginnameCommand) { ); if (!methods.authMethodTypes || !methods.authMethodTypes.length) { + if ( + users.result[0].type.case === "human" && + users.result[0].type.value.email && + !users.result[0].type.value.email.isVerified + ) { + const paramsVerify = new URLSearchParams({ + loginName: session.factors?.user?.loginName, + userId: session.factors?.user?.id, // verify needs user id + }); + + if (command.organization || session.factors?.user?.organizationId) { + paramsVerify.append( + "organization", + command.organization ?? session.factors?.user?.organizationId, + ); + } + + if (command.authRequestId) { + paramsVerify.append("authRequestId", command.authRequestId); + } + + redirect("/verify?" + paramsVerify); + } return { error: "User has no available authentication methods. Contact your administrator to setup authentication for the requested user.", diff --git a/packages/zitadel-client/src/index.ts b/packages/zitadel-client/src/index.ts index d78559257d2..dd677643fca 100644 --- a/packages/zitadel-client/src/index.ts +++ b/packages/zitadel-client/src/index.ts @@ -3,5 +3,5 @@ export { NewAuthorizationBearerInterceptor } from "./interceptors"; // TODO: Move this to `./protobuf.ts` and export it from there export { create, fromJson, toJson } from "@bufbuild/protobuf"; -export { TimestampSchema, timestampDate, timestampFromDate, timestampMs } from "@bufbuild/protobuf/wkt"; +export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt"; export type { Timestamp } from "@bufbuild/protobuf/wkt";