mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-11 20:02:34 +00:00
loginname to ignore usernames, description
This commit is contained in:
@@ -43,3 +43,33 @@ This is going to be our next UI for the hosted login. It's based on Next.js 13 a
|
||||
passkey-- notVerified --> verify
|
||||
verify --> B[signedin]
|
||||
```
|
||||
|
||||
### /loginname
|
||||
|
||||
This page shows a loginname field and Identity Providers to login or register.
|
||||
If `loginSettings(org?).allowRegister` is `true`, if will also show a link to jump to /register
|
||||
|
||||
- `getLoginSettings(org?)`
|
||||
- `getLegalAndSupportSettings(org?)`
|
||||
- `getIdentityProviders(org?)`
|
||||
- `getBrandingSettings(org?)`
|
||||
- `getActiveIdentityProviders(org?)`
|
||||
- `startIdentityProviderFlow`
|
||||
- `listUsers(org?)`
|
||||
- `listAuthenticationMethodTypes`
|
||||
|
||||
After a loginname is entered, a `listUsers` request is made using the loginName query to identify already registered users.
|
||||
|
||||
If only one user is found, we query `listAuthenticationMethodTypes` to identify future steps.
|
||||
If no authentication methods are found, we render an error stating: _User has no available authentication methods._
|
||||
Now if only one method is found, we continue with the corresponding step (/password, /passkey/login).
|
||||
If multiple methods are set, we prefer passkeys over any other method, so we redirect to /passkey, second option is IDP, and third is password.
|
||||
If password is the next step, we check `loginSettings.passkeysType` for PasskeysType.ALLOWED, and prompt the user to setup passkeys afterwards.
|
||||
|
||||
If no user is found, we check whether registering is allowed using `loginSettings.allowRegister`.
|
||||
If `loginSettings?.allowUsernamePassword` is not allowed we continue to check for available IDPs. If a single IDP is available, we directly redirect the user to signup.
|
||||
|
||||
If no single IDP is set, we check for `loginSettings.allowUsernamePassword` and redirect the user to /register page.
|
||||
If no previous condition is met, we check whether `loginSettings?.ignoreUnknownUsernames` is `false` and in such case, we return a user not found error. If not, we redirect to the /password page, regardless (to not leak information about a registered user).
|
||||
|
||||
> NOTE: We ignore `loginSettings.allowExternalIdp` as the information whether IDPs are available comes as response from `getActiveIdentityProviders(org?)`
|
||||
|
||||
@@ -15,6 +15,10 @@ export default async function Page({
|
||||
const { firstname, lastname, email, organization, authRequestId } =
|
||||
searchParams;
|
||||
|
||||
if (!organization) {
|
||||
// TODO: get default organization
|
||||
}
|
||||
|
||||
const setPassword = !!(firstname && lastname && email);
|
||||
|
||||
const legal = await getLegalAndSupportSettings(organization);
|
||||
|
||||
@@ -28,6 +28,45 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
|
||||
const loginSettings = await getLoginSettings(command.organization);
|
||||
|
||||
const redirectUserToSingleIDPIfAvailable = async () => {
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
command.organization,
|
||||
).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
if (identityProviders.length === 1) {
|
||||
const host = headers().get("host");
|
||||
const identityProviderType = identityProviders[0].type;
|
||||
|
||||
const provider = idpTypeToSlug(identityProviderType);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.set("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.set("organization", command.organization);
|
||||
}
|
||||
|
||||
const resp = await startIdentityProviderFlow({
|
||||
idpId: identityProviders[0].id,
|
||||
urls: {
|
||||
successUrl:
|
||||
`${host}/idp/${provider}/success?` + new URLSearchParams(params),
|
||||
failureUrl:
|
||||
`${host}/idp/${provider}/failure?` + new URLSearchParams(params),
|
||||
},
|
||||
});
|
||||
|
||||
if (resp?.nextStep.case === "authUrl") {
|
||||
return redirect(resp.nextStep.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
|
||||
const userId = users.result[0].userId;
|
||||
const session = await createSessionForUserIdAndUpdateCookie(
|
||||
@@ -115,13 +154,14 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
methods.authMethodTypes.includes(AuthenticationMethodType.IDP)
|
||||
) {
|
||||
// TODO: redirect user to idp
|
||||
await redirectUserToSingleIDPIfAvailable();
|
||||
} else if (
|
||||
methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)
|
||||
) {
|
||||
// user has no passkey setup and login settings allow passkeys
|
||||
const paramsPasswordDefault: any = { loginName: command.loginName };
|
||||
|
||||
if (loginSettings?.passkeysType === 1) {
|
||||
if (loginSettings?.passkeysType === PasskeysType.ALLOWED) {
|
||||
paramsPasswordDefault.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||
}
|
||||
|
||||
@@ -146,42 +186,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
|
||||
if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) {
|
||||
// TODO redirect to loginname page with idp hint
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
command.organization,
|
||||
).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
if (identityProviders.length === 1) {
|
||||
const host = headers().get("host");
|
||||
const identityProviderType = identityProviders[0].type;
|
||||
|
||||
const provider = idpTypeToSlug(identityProviderType);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.set("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.set("organization", command.organization);
|
||||
}
|
||||
|
||||
const resp = await startIdentityProviderFlow({
|
||||
idpId: identityProviders[0].id,
|
||||
urls: {
|
||||
successUrl:
|
||||
`${host}/idp/${provider}/success?` + new URLSearchParams(params),
|
||||
failureUrl:
|
||||
`${host}/idp/${provider}/failure?` + new URLSearchParams(params),
|
||||
},
|
||||
});
|
||||
|
||||
if (resp?.nextStep.case === "authUrl") {
|
||||
return redirect(resp.nextStep.value);
|
||||
}
|
||||
}
|
||||
await redirectUserToSingleIDPIfAvailable();
|
||||
|
||||
throw Error("Could not find user");
|
||||
} else if (
|
||||
@@ -205,5 +210,23 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
return redirect(registerUrl);
|
||||
}
|
||||
|
||||
if (loginSettings?.ignoreUnknownUsernames) {
|
||||
const paramsPasswordDefault: any = { loginName: command.loginName };
|
||||
|
||||
if (loginSettings?.passkeysType === PasskeysType.ALLOWED) {
|
||||
paramsPasswordDefault.promptPasswordless = `true`;
|
||||
}
|
||||
|
||||
if (command.authRequestId) {
|
||||
paramsPasswordDefault.authRequestId = command.authRequestId;
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
paramsPasswordDefault.organization = command.organization;
|
||||
}
|
||||
|
||||
return redirect("/password?" + new URLSearchParams(paramsPasswordDefault));
|
||||
}
|
||||
|
||||
throw Error("Could not find user");
|
||||
}
|
||||
|
||||
@@ -59,17 +59,10 @@ export default function UsernameForm({
|
||||
return res;
|
||||
}
|
||||
|
||||
async function setLoginNameAndGetAuthMethods(
|
||||
values: Inputs,
|
||||
organization?: string,
|
||||
) {
|
||||
const response = await submitLoginName(values, organization);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (submit && loginName) {
|
||||
// When we navigate to this page, we always want to be redirected if submit is true and the parameters are valid.
|
||||
setLoginNameAndGetAuthMethods({ loginName }, organization);
|
||||
submitLoginName({ loginName }, organization);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -120,9 +113,7 @@ export default function UsernameForm({
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !formState.isValid}
|
||||
onClick={handleSubmit((e) =>
|
||||
setLoginNameAndGetAuthMethods(e, organization),
|
||||
)}
|
||||
onClick={handleSubmit((e) => submitLoginName(e, organization))}
|
||||
>
|
||||
{loading && <Spinner className="h-5 w-5 mr-2" />}
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user