From 07e5d548f06c020167cf605707d73aeef223d3a6 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 5 Jun 2025 09:20:05 +0200 Subject: [PATCH 01/13] register with idp intent --- .../(login)/idp/[provider]/success/page.tsx | 12 ++++++++++ apps/login/src/app/(login)/register/page.tsx | 22 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 1cee8b587c..cf5f0c8637 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -24,6 +24,7 @@ import { } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +import { redirect } from "next/navigation"; const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; @@ -205,6 +206,7 @@ export default async function Page(props: { } } + // if addHumanUser is provided in the intent, expect that it can be created otherwise show an error if (addHumanUser) { let addHumanUserWithOrganization: AddHumanUserRequest; if (orgToRegisterOn) { @@ -241,6 +243,16 @@ export default async function Page(props: { : "Could not create user", ); } + } else { + // if no user was found, we will create a new user manually / redirect to the registration page + if (options.isCreationAllowed) { + const registerParams = new URLSearchParams({ + idpIntentId: id, + idpIntentToken: token, + organization: organization ?? "", + }); + return redirect(`/register?${registerParams})}`); + } } if (newUser) { diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index e50511edb1..d042b52c60 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -7,6 +7,7 @@ import { getLegalAndSupportSettings, getLoginSettings, getPasswordComplexitySettings, + retrieveIDPIntent, } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { getLocale, getTranslations } from "next-intl/server"; @@ -19,7 +20,15 @@ export default async function Page(props: { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "register" }); - let { firstname, lastname, email, organization, requestId } = searchParams; + let { + firstname, + lastname, + email, + organization, + requestId, + idpIntentId, + idpIntentToken, + } = searchParams; const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -33,6 +42,17 @@ export default async function Page(props: { } } + let idpIntent; + if (idpIntentId && idpIntentToken) { + idpIntent = await retrieveIDPIntent({ + serviceUrl, + id: idpIntentId, + token: idpIntentToken, + }); + + const { idpInformation, userId } = idpIntent; + } + const legal = await getLegalAndSupportSettings({ serviceUrl, organization, From 738f1f04487881f2a75cf746c994f03aeb84ad7c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 6 Jun 2025 13:46:25 +0200 Subject: [PATCH 02/13] conditionally hide options --- apps/login/locales/de.json | 2 ++ apps/login/locales/en.json | 2 ++ apps/login/locales/es.json | 2 ++ apps/login/locales/it.json | 2 ++ apps/login/locales/pl.json | 2 ++ apps/login/locales/ru.json | 2 ++ apps/login/locales/zh.json | 2 ++ apps/login/src/app/(login)/register/page.tsx | 28 ++++++++++++++++++++ apps/login/src/components/register-form.tsx | 11 +++++++- 9 files changed, 52 insertions(+), 1 deletion(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 9da622340b..bddc703771 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -149,11 +149,13 @@ }, "title": "Registrieren", "description": "Erstellen Sie Ihr ZITADEL-Konto.", + "noMethodAvailableWarning": "Keine Authentifizierungsmethode verfügbar. Bitte wenden Sie sich an den Administrator.", "selectMethod": "Wählen Sie die Methode, mit der Sie sich authentifizieren möchten", "agreeTo": "Um sich zu registrieren, müssen Sie den Nutzungsbedingungen zustimmen", "termsOfService": "Nutzungsbedingungen", "privacyPolicy": "Datenschutzrichtlinie", "submit": "Weiter", + "orUseIDP": "oder verwenden Sie einen Identitätsanbieter", "password": { "title": "Passwort festlegen", "description": "Legen Sie das Passwort für Ihr Konto fest", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 37a1b62289..7bf71fd259 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -149,11 +149,13 @@ }, "title": "Register", "description": "Create your ZITADEL account.", + "noMethodAvailableWarning": "No authentication method available. Please contact your administrator.", "selectMethod": "Select the method you would like to authenticate", "agreeTo": "To register you must agree to the terms and conditions", "termsOfService": "Terms of Service", "privacyPolicy": "Privacy Policy", "submit": "Continue", + "orUseIDP": "or use an Identity Provider", "password": { "title": "Set Password", "description": "Set the password for your account", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 8969618c67..225a5c84db 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -149,11 +149,13 @@ }, "title": "Registrarse", "description": "Crea tu cuenta ZITADEL.", + "noMethodAvailableWarning": "No hay métodos de autenticación disponibles. Por favor, contacta a tu administrador.", "selectMethod": "Selecciona el método con el que deseas autenticarte", "agreeTo": "Para registrarte debes aceptar los términos y condiciones", "termsOfService": "Términos de Servicio", "privacyPolicy": "Política de Privacidad", "submit": "Continuar", + "orUseIDP": "o usa un Proveedor de Identidad", "password": { "title": "Establecer Contraseña", "description": "Establece la contraseña para tu cuenta", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 83fc5f3bfc..effe09047a 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -149,11 +149,13 @@ }, "title": "Registrati", "description": "Crea il tuo account ZITADEL.", + "noMethodAvailableWarning": "Nessun metodo di autenticazione disponibile. Contatta l'amministratore di sistema per assistenza.", "selectMethod": "Seleziona il metodo con cui desideri autenticarti", "agreeTo": "Per registrarti devi accettare i termini e le condizioni", "termsOfService": "Termini di Servizio", "privacyPolicy": "Informativa sulla Privacy", "submit": "Continua", + "orUseIDP": "o usa un Identity Provider", "password": { "title": "Imposta Password", "description": "Imposta la password per il tuo account", diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json index ad9f5d9a65..95f3cb2d2b 100644 --- a/apps/login/locales/pl.json +++ b/apps/login/locales/pl.json @@ -149,11 +149,13 @@ }, "title": "Rejestracja", "description": "Utwórz konto ZITADEL.", + "noMethodAvailableWarning": "Brak dostępnych metod uwierzytelniania. Skontaktuj się z administratorem.", "selectMethod": "Wybierz metodę uwierzytelniania, której chcesz użyć", "agreeTo": "Aby się zarejestrować, musisz zaakceptować warunki korzystania", "termsOfService": "Regulamin", "privacyPolicy": "Polityka prywatności", "submit": "Kontynuuj", + "orUseIDP": "lub użyj dostawcy tożsamości", "password": { "title": "Ustaw hasło", "description": "Ustaw hasło dla swojego konta", diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json index 73b0810e93..9c34a1e6b1 100644 --- a/apps/login/locales/ru.json +++ b/apps/login/locales/ru.json @@ -149,11 +149,13 @@ }, "title": "Регистрация", "description": "Создайте свой аккаунт ZITADEL.", + "noMethodAvailableWarning": "Нет доступных методов аутентификации. Обратитесь к администратору.", "selectMethod": "Выберите метод аутентификации", "agreeTo": "Для регистрации необходимо принять условия:", "termsOfService": "Условия использования", "privacyPolicy": "Политика конфиденциальности", "submit": "Продолжить", + "orUseIDP": "или используйте Identity Provider", "password": { "title": "Установить пароль", "description": "Установите пароль для вашего аккаунта", diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index bba15c62dd..fae81906d2 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -149,11 +149,13 @@ }, "title": "注册", "description": "创建您的 ZITADEL 账户。", + "noMethodAvailableWarning": "没有可用的认证方法。请联系您的系统管理员。", "selectMethod": "选择您想使用的认证方法", "agreeTo": "注册即表示您同意条款和条件", "termsOfService": "服务条款", "privacyPolicy": "隐私政策", "submit": "继续", + "orUseIDP": "或使用身份提供者", "password": { "title": "设置密码", "description": "为您的账户设置密码", diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index d042b52c60..550a214102 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -1,7 +1,9 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { RegisterForm } from "@/components/register-form"; +import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { + getActiveIdentityProviders, getBrandingSettings, getDefaultOrg, getLegalAndSupportSettings, @@ -72,6 +74,15 @@ export default async function Page(props: { organization, }); + const identityProviders = await getActiveIdentityProviders({ + serviceUrl, + orgId: organization, + }).then((resp) => { + return resp.identityProviders.filter((idp) => { + return idp.options?.isAutoCreation || idp.options?.isCreationAllowed; // check if IDP allows to create account automatically or manual creation is allowed + }); + }); + if (!loginSettings?.allowRegister) { return ( @@ -91,6 +102,9 @@ export default async function Page(props: { {legal && passwordComplexitySettings && ( )} + + {loginSettings?.allowExternalIdp && !!identityProviders.length && ( + <> +
+

