Files
web/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/otp/page.tsx
Joakim Jäderberg 46ebbbba8f Merged in feature/sas-login (pull request #1256)
First steps towards the SAS partnership

* otp flow now pretends to do the linking

* Update LinkAccountForm header

* Update redirect times

* Clean up comments

* Set maxAge on sas cookies

* make all SAS routes protected

* Merge remote-tracking branch 'refs/remotes/origin/feature/sas-login' into feature/sas-login

* Require auth for sas link flow

* Fix resend otp

* Add error support to OneTimePasswordForm

* Add Sentry to SAS error boundary

* Move SAS_REQUEST_OTP_STATE_STORAGE_COOKIE_NAME

* Add missing translations

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/sas-login

* Merge branch 'feature/sas-login' of bitbucket.org:scandic-swap/web into feature/sas-login

* Add TooManyCodesError component

* Refactor GenericError to support new errors

* Add FailedAttemptsError

* remove removed component <VWOScript/>

* Merge branch 'feature/sas-login' of bitbucket.org:scandic-swap/web into feature/sas-login

* remove local cookie-bot reference

* Fix sas campaign logo scaling

* feature toggle the SAS stuff

* Merge branch 'feature/sas-login' of bitbucket.org:scandic-swap/web into feature/sas-login

* fix: use env vars for SAS endpoints


Approved-by: Linus Flood
2025-02-05 14:43:14 +00:00

129 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<LangParams> & 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 youve 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 (
<OneTimePasswordForm
heading={intl.formatMessage({ id: "Verification code" })}
ingress={intl.formatMessage<React.ReactNode>(
{
id: "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
},
{
maskedContactInfo: () => (
<>
<br />
<strong>{to}</strong>
<br />
</>
),
}
)}
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
}
}