TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1"use client";2import { useEffect, useState } from "react";3import { useRouter, useSearchParams } from "next/navigation";4import { Notice } from "@/components/ui";5export function MfaView() {6 const [st, setSt] = useState<{ enrolled: boolean; fresh: boolean; setup: { base32: string; uri: string } | null } | null>(null); const [code, setCode] = useState(""); const [err, setErr] = useState<string | null>(null); const router = useRouter(); const sp = useSearchParams();7 useEffect(() => { fetch("/api/admin/mfa").then((r) => r.json()).then(setSt); }, []);8 const submit = async () => { setErr(null); const r = await fetch("/api/admin/mfa", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ code, base32: st?.setup?.base32 }) }); if (!r.ok) { setErr("Code invalide"); return; } const next = sp.get("next") && sp.get("next")!.startsWith("/admin") ? sp.get("next")! : "/admin"; router.push(next); router.refresh(); };9 if (!st) return <p className="text-sm text-ink-3">Chargement…</p>;10 return (11 <div className="max-w-md space-y-4">12 <h1 className="text-2xl font-extrabold tracking-tight">Vérification forte</h1>13 {!st.enrolled && st.setup && (<div className="tv-card p-4 space-y-2 text-sm"><p className="font-semibold">Activer l'application d'authentification</p><p className="text-ink-2">Ajoutez cette clé dans votre application TOTP (1Password, Google Authenticator, Authy…), puis entrez le code généré.</p><p className="font-mono text-xs break-all bg-surface-2 p-2 rounded-sm">{st.setup.base32}</p><a href={st.setup.uri} className="text-xs underline text-brand-dark">Ouvrir dans l'application (otpauth://)</a></div>)}14 <div className="tv-card p-4 space-y-3"><label className="block text-sm"><span className="tv-label">Code à 6 chiffres</span><input inputMode="numeric" maxLength={6} value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, ""))} className="tv-input !pl-4 mt-1 text-center text-2xl tracking-[.4em] font-bold" /></label>{err && <Notice tone="warn">{err}</Notice>}<button type="button" onClick={submit} disabled={code.length !== 6} className="tv-btn tv-btn-primary w-full">{st.enrolled ? "Vérifier" : "Activer et vérifier"}</button></div>15 <Notice>Le code courriel seul ne suffit pas pour les actions d'administration : un second facteur TOTP est exigé et renouvelé toutes les 30 minutes. Chaque action est journalisée (append-only).</Notice>16 </div>17 );18}19