import { useEffect, useState } from "react"; import { Navigate, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { useAuth } from "../auth"; import { api } from "../api"; import { useToast } from "../toast"; import { useConfirm } from "../confirm"; import Icon from "../components/Icon"; import PasswordInput from "../components/PasswordInput"; /** Codeliste als Textdatei sichern – Zwischenablage ist nicht überall erlaubt. */ function codesSichern(codes, benutzer) { const inhalt = `Certain Finance – Wiederherstellungscodes für ${benutzer}\n` + `Jeder Code funktioniert genau einmal. Sicher aufbewahren.\n\n` + codes.join("\n") + "\n"; const url = URL.createObjectURL(new Blob([inhalt], { type: "text/plain;charset=utf-8" })); const link = document.createElement("a"); link.href = url; link.download = `certain-finance_wiederherstellungscodes_${benutzer}.txt`; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); } /** Anzeige der frisch erzeugten Codes – sie sind danach nicht mehr abrufbar. */ function CodeListe({ codes, benutzer, onFertig }) { const { t } = useTranslation(); const toast = useToast(); async function kopieren() { try { await navigator.clipboard.writeText(codes.join("\n")); toast(t("twofactor.copied")); } catch { // Ohne sicheren Kontext (http) gibt es keine Zwischenablage – dann // bleibt der Download, und markieren geht ohnehin. codesSichern(codes, benutzer); } } return (

{t("twofactor.codesTitle")}

{t("twofactor.codesHint")}

{codes.map((c) => {c})}
); } export default function TwoFactor() { const { user, aktualisieren } = useAuth(); const { t } = useTranslation(); const toast = useToast(); const confirm = useConfirm(); const navigate = useNavigate(); const [status, setStatus] = useState(null); const [policy, setPolicy] = useState(null); const [setup, setSetup] = useState(null); // { secret, otpauth_uri, qr_png } const [code, setCode] = useState(""); const [codes, setCodes] = useState(null); // frisch erzeugte Wiederherstellungscodes const [fehler, setFehler] = useState(null); const [busy, setBusy] = useState(false); async function laden() { try { const [s, p] = await Promise.all([api.twoFactorStatus(), api.getPolicy()]); setStatus(s); setPolicy(p); } catch (err) { setFehler(err.message); } } useEffect(() => { laden(); }, []); if (!user) return ; // Erst Passwort wechseln, dann zweiter Faktor – dieselbe Reihenfolge, die // der Server erzwingt. if (user.must_change_password) return ; const pflicht = Boolean(policy?.mfa_required); const erzwungen = pflicht && !status?.enabled; async function starten() { setFehler(null); setBusy(true); try { setSetup(await api.twoFactorSetup()); } catch (err) { setFehler(err.message); } finally { setBusy(false); } } async function aktivieren(e) { e.preventDefault(); setFehler(null); setBusy(true); try { const res = await api.twoFactorActivate(code.trim()); setCodes(res.codes); setSetup(null); setCode(""); // Die Sperre „MFA einrichten" fällt damit weg – Benutzer neu laden. aktualisieren(await api.me()); await laden(); } catch (err) { setFehler(err.message); } finally { setBusy(false); } } async function abschalten() { const passwort = await confirm({ title: t("twofactor.disableTitle"), message: t("twofactor.disableHint"), confirmLabel: t("twofactor.disable"), danger: true, prompt: true, inputType: "password", }); if (!passwort) return; try { aktualisieren(await api.twoFactorDisable(passwort)); toast(t("twofactor.disabled")); await laden(); } catch (err) { setFehler(err.message); } } async function codesErneuern() { const passwort = await confirm({ title: t("twofactor.newCodesTitle"), message: t("twofactor.newCodesHint"), confirmLabel: t("twofactor.newCodes"), prompt: true, inputType: "password", }); if (!passwort) return; try { const res = await api.newRecoveryCodes(passwort); setCodes(res.codes); await laden(); } catch (err) { setFehler(err.message); } } return ( <>

{t("twofactor.title")}

{fehler && (
{fehler}
)} {erzwungen && (
{t("twofactor.requiredHint")}
)}

{status?.enabled ? t("twofactor.on") : t("twofactor.off")}

{t("twofactor.intro")}

{status?.enabled ? ( <>

{t("twofactor.codesLeft", { n: status.recovery_codes_left })}

{!pflicht && ( )}
) : setup ? (

{t("twofactor.scan")}

{t("twofactor.qrAlt")}

{t("twofactor.manual")}

{setup.secret}
) : ( )}
{codes && ( { setCodes(null); if (erzwungen) navigate("/"); }} /> )} ); }