fix(login): add email verification check before callback (#10516)

Closes https://github.com/zitadel/typescript/issues/539

This PR adds an additional email verification check before completing an
auth flow, if the environment configuration `EMAIL_VERIFICATION` asks
for it.

# Which Problems Are Solved

https://github.com/zitadel/typescript/issues/539

# How the Problems Are Solved

Adds an additional check before completing an auth flow
This commit is contained in:
Max Peintner
2025-08-25 13:39:37 +02:00
committed by GitHub
parent 89221fa402
commit b23c0bc6ad

View File

@@ -5,11 +5,7 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { getMostRecentCookieWithLoginname } from "./cookies";
import {
getLoginSettings,
getSession,
listAuthenticationMethodTypes,
} from "./zitadel";
import { getLoginSettings, getSession, getUserByID, listAuthenticationMethodTypes } from "./zitadel";
type LoadMostRecentSessionParams = {
serviceUrl: string;
@@ -39,13 +35,7 @@ export async function loadMostRecentSession({
* mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
* to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
**/
export async function isSessionValid({
serviceUrl,
session,
}: {
serviceUrl: string;
session: Session;
}): Promise<boolean> {
export async function isSessionValid({ serviceUrl, session }: { serviceUrl: string; session: Session }): Promise<boolean> {
// session can't be checked without user
if (!session.factors?.user) {
console.warn("Session has no user");
@@ -63,43 +53,22 @@ export async function isSessionValid({
if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) {
mfaValid = !!session.factors.totp?.verifiedAt;
if (!mfaValid) {
console.warn(
"Session has no valid totpEmail factor",
session.factors.totp?.verifiedAt,
);
console.warn("Session has no valid totpEmail factor", session.factors.totp?.verifiedAt);
}
} else if (
authMethods &&
authMethods.includes(AuthenticationMethodType.OTP_EMAIL)
) {
} else if (authMethods && authMethods.includes(AuthenticationMethodType.OTP_EMAIL)) {
mfaValid = !!session.factors.otpEmail?.verifiedAt;
if (!mfaValid) {
console.warn(
"Session has no valid otpEmail factor",
session.factors.otpEmail?.verifiedAt,
);
console.warn("Session has no valid otpEmail factor", session.factors.otpEmail?.verifiedAt);
}
} else if (
authMethods &&
authMethods.includes(AuthenticationMethodType.OTP_SMS)
) {
} else if (authMethods && authMethods.includes(AuthenticationMethodType.OTP_SMS)) {
mfaValid = !!session.factors.otpSms?.verifiedAt;
if (!mfaValid) {
console.warn(
"Session has no valid otpSms factor",
session.factors.otpSms?.verifiedAt,
);
console.warn("Session has no valid otpSms factor", session.factors.otpSms?.verifiedAt);
}
} else if (
authMethods &&
authMethods.includes(AuthenticationMethodType.U2F)
) {
} else if (authMethods && authMethods.includes(AuthenticationMethodType.U2F)) {
mfaValid = !!session.factors.webAuthN?.verifiedAt;
if (!mfaValid) {
console.warn(
"Session has no valid u2f factor",
session.factors.webAuthN?.verifiedAt,
);
console.warn("Session has no valid u2f factor", session.factors.webAuthN?.verifiedAt);
}
} else {
// only check settings if no auth methods are available, as this would require a setup
@@ -128,22 +97,42 @@ export async function isSessionValid({
const validPasskey = session?.factors?.webAuthN?.verifiedAt;
const validIDP = session?.factors?.intent?.verifiedAt;
const stillValid = session.expirationDate
? timestampDate(session.expirationDate).getTime() > new Date().getTime()
: true;
const stillValid = session.expirationDate ? timestampDate(session.expirationDate).getTime() > new Date().getTime() : true;
if (!stillValid) {
console.warn(
"Session is expired",
session.expirationDate
? timestampDate(session.expirationDate).toDateString()
: "no expiration date",
session.expirationDate ? timestampDate(session.expirationDate).toDateString() : "no expiration date",
);
return false;
}
const validChecks = !!(validPassword || validPasskey || validIDP);
return stillValid && validChecks && mfaValid;
if (!validChecks) {
return false;
}
if (!mfaValid) {
return false;
}
// Check email verification if EMAIL_VERIFICATION environment variable is enabled
if (process.env.EMAIL_VERIFICATION === "true") {
const userResponse = await getUserByID({
serviceUrl,
userId: session.factors.user.id,
});
const humanUser = userResponse?.user?.type.case === "human" ? userResponse?.user.type.value : undefined;
if (humanUser && !humanUser.email?.isVerified) {
console.warn("Session invalid: Email not verified and EMAIL_VERIFICATION is enabled", session.factors.user.id);
return false;
}
}
return true;
}
export async function findValidSession({