"use client"; import React, { useCallback, useEffect, useState } from "react"; import { useTranslations } from "../../../../i18n/provider"; export default function UserSettingsPage() { const t = useTranslations(); const [mfaEnabled, setMfaEnabled] = useState(false); const [qrSecret, setQrSecret] = useState(null); const [qrUri, setQrUri] = useState(null); const [totpCode, setTotpCode] = useState(""); const [recoveryCodes, setRecoveryCodes] = useState(null); const [appPasswords, setAppPasswords] = useState<{ id: string; label: string; created_at?: string }[]>([]); const [newAppPassLabel, setNewAppPassLabel] = useState(""); const [generatedAppPass, setGeneratedAppPass] = useState(null); const [message, setMessage] = useState(null); const startTotpEnrollment = async () => { try { const res = await fetch("/api/v1/user/mfa/totp/enroll", { method: "POST" }); const data = await res.json(); setQrSecret(data.secret); setQrUri(data.uri); } catch { setMessage("Failed to initiate 2FA enrollment"); } }; const confirmTotpEnrollment = async (e: React.FormEvent) => { e.preventDefault(); try { const res = await fetch("/api/v1/user/mfa/totp/confirm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code: totpCode }), }); const data = await res.json(); if (!res.ok) throw new Error(data.message || "TOTP verification failed"); setMfaEnabled(true); setRecoveryCodes(data.recovery_codes); setQrSecret(null); setMessage("2FA successfully enabled! Save your recovery codes."); } catch (err: unknown) { setMessage(err instanceof Error ? err.message : "Error confirming 2FA"); } }; const loadAppPasswords = useCallback(async () => { try { const res = await fetch("/api/v1/user/app-passwords"); if (res.ok) setAppPasswords(await res.json()); } catch { // Leaving the list as it is beats replacing it with an empty one. } }, []); const loadProfile = useCallback(async () => { try { const res = await fetch("/api/v1/user/me"); if (res.ok) { const data = await res.json(); setMfaEnabled(Boolean(data.mfa_enrolled)); } } catch { // Non-fatal: the enrollment panel simply starts collapsed. } }, []); useEffect(() => { void loadProfile(); void loadAppPasswords(); }, [loadProfile, loadAppPasswords]); const createNewAppPassword = async (e: React.FormEvent) => { e.preventDefault(); if (!newAppPassLabel) return; try { const res = await fetch("/api/v1/user/app-passwords", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label: newAppPassLabel }), }); const data = await res.json(); if (!res.ok) throw new Error(data.message || "Could not create app password"); // Shown once; the server keeps only an Argon2id hash of it. setGeneratedAppPass(data.password); setNewAppPassLabel(""); await loadAppPasswords(); } catch (err: unknown) { setMessage(err instanceof Error ? err.message : "Error creating app password"); } }; const revokeAppPassword = async (id: string) => { await fetch(`/api/v1/user/app-passwords/${id}`, { method: "DELETE" }); await loadAppPasswords(); }; return (

{t("settings.title")}

Manage 2FA TOTP (RFC 6238), App Passwords, and active webmail sessions.

{message && (
{message}
)} {/* 2FA TOTP SECTION */}

{t("ui.twoFactorSection")}

Protect your webmail session with Google Authenticator, Aegis, Authy, or 1Password.

{mfaEnabled ? "2FA ENABLED" : "2FA DISABLED"}
{!mfaEnabled && !qrSecret && ( )} {qrSecret && (
Step 1: Scan this URI in your authenticator app or copy the secret key.
Secret: {qrSecret}
URI: {qrUri}
setTotpCode(e.target.value)} placeholder="Enter 6-digit code" maxLength={6} required className="px-4 py-2 rounded-lg bg-slate-950 border border-slate-800 text-white text-xs font-mono text-center tracking-wider focus:outline-none focus:border-indigo-500" />
)} {recoveryCodes && (

10 Recovery Codes (Argon2id Hashed)

Store these recovery codes in a safe password manager. Each code can be used once.

{recoveryCodes.map((code, idx) => (
{code}
))}
)}
{/* APP PASSWORDS SECTION (ADR-010) */}

{t("ui.appPasswordsSection")}

Thunderbird, iOS Mail, and Android clients do not support 2FA. Generate dedicated high-entropy passwords for IMAP/SMTP.

setNewAppPassLabel(e.target.value)} placeholder="App Label (e.g. Thunderbird Laptop)" required className="flex-1 px-4 py-2 text-xs rounded-lg bg-slate-900 border border-slate-800 text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500" />
{generatedAppPass && (
{t("ui.appPasswordGenerated")}
{generatedAppPass}

{t("ui.copyPasswordNow")}

)}
{appPasswords.length === 0 ? (
{t("ui.noAppPasswords")}
) : ( appPasswords.map((ap) => (
{ap.label} {ap.created_at ? new Date(ap.created_at).toLocaleDateString() : ""}
)) )}
{/* ACTIVE SESSIONS SECTION */}

{t("ui.activeSessions")}

Individually revocable sessions with surface isolation.

{t("ui.currentSession")}
Cookie: Path=/user | Aud: lambdamail:user
{t("ui.active")}
); }