"use 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 { 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; }; type Inputs = { code: string; }; export function LoginOTP({ loginName, sessionId, authRequestId, organization, method, code, }: 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() .then((response) => { setLoading(false); }) .catch((error) => { setError(error); setLoading(false); }); } }, []); async function updateSessionForOTPChallenge() { let challenges; if (method === "email") { challenges = create(RequestChallengesSchema, { otpEmail: { deliveryType: { case: "sendCode", value: {} } }, }); } 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"); setLoading(false); }); 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((error) => { setError(error.message ?? "Could not verify OTP code"); setLoading(false); }); setLoading(false); return response; } function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then((response) => { if (authRequestId && response && response.sessionId) { const params = new URLSearchParams({ sessionId: response.sessionId, authRequest: authRequestId, }); if (organization) { params.append("organization", organization); } if (authRequestId) { params.append("authRequest", authRequestId); } if (sessionId) { params.append("sessionId", sessionId); } return router.push(`/login?` + params); } else { const params = new URLSearchParams(); if (response?.factors?.user?.loginName) { params.append("loginName", response.factors.user.loginName); } if (authRequestId) { params.append("authRequestId", authRequestId); } if (organization) { params.append("organization", organization); } return router.push(`/signedin?` + params); } }); } return (
{["email", "sms"].includes(method) && (
{t("verify.noCodeReceived")}
)}
{error && (
{error}
)}
); }