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 ? (
) : (
)}
{codes && (
{ setCodes(null); if (erzwungen) navigate("/"); }}
/>
)}
>
);
}