From 2e5b3b710ec384cefb169394edce549f06b4ff6c Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 6 Dec 2024 10:53:44 +0100 Subject: [PATCH 01/21] fix: refactor user discovery search --- apps/login/src/lib/server/loginname.ts | 65 +++++++++++--------- apps/login/src/lib/zitadel.ts | 84 +++++++++++++++++++------- 2 files changed, 100 insertions(+), 49 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 295f9b455f..4c05931f8a 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -16,6 +16,7 @@ import { listAuthenticationMethodTypes, listIDPLinks, listUsers, + ListUsersCommand, startIdentityProviderFlow, } from "../zitadel"; import { createSessionAndUpdateCookie } from "./cookie"; @@ -29,21 +30,22 @@ export type SendLoginnameCommand = { const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { - const users = await listUsers({ - loginName: command.loginName, + const loginSettingsByContext = await getLoginSettings(command.organization); + + let listUsersRequest: ListUsersCommand = { + userName: command.loginName, organizationId: command.organization, - }); + }; - const loginSettings = await getLoginSettings(command.organization); + if (!loginSettingsByContext?.disableLoginWithEmail) { + listUsersRequest.email = command.loginName; + } - const potentialUsers = users.result.filter((u) => { - const human = u.type.case === "human" ? u.type.value : undefined; - return loginSettings?.disableLoginWithEmail - ? human?.email?.isVerified && human?.email?.email !== command.loginName - : loginSettings?.disableLoginWithPhone - ? human?.phone?.isVerified && human?.phone?.phone !== command.loginName - : true; - }); + if (!loginSettingsByContext?.disableLoginWithPhone) { + listUsersRequest.phone = command.loginName; + } + + const { result: potentialUsers } = await listUsers(listUsersRequest); const redirectUserToSingleIDPIfAvailable = async () => { const identityProviders = await getActiveIdentityProviders( @@ -144,9 +146,16 @@ export async function sendLoginname(command: SendLoginnameCommand) { } }; - if (potentialUsers.length == 1 && potentialUsers[0].userId) { + if (potentialUsers.length > 1) { + return { error: "More than one user found. Provide a unique identifier." }; + } else if (potentialUsers.length == 1 && potentialUsers[0].userId) { + const user = potentialUsers[0]; const userId = potentialUsers[0].userId; + const userLoginSettings = await getLoginSettings( + user.details?.resourceOwner, + ); + const checks = create(ChecksSchema, { user: { search: { case: "userId", value: userId } }, }); @@ -162,7 +171,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } // TODO: check if handling of userstate INITIAL is needed - if (potentialUsers[0].state === UserState.INITIAL) { + if (user.state === UserState.INITIAL) { return { error: "Initial User not supported" }; } @@ -172,9 +181,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (!methods.authMethodTypes || !methods.authMethodTypes.length) { if ( - potentialUsers[0].type.case === "human" && - potentialUsers[0].type.value.email && - !potentialUsers[0].type.value.email.isVerified + user.type.case === "human" && + user.type.value.email && + !user.type.value.email.isVerified ) { const paramsVerify = new URLSearchParams({ loginName: session.factors?.user?.loginName, @@ -219,7 +228,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { const method = methods.authMethodTypes[0]; switch (method) { case AuthenticationMethodType.PASSWORD: // user has only password as auth method - if (!loginSettings?.allowUsernamePassword) { + if (!userLoginSettings?.allowUsernamePassword) { return { error: "Username Password not allowed! Contact your administrator for more information.", @@ -246,7 +255,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY - if (loginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) { + if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) { return { error: "Passkeys not allowed! Contact your administrator for more information.", @@ -309,22 +318,24 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - // user not found, check if register is enabled on organization - if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) { - // TODO: do we need to handle login hints for IDPs here? + // user not found, check if register is enabled on instance / organization context + if ( + loginSettingsByContext?.allowRegister && + !loginSettingsByContext?.allowUsernamePassword + ) { const resp = await redirectUserToSingleIDPIfAvailable(); if (resp) { return resp; } return { error: "Could not find user" }; } else if ( - loginSettings?.allowRegister && - loginSettings?.allowUsernamePassword + loginSettingsByContext?.allowRegister && + loginSettingsByContext?.allowUsernamePassword ) { let orgToRegisterOn: string | undefined = command.organization; if ( - !loginSettings?.ignoreUnknownUsernames && + !loginSettingsByContext?.ignoreUnknownUsernames && !orgToRegisterOn && command.loginName && ORG_SUFFIX_REGEX.test(command.loginName) @@ -344,7 +355,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } // do not register user if ignoreUnknownUsernames is set - if (orgToRegisterOn && !loginSettings?.ignoreUnknownUsernames) { + if (orgToRegisterOn && !loginSettingsByContext?.ignoreUnknownUsernames) { const params = new URLSearchParams({ organization: orgToRegisterOn }); if (command.authRequestId) { @@ -358,7 +369,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - if (loginSettings?.ignoreUnknownUsernames) { + if (loginSettingsByContext?.ignoreUnknownUsernames) { const paramsPasswordDefault = new URLSearchParams({ loginName: command.loginName, }); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 0afc4c4dc1..1858145016 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -291,19 +291,24 @@ export async function createInviteCode(userId: string, host: string | null) { ); } -export async function listUsers({ - loginName, - userName, - email, - organizationId, -}: { +export type ListUsersCommand = { loginName?: string; userName?: string; email?: string; + phone?: string; organizationId?: string; -}) { +}; + +export async function listUsers({ + loginName, + userName, + phone, + email, + organizationId, +}: ListUsersCommand) { const queries: SearchQuery[] = []; + // either loginName or userName and email or phone are required if (loginName) { queries.push( create(SearchQuerySchema, { @@ -316,9 +321,57 @@ export async function listUsers({ }, }), ); - } + } else if (userName && (email || phone)) { + const userNameQuery = create(SearchQuerySchema, { + query: { + case: "userNameQuery", + value: { + userName: userName, + method: TextQueryMethod.EQUALS, + }, + }, + }); - if (userName) { + const orQueries: SearchQuery[] = [userNameQuery]; + + if (email) { + const emailQuery = create(SearchQuerySchema, { + query: { + case: "emailQuery", + value: { + emailAddress: email, + method: TextQueryMethod.EQUALS, + }, + }, + }); + orQueries.push(emailQuery); + } + + if (phone) { + // TODO add after https://github.com/zitadel/zitadel/issues/9016 is merged + // const phoneQuery = create(SearchQuerySchema, { + // query: { + // case: "phoneQuery", + // value: { + // emailAddress: email, + // method: TextQueryMethod.EQUALS, + // }, + // }, + // }); + // orQueries.push(phoneQuery); + } + + queries.push( + create(SearchQuerySchema, { + query: { + case: "orQuery", + value: { + queries: orQueries, + }, + }, + }), + ); + } else if (userName) { queries.push( create(SearchQuerySchema, { query: { @@ -345,19 +398,6 @@ export async function listUsers({ ); } - if (email) { - queries.push( - create(SearchQuerySchema, { - query: { - case: "emailQuery", - value: { - emailAddress: email, - }, - }, - }), - ); - } - return userService.listUsers({ queries: queries }); } From d62093e48b817979498504fe355c645037fbe5fc Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 13:10:01 +0100 Subject: [PATCH 02/21] fix user query --- apps/login/src/lib/zitadel.ts | 59 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 62c158bb11..9666402e12 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -311,7 +311,7 @@ export async function listUsers({ }: ListUsersCommand) { const queries: SearchQuery[] = []; - // either loginName or userName and email or phone are required + // either use loginName or userName, email, phone if (loginName) { queries.push( create(SearchQuerySchema, { @@ -324,18 +324,20 @@ export async function listUsers({ }, }), ); - } else if (userName && (email || phone)) { - const userNameQuery = create(SearchQuerySchema, { - query: { - case: "userNameQuery", - value: { - userName: userName, - method: TextQueryMethod.EQUALS, + } else if (userName || email || phone) { + const orQueries: SearchQuery[] = []; + if (userName) { + const userNameQuery = create(SearchQuerySchema, { + query: { + case: "userNameQuery", + value: { + userName: userName, + method: TextQueryMethod.EQUALS, + }, }, - }, - }); - - const orQueries: SearchQuery[] = [userNameQuery]; + }); + orQueries.push(userNameQuery); + } if (email) { const emailQuery = create(SearchQuerySchema, { @@ -351,17 +353,16 @@ export async function listUsers({ } if (phone) { - // TODO add after https://github.com/zitadel/zitadel/issues/9016 is merged - // const phoneQuery = create(SearchQuerySchema, { - // query: { - // case: "phoneQuery", - // value: { - // emailAddress: email, - // method: TextQueryMethod.EQUALS, - // }, - // }, - // }); - // orQueries.push(phoneQuery); + const phoneQuery = create(SearchQuerySchema, { + query: { + case: "phoneQuery", + value: { + number: phone, + method: TextQueryMethod.EQUALS, + }, + }, + }); + orQueries.push(phoneQuery); } queries.push( @@ -374,18 +375,6 @@ export async function listUsers({ }, }), ); - } else if (userName) { - queries.push( - create(SearchQuerySchema, { - query: { - case: "userNameQuery", - value: { - userName: userName, - method: TextQueryMethod.EQUALS, - }, - }, - }), - ); } if (organizationId) { From aa22a8027e8bbff820276f2690bae93243ed6600 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 13:16:44 +0100 Subject: [PATCH 03/21] cleanup --- apps/login/src/lib/zitadel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 9666402e12..8a85a9b167 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -326,6 +326,7 @@ export async function listUsers({ ); } else if (userName || email || phone) { const orQueries: SearchQuery[] = []; + if (userName) { const userNameQuery = create(SearchQuerySchema, { query: { From 50cb902595cec64cf96c3c9ed0345e932cf7c657 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 2 Jan 2025 13:21:45 +0100 Subject: [PATCH 04/21] update readme --- apps/login/readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/login/readme.md b/apps/login/readme.md index a411b9963f..fd6ba6f48c 100644 --- a/apps/login/readme.md +++ b/apps/login/readme.md @@ -395,6 +395,5 @@ Timebased features like the multifactor init prompt or password expiry, are not - Password Expiry Settings - Login Settings: multifactor init prompt - forceMFA on login settings is not checked for IDPs -- disablePhone / disableEmail from loginSettings will be implemented right after https://github.com/zitadel/zitadel/issues/9016 is merged Also note that IDP logins are considered as valid MFA. An additional MFA check will be implemented in future if enforced. From a7acca52ca65e7b15867e67f9a2ae0cc3105b592 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 8 Jan 2025 09:54:43 +0100 Subject: [PATCH 05/21] update zitadel binary --- acceptance/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/docker-compose.yaml b/acceptance/docker-compose.yaml index de6990387d..85aad63d6f 100644 --- a/acceptance/docker-compose.yaml +++ b/acceptance/docker-compose.yaml @@ -1,7 +1,7 @@ services: zitadel: user: "${ZITADEL_DEV_UID}" - image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.65.0}" + image: "${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:v2.67.1}" command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled --config /zitadel.yaml --steps /zitadel.yaml' ports: - "8080:8080" @@ -22,7 +22,7 @@ services: - POSTGRES_HOST_AUTH_METHOD=trust command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0 healthcheck: - test: [ "CMD-SHELL", "pg_isready" ] + test: ["CMD-SHELL", "pg_isready"] interval: "10s" timeout: "30s" retries: 5 From 3809b8dc932ee41f67ce6d5c47d9334dbb365158 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 9 Jan 2025 09:38:29 +0100 Subject: [PATCH 06/21] change search user --- apps/login/src/app/(login)/loginname/page.tsx | 1 + apps/login/src/components/username-form.tsx | 17 ++- apps/login/src/lib/server/loginname.ts | 23 ++-- apps/login/src/lib/zitadel.ts | 113 ++++++++++++++++++ 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 44601e1845..8f96927dd0 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -54,6 +54,7 @@ export default async function Page(props: { loginName={loginName} authRequestId={authRequestId} organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user + loginSettings={loginSettings} submit={submit} allowRegister={!!loginSettings?.allowRegister} > diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 887c454333..3a35bdd120 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -1,6 +1,7 @@ "use client"; import { sendLoginname } from "@/lib/server/loginname"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; @@ -18,6 +19,7 @@ type Inputs = { type Props = { loginName: string | undefined; authRequestId: string | undefined; + loginSettings: LoginSettings | undefined; organization?: string; submit: boolean; allowRegister: boolean; @@ -28,6 +30,7 @@ export function UsernameForm({ loginName, authRequestId, organization, + loginSettings, submit, allowRegister, children, @@ -80,6 +83,18 @@ export function UsernameForm({ } }, []); + let inputLabel = "Loginname"; + if ( + loginSettings?.disableLoginWithEmail && + loginSettings?.disableLoginWithPhone + ) { + inputLabel = "Username"; + } else if (loginSettings?.disableLoginWithEmail) { + inputLabel = "Username or phone number"; + } else if (loginSettings?.disableLoginWithPhone) { + inputLabel = "Username or email"; + } + return (
@@ -87,7 +102,7 @@ export function UsernameForm({ type="text" autoComplete="username" {...register("loginName", { required: "This field is required" })} - label="Loginname" + label={inputLabel} data-testid="username-text-input" /> {allowRegister && ( diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 7cb74b8d7a..704c01b967 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -16,8 +16,8 @@ import { getOrgsByDomain, listAuthenticationMethodTypes, listIDPLinks, - listUsers, - ListUsersCommand, + searchUsers, + SearchUsersCommand, startIdentityProviderFlow, } from "../zitadel"; import { createSessionAndUpdateCookie } from "./cookie"; @@ -33,20 +33,17 @@ const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { const loginSettingsByContext = await getLoginSettings(command.organization); - let listUsersRequest: ListUsersCommand = { - userName: command.loginName, + if (!loginSettingsByContext) { + return { error: "Could not get login settings" }; + } + + let searchUsersRequest: SearchUsersCommand = { + searchValue: command.loginName, organizationId: command.organization, + loginSettings: loginSettingsByContext, }; - if (!loginSettingsByContext?.disableLoginWithEmail) { - listUsersRequest.email = command.loginName; - } - - if (!loginSettingsByContext?.disableLoginWithPhone) { - listUsersRequest.phone = command.loginName; - } - - const { result: potentialUsers } = await listUsers(listUsersRequest); + const { result: potentialUsers } = await searchUsers(searchUsersRequest); const redirectUserToSingleIDPIfAvailable = async () => { const identityProviders = await getActiveIdentityProviders( diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 112b7bd81d..bda31b0ab9 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -26,6 +26,7 @@ import { create, Duration } from "@zitadel/client"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { @@ -408,6 +409,118 @@ export async function listUsers({ ); } + if (organizationId) { + queries.push( + create(SearchQuerySchema, { + query: { + case: "organizationIdQuery", + value: { + organizationId, + }, + }, + }), + ); + } + console.log(queries); + + return userService.listUsers({ queries: queries }); +} + +export type SearchUsersCommand = { + searchValue: string; + loginSettings: LoginSettings; + organizationId?: string; +}; + +const PhoneQuery = (searchValue: string) => + create(SearchQuerySchema, { + query: { + case: "phoneQuery", + value: { + number: searchValue, + method: TextQueryMethod.EQUALS, + }, + }, + }); + +const UserNameQuery = (searchValue: string) => + create(SearchQuerySchema, { + query: { + case: "userNameQuery", + value: { + userName: searchValue, + method: TextQueryMethod.EQUALS, + }, + }, + }); + +const EmailQuery = (searchValue: string) => + create(SearchQuerySchema, { + query: { + case: "emailQuery", + value: { + emailAddress: searchValue, + method: TextQueryMethod.EQUALS, + }, + }, + }); + +export async function searchUsers({ + searchValue, + loginSettings, + organizationId, +}: SearchUsersCommand) { + console.log(loginSettings); + const queries: SearchQuery[] = []; + const orQueries: SearchQuery[] = []; + + // either use loginName or userName, email, phone + if ( + loginSettings.disableLoginWithEmail && + loginSettings.disableLoginWithPhone + ) { + const userNameQuery = UserNameQuery(searchValue); + queries.push(userNameQuery); + } else if (loginSettings.disableLoginWithEmail) { + const userNameQuery = UserNameQuery(searchValue); + orQueries.push(userNameQuery); + + if (searchValue.length <= 20) { + const phoneQuery = PhoneQuery(searchValue); + orQueries.push(phoneQuery); + } + } else if (loginSettings.disableLoginWithPhone) { + const userNameQuery = UserNameQuery(searchValue); + orQueries.push(userNameQuery); + + const emailQuery = EmailQuery(searchValue); + orQueries.push(emailQuery); + } else { + const userNameQuery = UserNameQuery(searchValue); + orQueries.push(userNameQuery); + + const emailQuery = EmailQuery(searchValue); + orQueries.push(emailQuery); + + if (searchValue.length <= 20) { + const phoneQuery = PhoneQuery(searchValue); + orQueries.push(phoneQuery); + } + } + + if (orQueries.length > 0) { + queries.push( + create(SearchQuerySchema, { + query: { + case: "orQuery", + value: { + queries: orQueries, + }, + }, + }), + ); + } + if (organizationId) { queries.push( create(SearchQuerySchema, { From 36b6362974aa891c08f5e50b5ea11fe0b4437e16 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 9 Jan 2025 09:47:58 +0100 Subject: [PATCH 07/21] recheck settings --- apps/login/src/lib/server/loginname.ts | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 704c01b967..10d9501b05 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -154,6 +154,40 @@ export async function sendLoginname(command: SendLoginnameCommand) { user.details?.resourceOwner, ); + // recheck login settings after user discovery, as the search might have been done without org scope + if ( + userLoginSettings?.disableLoginWithEmail && + userLoginSettings?.disableLoginWithPhone + ) { + if (user.username !== command.loginName) { + return { error: "User not found in the system!" }; + } + } else if (userLoginSettings?.disableLoginWithEmail) { + const humanUser = + potentialUsers[0].type.case === "human" + ? potentialUsers[0].type.value + : undefined; + + if ( + user.username !== command.loginName || + humanUser?.phone?.phone !== command.loginName + ) { + return { error: "User not found in the system!" }; + } + } else if (userLoginSettings?.disableLoginWithPhone) { + const humanUser = + potentialUsers[0].type.case === "human" + ? potentialUsers[0].type.value + : undefined; + + if ( + user.username !== command.loginName || + humanUser?.email?.email !== command.loginName + ) { + return { error: "User not found in the system!" }; + } + } + const checks = create(ChecksSchema, { user: { search: { case: "userId", value: userId } }, }); From c93f21174b10bfd982a6ef48dc52d785d7023e86 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 9 Jan 2025 10:05:32 +0100 Subject: [PATCH 08/21] rm logs --- apps/login/src/lib/zitadel.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index bda31b0ab9..c837295994 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -470,7 +470,6 @@ export async function searchUsers({ loginSettings, organizationId, }: SearchUsersCommand) { - console.log(loginSettings); const queries: SearchQuery[] = []; const orQueries: SearchQuery[] = []; From dc6454bc7def1f045592864b1d7aa2edf381ce9b Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 9 Jan 2025 13:38:00 +0100 Subject: [PATCH 09/21] implement org suffix --- apps/login/src/app/(login)/loginname/page.tsx | 6 +++++- apps/login/src/app/login/route.ts | 5 +++++ apps/login/src/components/input.tsx | 10 +++++++++- apps/login/src/components/username-form.tsx | 3 +++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 8f96927dd0..5400f64b9d 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -20,6 +20,7 @@ export default async function Page(props: { const loginName = searchParams?.loginName; const authRequestId = searchParams?.authRequestId; const organization = searchParams?.organization; + const suffix = searchParams?.suffix; const submit: boolean = searchParams?.submit === "true"; let defaultOrganization; @@ -34,6 +35,8 @@ export default async function Page(props: { organization ?? defaultOrganization, ); + const contextLoginSettings = await getLoginSettings(organization); + const identityProviders = await getActiveIdentityProviders( organization ?? defaultOrganization, ).then((resp) => { @@ -54,7 +57,8 @@ export default async function Page(props: { loginName={loginName} authRequestId={authRequestId} organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user - loginSettings={loginSettings} + loginSettings={contextLoginSettings} + suffix={suffix} submit={submit} allowRegister={!!loginSettings?.allowRegister} > diff --git a/apps/login/src/app/login/route.ts b/apps/login/src/app/login/route.ts index e96326518f..f5741bb39c 100644 --- a/apps/login/src/app/login/route.ts +++ b/apps/login/src/app/login/route.ts @@ -300,6 +300,7 @@ export async function GET(request: NextRequest) { const { authRequest } = await getAuthRequest({ authRequestId }); let organization = ""; + let suffix = ""; let idpId = ""; if (authRequest?.scope) { @@ -326,6 +327,7 @@ export async function GET(request: NextRequest) { const orgs = await getOrgsByDomain(orgDomain); if (orgs.result && orgs.result.length === 1) { organization = orgs.result[0].id ?? ""; + suffix = orgDomain; } } } @@ -448,6 +450,9 @@ export async function GET(request: NextRequest) { if (organization) { loginNameUrl.searchParams.set("organization", organization); } + if (suffix) { + loginNameUrl.searchParams.set("suffix", suffix); + } return NextResponse.redirect(loginNameUrl); } else if (authRequest.prompt.includes(Prompt.NONE)) { /** diff --git a/apps/login/src/components/input.tsx b/apps/login/src/components/input.tsx index 0cb9e58467..4c5723858a 100644 --- a/apps/login/src/components/input.tsx +++ b/apps/login/src/components/input.tsx @@ -15,6 +15,7 @@ export type TextInputProps = DetailedHTMLProps< HTMLInputElement > & { label: string; + suffix?: string; placeholder?: string; defaultValue?: string; error?: string | ReactNode; @@ -45,6 +46,7 @@ export const TextInput = forwardRef( label, placeholder, defaultValue, + suffix, required = false, error, disabled, @@ -56,7 +58,7 @@ export const TextInput = forwardRef( ref, ) => { return ( -
@@ -174,7 +174,7 @@ export function ChangePasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} - data-testid="password-confirm-text-input" + data-testid="password-change-confirm-text-input" />
diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index d093d1d131..ec6cf3cc6b 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -237,7 +237,7 @@ export function SetPasswordForm({ })} label="New Password" error={errors.password?.message as string} - data-testid="password-text-input" + data-testid="password-set-text-input" />
@@ -250,7 +250,7 @@ export function SetPasswordForm({ })} label="Confirm Password" error={errors.confirmPassword?.message as string} - data-testid="password-confirm-text-input" + data-testid="password-set-confirm-text-input" />
diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 6b6189297e..1982375ba1 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -115,7 +115,7 @@ export function VerifyForm({ {t("verify.noCodeReceived")}