"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { startRegistration } from "@simplewebauthn/browser"; import { api } from "./api"; export function SecurityPanel({ email, totpEnabled, passkeys, mustChange }: { email: string; totpEnabled: boolean; passkeys: any[]; mustChange: boolean }) { const r = useRouter(); const [msg, setMsg] = useState(""); const [setup, setSetup] = useState(null); const [sessions, setSessions] = useState([]); useEffect(() => { api("/api/auth/sessions").then(setSessions).catch(() => {}); }, []); const note = (t: string) => setMsg(t); return
{msg &&

{msg}

}

Password

{email} · at least 14 characters with upper/lower case and a digit. Changing it signs out other sessions.

{ e.preventDefault(); const fd = new FormData(e.currentTarget); try { await api("/api/auth/password", { method: "POST", body: JSON.stringify({ current: fd.get("current"), next: fd.get("next") }) }); note("Password changed."); if (mustChange) r.push("/admin"); r.refresh(); } catch (e) { note((e as Error).message); } }}>

Authenticator app (TOTP) — {totpEnabled ? enabled : disabled}

{!totpEnabled ? (!setup ? :
{ e.preventDefault(); const code = new FormData(e.currentTarget).get("code"); try { await api("/api/auth/totp", { method: "POST", body: JSON.stringify({ step: "enable", code, secret: setup.secret }) }); note("TOTP enabled. Other sessions were signed out."); setSetup(null); r.refresh(); } catch (e) { note((e as Error).message); } }}>TOTP QR code

Scan with your authenticator, then enter the 6-digit code.

{setup.secret}

) :
{ e.preventDefault(); const code = new FormData(e.currentTarget).get("code"); try { await api("/api/auth/totp", { method: "POST", body: JSON.stringify({ step: "disable", code }) }); note("TOTP disabled."); r.refresh(); } catch (e) { note((e as Error).message); } }}>
}

Passkeys (WebAuthn)

Passkeys sign in with Face ID / Touch ID / a security key and count as MFA. Recommended as the primary method.

    {passkeys.map((p) =>
  • {p.label} · added {new Date(p.createdAt).toLocaleDateString("en-CA")}{p.lastUsedAt ? ` · used ${new Date(p.lastUsedAt).toLocaleDateString("en-CA")}` : ""}
  • )}{!passkeys.length &&
  • No passkeys registered.
  • }

Active sessions

    {sessions.map((s) =>
  • {s.current ? "This session" : "Session"} · {s.ip} · {s.userAgent?.slice(0, 60)} · last seen {new Date(s.lastSeenAt).toLocaleString("en-CA")}{!s.current && }
  • )}
{sessions.length > 1 && }
; }