"use client"; import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { startRegistration } from "@simplewebauthn/browser"; import { ShieldCheck, ShieldOff, Smartphone, LogOut, Trash2, Fingerprint, Plus, Monitor, RefreshCw, History } from "lucide-react"; import { api } from "@/lib/client/api"; import { cn, timeAgo, fmtDate } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Input, Label } from "@/components/ui/input"; import { Badge, Dialog, DialogContent, Spinner } from "@/components/ui/primitives"; import { toast } from "@/components/ui/toast"; import type { SettingsProfile } from "./SettingsView"; interface Session { id: string; device: string; userAgent: string | null; ip: string | null; createdAt: string; lastSeenAt: string; expiresAt: string; mfaPassed: boolean; current: boolean } interface Passkey { id: string; label: string | null; deviceType: string | null; backedUp: boolean | null; transports: string[] | null; createdAt: string; lastUsedAt: string | null } interface AuditRow { id: string; action: string; targetType: string | null; targetId: string | null; detail: Record | null; ip: string | null; createdAt: string } export function SecuritySection({ profile, refresh }: { profile: SettingsProfile; refresh: () => void }) { const qc = useQueryClient(); const { data: sessions = [], isLoading: sLoading } = useQuery({ queryKey: ["settings-sessions"], queryFn: () => api("/api/settings/sessions") }); const { data: passkeys = [] } = useQuery({ queryKey: ["settings-passkeys"], queryFn: () => api("/api/settings/passkeys") }); const { data: auditRows = [], isLoading: aLoading, refetch: refetchAudit } = useQuery({ queryKey: ["settings-audit"], queryFn: () => api("/api/settings/audit?limit=50") }); const inv = (k: string) => qc.invalidateQueries({ queryKey: [k] }); // sessions const [revokeAllOpen, setRevokeAllOpen] = useState(false); async function revoke(s: Session) { try { const r = await api("/api/settings/sessions", { method: "DELETE", json: { id: s.id } }); if (r.current) { window.location.href = "/login"; return; } toast("Session revoked"); inv("settings-sessions"); inv("settings-audit"); } catch (e: any) { toast("Could not revoke", { description: e.message, tone: "danger" }); } } async function revokeAll() { try { await api("/api/settings/sessions", { method: "DELETE", json: { all: true } }); toast("Signed out everywhere else", { tone: "success" }); setRevokeAllOpen(false); inv("settings-sessions"); inv("settings-audit"); } catch (e: any) { toast("Failed", { description: e.message, tone: "danger" }); } } // TOTP const [totp, setTotp] = useState<{ step: "idle" | "setup" | "disable"; qr?: string; secret?: string; code: string; busy: boolean }>({ step: "idle", code: "", busy: false }); async function totpSetup() { setTotp((t) => ({ ...t, busy: true })); try { const r = await api("/api/auth/totp", { method: "PUT", json: { action: "setup" } }); setTotp({ step: "setup", qr: r.qr, secret: r.secret, code: "", busy: false }); } catch (e: any) { toast("Setup failed", { description: e.message, tone: "danger" }); setTotp((t) => ({ ...t, busy: false })); } } async function totpEnable() { setTotp((t) => ({ ...t, busy: true })); try { await api("/api/auth/totp", { method: "PUT", json: { action: "enable", code: totp.code.replace(/\s/g, "") } }); toast("Two-factor authentication enabled", { tone: "success" }); setTotp({ step: "idle", code: "", busy: false }); refresh(); inv("settings-audit"); } catch (e: any) { toast("Invalid code", { description: e.message, tone: "danger" }); setTotp((t) => ({ ...t, busy: false })); } } async function totpDisable() { setTotp((t) => ({ ...t, busy: true })); try { await api("/api/auth/totp", { method: "PUT", json: { action: "disable", code: totp.code.replace(/\s/g, "") } }); toast("Two-factor authentication disabled"); setTotp({ step: "idle", code: "", busy: false }); refresh(); inv("settings-audit"); } catch (e: any) { toast("Invalid code", { description: e.message, tone: "danger" }); setTotp((t) => ({ ...t, busy: false })); } } // passkeys const [pkBusy, setPkBusy] = useState(false); async function registerPasskey() { setPkBusy(true); try { const o = await api("/api/auth/passkey?step=register"); const resp = await startRegistration({ optionsJSON: o.options }); const label = window.prompt("Name this passkey (e.g. MacBook Touch ID, iPhone)", "Passkey") ?? "Passkey"; await api("/api/auth/passkey", { method: "POST", json: { challengeId: o.challengeId, response: resp, label } }); toast("Passkey registered", { tone: "success" }); inv("settings-passkeys"); inv("settings-audit"); } catch (e: any) { if (e?.name !== "NotAllowedError") toast("Passkey not registered", { description: e.message, tone: "danger" }); } finally { setPkBusy(false); } } async function deletePasskey(p: Passkey) { if (!window.confirm(`Remove passkey "${p.label ?? p.id.slice(0, 8)}"?`)) return; try { await api("/api/auth/passkey", { method: "DELETE", json: { id: p.id } }); toast("Passkey removed"); inv("settings-passkeys"); inv("settings-audit"); } catch (e: any) { toast("Failed", { description: e.message, tone: "danger" }); } } const H = ({ icon, title, hint, action }: { icon: React.ReactNode; title: string; hint: string; action?: React.ReactNode }) =>

{icon}{title}

{hint}

{action}
; return (
} title="Sessions" hint="Devices currently signed in to your workspace." action={} />
{sLoading ?
: sessions.map((s) => (
{/iOS|Android/.test(s.device) ? : }
{s.device}{s.current && this device}{s.mfaPassed && 2FA}
{s.ip ?? "unknown IP"} · last seen {timeAgo(s.lastSeenAt)} · signed in {fmtDate(s.createdAt, { dateStyle: "medium" })}
))}
: } title="Two-factor authentication (TOTP)" hint={profile.totpEnabled ? "Enabled. A code from your authenticator app is required at each sign-in." : "Add a time-based one-time code (Google Authenticator, 1Password, Apple Passwords…) as a second factor."} action={profile.totpEnabled ? Enabled : Off} /> {totp.step === "idle" && (profile.totpEnabled ? : )} {totp.step === "setup" && (
{totp.qr && TOTP QR code}
  1. Scan the QR code with your authenticator app.
  2. Or enter this key manually: {totp.secret}
  3. Enter the 6-digit code to confirm.
{ e.preventDefault(); totpEnable(); }}>
setTotp({ ...totp, code: e.target.value })} className="w-32 font-mono tracking-widest" placeholder="123 456" autoFocus />
)} {totp.step === "disable" &&
{ e.preventDefault(); totpDisable(); }}>
setTotp({ ...totp, code: e.target.value })} className="w-32 font-mono tracking-widest" placeholder="123 456" autoFocus />
}
} title="Passkeys" hint="Sign in with Touch ID, Face ID or a security key — phishing-resistant and no password needed." action={} />
{passkeys.length ? passkeys.map((p) =>
{p.label ?? "Passkey"} {p.backedUp && synced}
{p.deviceType === "multiDevice" ? "Multi-device" : "Single device"} · added {fmtDate(p.createdAt, { dateStyle: "medium" })}{p.lastUsedAt ? ` · last used ${timeAgo(p.lastUsedAt)}` : " · never used"}
) :
No passkey registered yet.
}
} title="Recent activity" hint="Last 50 security and content events recorded for your account." action={} />
{aLoading ? : auditRows.length ? auditRows.map((a) => ) : }
WhenActionTargetIP
{timeAgo(a.createdAt)}{a.action}{a.targetType ? `${a.targetType} ${a.targetId?.slice(0, 8) ?? ""}` : ""}{a.detail && Object.keys(a.detail).length ? {JSON.stringify(a.detail).slice(0, 80)} : null}{a.ip ?? ""}
No activity yet.
); }