feat(admin-ui): TOTP MFA enrollment + two-step login; remove stub 2FA toggle

Frontend for #738.
- mfa.service.ts + MfaSettingsCard (Settings → General → Admin Account):
  per-user setup (QR + manual secret + verify), recovery codes shown once
  (copy/download/confirm), status, regenerate, disable. Renders for
  super_admin (closes #735).
- Two-step login in AdminLoginPage: on {mfaRequired,mfaToken} swap to a
  code step (TOTP or recovery), call /auth/admin/login/mfa; handle
  MFA_INVALID / MFA_SESSION_EXPIRED / 423 lockout.
- Removed the non-functional global enable_2fa checkbox from SecurityTab
  (and its persistence) — replaced with a note pointing to per-user setup.
- en + de i18n.

Verified live in-browser: enroll (QR→code→recovery codes), logout, and
the two-step challenge into the dashboard as super_admin.
This commit is contained in:
Paul Nothaft
2026-07-03 11:44:07 +02:00
parent cdbfb514bd
commit 96e3c68b9d
10 changed files with 679 additions and 24 deletions
@@ -35,7 +35,6 @@ export interface GeneralSettings {
export interface SecuritySettings {
password_min_length: number;
password_complexity: string;
enable_2fa: boolean;
session_timeout_minutes: number;
max_login_attempts: number;
attempt_window_minutes: number;
@@ -134,7 +133,6 @@ export function useSettingsState() {
const [securitySettings, setSecuritySettings] = useState<SecuritySettings>({
password_min_length: 8,
password_complexity: 'moderate',
enable_2fa: false,
session_timeout_minutes: 60,
max_login_attempts: 5,
attempt_window_minutes: 15,
@@ -231,7 +229,6 @@ export function useSettingsState() {
setSecuritySettings({
password_min_length: toNumber(settings.security_password_min_length, 8),
password_complexity: settings.security_password_complexity ?? 'moderate',
enable_2fa: toBoolean(settings.security_enable_2fa, false),
session_timeout_minutes: toNumber(settings.security_session_timeout_minutes, 60),
max_login_attempts: toNumber(settings.security_max_login_attempts, 5),
attempt_window_minutes: toNumber(settings.security_attempt_window_minutes, 15),