diff --git a/apps/login/integration/integration/invite.cy.ts b/apps/login/integration/integration/invite.cy.ts index 344950db4..bf4d46554 100644 --- a/apps/login/integration/integration/invite.cy.ts +++ b/apps/login/integration/integration/invite.cy.ts @@ -83,7 +83,7 @@ describe("verify invite", () => { data: { settings: { passkeysType: 1, - allowUsernamePassword: true, + allowLocalAuthentication: true, }, }, }); diff --git a/apps/login/integration/integration/login.cy.ts b/apps/login/integration/integration/login.cy.ts index 74da3c34b..1cd18abc6 100644 --- a/apps/login/integration/integration/login.cy.ts +++ b/apps/login/integration/integration/login.cy.ts @@ -48,7 +48,7 @@ describe("login", () => { data: { settings: { passkeysType: 1, - allowUsernamePassword: true, + allowLocalAuthentication: true, }, }, }); diff --git a/apps/login/integration/integration/register.cy.ts b/apps/login/integration/integration/register.cy.ts index 65c25a942..a8029d410 100644 --- a/apps/login/integration/integration/register.cy.ts +++ b/apps/login/integration/integration/register.cy.ts @@ -15,7 +15,7 @@ describe("register", () => { settings: { passkeysType: 1, allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, defaultRedirectUri: "", }, }, diff --git a/apps/login/locales/ar.json b/apps/login/locales/ar.json index 26816037b..a13826b2b 100644 --- a/apps/login/locales/ar.json +++ b/apps/login/locales/ar.json @@ -47,6 +47,7 @@ "userNotFound": "المستخدم غير موجود في النظام", "couldNotCreateSession": "تعذر إنشاء جلسة للمستخدم", "initialUserNotSupported": "المستخدم الأولي غير مدعوم", + "localAuthenticationNotAllowed": "المصادقة المحلية غير مسموح بها! اتصل بالمسؤول لمزيد من المعلومات.", "usernamePasswordNotAllowed": "اسم المستخدم وكلمة المرور غير مسموح بهما! اتصل بالمسؤول لمزيد من المعلومات.", "passkeysNotAllowed": "مفاتيح المرور غير مسموح بها! اتصل بالمسؤول لمزيد من المعلومات.", "couldNotFindIdentityProvider": "تعذر العثور على مزود الهوية.", diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 7e4b9161e..3ddc76690 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -47,7 +47,7 @@ "userNotFound": "Benutzer im System nicht gefunden", "couldNotCreateSession": "Sitzung für Benutzer konnte nicht erstellt werden", "initialUserNotSupported": "Initialer Benutzer wird nicht unterstützt", - "usernamePasswordNotAllowed": "Benutzername und Passwort nicht zulässig! Wenden Sie sich für weitere Informationen an Ihren Administrator.", + "localAuthenticationNotAllowed": "Lokale Authentifizierung nicht zulässig! Wenden Sie sich für weitere Informationen an Ihren Administrator.", "passkeysNotAllowed": "Passkeys nicht zulässig! Wenden Sie sich für weitere Informationen an Ihren Administrator.", "couldNotFindIdentityProvider": "Identitätsanbieter konnte nicht gefunden werden.", "userNotActive": "Das Benutzerkonto ist inaktiv. Wenden Sie sich für weitere Informationen an Ihren Administrator." diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 3e09f31b0..477d58847 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -47,7 +47,7 @@ "userNotFound": "User not found in the system", "couldNotCreateSession": "Could not create session for user", "initialUserNotSupported": "Initial User not supported", - "usernamePasswordNotAllowed": "Username Password not allowed! Contact your administrator for more information.", + "localAuthenticationNotAllowed": "Local authentication is not allowed! Contact your administrator for more information.", "passkeysNotAllowed": "Passkeys not allowed! Contact your administrator for more information.", "couldNotFindIdentityProvider": "Could not find identity provider.", "userNotActive": "User is not active. Contact your administrator for more information." diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index a7e8fda90..43d6bad05 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -47,7 +47,7 @@ "userNotFound": "Usuario no encontrado en el sistema", "couldNotCreateSession": "No se pudo crear la sesión para el usuario", "initialUserNotSupported": "Usuario inicial no admitido", - "usernamePasswordNotAllowed": "El inicio de sesión con usuario y contraseña no está permitido. Contacte con su administrador para más información.", + "localAuthenticationNotAllowed": "¡La autenticación local no está permitida! Contacte con su administrador para más información.", "passkeysNotAllowed": "Las passkeys no están permitidas. Contacte con su administrador para más información.", "couldNotFindIdentityProvider": "No se pudo encontrar el proveedor de identidad.", "userNotActive": "El usuario no está activo. Contacte con su administrador para más información." diff --git a/apps/login/locales/fr.json b/apps/login/locales/fr.json index ee5b7b53e..0e5ced94c 100644 --- a/apps/login/locales/fr.json +++ b/apps/login/locales/fr.json @@ -47,7 +47,7 @@ "userNotFound": "Utilisateur non trouvé dans le système", "couldNotCreateSession": "Impossible de créer une session pour l'utilisateur", "initialUserNotSupported": "Utilisateur initial non pris en charge", - "usernamePasswordNotAllowed": "L'identifiant et le mot de passe ne sont pas autorisés ! Contactez votre administrateur pour plus d'informations.", + "localAuthenticationNotAllowed": "L'authentification locale n'est pas autorisée ! Contactez votre administrateur pour plus d'informations.", "passkeysNotAllowed": "Les clés d'accès ne sont pas autorisées ! Contactez votre administrateur pour plus d'informations.", "couldNotFindIdentityProvider": "Impossible de trouver le fournisseur d'identité.", "userNotActive": "L'utilisateur n'est pas actif. Contactez votre administrateur pour plus d'informations." diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index d2ed20ccc..6484fa276 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -47,7 +47,7 @@ "userNotFound": "Utente non trovato nel sistema", "couldNotCreateSession": "Impossibile creare la sessione per l'utente", "initialUserNotSupported": "Utente iniziale non supportato", - "usernamePasswordNotAllowed": "Nome utente e password non consentiti! Contatta l'amministratore per maggiori informazioni.", + "localAuthenticationNotAllowed": "L'autenticazione locale non è consentita! Contatta il tuo amministratore per ulteriori informazioni.", "passkeysNotAllowed": "Passkey non consentite! Contatta l'amministratore per maggiori informazioni.", "couldNotFindIdentityProvider": "Impossibile trovare il provider di identità.", "userNotActive": "L'utente non è attivo. Contatta l'amministratore per maggiori informazioni." diff --git a/apps/login/locales/ja.json b/apps/login/locales/ja.json index e0849791d..1f9502f2f 100644 --- a/apps/login/locales/ja.json +++ b/apps/login/locales/ja.json @@ -47,7 +47,7 @@ "userNotFound": "ユーザーがシステムに見つかりません", "couldNotCreateSession": "ユーザーのセッションを作成できませんでした", "initialUserNotSupported": "初期ユーザーはサポートされていません", - "usernamePasswordNotAllowed": "ユーザー名とパスワードは許可されていません!詳細については管理者にお問い合わせください。", + "localAuthenticationNotAllowed": "ローカル認証は許可されていません! 詳細については管理者に問い合わせてください。", "passkeysNotAllowed": "パスキーは許可されていません!詳細については管理者にお問い合わせください。", "couldNotFindIdentityProvider": "IDプロバイダーが見つかりませんでした。", "userNotActive": "ユーザーはアクティブではありません。詳細については管理者にお問い合わせください。" diff --git a/apps/login/locales/nl.json b/apps/login/locales/nl.json index f3ee3f379..9c08682ac 100644 --- a/apps/login/locales/nl.json +++ b/apps/login/locales/nl.json @@ -47,7 +47,7 @@ "userNotFound": "Gebruiker niet in het systeem gevonden", "couldNotCreateSession": "Kon sessie voor gebruiker niet aanmaken", "initialUserNotSupported": "Initiële gebruiker wordt niet ondersteund", - "usernamePasswordNotAllowed": "Gebruikersnaam en wachtwoord niet toegestaan! Neem contact op met jouw beheerder voor meer informatie.", + "localAuthenticationNotAllowed": "Lokale authenticatie is niet toegestaan! Neem contact op met uw beheerder voor meer informatie.", "passkeysNotAllowed": "Passkeys niet toegestaan! Neem contact op met jouw beheerder voor meer informatie.", "couldNotFindIdentityProvider": "Kon identiteitsprovider niet vinden.", "userNotActive": "Gebruiker is niet actief. Neem contact op met jouw beheerder voor meer informatie." diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json index d2c3c08d9..6551954d7 100644 --- a/apps/login/locales/pl.json +++ b/apps/login/locales/pl.json @@ -47,7 +47,7 @@ "userNotFound": "Użytkownik nie został znaleziony w systemie", "couldNotCreateSession": "Nie udało się utworzyć sesji dla użytkownika", "initialUserNotSupported": "Użytkownik inicjalny nie jest obsługiwany", - "usernamePasswordNotAllowed": "Logowanie przy użyciu nazwy użytkownika i hasła jest niedozwolone! Skontaktuj się z administratorem, aby uzyskać więcej informacji.", + "localAuthenticationNotAllowed": "Uwierzytelnianie lokalne nie jest dozwolone! Skontaktuj się z administratorem, aby uzyskać więcej informacji.", "passkeysNotAllowed": "Klucze dostępu są niedozwolone! Skontaktuj się z administratorem, aby uzyskać więcej informacji.", "couldNotFindIdentityProvider": "Nie udało się znaleźć dostawcy tożsamości.", "userNotActive": "Użytkownik jest nieaktywny. Skontaktuj się z administratorem, aby uzyskać więcej informacji." diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json index 01340c492..d723e3da6 100644 --- a/apps/login/locales/ru.json +++ b/apps/login/locales/ru.json @@ -47,7 +47,7 @@ "userNotFound": "Пользователь не найден в системе", "couldNotCreateSession": "Не удалось создать сессию для пользователя", "initialUserNotSupported": "Начальный пользователь не поддерживается", - "usernamePasswordNotAllowed": "Использование имени пользователя и пароля не разрешено! Обратитесь к администратору за дополнительной информацией.", + "localAuthenticationNotAllowed": "Локальная аутентификация запрещена! Обратитесь к администратору за дополнительной информацией.", "passkeysNotAllowed": "Ключи доступа не разрешены! Обратитесь к администратору за дополнительной информацией.", "couldNotFindIdentityProvider": "Не удалось найти поставщика удостоверений.", "userNotActive": "Пользователь неактивен. Обратитесь к администратору за дополнительной информацией." diff --git a/apps/login/locales/tr.json b/apps/login/locales/tr.json index 3f650da0c..e11cc332b 100644 --- a/apps/login/locales/tr.json +++ b/apps/login/locales/tr.json @@ -47,7 +47,7 @@ "userNotFound": "Kullanıcı sistemde bulunamadı", "couldNotCreateSession": "Kullanıcı için oturum oluşturulamadı", "initialUserNotSupported": "İlk kullanıcı desteklenmiyor", - "usernamePasswordNotAllowed": "Kullanıcı adı ve şifre izin verilmiyor! Daha fazla bilgi için yöneticinizle iletişime geçin.", + "localAuthenticationNotAllowed": "Yerel kimlik doğrulamasına izin verilmiyor! Daha fazla bilgi için yöneticinize başvurun.", "passkeysNotAllowed": "Passkey'ler izin verilmiyor! Daha fazla bilgi için yöneticinizle iletişime geçin.", "couldNotFindIdentityProvider": "Kimlik sağlayıcı bulunamadı.", "userNotActive": "Kullanıcı aktif değil. Daha fazla bilgi için yöneticinizle iletişime geçin." diff --git a/apps/login/locales/uk.json b/apps/login/locales/uk.json index 0becfc34f..98d5fbd79 100644 --- a/apps/login/locales/uk.json +++ b/apps/login/locales/uk.json @@ -47,7 +47,7 @@ "userNotFound": "Користувача не знайдено в системі", "couldNotCreateSession": "Не вдалося створити сеанс для користувача", "initialUserNotSupported": "Початковий користувач не підтримується", - "usernamePasswordNotAllowed": "Ім'я користувача та пароль не дозволені! Зверніться до адміністратора для отримання додаткової інформації.", + "localAuthenticationNotAllowed": "Локальна аутентифікація заборонена! Зверніться до адміністратора за додатковою інформацією.", "passkeysNotAllowed": "Ключі доступу не дозволені! Зверніться до адміністратора для отримання додаткової інформації.", "couldNotFindIdentityProvider": "Не вдалося знайти постачальника ідентичності.", "userNotActive": "Користувач неактивний. Зверніться до адміністратора для отримання додаткової інформації." diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index 2e5911d94..57edc2736 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -47,7 +47,7 @@ "userNotFound": "系统中未找到用户", "couldNotCreateSession": "无法为用户创建会话", "initialUserNotSupported": "不支持初始用户", - "usernamePasswordNotAllowed": "不允许使用用户名和密码!请联系管理员了解更多信息。", + "localAuthenticationNotAllowed": "不允许本地身份验证!请联系您的管理员以获取更多信息。", "passkeysNotAllowed": "不允许使用 Passkey!请联系管理员了解更多信息。", "couldNotFindIdentityProvider": "找不到身份提供者。", "userNotActive": "用户未激活。请联系管理员了解更多信息。" diff --git a/apps/login/src/app/(login)/loginname/page.tsx b/apps/login/src/app/(login)/loginname/page.tsx index 23f543017..eff635139 100644 --- a/apps/login/src/app/(login)/loginname/page.tsx +++ b/apps/login/src/app/(login)/loginname/page.tsx @@ -57,7 +57,7 @@ export default async function Page(props: { searchParams: Promise
- + >} {loginSettings?.allowExternalIdp && !!identityProviders?.length && (
@@ -75,6 +75,7 @@ export default async function Page(props: { searchParams: Promise
)} diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 85d5e7195..2a74acfe8 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -13,7 +13,6 @@ import { getPasswordComplexitySettings, } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; -import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; @@ -93,7 +92,7 @@ export default async function Page(props: { searchParams: Promise
- ) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? ( + ) : loginSettings?.allowRegister && loginSettings.allowLocalAuthentication ? (

diff --git a/apps/login/src/components/choose-authenticator-to-login.tsx b/apps/login/src/components/choose-authenticator-to-login.tsx index ccebf83e6..9263710c3 100644 --- a/apps/login/src/components/choose-authenticator-to-login.tsx +++ b/apps/login/src/components/choose-authenticator-to-login.tsx @@ -20,16 +20,17 @@ export function ChooseAuthenticatorToLogin({ return ( <> {authMethods.includes(AuthenticationMethodType.PASSWORD) && - loginSettings?.allowUsernamePassword && ( + loginSettings?.allowLocalAuthentication && (
)}
{authMethods.includes(AuthenticationMethodType.PASSWORD) && - loginSettings?.allowUsernamePassword && + loginSettings?.allowLocalAuthentication && PASSWORD(false, "/password?" + params)} {authMethods.includes(AuthenticationMethodType.PASSKEY) && + loginSettings?.allowLocalAuthentication && loginSettings?.passkeysType == PasskeysType.ALLOWED && PASSKEYS(false, "/passkey?" + params)}
diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index 780fb3ab7..e89a7af87 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -21,7 +21,7 @@ export function ChooseAuthenticatorToSetup({ authMethods, params, loginSettings return (
{!authMethods.includes(AuthenticationMethodType.PASSWORD) && - loginSettings.allowUsernamePassword && + loginSettings.allowLocalAuthentication && PASSWORD(false, "/password/set?" + params)} {!authMethods.includes(AuthenticationMethodType.PASSKEY) && loginSettings.passkeysType == PasskeysType.ALLOWED && diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index ba2c13c84..444c40381 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -159,7 +159,7 @@ export function RegisterForm({ )} {/* show chooser if both methods are allowed */} - {loginSettings && loginSettings.allowUsernamePassword && loginSettings.passkeysType == PasskeysType.ALLOWED && ( + {loginSettings && loginSettings.allowLocalAuthentication && loginSettings.passkeysType == PasskeysType.ALLOWED && ( <>

@@ -170,7 +170,7 @@ export function RegisterForm({

)} - {!loginSettings?.allowUsernamePassword && + {!loginSettings?.allowLocalAuthentication && loginSettings?.passkeysType !== PasskeysType.ALLOWED && (!loginSettings?.allowExternalIdp || !idpCount) && (
@@ -194,9 +194,9 @@ export function RegisterForm({ disabled={loading || !canSubmit} onClick={handleSubmit((values) => { const usePasswordToContinue: boolean = - loginSettings?.allowUsernamePassword && loginSettings?.passkeysType == PasskeysType.ALLOWED + loginSettings?.allowLocalAuthentication && loginSettings?.passkeysType == PasskeysType.ALLOWED ? !(selected === methods[0]) // choose selection if both available - : !!loginSettings?.allowUsernamePassword; // if password is chosen + : !!loginSettings?.allowLocalAuthentication; // if password is chosen // set password as default if only password is allowed return submitAndContinue(values, usePasswordToContinue); })} diff --git a/apps/login/src/lib/server/loginname.test.ts b/apps/login/src/lib/server/loginname.test.ts index 731f21630..a7b3e1441 100644 --- a/apps/login/src/lib/server/loginname.test.ts +++ b/apps/login/src/lib/server/loginname.test.ts @@ -134,7 +134,7 @@ describe("sendLoginname", () => { }); test("should return error when user search fails", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({ error: "Search failed" }); const result = await sendLoginname({ @@ -145,7 +145,7 @@ describe("sendLoginname", () => { }); test("should return error when search result has no result field", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({}); const result = await sendLoginname({ @@ -156,7 +156,7 @@ describe("sendLoginname", () => { }); test("should return error when more than one user found", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({ result: [ { userId: "user1", preferredLoginName: "user1@example.com" }, @@ -192,7 +192,7 @@ describe("sendLoginname", () => { }; beforeEach(() => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); mockCreate.mockReturnValue({}); mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} }); @@ -232,7 +232,7 @@ describe("sendLoginname", () => { }); test("should attempt IDP redirect when password is not allowed but user has IDP links", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: false }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], }); @@ -253,7 +253,7 @@ describe("sendLoginname", () => { }); test("should return error when password not allowed and no IDP links available", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: false }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], }); @@ -265,12 +265,12 @@ describe("sendLoginname", () => { }); expect(result).toEqual({ - error: "errors.usernamePasswordNotAllowed", + error: "errors.localAuthenticationNotAllowed", }); }); test("should redirect to organization IDP when password not allowed, no user IDP links, but organization has active IDP", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: false }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], }); @@ -293,7 +293,7 @@ describe("sendLoginname", () => { }); test("should redirect to passkey when user has only passkey method and it's allowed", async () => { - mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED }); + mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED, allowLocalAuthentication: true }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSKEY], }); @@ -310,7 +310,25 @@ describe("sendLoginname", () => { }); test("should return error when passkeys are not allowed", async () => { - mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.NOT_ALLOWED }); + mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.NOT_ALLOWED, allowLocalAuthentication: true }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSKEY], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ + error: "errors.passkeysNotAllowed", + }); + }); + + test("should return error when passkeys are allowed but allowLocalAuthentication is false", async () => { + mockGetLoginSettings.mockResolvedValue({ + passkeysType: PasskeysType.ALLOWED, + allowLocalAuthentication: false, + }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSKEY], }); @@ -342,7 +360,7 @@ describe("sendLoginname", () => { test("should NOT create session when ignoreUnknownUsernames is true", async () => { mockGetLoginSettings.mockResolvedValue({ - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: true, }); mockListAuthenticationMethodTypes.mockResolvedValue({ @@ -377,8 +395,8 @@ describe("sendLoginname", () => { expect((result as any).redirect).toContain("altPassword=true"); // password is allowed }); - test("should not show password alternative when password is not allowed", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + test("should return error when allowLocalAuthentication is false (disabling both password and passkey)", async () => { + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: false }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.PASSKEY], }); @@ -387,9 +405,9 @@ describe("sendLoginname", () => { loginName: "user@example.com", }); - expect(result).toBeDefined(); - expect(result?.redirect).toMatch(/^\/passkey\?/); - expect(result?.redirect).toContain("altPassword=false"); // password is not allowed + expect(result).toEqual({ + error: "errors.localAuthenticationNotAllowed", + }); }); test("should redirect to IDP when no passkey but IDP available", async () => { @@ -422,7 +440,7 @@ describe("sendLoginname", () => { }); test("should return error when password is only method in multi-method scenario but not allowed", async () => { - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: false }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], }); @@ -434,7 +452,7 @@ describe("sendLoginname", () => { }); expect(result).toEqual({ - error: "errors.usernamePasswordNotAllowed", + error: "errors.localAuthenticationNotAllowed", }); }); }); @@ -448,7 +466,7 @@ describe("sendLoginname", () => { test("should redirect to single IDP when register allowed but password not allowed", async () => { mockGetLoginSettings.mockResolvedValue({ allowRegister: true, - allowUsernamePassword: false, + allowLocalAuthentication: false, }); mockGetActiveIdentityProviders.mockResolvedValue({ identityProviders: [{ id: "idp123", type: "OIDC" }], @@ -465,7 +483,7 @@ describe("sendLoginname", () => { test("should redirect to register when both register and password allowed", async () => { mockGetLoginSettings.mockResolvedValue({ allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }); @@ -504,7 +522,7 @@ describe("sendLoginname", () => { test("should return error when user not found and no registration allowed", async () => { mockGetLoginSettings.mockResolvedValue({ allowRegister: false, - allowUsernamePassword: true, + allowLocalAuthentication: true, }); const result = await sendLoginname({ @@ -519,14 +537,14 @@ describe("sendLoginname", () => { mockGetLoginSettings .mockResolvedValueOnce({ allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }) // Mock login settings for discovered org - must include all necessary flags .mockResolvedValueOnce({ allowDomainDiscovery: true, allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }); @@ -559,13 +577,13 @@ describe("sendLoginname", () => { mockGetLoginSettings .mockResolvedValueOnce({ allowRegister: true, - allowUsernamePassword: false, + allowLocalAuthentication: false, }) // Mock login settings for discovered org - must include all necessary flags .mockResolvedValueOnce({ allowDomainDiscovery: true, allowRegister: true, - allowUsernamePassword: false, + allowLocalAuthentication: false, }); // Mock org discovery to return one org with matching domain @@ -603,7 +621,7 @@ describe("sendLoginname", () => { mockGetLoginSettings .mockResolvedValueOnce({ allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }) // Mock login settings for org with domain discovery disabled @@ -627,7 +645,7 @@ describe("sendLoginname", () => { test("should not discover org if multiple orgs match the domain", async () => { mockGetLoginSettings.mockResolvedValue({ allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }); @@ -651,7 +669,7 @@ describe("sendLoginname", () => { test("should use provided organization instead of discovering when org context exists", async () => { mockGetLoginSettings.mockResolvedValue({ allowRegister: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: false, }); @@ -669,11 +687,11 @@ describe("sendLoginname", () => { expect(mockGetOrgsByDomain).not.toHaveBeenCalled(); }); - test("should redirect to password when ignoreUnknownUsernames is true, allowRegister is true, but allowUsernamePassword is false (User not found)", async () => { + test("should redirect to password when ignoreUnknownUsernames is true, allowRegister is true, but allowLocalAuthentication is false (User not found)", async () => { mockSearchUsers.mockResolvedValue({ result: [] }); mockGetLoginSettings.mockResolvedValue({ allowRegister: true, - allowUsernamePassword: false, + allowLocalAuthentication: false, ignoreUnknownUsernames: true, }); mockGetActiveIdentityProviders.mockResolvedValue({ identityProviders: [] }); @@ -747,7 +765,7 @@ describe("sendLoginname", () => { }); mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], @@ -777,7 +795,7 @@ describe("sendLoginname", () => { }); mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true, - allowUsernamePassword: true, + allowLocalAuthentication: true, }); mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [AuthenticationMethodType.PASSWORD], @@ -809,7 +827,7 @@ describe("sendLoginname", () => { }; mockGetLoginSettings.mockResolvedValue({ - allowUsernamePassword: false, + allowLocalAuthentication: false, ignoreUnknownUsernames: true, }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); @@ -826,7 +844,7 @@ describe("sendLoginname", () => { ignoreUnknownUsernames: true, }); - expect(result).not.toEqual({ error: "errors.usernamePasswordNotAllowed" }); + expect(result).not.toEqual({ error: "errors.localAuthenticationNotAllowed" }); expect(result).toHaveProperty("redirect"); expect((result as any).redirect).toMatch(/^\/password\?/); }); @@ -892,7 +910,7 @@ describe("sendLoginname", () => { }, }; - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); mockCreate.mockReturnValue({}); mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} }); @@ -923,7 +941,7 @@ describe("sendLoginname", () => { }, }; - mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockGetLoginSettings.mockResolvedValue({ allowLocalAuthentication: true }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); mockCreate.mockReturnValue({}); mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} }); @@ -962,7 +980,7 @@ describe("sendLoginname", () => { }; mockGetLoginSettings.mockResolvedValue({ - allowUsernamePassword: true, + allowLocalAuthentication: true, ignoreUnknownUsernames: true, }); // Mock search result returns a user with resolved/different loginName @@ -1006,6 +1024,7 @@ describe("sendLoginname", () => { mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED, + allowLocalAuthentication: true, ignoreUnknownUsernames: true, }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); @@ -1047,6 +1066,7 @@ describe("sendLoginname", () => { mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED, + allowLocalAuthentication: true, ignoreUnknownUsernames: true, }); mockSearchUsers.mockResolvedValue({ result: [mockUser] }); @@ -1088,10 +1108,10 @@ describe("sendLoginname", () => { // Mock implementation to return different settings based on organization mockGetLoginSettings.mockImplementation(async (args: any) => { if (args.organization === "context-org") { - return { allowUsernamePassword: true, ignoreUnknownUsernames: true }; + return { allowLocalAuthentication: true, ignoreUnknownUsernames: true }; } if (args.organization === "user-org") { - return { allowUsernamePassword: true, ignoreUnknownUsernames: false }; + return { allowLocalAuthentication: true, ignoreUnknownUsernames: false }; } return {}; }); @@ -1136,10 +1156,10 @@ describe("sendLoginname", () => { mockGetLoginSettings.mockImplementation(async (args: any) => { if (args.organization === "context-org") { - return { allowUsernamePassword: true, ignoreUnknownUsernames: false }; + return { allowLocalAuthentication: true, ignoreUnknownUsernames: false }; } if (args.organization === "user-org") { - return { allowUsernamePassword: true, ignoreUnknownUsernames: true }; + return { allowLocalAuthentication: true, ignoreUnknownUsernames: true }; } return {}; }); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index e8260126e..8b285ecba 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -336,7 +336,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 (!userLoginSettings?.allowUsernamePassword) { + if (!userLoginSettings?.allowLocalAuthentication) { // Check if user has IDPs available as alternative, that could eventually be used to register/link. const idpResp = await redirectUserToIDP(userId, organization); if (idpResp?.redirect) { @@ -348,7 +348,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } return { - error: t("errors.usernamePasswordNotAllowed"), + error: t("errors.localAuthenticationNotAllowed"), }; } @@ -371,7 +371,10 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY - if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) { + if ( + userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED || + !userLoginSettings?.allowLocalAuthentication + ) { if (command.ignoreUnknownUsernames) { return preventUserEnumeration(command.organization); } @@ -406,12 +409,16 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } else { // prefer passkey in favor of other methods - if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) { + if ( + methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY) && + userLoginSettings?.passkeysType !== PasskeysType.NOT_ALLOWED && + userLoginSettings?.allowLocalAuthentication + ) { const passkeyParams = new URLSearchParams({ loginName: command.ignoreUnknownUsernames ? command.loginName : (session?.factors?.user?.loginName ?? user.preferredLoginName), - altPassword: `${methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) && userLoginSettings?.allowUsernamePassword}`, // show alternative password option only if allowed + altPassword: `${methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) && userLoginSettings?.allowLocalAuthentication}`, // show alternative password option only if allowed }); if (command.requestId) { @@ -427,12 +434,12 @@ export async function sendLoginname(command: SendLoginnameCommand) { return redirectUserToIDP(userId, organization); } else if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)) { // Check if password authentication is allowed - if (!userLoginSettings?.allowUsernamePassword) { + if (!userLoginSettings?.allowLocalAuthentication) { if (command.ignoreUnknownUsernames) { return preventUserEnumeration(command.organization); } return { - error: "Username Password not allowed! Contact your administrator for more information.", + error: t("errors.localAuthenticationNotAllowed"), }; } @@ -497,7 +504,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } // user not found, check if register is enabled on instance / organization context - if (effectiveLoginSettings?.allowRegister && !effectiveLoginSettings?.allowUsernamePassword) { + if (effectiveLoginSettings?.allowRegister && !effectiveLoginSettings?.allowLocalAuthentication) { console.log("redirecting to IDP (register allowed, password not allowed)"); const resp = await redirectUserToIDP(undefined, discoveredOrganization); if (resp) { @@ -506,7 +513,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { console.log("IDP redirect failed, returning user not found"); return preventUserEnumeration(discoveredOrganization); - } else if (effectiveLoginSettings?.allowRegister && effectiveLoginSettings?.allowUsernamePassword) { + } else if (effectiveLoginSettings?.allowRegister && effectiveLoginSettings?.allowLocalAuthentication) { console.log("register and password both allowed"); // do not register user if ignoreUnknownUsernames is set if (discoveredOrganization && !effectiveLoginSettings?.ignoreUnknownUsernames) { diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html index 555f5678d..1c7b07145 100644 --- a/console/src/app/modules/policies/login-policy/login-policy.component.html +++ b/console/src/app/modules/policies/login-policy/login-policy.component.html @@ -229,7 +229,7 @@