{t("orUseIDP")}

+
+ + + + )}
); diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index 09e3f0b89b..4ce4860b16 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -10,7 +10,7 @@ import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; -import { Alert } from "./alert"; +import { Alert, AlertType } from "./alert"; import { AuthenticationMethod, AuthenticationMethodRadio, @@ -38,6 +38,7 @@ type Props = { organization?: string; requestId?: string; loginSettings?: LoginSettings; + idpCount: number; }; export function RegisterForm({ @@ -48,6 +49,7 @@ export function RegisterForm({ organization, requestId, loginSettings, + idpCount = 0, }: Props) { const t = useTranslations("register"); @@ -178,11 +180,18 @@ export function RegisterForm({ )} + {(!loginSettings?.allowUsernamePassword || + loginSettings?.passkeysType != PasskeysType.ALLOWED) && + !idpCount && ( + {t("noMethodAvailableWarning")} + )} + {error && (
{error}
)} +
+
+ + ); +} diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 88e0b48290..841fc06b3a 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -109,15 +109,20 @@ export async function createSessionAndUpdateCookie(command: { } } -export async function createSessionForIdpAndUpdateCookie( - userId: string, +export async function createSessionForIdpAndUpdateCookie({ + userId, + idpIntent, + requestId, + lifetime, +}: { + userId: string; idpIntent: { idpIntentId?: string | undefined; idpIntentToken?: string | undefined; - }, - requestId: string | undefined, - lifetime?: Duration, -): Promise { + }; + requestId: string | undefined; + lifetime?: Duration; +}): Promise { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); diff --git a/apps/login/src/lib/server/idp.ts b/apps/login/src/lib/server/idp.ts index 5cac537690..aaf5b77779 100644 --- a/apps/login/src/lib/server/idp.ts +++ b/apps/login/src/lib/server/idp.ts @@ -122,12 +122,12 @@ export async function createNewSessionFromIdpIntent( organization: userResponse.user.details?.resourceOwner, }); - const session = await createSessionForIdpAndUpdateCookie( - command.userId, - command.idpIntent, - command.requestId, - loginSettings?.externalLoginCheckLifetime, - ); + const session = await createSessionForIdpAndUpdateCookie({ + userId: command.userId, + idpIntent: command.idpIntent, + requestId: command.requestId, + lifetime: loginSettings?.externalLoginCheckLifetime, + }); if (!session || !session.factors?.user) { return { error: "Could not create session" }; diff --git a/apps/login/src/lib/server/register.ts b/apps/login/src/lib/server/register.ts index 25bea33527..b08099817d 100644 --- a/apps/login/src/lib/server/register.ts +++ b/apps/login/src/lib/server/register.ts @@ -1,6 +1,9 @@ "use server"; -import { createSessionAndUpdateCookie } from "@/lib/server/cookie"; +import { + createSessionAndUpdateCookie, + createSessionForIdpAndUpdateCookie, +} from "@/lib/server/cookie"; import { addHumanUser, getLoginSettings, getUserByID } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; @@ -133,3 +136,79 @@ export async function registerUser(command: RegisterUserCommand) { return { redirect: url }; } } + +type RegisterUserAndLinkToIDPommand = { + email: string; + firstName: string; + lastName: string; + organization?: string; + requestId?: string; + idpIntent: { + idpIntentId: string; + idpIntentToken: string; + }; + userId: string; +}; + +export type registerUserAndLinkToIDPResponse = { + userId: string; + sessionId: string; + factors: Factors | undefined; +}; +export async function registerUserAndLinkToIDP( + command: RegisterUserAndLinkToIDPommand, +) { + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + const host = _headers.get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found"); + } + + const addResponse = await addHumanUser({ + serviceUrl, + email: command.email, + firstName: command.firstName, + lastName: command.lastName, + organization: command.organization, + }); + + if (!addResponse) { + return { error: "Could not create user" }; + } + + const loginSettings = await getLoginSettings({ + serviceUrl, + organization: command.organization, + }); + + // TODO: addIDPLink to addResponse + + const session = await createSessionForIdpAndUpdateCookie({ + requestId: command.requestId, + userId: command.userId, + idpIntent: command.idpIntent, + lifetime: loginSettings?.externalLoginCheckLifetime, + }); + + if (!session || !session.factors?.user) { + return { error: "Could not create session" }; + } + + const url = await getNextUrl( + command.requestId && session.id + ? { + sessionId: session.id, + requestId: command.requestId, + organization: session.factors.user.organizationId, + } + : { + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }, + loginSettings?.defaultRedirectUri, + ); + + return { redirect: url }; +} diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index a0e91a021c..c53d622d7d 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -387,7 +387,7 @@ export type AddHumanUserData = { firstName: string; lastName: string; email: string; - password: string | undefined; + password?: string; organization: string | undefined; }; From dbf458c685b131903bba65ab49a8dc95ebc00e6a Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 10 Jun 2025 14:02:35 +0200 Subject: [PATCH 04/13] idpIntent properties --- .../app/(login)/idp/[provider]/success/page.tsx | 1 + .../src/components/idps/pages/complete-idp.tsx | 6 ++++++ .../components/register-form-idp-incomplete.tsx | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 3e4a7b253a..b56a425138 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -248,6 +248,7 @@ export default async function Page(props: { if (options?.isCreationAllowed) { return completeIDP({ branding, + idpIntent: { idpIntentId: id, idpIntentToken: token }, idpInformation, organization, requestId, diff --git a/apps/login/src/components/idps/pages/complete-idp.tsx b/apps/login/src/components/idps/pages/complete-idp.tsx index 4443a9317b..a2a89265c8 100644 --- a/apps/login/src/components/idps/pages/complete-idp.tsx +++ b/apps/login/src/components/idps/pages/complete-idp.tsx @@ -10,12 +10,17 @@ export async function completeIDP({ requestId, organization, branding, + idpIntent, }: { userId: string; idpInformation: IDPInformation; requestId?: string; organization?: string; branding?: BrandingSettings; + idpIntent: { + idpIntentId: string; + idpIntentToken: string; + }; }) { const locale = getLocale(); const t = await getTranslations({ locale, namespace: "idp" }); @@ -31,6 +36,7 @@ export async function completeIDP({ idpInformation={idpInformation} requestId={requestId} organization={organization} + idpIntent={idpIntent} /> diff --git a/apps/login/src/components/register-form-idp-incomplete.tsx b/apps/login/src/components/register-form-idp-incomplete.tsx index 35324be2e2..5fd7e43786 100644 --- a/apps/login/src/components/register-form-idp-incomplete.tsx +++ b/apps/login/src/components/register-form-idp-incomplete.tsx @@ -1,5 +1,6 @@ "use client"; +import { registerUserAndLinkToIDP } from "@/lib/server/register"; import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -23,13 +24,20 @@ type Inputs = type Props = { organization?: string; requestId?: string; - idpInformation?: IDPInformation; + idpIntent: { + idpIntentId: string; + idpIntentToken: string; + }; + idpInformation: IDPInformation; + userId: string; }; export function RegisterFormIDPIncomplete({ organization, requestId, + idpIntent, idpInformation, + userId, }: Props) { const t = useTranslations("register"); @@ -51,11 +59,13 @@ export function RegisterFormIDPIncomplete({ async function submitAndRegister(values: Inputs) { setLoading(true); const response = await registerUserAndLinkToIDP({ + userId: userId, email: values.email, firstName: values.firstname, lastName: values.lastname, organization: organization, requestId: requestId, + idpIntent: idpIntent, }) .catch(() => { setError("Could not register user"); @@ -130,7 +140,7 @@ export function RegisterFormIDPIncomplete({ - - - ); -} From 41170310dc17bd1402c0c3f6bc7e06eaef2eca13 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 11 Jun 2025 14:04:40 +0200 Subject: [PATCH 10/13] cleanup invite server code --- apps/login/src/lib/server/invite.ts | 58 ----------------------------- 1 file changed, 58 deletions(-) delete mode 100644 apps/login/src/lib/server/invite.ts diff --git a/apps/login/src/lib/server/invite.ts b/apps/login/src/lib/server/invite.ts deleted file mode 100644 index 40225d9916..0000000000 --- a/apps/login/src/lib/server/invite.ts +++ /dev/null @@ -1,58 +0,0 @@ -"use server"; - -import { addHumanUser, createInviteCode } from "@/lib/zitadel"; -import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { headers } from "next/headers"; -import { getServiceUrlFromHeaders } from "../service-url"; - -type InviteUserCommand = { - email: string; - firstName: string; - lastName: string; - password?: string; - organization: string; - requestId?: string; -}; - -export type RegisterUserResponse = { - userId: string; - sessionId: string; - factors: Factors | undefined; -}; - -export async function inviteUser(command: InviteUserCommand) { - const _headers = await headers(); - const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get domain" }; - } - - const human = await addHumanUser({ - serviceUrl, - email: command.email, - firstName: command.firstName, - lastName: command.lastName, - password: command.password ? command.password : undefined, - organization: command.organization, - }); - - if (!human) { - return { error: "Could not create user" }; - } - - const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; - - const codeResponse = await createInviteCode({ - serviceUrl, - urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`, - userId: human.userId, - }); - - if (!codeResponse || !human) { - return { error: "Could not create invite code" }; - } - - return human.userId; -} From 3e777662e6d1955ca5e80a8121fab41247f43d6d Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 11 Jun 2025 14:09:56 +0200 Subject: [PATCH 11/13] require organization --- apps/login/src/app/(login)/register/password/page.tsx | 4 ++-- apps/login/src/components/set-register-password-form.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/login/src/app/(login)/register/password/page.tsx b/apps/login/src/app/(login)/register/password/page.tsx index ee6fa03e59..d6a24fa47f 100644 --- a/apps/login/src/app/(login)/register/password/page.tsx +++ b/apps/login/src/app/(login)/register/password/page.tsx @@ -33,7 +33,7 @@ export default async function Page(props: { } } - const missingData = !firstname || !lastname || !email; + const missingData = !firstname || !lastname || !email || !organization; const legal = await getLegalAndSupportSettings({ serviceUrl, @@ -73,7 +73,7 @@ export default async function Page(props: { email={email} firstname={firstname} lastname={lastname} - organization={organization} + organization={organization as string} // organization is guaranteed to be a string here otherwise we would have returned earlier requestId={requestId} > )} diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index 3f38a408d0..3e48c649c1 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -31,7 +31,7 @@ type Props = { email: string; firstname: string; lastname: string; - organization?: string; + organization: string; requestId?: string; }; From 756bf7ec2623b8b41a7a4c88e268f59068ba0c39 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 18 Jun 2025 10:22:49 +0200 Subject: [PATCH 12/13] fix: update human on login --- .../(login)/idp/[provider]/success/page.tsx | 43 +++++++++++++------ apps/login/src/lib/zitadel.ts | 16 +++++++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index a31aed4bb3..0822c6576b 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -16,6 +16,7 @@ import { getOrgsByDomain, listUsers, retrieveIDPIntent, + updateHuman, } from "@/lib/zitadel"; import { ConnectError, create } from "@zitadel/client"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; @@ -24,6 +25,7 @@ import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { AddHumanUserRequest, AddHumanUserRequestSchema, + UpdateHumanUserRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -106,18 +108,6 @@ export default async function Page(props: { const { idpInformation, userId } = intent; let { addHumanUser } = intent; - // sign in user. If user should be linked continue - if (userId && !link) { - // TODO: update user if idp.options.isAutoUpdate is true - - return loginSuccess( - userId, - { idpIntentId: id, idpIntentToken: token }, - requestId, - branding, - ); - } - if (!idpInformation) { return loginFailed(branding, "IDP information missing"); } @@ -126,12 +116,41 @@ export default async function Page(props: { serviceUrl, id: idpInformation.idpId, }); + const options = idp?.config?.options; if (!idp) { throw new Error("IDP not found"); } + // sign in user. If user should be linked continue + if (userId && !link) { + // if auto update is enabled, we will update the user with the new information + if (options?.isAutoUpdate && addHumanUser) { + try { + await updateHuman({ + serviceUrl, + request: create(UpdateHumanUserRequestSchema, { + userId: userId, + profile: addHumanUser.profile, + email: addHumanUser.email, + phone: addHumanUser.phone, + }), + }); + } catch (error: unknown) { + // Log the error and continue with the login process + console.warn("An error occurred while updating the user:", error); + } + } + + return loginSuccess( + userId, + { idpIntentId: id, idpIntentToken: token }, + requestId, + branding, + ); + } + if (link) { if (!options?.isLinkingAllowed) { // linking was probably disallowed since the invitation was created diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index d5045df041..8cc4efe0bc 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -41,6 +41,7 @@ import { SendEmailCodeRequestSchema, SetPasswordRequest, SetPasswordRequestSchema, + UpdateHumanUserRequest, UserService, VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, @@ -455,6 +456,21 @@ export async function addHuman({ return userService.addHumanUser(request); } +export async function updateHuman({ + serviceUrl, + request, +}: { + serviceUrl: string; + request: UpdateHumanUserRequest; +}) { + const userService: Client = await createServiceForHost( + UserService, + serviceUrl, + ); + + return userService.updateHumanUser(request); +} + export async function verifyTOTPRegistration({ serviceUrl, code, From 85cc98f68aca4d7313f9e647f58e6aa2e1d7e228 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 18 Jun 2025 14:23:48 +0200 Subject: [PATCH 13/13] fix: scoped branding for complete page --- .../src/app/(login)/idp/[provider]/success/page.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 0822c6576b..bfbde8b252 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -81,7 +81,7 @@ export default async function Page(props: { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const branding = await getBrandingSettings({ + let branding = await getBrandingSettings({ serviceUrl, organization, }); @@ -294,6 +294,13 @@ export default async function Page(props: { serviceUrl, }); + if (orgToRegisterOn) { + branding = await getBrandingSettings({ + serviceUrl, + organization: orgToRegisterOn, + }); + } + if (!orgToRegisterOn) { return loginFailed(branding, "No organization found for registration"); }