implement mfa init prompt

This commit is contained in:
Max Peintner
2025-02-26 09:48:52 +01:00
parent 5a22cff831
commit 2e5e4f87d5
9 changed files with 62 additions and 36 deletions

View File

@@ -71,7 +71,8 @@
}, },
"set": { "set": {
"title": "2-Faktor einrichten", "title": "2-Faktor einrichten",
"description": "Wählen Sie einen der folgenden zweiten Faktoren." "description": "Wählen Sie einen der folgenden zweiten Faktoren.",
"skip": "Überspringen"
} }
}, },
"otp": { "otp": {

View File

@@ -71,7 +71,8 @@
}, },
"set": { "set": {
"title": "Set up 2-Factor", "title": "Set up 2-Factor",
"description": "Choose one of the following second factors." "description": "Choose one of the following second factors.",
"skip": "Skip"
} }
}, },
"otp": { "otp": {

View File

@@ -71,7 +71,8 @@
}, },
"set": { "set": {
"title": "Configurar autenticación de 2 factores", "title": "Configurar autenticación de 2 factores",
"description": "Elige uno de los siguientes factores secundarios." "description": "Elige uno de los siguientes factores secundarios.",
"skip": "Omitir"
} }
}, },
"otp": { "otp": {

View File

@@ -71,7 +71,8 @@
}, },
"set": { "set": {
"title": "Configura l'autenticazione a 2 fattori", "title": "Configura l'autenticazione a 2 fattori",
"description": "Scegli uno dei seguenti secondi fattori." "description": "Scegli uno dei seguenti secondi fattori.",
"skip": "Salta"
} }
}, },
"otp": { "otp": {

View File

@@ -71,7 +71,8 @@
}, },
"set": { "set": {
"title": "设置双因素认证", "title": "设置双因素认证",
"description": "选择以下的一个第二因素。" "description": "选择以下的一个第二因素。",
"skip": "跳过"
} }
}, },
"otp": { "otp": {

View File

@@ -161,6 +161,12 @@ export default async function Page(props: {
></ChooseSecondFactorToSetup> ></ChooseSecondFactorToSetup>
)} )}
{force !== "true" && (
<div>
<p>{t("set.skip")}</p>
</div>
)}
<div className="mt-8 flex w-full flex-row items-center"> <div className="mt-8 flex w-full flex-row items-center">
<BackButton /> <BackButton />
<span className="flex-grow"></span> <span className="flex-grow"></span>

View File

@@ -16,7 +16,7 @@ import {
setPassword, setPassword,
setUserPassword, setUserPassword,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { create } from "@zitadel/client"; import { ConnectError, create } from "@zitadel/client";
import { createServerTransport } from "@zitadel/client/node"; import { createServerTransport } from "@zitadel/client/node";
import { createUserServiceClient } from "@zitadel/client/v2"; import { createUserServiceClient } from "@zitadel/client/v2";
import { import {
@@ -267,7 +267,8 @@ export async function sendPassword(command: UpdateSessionCommand) {
return { error: "Could not verify password!" }; return { error: "Could not verify password!" };
} }
const mfaFactorCheck = checkMFAFactors( const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session, session,
loginSettings, loginSettings,
authMethods, authMethods,
@@ -433,7 +434,7 @@ export async function checkSessionAndSetPassword({
}, },
{}, {},
) )
.catch((error) => { .catch((error: ConnectError) => {
console.log(error); console.log(error);
if (error.code === 7) { if (error.code === 7) {
return { error: "Session is not valid." }; return { error: "Session is not valid." };

View File

@@ -203,7 +203,8 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
} }
// redirect to mfa factor if user has one, or redirect to set one up // redirect to mfa factor if user has one, or redirect to set one up
const mfaFactorCheck = checkMFAFactors( const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session, session,
loginSettings, loginSettings,
authMethodResponse.authMethodTypes, authMethodResponse.authMethodTypes,
@@ -407,12 +408,12 @@ export async function sendVerificationRedirectWithoutCheck(
const loginSettings = await getLoginSettings({ const loginSettings = await getLoginSettings({
serviceUrl, serviceUrl,
organization: user.details?.resourceOwner, organization: user.details?.resourceOwner,
}); });
// redirect to mfa factor if user has one, or redirect to set one up // redirect to mfa factor if user has one, or redirect to set one up
const mfaFactorCheck = checkMFAFactors( const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session, session,
loginSettings, loginSettings,
authMethodResponse.authMethodTypes, authMethodResponse.authMethodTypes,

View File

@@ -5,6 +5,7 @@ import { PasswordExpirySettings } from "@zitadel/proto/zitadel/settings/v2/passw
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import moment from "moment"; import moment from "moment";
import { getUserByID } from "./zitadel";
export function checkPasswordChangeRequired( export function checkPasswordChangeRequired(
expirySettings: PasswordExpirySettings | undefined, expirySettings: PasswordExpirySettings | undefined,
@@ -100,7 +101,8 @@ export function checkEmailVerification(
} }
} }
export function checkMFAFactors( export async function checkMFAFactors(
serviceUrl: string,
session: Session, session: Session,
loginSettings: LoginSettings | undefined, loginSettings: LoginSettings | undefined,
authMethods: AuthenticationMethodType[], authMethods: AuthenticationMethodType[],
@@ -188,31 +190,42 @@ export function checkMFAFactors(
); );
} }
// TODO: provide a way to setup passkeys on mfa page?
return { redirect: `/mfa/set?` + params };
} else if (
loginSettings?.mfaInitSkipLifetime &&
(loginSettings.mfaInitSkipLifetime.nanos > 0 ||
loginSettings.mfaInitSkipLifetime.seconds > 0) &&
!availableMultiFactors.length &&
session?.factors?.user?.id
) {
const user = await getUserByID({
serviceUrl,
userId: session.factors?.user?.id,
});
if (
user.user?.type?.case === "human" &&
user.user?.type?.value.mfaInitSkipped
) {
}
const params = new URLSearchParams({
loginName: session.factors?.user?.loginName as string,
force: "false", // this defines if the mfa is not forced in the settings and can be skipped
checkAfter: "true", // this defines if the check is directly made after the setup
});
if (requestId) {
params.append("requestId", requestId);
}
if (organization || session.factors?.user?.organizationId) {
params.append(
"organization",
organization ?? (session.factors?.user?.organizationId as string),
);
}
// TODO: provide a way to setup passkeys on mfa page? // TODO: provide a way to setup passkeys on mfa page?
return { redirect: `/mfa/set?` + params }; return { redirect: `/mfa/set?` + params };
} }
// TODO: implement passkey setup
// 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 (requestId) {
// params.append("requestId", requestId);
// }
// if (organization) {
// params.append("organization", organization);
// }
// return router.push(`/passkey/set?` + params);
// }
} }