import { cookies } from "next/headers" import { redirect, RedirectType } from "next/navigation" import { z } from "zod" import { serverClient } from "@/lib/trpc/server" import { getIntl } from "@/i18n" import { safeTry } from "@/utils/safeTry" import { SAS_TOKEN_STORAGE_KEY } from "../sasUtils" import OneTimePasswordForm from "./OneTimePasswordForm" import type { LangParams, PageArgs, SearchParams } from "@/types/params" import type { Lang } from "@/constants/languages" const searchParamsSchema = z.object({ intent: z.enum(["link"]), to: z.string(), error: z.enum(["invalidCode"]).optional(), }) export default async function SASxScandicOneTimePasswordPage({ searchParams, params, }: PageArgs & SearchParams) { const intl = await getIntl() const cookieStore = cookies() const tokenCookie = cookieStore.get(SAS_TOKEN_STORAGE_KEY) const result = searchParamsSchema.safeParse(searchParams) if (!result.success) { throw new Error("Invalid search params") } const { intent, to, error } = result.data if (!verifyTokenValidity(tokenCookie?.value)) { redirect(`/${params.lang}/sas-x-scandic/login?intent=${intent}`) } const errors = { invalidCode: intl.formatMessage({ id: "The code you’ve entered is incorrect.", }), } async function handleOtpVerified({ otp }: { otp: string }) { "use server" const [data, error] = await safeTry( serverClient().partner.sas.verifyOtp({ otp }) ) // TODO correct status? // TODO handle all errors // STATUS === VERIFIED => ok // STATUS === ABUSED => otpRetryCount > otpMaxRetryCount if (error || data?.status !== "VERIFIED") { const search = new URLSearchParams({ ...searchParams, error: "invalidCode", }).toString() redirect(`/${params.lang}/sas-x-scandic/otp?${search}`) } switch (intent) { case "link": return handleLinkAccount({ lang: params.lang }) default: throw new Error("") } } return ( ( { id: "Please enter the code sent to in order to confirm your account linking.", }, { maskedContactInfo: () => ( <>
{to}
), } )} footnote={intl.formatMessage({ id: "This verifcation is needed for additional security.", })} otpLength={6} onSubmit={handleOtpVerified} error={error ? errors[error] : undefined} /> ) } function verifyTokenValidity(token: string | undefined) { if (!token) { return false } try { const decoded = JSON.parse(atob(token.split(".")[1])) const expiry = decoded.exp * 1000 return Date.now() < expiry } catch (error) { return false } } async function handleLinkAccount({ lang }: { lang: Lang }) { const [res, error] = await safeTry(serverClient().partner.sas.linkAccount()) if (!res || error) { console.error("[SAS] link account error", error) redirect(`/${lang}/sas-x-scandic/error?errorCode=link_error`) } console.log("[SAS] link account response", res) switch (res.linkingState) { case "linked": redirect(`/${lang}/sas-x-scandic/link/success`, RedirectType.replace) break } }