diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 3e1b49eed0..63704b87eb 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -7,6 +7,7 @@ import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; +import { checkUserVerification } from "@/lib/verify-helper"; import { getActiveIdentityProviders, getBrandingSettings, @@ -18,6 +19,7 @@ import { import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +import { redirect } from "next/navigation"; export default async function Page(props: { searchParams: Promise>; @@ -92,20 +94,49 @@ export default async function Page(props: { }); } - if (!sessionWithData) { + if ( + !sessionWithData || + !sessionWithData.factors || + !sessionWithData.factors.user + ) { return {tError("unknownContext")}; } const branding = await getBrandingSettings({ serviceUrl, - organization: sessionWithData.factors?.user?.organizationId, + organization: sessionWithData.factors.user?.organizationId, }); const loginSettings = await getLoginSettings({ serviceUrl, - organization: sessionWithData.factors?.user?.organizationId, + organization: sessionWithData.factors.user?.organizationId, }); + // check if user was verified recently + const isUserVerified = await checkUserVerification( + sessionWithData.factors.user?.id, + ); + + if (!isUserVerified) { + const params = new URLSearchParams({ + loginName: sessionWithData.factors.user.loginName as string, + send: "true", // set this to true to request a new code immediately + }); + + if (requestId) { + params.append("requestId", requestId); + } + + if (organization || sessionWithData.factors.user.organizationId) { + params.append( + "organization", + organization ?? (sessionWithData.factors.user.organizationId as string), + ); + } + + redirect(`/verify?` + params); + } + const identityProviders = await getActiveIdentityProviders({ serviceUrl, orgId: sessionWithData.factors?.user?.organizationId, diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index fcbc4fd34a..121e250dc3 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -2,18 +2,11 @@ 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 { VerifyRedirectButton } from "@/components/verify-redirect-button"; import { sendEmailCode } from "@/lib/server/verify"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; -import { checkUserVerification } from "@/lib/verify-helper"; -import { - getBrandingSettings, - getUserByID, - listAuthenticationMethodTypes, -} from "@/lib/zitadel"; +import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -101,19 +94,6 @@ export default async function Page(props: { searchParams: Promise }) { throw Error("Failed to get user id"); } - let authMethods: AuthenticationMethodType[] | null = null; - if (human?.email?.isVerified) { - const authMethodsResponse = await listAuthenticationMethodTypes({ - serviceUrl, - userId, - }); - if (authMethodsResponse.authMethodTypes) { - authMethods = authMethodsResponse.authMethodTypes; - } - } - - const hasValidUserVerificationCheck = await checkUserVerification(id); - const params = new URLSearchParams({ userId: userId, initial: "true", // defines that a code is not required and is therefore not shown in the UI @@ -171,27 +151,15 @@ export default async function Page(props: { searchParams: Promise }) { ) )} - {/* show a button to setup auth method for the user otherwise show the UI for reverifying */} - {human?.email?.isVerified && hasValidUserVerificationCheck ? ( - // show page for already verified users - - ) : ( - // check if auth methods are set - - )} + {/* always show the code form / TODO improve UI for email links which were already used (currently we get an error code 3 due being reused) */} + ); diff --git a/apps/login/src/components/verify-redirect-button.tsx b/apps/login/src/components/verify-redirect-button.tsx index 009dda3ffd..c968da86df 100644 --- a/apps/login/src/components/verify-redirect-button.tsx +++ b/apps/login/src/components/verify-redirect-button.tsx @@ -6,6 +6,7 @@ import { } from "@/lib/server/verify"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useTranslations } from "next-intl"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { Alert, AlertType } from "./alert"; import { BackButton } from "./back-button"; @@ -29,6 +30,7 @@ export function VerifyRedirectButton({ const [error, setError] = useState(""); const [loading, setLoading] = useState(false); + const router = useRouter(); async function submitAndContinue(): Promise { setLoading(true); @@ -50,7 +52,7 @@ export function VerifyRedirectButton({ } as SendVerificationRedirectWithoutCheckCommand; } - await sendVerificationRedirectWithoutCheck(command) + const response = await sendVerificationRedirectWithoutCheck(command) .catch(() => { setError("Could not verify"); return; @@ -58,6 +60,16 @@ export function VerifyRedirectButton({ .finally(() => { setLoading(false); }); + + if (response && "error" in response && response.error) { + setError(response.error); + return; + } + + if (response && "redirect" in response && response.redirect) { + router.push(response.redirect); + return true; + } } return ( diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 0ab4c5465d..88bb0139b3 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -1,11 +1,11 @@ "use server"; import { - createInviteCode, getLoginSettings, getSession, getUserByID, listAuthenticationMethodTypes, + resendInviteCode, verifyEmail, verifyInviteCode, verifyTOTPRegistration, @@ -71,14 +71,16 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { serviceUrl, userId: command.userId, verificationCode: command.code, - }).catch(() => { + }).catch((error) => { + console.warn(error); return { error: "Could not verify invite" }; }) : await verifyEmail({ serviceUrl, userId: command.userId, verificationCode: command.code, - }).catch(() => { + }).catch((error) => { + console.warn(error); return { error: "Could not verify email" }; }); @@ -273,15 +275,11 @@ export async function resendVerification(command: resendVerifyEmailCommand) { const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; - // create a new invite whenever the resend is called return command.isInvite - ? createInviteCode({ + ? resendInviteCode({ serviceUrl, userId: command.userId, - urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + - (command.requestId ? `&requestId=${command.requestId}` : ""), - }) //resendInviteCode({ serviceUrl, userId: command.userId }) + }) : sendEmailCode({ userId: command.userId, serviceUrl,