mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-29 21:46:05 +00:00
fix(login): Redirect to IDP flow when password auth is disabled (#10839)
Closes #10671
# Which Problems Are Solved
Users with only password authentication method were immediately shown an
error "Username Password not allowed" when
`loginSettings.allowUsernamePassword` was set to false. However, the IDP
flow could potentially allow the user to register a new account or link
an existing account, providing a better user experience than a dead-end
error.
# How the Problems Are Solved
- Modified single password method case to attempt IDP redirect before
showing error
- This allows users to potentially register or link accounts through the
IDP flow instead of hitting an immediate error
- Only show error as last resort when no IDP alternative is available
(cherry picked from commit 695db96745)
This commit is contained in:
committed by
Livio Spring
parent
826935577c
commit
1014c6d93f
548
apps/login/src/lib/server/loginname.test.ts
Normal file
548
apps/login/src/lib/server/loginname.test.ts
Normal file
@@ -0,0 +1,548 @@
|
||||
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
|
||||
import { sendLoginname } from "./loginname";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getIDPByID } from "../zitadel";
|
||||
|
||||
// Mock all the dependencies
|
||||
vi.mock("next/headers", () => ({
|
||||
headers: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@zitadel/client", () => ({
|
||||
create: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../service-url", () => ({
|
||||
getServiceUrlFromHeaders: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../idp", () => ({
|
||||
idpTypeToIdentityProviderType: vi.fn(),
|
||||
idpTypeToSlug: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../zitadel", () => ({
|
||||
getActiveIdentityProviders: vi.fn(),
|
||||
getIDPByID: vi.fn(),
|
||||
getLoginSettings: vi.fn(),
|
||||
getOrgsByDomain: vi.fn(),
|
||||
listAuthenticationMethodTypes: vi.fn(),
|
||||
listIDPLinks: vi.fn(),
|
||||
searchUsers: vi.fn(),
|
||||
startIdentityProviderFlow: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./cookie", () => ({
|
||||
createSessionAndUpdateCookie: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./host", () => ({
|
||||
getOriginalHost: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("sendLoginname", () => {
|
||||
// Mock modules
|
||||
let mockHeaders: any;
|
||||
let mockCreate: any;
|
||||
let mockGetServiceUrlFromHeaders: any;
|
||||
let mockGetLoginSettings: any;
|
||||
let mockSearchUsers: any;
|
||||
let mockCreateSessionAndUpdateCookie: any;
|
||||
let mockListAuthenticationMethodTypes: any;
|
||||
let mockListIDPLinks: any;
|
||||
let mockGetOriginalHost: any;
|
||||
let mockStartIdentityProviderFlow: any;
|
||||
let mockGetActiveIdentityProviders: any;
|
||||
let mockGetIDPByID: any;
|
||||
let mockIdpTypeToSlug: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Import mocked modules
|
||||
const { headers } = await import("next/headers");
|
||||
const { create } = await import("@zitadel/client");
|
||||
const { getServiceUrlFromHeaders } = await import("../service-url");
|
||||
const {
|
||||
getLoginSettings,
|
||||
searchUsers,
|
||||
listAuthenticationMethodTypes,
|
||||
listIDPLinks,
|
||||
startIdentityProviderFlow,
|
||||
getActiveIdentityProviders,
|
||||
} = await import("../zitadel");
|
||||
const { createSessionAndUpdateCookie } = await import("./cookie");
|
||||
const { getOriginalHost } = await import("./host");
|
||||
const { idpTypeToSlug } = await import("../idp");
|
||||
|
||||
// Setup mocks
|
||||
mockHeaders = vi.mocked(headers);
|
||||
mockCreate = vi.mocked(create);
|
||||
mockGetServiceUrlFromHeaders = vi.mocked(getServiceUrlFromHeaders);
|
||||
mockGetLoginSettings = vi.mocked(getLoginSettings);
|
||||
mockSearchUsers = vi.mocked(searchUsers);
|
||||
mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie);
|
||||
mockListAuthenticationMethodTypes = vi.mocked(listAuthenticationMethodTypes);
|
||||
mockListIDPLinks = vi.mocked(listIDPLinks);
|
||||
mockGetOriginalHost = vi.mocked(getOriginalHost);
|
||||
mockStartIdentityProviderFlow = vi.mocked(startIdentityProviderFlow);
|
||||
mockGetActiveIdentityProviders = vi.mocked(getActiveIdentityProviders);
|
||||
mockGetIDPByID = vi.mocked(getIDPByID);
|
||||
mockIdpTypeToSlug = vi.mocked(idpTypeToSlug);
|
||||
|
||||
// Default mock implementations
|
||||
mockHeaders.mockResolvedValue({} as any);
|
||||
mockGetServiceUrlFromHeaders.mockReturnValue({ serviceUrl: "https://api.example.com" });
|
||||
mockGetOriginalHost.mockResolvedValue("example.com");
|
||||
mockIdpTypeToSlug.mockReturnValue("google");
|
||||
mockGetIDPByID.mockResolvedValue({
|
||||
id: "idp123",
|
||||
name: "Google",
|
||||
type: "GOOGLE",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Error cases", () => {
|
||||
test("should return error when login settings cannot be retrieved", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue(null);
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "Could not get login settings" });
|
||||
});
|
||||
|
||||
test("should return error when user search fails", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({ error: "Search failed" });
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "Search failed" });
|
||||
});
|
||||
|
||||
test("should return error when search result has no result field", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "Could not search users" });
|
||||
});
|
||||
|
||||
test("should return error when more than one user found", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({
|
||||
result: [
|
||||
{ userId: "user1", preferredLoginName: "user1@example.com" },
|
||||
{ userId: "user2", preferredLoginName: "user2@example.com" },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "More than one user found. Provide a unique identifier." });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Single user found - authentication method handling", () => {
|
||||
const mockUser = {
|
||||
userId: "user123",
|
||||
preferredLoginName: "user@example.com",
|
||||
details: { resourceOwner: "org123" },
|
||||
type: { case: "human", value: { email: { email: "user@example.com" } } },
|
||||
state: UserState.ACTIVE,
|
||||
};
|
||||
|
||||
const mockSession = {
|
||||
factors: {
|
||||
user: {
|
||||
id: "user123",
|
||||
loginName: "user@example.com",
|
||||
organizationId: "org123",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({ result: [mockUser] });
|
||||
mockCreate.mockReturnValue({});
|
||||
mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
|
||||
});
|
||||
|
||||
test("should redirect to verify when user has no authentication methods", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [] });
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
requestId: "req123",
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty("redirect");
|
||||
expect((result as any).redirect).toMatch(/^\/verify\?/);
|
||||
expect((result as any).redirect).toContain("loginName=user%40example.com");
|
||||
expect((result as any).redirect).toContain("send=true");
|
||||
expect((result as any).redirect).toContain("invite=true");
|
||||
expect((result as any).redirect).toContain("requestId=req123");
|
||||
});
|
||||
|
||||
describe("Single authentication method", () => {
|
||||
test("should redirect to password when user has only password method and it's allowed", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
requestId: "req123",
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty("redirect");
|
||||
expect((result as any).redirect).toMatch(/^\/password\?/);
|
||||
expect((result as any).redirect).toContain("loginName=user%40example.com");
|
||||
expect((result as any).redirect).toContain("requestId=req123");
|
||||
});
|
||||
|
||||
test("should attempt IDP redirect when password is not allowed but user has IDP links", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
mockListIDPLinks.mockResolvedValue({
|
||||
result: [{ idpId: "idp123" }],
|
||||
});
|
||||
mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth");
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
|
||||
expect(mockListIDPLinks).toHaveBeenCalledWith({
|
||||
serviceUrl: "https://api.example.com",
|
||||
userId: "user123",
|
||||
});
|
||||
});
|
||||
|
||||
test("should return error when password not allowed and no IDP links available", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
mockListIDPLinks.mockResolvedValue({ result: [] });
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
error: "Username Password not allowed! Contact your administrator for more information.",
|
||||
});
|
||||
});
|
||||
|
||||
test("should redirect to passkey when user has only passkey method and it's allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSKEY],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
requestId: "req123",
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty("redirect");
|
||||
expect((result as any).redirect).toMatch(/^\/passkey\?/);
|
||||
expect((result as any).redirect).toContain("loginName=user%40example.com");
|
||||
expect((result as any).redirect).toContain("requestId=req123");
|
||||
});
|
||||
|
||||
test("should return error when passkeys are not allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.NOT_ALLOWED });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSKEY],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
error: "Passkeys not allowed! Contact your administrator for more information.",
|
||||
});
|
||||
});
|
||||
|
||||
test("should redirect to IDP when user has only IDP method", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.IDP],
|
||||
});
|
||||
mockListIDPLinks.mockResolvedValue({
|
||||
result: [{ idpId: "idp123" }],
|
||||
});
|
||||
mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth");
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multiple authentication methods", () => {
|
||||
test("should prefer passkey when multiple methods available", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.PASSKEY],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty("redirect");
|
||||
expect((result as any).redirect).toMatch(/^\/passkey\?/);
|
||||
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 });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.PASSKEY],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.redirect).toMatch(/^\/passkey\?/);
|
||||
expect(result?.redirect).toContain("altPassword=false"); // password is not allowed
|
||||
});
|
||||
|
||||
test("should redirect to IDP when no passkey but IDP available", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.IDP],
|
||||
});
|
||||
mockListIDPLinks.mockResolvedValue({
|
||||
result: [{ idpId: "idp123" }],
|
||||
});
|
||||
mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth");
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
|
||||
});
|
||||
|
||||
test("should redirect to password when no passkey or IDP, only password available and allowed", async () => {
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.redirect).toMatch(/^\/password\?/);
|
||||
});
|
||||
|
||||
test("should return error when password is only method in multi-method scenario but not allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false });
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
mockListIDPLinks.mockResolvedValue({ result: [] });
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
error: "Username Password not allowed! Contact your administrator for more information.",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("User not found scenarios", () => {
|
||||
beforeEach(() => {
|
||||
mockSearchUsers.mockResolvedValue({ result: [] });
|
||||
});
|
||||
|
||||
test("should redirect to single IDP when register allowed but password not allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({
|
||||
allowRegister: true,
|
||||
allowUsernamePassword: false,
|
||||
});
|
||||
mockGetActiveIdentityProviders.mockResolvedValue({
|
||||
identityProviders: [{ id: "idp123", type: "OIDC" }],
|
||||
});
|
||||
mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth");
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
|
||||
});
|
||||
|
||||
test("should redirect to register when both register and password allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({
|
||||
allowRegister: true,
|
||||
allowUsernamePassword: true,
|
||||
ignoreUnknownUsernames: false,
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
organization: "org123",
|
||||
requestId: "req123",
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.redirect).toMatch(/^\/register\?/);
|
||||
expect(result?.redirect).toContain("organization=org123");
|
||||
expect(result?.redirect).toContain("requestId=req123");
|
||||
expect(result?.redirect).toContain("email=user%40example.com");
|
||||
});
|
||||
|
||||
test("should redirect to password when ignoreUnknownUsernames is true", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({
|
||||
ignoreUnknownUsernames: true,
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
requestId: "req123",
|
||||
organization: "org123",
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.redirect).toMatch(/^\/password\?/);
|
||||
expect(result?.redirect).toContain("loginName=user%40example.com");
|
||||
expect(result?.redirect).toContain("requestId=req123");
|
||||
expect(result?.redirect).toContain("organization=org123");
|
||||
});
|
||||
|
||||
test("should return error when user not found and no registration allowed", async () => {
|
||||
mockGetLoginSettings.mockResolvedValue({
|
||||
allowRegister: false,
|
||||
allowUsernamePassword: true,
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "User not found in the system" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
test("should handle session creation failure", async () => {
|
||||
const mockUser = {
|
||||
userId: "user123",
|
||||
preferredLoginName: "user@example.com",
|
||||
details: { resourceOwner: "org123" },
|
||||
type: { case: "human", value: { email: { email: "user@example.com" } } },
|
||||
state: UserState.ACTIVE,
|
||||
};
|
||||
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({ result: [mockUser] });
|
||||
mockCreate.mockReturnValue({});
|
||||
mockCreateSessionAndUpdateCookie.mockResolvedValue({ factors: {} }); // No user in session
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "Could not create session for user" });
|
||||
});
|
||||
|
||||
test("should handle initial user state", async () => {
|
||||
const mockUser = {
|
||||
userId: "user123",
|
||||
preferredLoginName: "user@example.com",
|
||||
details: { resourceOwner: "org123" },
|
||||
type: { case: "human", value: { email: { email: "user@example.com" } } },
|
||||
state: UserState.INITIAL,
|
||||
};
|
||||
|
||||
const mockSession = {
|
||||
factors: {
|
||||
user: {
|
||||
id: "user123",
|
||||
loginName: "user@example.com",
|
||||
organizationId: "org123",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({ result: [mockUser] });
|
||||
mockCreate.mockReturnValue({});
|
||||
mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ error: "Initial User not supported" });
|
||||
});
|
||||
|
||||
test("should handle organization parameter in all redirects", async () => {
|
||||
const mockUser = {
|
||||
userId: "user123",
|
||||
preferredLoginName: "user@example.com",
|
||||
details: { resourceOwner: "org123" },
|
||||
type: { case: "human", value: { email: { email: "user@example.com" } } },
|
||||
state: UserState.ACTIVE,
|
||||
};
|
||||
|
||||
const mockSession = {
|
||||
factors: {
|
||||
user: {
|
||||
id: "user123",
|
||||
loginName: "user@example.com",
|
||||
organizationId: "org123",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
|
||||
mockSearchUsers.mockResolvedValue({ result: [mockUser] });
|
||||
mockCreate.mockReturnValue({});
|
||||
mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
|
||||
mockListAuthenticationMethodTypes.mockResolvedValue({
|
||||
authMethodTypes: [AuthenticationMethodType.PASSWORD],
|
||||
});
|
||||
|
||||
const result = await sendLoginname({
|
||||
loginName: "user@example.com",
|
||||
organization: "custom-org",
|
||||
requestId: "req123",
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.redirect).toContain("organization=custom-org");
|
||||
expect(result?.redirect).toContain("requestId=req123");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -255,6 +255,12 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
switch (method) {
|
||||
case AuthenticationMethodType.PASSWORD: // user has only password as auth method
|
||||
if (!userLoginSettings?.allowUsernamePassword) {
|
||||
// Check if user has IDPs available as alternative, that could eventually be used to register/link.
|
||||
const idpResp = await redirectUserToIDP(userId);
|
||||
if (idpResp?.redirect) {
|
||||
return idpResp;
|
||||
}
|
||||
|
||||
return {
|
||||
error: "Username Password not allowed! Contact your administrator for more information.",
|
||||
};
|
||||
@@ -312,7 +318,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) {
|
||||
const passkeyParams = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName,
|
||||
altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option
|
||||
altPassword: `${methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) && userLoginSettings?.allowUsernamePassword}`, // show alternative password option only if allowed
|
||||
});
|
||||
|
||||
if (command.requestId) {
|
||||
@@ -327,7 +333,14 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
} else if (methods.authMethodTypes.includes(AuthenticationMethodType.IDP)) {
|
||||
return redirectUserToIDP(userId);
|
||||
} else if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)) {
|
||||
// user has no passkey setup and login settings allow passkeys
|
||||
// Check if password authentication is allowed
|
||||
if (!userLoginSettings?.allowUsernamePassword) {
|
||||
return {
|
||||
error: "Username Password not allowed! Contact your administrator for more information.",
|
||||
};
|
||||
}
|
||||
|
||||
// user has no passkey setup and login settings allow passwords
|
||||
const paramsPasswordDefault = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user