mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 00:25:12 +00:00
fix(login, console): replace allowUsernamePassword with allowLocalAuthentication in login, change translation in console (#11413)
# Which Problems Are Solved This PR deprecates and replaces the use of `allowUsernamePassword` with `allowLocalAuthentication`. It additionally disables the username on organizations that disallow local authentication. In such a case, only IDPs are shown. # How the Problems Are Solved - replace `allowUsernamePassword` by `allowLocalAuthentication` - disable username form if !allowLocalAuthentication - translate property in console UI
This commit is contained in:
@@ -83,7 +83,7 @@ describe("verify invite", () => {
|
||||
data: {
|
||||
settings: {
|
||||
passkeysType: 1,
|
||||
allowUsernamePassword: true,
|
||||
allowLocalAuthentication: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("login", () => {
|
||||
data: {
|
||||
settings: {
|
||||
passkeysType: 1,
|
||||
allowUsernamePassword: true,
|
||||
allowLocalAuthentication: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("register", () => {
|
||||
settings: {
|
||||
passkeysType: 1,
|
||||
allowRegister: true,
|
||||
allowUsernamePassword: true,
|
||||
allowLocalAuthentication: true,
|
||||
defaultRedirectUri: "",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"userNotFound": "المستخدم غير موجود في النظام",
|
||||
"couldNotCreateSession": "تعذر إنشاء جلسة للمستخدم",
|
||||
"initialUserNotSupported": "المستخدم الأولي غير مدعوم",
|
||||
"localAuthenticationNotAllowed": "المصادقة المحلية غير مسموح بها! اتصل بالمسؤول لمزيد من المعلومات.",
|
||||
"usernamePasswordNotAllowed": "اسم المستخدم وكلمة المرور غير مسموح بهما! اتصل بالمسؤول لمزيد من المعلومات.",
|
||||
"passkeysNotAllowed": "مفاتيح المرور غير مسموح بها! اتصل بالمسؤول لمزيد من المعلومات.",
|
||||
"couldNotFindIdentityProvider": "تعذر العثور على مزود الهوية.",
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"userNotFound": "ユーザーがシステムに見つかりません",
|
||||
"couldNotCreateSession": "ユーザーのセッションを作成できませんでした",
|
||||
"initialUserNotSupported": "初期ユーザーはサポートされていません",
|
||||
"usernamePasswordNotAllowed": "ユーザー名とパスワードは許可されていません!詳細については管理者にお問い合わせください。",
|
||||
"localAuthenticationNotAllowed": "ローカル認証は許可されていません! 詳細については管理者に問い合わせてください。",
|
||||
"passkeysNotAllowed": "パスキーは許可されていません!詳細については管理者にお問い合わせください。",
|
||||
"couldNotFindIdentityProvider": "IDプロバイダーが見つかりませんでした。",
|
||||
"userNotActive": "ユーザーはアクティブではありません。詳細については管理者にお問い合わせください。"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"userNotFound": "Пользователь не найден в системе",
|
||||
"couldNotCreateSession": "Не удалось создать сессию для пользователя",
|
||||
"initialUserNotSupported": "Начальный пользователь не поддерживается",
|
||||
"usernamePasswordNotAllowed": "Использование имени пользователя и пароля не разрешено! Обратитесь к администратору за дополнительной информацией.",
|
||||
"localAuthenticationNotAllowed": "Локальная аутентификация запрещена! Обратитесь к администратору за дополнительной информацией.",
|
||||
"passkeysNotAllowed": "Ключи доступа не разрешены! Обратитесь к администратору за дополнительной информацией.",
|
||||
"couldNotFindIdentityProvider": "Не удалось найти поставщика удостоверений.",
|
||||
"userNotActive": "Пользователь неактивен. Обратитесь к администратору за дополнительной информацией."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"userNotFound": "Користувача не знайдено в системі",
|
||||
"couldNotCreateSession": "Не вдалося створити сеанс для користувача",
|
||||
"initialUserNotSupported": "Початковий користувач не підтримується",
|
||||
"usernamePasswordNotAllowed": "Ім'я користувача та пароль не дозволені! Зверніться до адміністратора для отримання додаткової інформації.",
|
||||
"localAuthenticationNotAllowed": "Локальна аутентифікація заборонена! Зверніться до адміністратора за додатковою інформацією.",
|
||||
"passkeysNotAllowed": "Ключі доступу не дозволені! Зверніться до адміністратора для отримання додаткової інформації.",
|
||||
"couldNotFindIdentityProvider": "Не вдалося знайти постачальника ідентичності.",
|
||||
"userNotActive": "Користувач неактивний. Зверніться до адміністратора для отримання додаткової інформації."
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"userNotFound": "系统中未找到用户",
|
||||
"couldNotCreateSession": "无法为用户创建会话",
|
||||
"initialUserNotSupported": "不支持初始用户",
|
||||
"usernamePasswordNotAllowed": "不允许使用用户名和密码!请联系管理员了解更多信息。",
|
||||
"localAuthenticationNotAllowed": "不允许本地身份验证!请联系您的管理员以获取更多信息。",
|
||||
"passkeysNotAllowed": "不允许使用 Passkey!请联系管理员了解更多信息。",
|
||||
"couldNotFindIdentityProvider": "找不到身份提供者。",
|
||||
"userNotActive": "用户未激活。请联系管理员了解更多信息。"
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<UsernameForm
|
||||
{loginSettings?.allowLocalAuthentication && <UsernameForm
|
||||
loginName={loginName}
|
||||
requestId={requestId}
|
||||
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
|
||||
@@ -66,7 +66,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
suffix={suffix}
|
||||
submit={submit}
|
||||
allowRegister={!!loginSettings?.allowRegister}
|
||||
></UsernameForm>
|
||||
></UsernameForm>}
|
||||
|
||||
{loginSettings?.allowExternalIdp && !!identityProviders?.length && (
|
||||
<div className="w-full pb-4 pt-6">
|
||||
@@ -75,6 +75,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
requestId={requestId}
|
||||
organization={organization}
|
||||
postErrorRedirectUrl="/loginname"
|
||||
showLabel={loginSettings?.allowLocalAuthentication}
|
||||
></SignInWithIdp>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<Record<string
|
||||
{legal &&
|
||||
passwordComplexitySettings &&
|
||||
organization &&
|
||||
(loginSettings.allowUsernamePassword || loginSettings.passkeysType == PasskeysType.ALLOWED) && (
|
||||
loginSettings.allowLocalAuthentication && (
|
||||
<RegisterForm
|
||||
idpCount={!loginSettings?.allowExternalIdp ? 0 : identityProviders.length}
|
||||
legal={legal}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
</div>
|
||||
<div className="w-full"></div>
|
||||
</DynamicTheme>
|
||||
) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? (
|
||||
) : loginSettings?.allowRegister && loginSettings.allowLocalAuthentication ? (
|
||||
<DynamicTheme branding={branding}>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<h1>
|
||||
|
||||
@@ -20,16 +20,17 @@ export function ChooseAuthenticatorToLogin({
|
||||
return (
|
||||
<>
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
loginSettings?.allowUsernamePassword && (
|
||||
loginSettings?.allowLocalAuthentication && (
|
||||
<div className="ztdl-p">
|
||||
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||
{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)}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function ChooseAuthenticatorToSetup({ authMethods, params, loginSettings
|
||||
return (
|
||||
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
loginSettings.allowUsernamePassword &&
|
||||
loginSettings.allowLocalAuthentication &&
|
||||
PASSWORD(false, "/password/set?" + params)}
|
||||
{!authMethods.includes(AuthenticationMethodType.PASSKEY) &&
|
||||
loginSettings.passkeysType == PasskeysType.ALLOWED &&
|
||||
|
||||
@@ -159,7 +159,7 @@ export function RegisterForm({
|
||||
<PrivacyPolicyCheckboxes legal={legal} onChange={setTosAndPolicyAccepted} />
|
||||
)}
|
||||
{/* show chooser if both methods are allowed */}
|
||||
{loginSettings && loginSettings.allowUsernamePassword && loginSettings.passkeysType == PasskeysType.ALLOWED && (
|
||||
{loginSettings && loginSettings.allowLocalAuthentication && loginSettings.passkeysType == PasskeysType.ALLOWED && (
|
||||
<>
|
||||
<p className="ztdl-p mb-6 mt-4 block text-left">
|
||||
<Translated i18nKey="selectMethod" namespace="register" />
|
||||
@@ -170,7 +170,7 @@ export function RegisterForm({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!loginSettings?.allowUsernamePassword &&
|
||||
{!loginSettings?.allowLocalAuthentication &&
|
||||
loginSettings?.passkeysType !== PasskeysType.ALLOWED &&
|
||||
(!loginSettings?.allowExternalIdp || !idpCount) && (
|
||||
<div className="py-4">
|
||||
@@ -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);
|
||||
})}
|
||||
|
||||
@@ -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: "[email protected]" },
|
||||
@@ -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: "[email protected]",
|
||||
});
|
||||
|
||||
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: "[email protected]",
|
||||
});
|
||||
|
||||
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 {};
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user