mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-11 19:42:16 +00:00
password page changes, doc
This commit is contained in:
@@ -80,3 +80,25 @@ If no previous condition is met we throw an error stating the user was not found
|
||||
**EXCEPTIONS:** If the outcome after this order produces a no authentication methods found, or user not found, we check whether `loginSettings?.ignoreUnknownUsernames` is set to `true` as in this case 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?)`.
|
||||
|
||||
### /password
|
||||
|
||||
<img src="./screenshots/password.png" alt="/password" width="400px" />
|
||||
|
||||
This page shows a password field to hydrate the current session with password as a factor.
|
||||
Below the password field, a reset password link is shown which allows to send a reset email.
|
||||
|
||||
Requests to the APIs made:
|
||||
|
||||
- `getLoginSettings(org?)`
|
||||
- `getBrandingSettings(org?)`
|
||||
- `listAuthenticationMethodTypes`
|
||||
|
||||
**MFA AVAILABLE:** After the password has been submitted, additional authentication Methods are loaded.
|
||||
If the user has set up an additional **single** second factor, it is redirected to add the next factor. Depending on the available method he is redirected to `/otp/time-based`,`/otp/sms?`, `/otp/email?` or `/u2f?`. If the user has multiple second factors, he is redirected to `/mfa` to select his preferred method to continue.
|
||||
|
||||
**NO MFA, FORCE MFA:** If no MFA method is available, and the settings force MFA, the user is sent to `/mfa/set` which prompts to setup a second factor.
|
||||
|
||||
**PROMPT PASSKEY** If the settings do not enforce MFA, we check if passkeys are allowed with `loginSettings?.passkeysType === PasskeysType.ALLOWED` and redirect the user to `/passkey/add` if no passkeys are setup. This step can be skipped.
|
||||
|
||||
If none of the previous conditions apply, we continue to sign in.
|
||||
|
||||
BIN
apps/login/screenshots/password.png
Normal file
BIN
apps/login/screenshots/password.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
@@ -18,8 +18,14 @@ export default async function Page({
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const { loginName, checkAfter, authRequestId, organization, sessionId } =
|
||||
searchParams;
|
||||
const {
|
||||
loginName,
|
||||
checkAfter,
|
||||
force,
|
||||
authRequestId,
|
||||
organization,
|
||||
sessionId,
|
||||
} = searchParams;
|
||||
|
||||
const sessionWithData = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
|
||||
@@ -10,18 +10,17 @@ export default async function Page({
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const { loginName, promptPasswordless, organization, authRequestId } =
|
||||
searchParams;
|
||||
const { loginName, prompt, organization, authRequestId } = searchParams;
|
||||
|
||||
const session = await loadMostRecentSession({
|
||||
loginName,
|
||||
organization,
|
||||
});
|
||||
|
||||
const title = !!promptPasswordless
|
||||
const title = !!prompt
|
||||
? "Authenticate with a passkey"
|
||||
: "Use your passkey to confirm it's really you";
|
||||
const description = !!promptPasswordless
|
||||
const description = !!prompt
|
||||
? "When set up, you will be able to authenticate without a password."
|
||||
: "Your device will ask for your fingerprint, face, or screen lock";
|
||||
|
||||
@@ -68,7 +67,7 @@ export default async function Page({
|
||||
{session?.id && (
|
||||
<RegisterPasskey
|
||||
sessionId={session.id}
|
||||
isPrompt={!!promptPasswordless}
|
||||
isPrompt={!!prompt}
|
||||
organization={organization}
|
||||
authRequestId={authRequestId}
|
||||
/>
|
||||
|
||||
@@ -4,14 +4,14 @@ import Alert from "@/ui/Alert";
|
||||
import DynamicTheme from "@/ui/DynamicTheme";
|
||||
import PasswordForm from "@/ui/PasswordForm";
|
||||
import UserAvatar from "@/ui/UserAvatar";
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string | number | symbol, string | undefined>;
|
||||
}) {
|
||||
const { loginName, organization, promptPasswordless, authRequestId, alt } =
|
||||
searchParams;
|
||||
const { loginName, organization, authRequestId, alt } = searchParams;
|
||||
|
||||
const sessionFactors = await loadMostRecentSession({
|
||||
loginName,
|
||||
@@ -51,7 +51,9 @@ export default async function Page({
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
loginSettings={loginSettings}
|
||||
promptPasswordless={promptPasswordless === "true"}
|
||||
promptPasswordless={
|
||||
loginSettings?.passkeysType === PasskeysType.ALLOWED
|
||||
}
|
||||
isAlternative={alt === "true"}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use server";
|
||||
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -108,13 +107,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
command.organization ?? session.factors?.user?.organizationId;
|
||||
}
|
||||
|
||||
if (
|
||||
loginSettings?.passkeysType &&
|
||||
loginSettings?.passkeysType === PasskeysType.ALLOWED
|
||||
) {
|
||||
paramsPassword.promptPasswordless = `true`;
|
||||
}
|
||||
|
||||
if (command.authRequestId) {
|
||||
paramsPassword.authRequestId = command.authRequestId;
|
||||
}
|
||||
@@ -164,10 +156,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
// user has no passkey setup and login settings allow passkeys
|
||||
const paramsPasswordDefault: any = { loginName: command.loginName };
|
||||
|
||||
if (loginSettings?.passkeysType === PasskeysType.ALLOWED) {
|
||||
paramsPasswordDefault.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||
}
|
||||
|
||||
if (command.authRequestId) {
|
||||
paramsPasswordDefault.authRequestId = command.authRequestId;
|
||||
}
|
||||
@@ -235,10 +223,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
if (loginSettings?.ignoreUnknownUsernames) {
|
||||
const paramsPasswordDefault: any = { loginName: command.loginName };
|
||||
|
||||
if (loginSettings?.passkeysType === PasskeysType.ALLOWED) {
|
||||
paramsPasswordDefault.promptPasswordless = `true`;
|
||||
}
|
||||
|
||||
if (command.authRequestId) {
|
||||
paramsPasswordDefault.authRequestId = command.authRequestId;
|
||||
}
|
||||
|
||||
@@ -153,29 +153,10 @@ export default function PasswordForm({
|
||||
}
|
||||
|
||||
return router.push(`/mfa?` + params);
|
||||
} else if (
|
||||
submitted.factors &&
|
||||
!submitted.factors.webAuthN && // if session was not verified with a passkey
|
||||
promptPasswordless && // if explicitly prompted due policy
|
||||
!isAlternative // escaped if password was used as an alternative method
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
promptPasswordless: "true",
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization) {
|
||||
params.append("organization", organization);
|
||||
}
|
||||
|
||||
return router.push(`/passkey/add?` + params);
|
||||
} else if (loginSettings?.forceMfa && !availableSecondFactors.length) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
force: "true", // this defines if the mfa is forced in the settings
|
||||
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||
});
|
||||
|
||||
@@ -189,6 +170,26 @@ export default function PasswordForm({
|
||||
|
||||
// TODO: provide a way to setup passkeys on mfa page?
|
||||
return router.push(`/mfa/set?` + params);
|
||||
} else if (
|
||||
submitted.factors &&
|
||||
!submitted.factors.webAuthN && // if session was not verified with a passkey
|
||||
promptPasswordless && // if explicitly prompted due policy
|
||||
!isAlternative // escaped if password was used as an alternative method
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
prompt: "true",
|
||||
});
|
||||
|
||||
if (authRequestId) {
|
||||
params.append("authRequestId", authRequestId);
|
||||
}
|
||||
|
||||
if (organization) {
|
||||
params.append("organization", organization);
|
||||
}
|
||||
|
||||
return router.push(`/passkey/add?` + params);
|
||||
} else if (authRequestId && submitted.sessionId) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: submitted.sessionId,
|
||||
|
||||
Reference in New Issue
Block a user