import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; import { beginTwoFactor, confirmTwoFactor, disableTwoFactor } from "@/actions/twofactor"; import { ContentCard } from "@/components/public/ui"; import { LaravelEncrypter } from "@/lib/auth/laravel-encrypter"; import { totpKeyUri } from "@/lib/auth/totp"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; import { env } from "@/env"; export const dynamic = "force-dynamic"; export default async function TwoFactorPage({ searchParams, }: { searchParams: Promise<{ error?: string; enabled?: string; disabled?: string }>; }) { const t = await getTranslations("pages.settings2fa"); const session = await auth(); if (!session?.user?.id) redirect("/login"); const sp = await searchParams; const id = Number(session.user.id); let user: { twoFactorSecret: string | null; twoFactorConfirmedAt: Date | null; twoFactorRecoveryCodes: string | null; } | null = null; try { user = await prisma.user.findUnique({ where: { id }, select: { twoFactorSecret: true, twoFactorConfirmedAt: true, twoFactorRecoveryCodes: true }, }); } catch { user = null; } const hasAppKey = Boolean(env.APP_KEY); const enabled = Boolean(user?.twoFactorConfirmedAt); const pending = Boolean(user?.twoFactorSecret && !user?.twoFactorConfirmedAt); const hotelName = (await siteSettings.get("hotel_name", "Atom")) ?? "Atom"; let secret = ""; let uri = ""; let recoveryCodes: string[] = []; if (pending && hasAppKey && user?.twoFactorSecret) { try { secret = new LaravelEncrypter(env.APP_KEY as string).decrypt(user.twoFactorSecret); uri = totpKeyUri(secret, session.user.name ?? "user", hotelName); if (user.twoFactorRecoveryCodes) { recoveryCodes = JSON.parse(user.twoFactorRecoveryCodes) as string[]; } } catch { secret = ""; } } return (

{t("backToSettings")}

{sp.enabled ? ( <>

{t("nowEnabled")}

Recovery Codes

Store these one-time use codes in a safe place. Each can be used once if you lose access to your authenticator app.

{recoveryCodes.map((code) => ( {code} ))}
) : null} {sp.disabled ? (

{t("disabledNotice")}

) : null} {sp.error === "badcode" ? (

{t("badCode")}

) : null} {sp.error === "ratelimit" ? (

{t("rateLimit")}

) : null} {!hasAppKey ? (

{t("noAppKeyPrefix")} APP_KEY {t("noAppKeyMiddle")} APP_KEY {t("noAppKeySuffix")}

) : enabled ? ( <>

{t("isEnabled")} {t("onYourAccount")}

) : pending ? ( <>

{t("scanOrEnter")}

{t("scanInstructions")}

{t("manualKey")} {secret}

{uri}

) : ( <>

{t("enableIntro")}

)}
); }