"use client"; import { getNextUrl } from "@/lib/client"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { Alert, AlertType } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; // either loginName or sessionId must be provided type Props = { loginName?: string; sessionId?: string; authRequestId?: string; organization?: string; method: string; code?: string; loginSettings?: LoginSettings; host: string | null; }; type Inputs = { code: string; }; export function LoginOTP({ loginName, sessionId, authRequestId, organization, method, code, loginSettings, host, }: Props) { const t = useTranslations("otp"); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); const initialized = useRef(false); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { code: code ? code : "", }, }); useEffect(() => { if (!initialized.current && ["email", "sms"].includes(method)) { initialized.current = true; setLoading(true); updateSessionForOTPChallenge() .catch((error) => { setError(error); return; }) .finally(() => { setLoading(false); }); } }, []); async function updateSessionForOTPChallenge() { let challenges; if (method === "email") { challenges = create(RequestChallengesSchema, { otpEmail: { deliveryType: { case: "sendCode", value: host ? { urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` + (authRequestId ? `&authRequestId=${authRequestId}` : ""), } : {}, }, }, }); } if (method === "sms") { challenges = create(RequestChallengesSchema, { otpSms: { returnCode: true }, }); } setLoading(true); const response = await updateSession({ loginName, sessionId, organization, challenges, authRequestId, }) .catch((error) => { setError(error.message ?? "Could not request OTP challenge"); return; }) .finally(() => { setLoading(false); }); return response; } async function submitCode(values: Inputs, organization?: string) { setLoading(true); let body: any = { code: values.code, method, }; if (organization) { body.organization = organization; } if (authRequestId) { body.authRequestId = authRequestId; } let checks; if (method === "sms") { checks = create(ChecksSchema, { otpSms: { code: values.code }, }); } if (method === "email") { checks = create(ChecksSchema, { otpEmail: { code: values.code }, }); } if (method === "time-based") { checks = create(ChecksSchema, { totp: { code: values.code }, }); } const response = await updateSession({ loginName, sessionId, organization, checks, authRequestId, }) .catch(() => { setError("Could not verify OTP code"); return; }) .finally(() => { setLoading(false); }); return response; } function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then(async (response) => { if (response) { const url = authRequestId && response.sessionId ? await getNextUrl( { sessionId: response.sessionId, authRequestId: authRequestId, organization: response.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ) : response.factors?.user ? await getNextUrl( { loginName: response.factors.user.loginName, organization: response.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ) : null; if (url) { router.push(url); } } }); } return (
{["email", "sms"].includes(method) && (
{t("verify.noCodeReceived")}
)}
{error && (
{error}
)}
); }