"use server"; import { createSessionAndUpdateCookie, setSessionAndUpdateCookie, } from "@/lib/server/cookie"; import { getLoginSettings, getUserByID, listAuthenticationMethodTypes, listUsers, passwordReset, setPassword, } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Checks, ChecksSchema, } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { getNextUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; type ResetPasswordCommand = { loginName: string; organization?: string; authRequestId?: string; }; export async function resetPassword(command: ResetPasswordCommand) { const host = (await headers()).get("host"); const users = await listUsers({ loginName: command.loginName, organizationId: command.organization, }); if ( !users.details || users.details.totalResult !== BigInt(1) || !users.result[0].userId ) { return { error: "Could not send Password Reset Link" }; } const userId = users.result[0].userId; return passwordReset(userId, host, command.authRequestId); } export type UpdateSessionCommand = { loginName: string; organization?: string; checks: Checks; authRequestId?: string; }; export async function sendPassword(command: UpdateSessionCommand) { let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, organization: command.organization, }).catch((error) => { console.warn("Ignored error:", error); }); let session; let user: User; let loginSettings: LoginSettings | undefined; if (!sessionCookie) { const users = await listUsers({ loginName: command.loginName, organizationId: command.organization, }); if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { user = users.result[0]; const checks = create(ChecksSchema, { user: { search: { case: "userId", value: users.result[0].userId } }, password: { password: command.checks.password?.password }, }); loginSettings = await getLoginSettings(command.organization); session = await createSessionAndUpdateCookie( checks, undefined, command.authRequestId, loginSettings?.passwordCheckLifetime, ); } // this is a fake error message to hide that the user does not even exist return { error: "Could not verify password" }; } else { session = await setSessionAndUpdateCookie( sessionCookie, command.checks, undefined, command.authRequestId, loginSettings?.passwordCheckLifetime, ); if (!session?.factors?.user?.id) { return { error: "Could not create session for user" }; } const userResponse = await getUserByID(session?.factors?.user?.id); if (!userResponse.user) { return { error: "Could not find user" }; } user = userResponse.user; } if (!loginSettings) { loginSettings = await getLoginSettings( command.organization ?? session.factors?.user?.organizationId, ); } if (!session?.factors?.user?.id || !sessionCookie) { return { error: "Could not create session for user" }; } // if password, check if user has MFA methods let authMethods; if (command.checks && command.checks.password && session.factors?.user?.id) { const response = await listAuthenticationMethodTypes( session.factors.user.id, ); if (response.authMethodTypes && response.authMethodTypes.length) { authMethods = response.authMethodTypes; } } if (!authMethods || !session.factors?.user?.loginName) { return { error: "Could not verify password!" }; } const availableSecondFactors = authMethods?.filter( (m: AuthenticationMethodType) => m !== AuthenticationMethodType.PASSWORD && m !== AuthenticationMethodType.PASSKEY, ); const humanUser = user.type.case === "human" ? user.type.value : undefined; console.log("humanUser", humanUser); if ( availableSecondFactors?.length == 0 && humanUser?.passwordChangeRequired ) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName, }); if (command.organization || session.factors?.user?.organizationId) { params.append("organization", session.factors?.user?.organizationId); } if (command.authRequestId) { params.append("authRequestId", command.authRequestId); } return { redirect: "/password/change?" + params }; } if (availableSecondFactors?.length == 1) { const params = new URLSearchParams({ loginName: session.factors?.user.loginName, }); if (command.authRequestId) { params.append("authRequestId", command.authRequestId); } if (command.organization || session.factors?.user?.organizationId) { params.append( "organization", command.organization ?? session.factors?.user?.organizationId, ); } const factor = availableSecondFactors[0]; // if passwordless is other method, but user selected password as alternative, perform a login if (factor === AuthenticationMethodType.TOTP) { return { redirect: `/otp/time-based?` + params }; } else if (factor === AuthenticationMethodType.OTP_SMS) { return { redirect: `/otp/sms?` + params }; } else if (factor === AuthenticationMethodType.OTP_EMAIL) { return { redirect: `/otp/email?` + params }; } else if (factor === AuthenticationMethodType.U2F) { return { redirect: `/u2f?` + params }; } } else if (availableSecondFactors?.length >= 1) { const params = new URLSearchParams({ loginName: session.factors.user.loginName, }); if (command.authRequestId) { params.append("authRequestId", command.authRequestId); } if (command.organization || session.factors?.user?.organizationId) { params.append( "organization", command.organization ?? session.factors?.user?.organizationId, ); } return { redirect: `/mfa?` + params }; } // TODO: check if handling of userstate INITIAL is needed // else if (user.state === UserState.INITIAL) { // const params = new URLSearchParams({ // loginName: session.factors.user.loginName, // }); // if (command.authRequestId) { // params.append("authRequestId", command.authRequestId); // } // if (command.organization || session.factors?.user?.organizationId) { // params.append( // "organization", // command.organization ?? session.factors?.user?.organizationId, // ); // } // return { redirect: `/password/change?` + params }; // } else if ( (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) && !availableSecondFactors.length ) { const params = new URLSearchParams({ loginName: session.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 }); if (command.authRequestId) { params.append("authRequestId", command.authRequestId); } if (command.organization || session.factors?.user?.organizationId) { params.append( "organization", command.organization ?? session.factors?.user?.organizationId, ); } // 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 (authRequestId) { // params.append("authRequestId", authRequestId); // } // if (organization) { // params.append("organization", organization); // } // return router.push(`/passkey/set?` + params); // } else if (command.authRequestId && session.id) { const nextUrl = await getNextUrl( { sessionId: session.id, authRequestId: command.authRequestId, organization: command.organization ?? session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); return { redirect: nextUrl }; } const url = await getNextUrl( { loginName: session.factors.user.loginName, organization: session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); return { redirect: url }; } export async function changePassword(command: { code?: string; userId: string; password: string; }) { // check for init state const { user } = await getUserByID(command.userId); if (!user || user.userId !== command.userId) { return { error: "Could not send Password Reset Link" }; } const userId = user.userId; return setPassword(userId, command.password, user, command.code); }