mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-08 15:15: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:
@@ -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