diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index e7c9f5e715..d56af5e582 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -12,13 +12,16 @@ import { verifyTOTPRegistration, sendEmailCode as zitadelSendEmailCode, } from "@/lib/zitadel"; +import crypto from "crypto"; + import { create } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { headers } from "next/headers"; +import { cookies, headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; +import { getFingerprintId } from "../fingerprint"; import { getServiceUrlFromHeaders } from "../service-url"; import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; @@ -193,6 +196,26 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { if (session.factors?.user?.loginName) { params.set("loginName", session.factors?.user?.loginName); } + + // set hash of userId and userAgentId to prevent replay attacks, TODO: check on the /authenticator/set page + + const cookiesList = await cookies(); + + const userAgentId = await getFingerprintId(); + + const verificationCheck = crypto + .createHash("sha256") + .update(`${user.userId}:${userAgentId}`) + .digest("hex"); + + await cookiesList.set({ + name: "verificationCheck", + value: verificationCheck, + httpOnly: true, + path: "/", + maxAge: 300, // 5 minutes + }); + return { redirect: `/authenticator/set?${params}` }; } diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 508baf1667..409e63e6cf 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -45,8 +45,10 @@ import { VerifyPasskeyRegistrationRequest, VerifyU2FRegistrationRequest, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import crypto from "crypto"; import { unstable_cacheLife as cacheLife } from "next/cache"; -import { getUserAgent } from "./fingerprint"; +import { cookies } from "next/headers"; +import { getFingerprintId, getUserAgent } from "./fingerprint"; import { createServiceForHost } from "./service"; const useCache = process.env.DEBUG !== "true"; @@ -1198,6 +1200,25 @@ export async function setUserPassword({ !(authmethods.authMethodTypes.length === 0) && user.state !== UserState.INITIAL ) { + // check if a verification was done earlier + + const cookiesList = await cookies(); + + const userAgentId = await getFingerprintId(); + + const verificationCheck = crypto + .createHash("sha256") + .update(`${user.userId}:${userAgentId}`) + .digest("hex"); + + await cookiesList.set({ + name: "verificationCheck", + value: verificationCheck, + httpOnly: true, + path: "/", + maxAge: 300, // 5 minutes + }); + return { error: "Provide a code to set a password" }; } }