fix: refactor user discovery search

This commit is contained in:
Max Peintner
2024-12-06 10:53:44 +01:00
parent 5b6848780b
commit 2e5b3b710e
2 changed files with 100 additions and 49 deletions

View File

@@ -16,6 +16,7 @@ import {
listAuthenticationMethodTypes,
listIDPLinks,
listUsers,
ListUsersCommand,
startIdentityProviderFlow,
} from "../zitadel";
import { createSessionAndUpdateCookie } from "./cookie";
@@ -29,21 +30,22 @@ export type SendLoginnameCommand = {
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
export async function sendLoginname(command: SendLoginnameCommand) {
const users = await listUsers({
loginName: command.loginName,
const loginSettingsByContext = await getLoginSettings(command.organization);
let listUsersRequest: ListUsersCommand = {
userName: command.loginName,
organizationId: command.organization,
});
};
const loginSettings = await getLoginSettings(command.organization);
if (!loginSettingsByContext?.disableLoginWithEmail) {
listUsersRequest.email = command.loginName;
}
const potentialUsers = users.result.filter((u) => {
const human = u.type.case === "human" ? u.type.value : undefined;
return loginSettings?.disableLoginWithEmail
? human?.email?.isVerified && human?.email?.email !== command.loginName
: loginSettings?.disableLoginWithPhone
? human?.phone?.isVerified && human?.phone?.phone !== command.loginName
: true;
});
if (!loginSettingsByContext?.disableLoginWithPhone) {
listUsersRequest.phone = command.loginName;
}
const { result: potentialUsers } = await listUsers(listUsersRequest);
const redirectUserToSingleIDPIfAvailable = async () => {
const identityProviders = await getActiveIdentityProviders(
@@ -144,9 +146,16 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
};
if (potentialUsers.length == 1 && potentialUsers[0].userId) {
if (potentialUsers.length > 1) {
return { error: "More than one user found. Provide a unique identifier." };
} else if (potentialUsers.length == 1 && potentialUsers[0].userId) {
const user = potentialUsers[0];
const userId = potentialUsers[0].userId;
const userLoginSettings = await getLoginSettings(
user.details?.resourceOwner,
);
const checks = create(ChecksSchema, {
user: { search: { case: "userId", value: userId } },
});
@@ -162,7 +171,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
// TODO: check if handling of userstate INITIAL is needed
if (potentialUsers[0].state === UserState.INITIAL) {
if (user.state === UserState.INITIAL) {
return { error: "Initial User not supported" };
}
@@ -172,9 +181,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
if (
potentialUsers[0].type.case === "human" &&
potentialUsers[0].type.value.email &&
!potentialUsers[0].type.value.email.isVerified
user.type.case === "human" &&
user.type.value.email &&
!user.type.value.email.isVerified
) {
const paramsVerify = new URLSearchParams({
loginName: session.factors?.user?.loginName,
@@ -219,7 +228,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const method = methods.authMethodTypes[0];
switch (method) {
case AuthenticationMethodType.PASSWORD: // user has only password as auth method
if (!loginSettings?.allowUsernamePassword) {
if (!userLoginSettings?.allowUsernamePassword) {
return {
error:
"Username Password not allowed! Contact your administrator for more information.",
@@ -246,7 +255,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
};
case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
if (loginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) {
if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) {
return {
error:
"Passkeys not allowed! Contact your administrator for more information.",
@@ -309,22 +318,24 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
}
// user not found, check if register is enabled on organization
if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) {
// TODO: do we need to handle login hints for IDPs here?
// user not found, check if register is enabled on instance / organization context
if (
loginSettingsByContext?.allowRegister &&
!loginSettingsByContext?.allowUsernamePassword
) {
const resp = await redirectUserToSingleIDPIfAvailable();
if (resp) {
return resp;
}
return { error: "Could not find user" };
} else if (
loginSettings?.allowRegister &&
loginSettings?.allowUsernamePassword
loginSettingsByContext?.allowRegister &&
loginSettingsByContext?.allowUsernamePassword
) {
let orgToRegisterOn: string | undefined = command.organization;
if (
!loginSettings?.ignoreUnknownUsernames &&
!loginSettingsByContext?.ignoreUnknownUsernames &&
!orgToRegisterOn &&
command.loginName &&
ORG_SUFFIX_REGEX.test(command.loginName)
@@ -344,7 +355,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
// do not register user if ignoreUnknownUsernames is set
if (orgToRegisterOn && !loginSettings?.ignoreUnknownUsernames) {
if (orgToRegisterOn && !loginSettingsByContext?.ignoreUnknownUsernames) {
const params = new URLSearchParams({ organization: orgToRegisterOn });
if (command.authRequestId) {
@@ -358,7 +369,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
}
if (loginSettings?.ignoreUnknownUsernames) {
if (loginSettingsByContext?.ignoreUnknownUsernames) {
const paramsPasswordDefault = new URLSearchParams({
loginName: command.loginName,
});

View File

@@ -291,19 +291,24 @@ export async function createInviteCode(userId: string, host: string | null) {
);
}
export async function listUsers({
loginName,
userName,
email,
organizationId,
}: {
export type ListUsersCommand = {
loginName?: string;
userName?: string;
email?: string;
phone?: string;
organizationId?: string;
}) {
};
export async function listUsers({
loginName,
userName,
phone,
email,
organizationId,
}: ListUsersCommand) {
const queries: SearchQuery[] = [];
// either loginName or userName and email or phone are required
if (loginName) {
queries.push(
create(SearchQuerySchema, {
@@ -316,9 +321,57 @@ export async function listUsers({
},
}),
);
}
} else if (userName && (email || phone)) {
const userNameQuery = create(SearchQuerySchema, {
query: {
case: "userNameQuery",
value: {
userName: userName,
method: TextQueryMethod.EQUALS,
},
},
});
if (userName) {
const orQueries: SearchQuery[] = [userNameQuery];
if (email) {
const emailQuery = create(SearchQuerySchema, {
query: {
case: "emailQuery",
value: {
emailAddress: email,
method: TextQueryMethod.EQUALS,
},
},
});
orQueries.push(emailQuery);
}
if (phone) {
// TODO add after https://github.com/zitadel/zitadel/issues/9016 is merged
// const phoneQuery = create(SearchQuerySchema, {
// query: {
// case: "phoneQuery",
// value: {
// emailAddress: email,
// method: TextQueryMethod.EQUALS,
// },
// },
// });
// orQueries.push(phoneQuery);
}
queries.push(
create(SearchQuerySchema, {
query: {
case: "orQuery",
value: {
queries: orQueries,
},
},
}),
);
} else if (userName) {
queries.push(
create(SearchQuerySchema, {
query: {
@@ -345,19 +398,6 @@ export async function listUsers({
);
}
if (email) {
queries.push(
create(SearchQuerySchema, {
query: {
case: "emailQuery",
value: {
emailAddress: email,
},
},
}),
);
}
return userService.listUsers({ queries: queries });
}