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": {
"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": {

View File

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

View File

@@ -71,7 +71,8 @@
},
"set": {
"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": {

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ import {
setPassword,
setUserPassword,
} from "@/lib/zitadel";
import { create } from "@zitadel/client";
import { ConnectError, create } from "@zitadel/client";
import { createServerTransport } from "@zitadel/client/node";
import { createUserServiceClient } from "@zitadel/client/v2";
import {
@@ -267,7 +267,8 @@ export async function sendPassword(command: UpdateSessionCommand) {
return { error: "Could not verify password!" };
}
const mfaFactorCheck = checkMFAFactors(
const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session,
loginSettings,
authMethods,
@@ -433,7 +434,7 @@ export async function checkSessionAndSetPassword({
},
{},
)
.catch((error) => {
.catch((error: ConnectError) => {
console.log(error);
if (error.code === 7) {
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
const mfaFactorCheck = checkMFAFactors(
const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session,
loginSettings,
authMethodResponse.authMethodTypes,
@@ -407,12 +408,12 @@ export async function sendVerificationRedirectWithoutCheck(
const loginSettings = await getLoginSettings({
serviceUrl,
organization: user.details?.resourceOwner,
});
// redirect to mfa factor if user has one, or redirect to set one up
const mfaFactorCheck = checkMFAFactors(
const mfaFactorCheck = await checkMFAFactors(
serviceUrl,
session,
loginSettings,
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 { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import moment from "moment";
import { getUserByID } from "./zitadel";
export function checkPasswordChangeRequired(
expirySettings: PasswordExpirySettings | undefined,
@@ -100,7 +101,8 @@ export function checkEmailVerification(
}
}
export function checkMFAFactors(
export async function checkMFAFactors(
serviceUrl: string,
session: Session,
loginSettings: LoginSettings | undefined,
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?
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);
// }
}