feat: 2FA, Passwortrichtlinie und wiederherstellbares JSON-Backup
Zwei-Faktor per TOTP mit zehn Einmal-Wiederherstellungscodes. Der Zwischenzustand "Passwort stimmt, Code fehlt" haengt an der vorhandenen Sitzungstabelle (pending_2fa, zehn Minuten Laufzeit) und gilt nirgends als angemeldet; nach dem Code wird das Cookie getauscht statt weiterverwendet. Der Wiederverwendungsschutz merkt sich das tatsaechlich getroffene Zeitfenster, nicht die aktuelle Uhrzeit - sonst liesse sich ein Code aus dem naechsten Fenster gleich darauf nochmal einloesen. Passwortrichtlinie (Laenge, Zeichenklassen, MFA-Pflicht) in der neuen globalen Tabelle app_settings, durchgesetzt beim Anlegen, Zuruecksetzen und Aendern von Passwoertern. Bestehende Passwoerter lassen sich nicht gegen eine neue Richtlinie pruefen - der Hash gibt nichts her. Beim Anmelden liegt das Passwort aber kurz im Klartext vor: dort wird geprueft und bei Verstoss ein Wechsel verlangt, mit passendem Grund fuer den Hinweistext. Nebenbei geschlossen: must_change_password war bisher nur im Frontend erzwungen, wer die API direkt ansprach kam vorbei. enforce_account_state haengt jetzt am /api-Sammelrouter und laesst nur noch durch, was man zum Erfuellen der Pflicht braucht. Das JSON-Backup liess sich bisher grundsaetzlich nicht zurueckladen: keine Version, Kategorien nur ueber mehrdeutige Namen, sieben Tabellen fehlten. Daneben steht nun ein Format v2, das Beziehungen ueber lokale IDs abbildet, je Benutzer oder systemweit. Zurueckgeladen wird nach einer Pruefansicht, wahlweise ersetzend oder ergaenzend; vor dem Ersetzen legt der Server einen Schnappschuss der Datenbank an. Der alte Ansichts-Export bleibt fuer Tabellenkalkulationen bestehen und wird beim Hochladen ausdruecklich abgewiesen, statt halb zu raten. Frontend: wiederverwendbares Passwortfeld mit Augensymbol, animierte Anforderungsliste, 2FA-Seite, Admin-Karte fuer die Richtlinie und Karte zum Zurueckladen. Neue Bewegung ist in prefers-reduced-motion hinterlegt; Secret und Codes sind markierbar, sonst verhinderte das globale user-select das Kopieren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1d5b23c127
commit
7fc8763d57
+93
-3
@@ -40,7 +40,8 @@
|
||||
"repeat": "Repeat new password",
|
||||
"submit": "Save password",
|
||||
"mismatch": "The new passwords do not match.",
|
||||
"saved": "Password changed."
|
||||
"saved": "Password changed.",
|
||||
"policyHint": "The password policy has changed. Your current password no longer meets it – please choose a new one."
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
@@ -56,7 +57,12 @@
|
||||
"resetPassword": "Reset password",
|
||||
"deleteConfirm": "Really delete this user?",
|
||||
"created": "User created.",
|
||||
"you": "(you)"
|
||||
"you": "(you)",
|
||||
"twoFactor": "Two-factor",
|
||||
"resetTwoFactor": "Reset two-factor",
|
||||
"resetTwoFactorConfirm": "Turn off two-factor for “{{name}}”? Only needed if both device and recovery codes are lost.",
|
||||
"twoFactorReset": "Two-factor reset.",
|
||||
"generatePassword": "Generate password"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -84,7 +90,14 @@
|
||||
"keywordReviewHint": "These keywords keep getting corrected away — maybe they no longer fit?",
|
||||
"keywordCorrected": "{{overridden}} of {{applied}} transactions corrected",
|
||||
"keep": "Keep",
|
||||
"remove": "Remove"
|
||||
"remove": "Remove",
|
||||
"security": "Security",
|
||||
"securityHint": "Two-factor authentication for your own account.",
|
||||
"twoFactor": "Manage two-factor",
|
||||
"backupHint": "The full backup contains all of your data and can be restored below.",
|
||||
"exportBackup": "Full backup (JSON)",
|
||||
"systemBackup": "System-wide full backup (JSON)",
|
||||
"systemBackupWarning": "The system-wide backup contains password hashes and two-factor secrets of every user. Treat the file like a password."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Overview",
|
||||
@@ -280,5 +293,82 @@
|
||||
"undone": "undone",
|
||||
"needFile": "Please choose a CSV file first.",
|
||||
"needFileAccount": "Please choose an account and a file."
|
||||
},
|
||||
"password": {
|
||||
"show": "Show password",
|
||||
"hide": "Hide password",
|
||||
"met": "(met)",
|
||||
"unmet": "(not met)",
|
||||
"ruleLength": "At least {{n}} characters",
|
||||
"ruleUpper": "At least one uppercase letter",
|
||||
"ruleLower": "At least one lowercase letter",
|
||||
"ruleDigit": "At least one digit",
|
||||
"ruleSpecial": "At least one special character"
|
||||
},
|
||||
"twofactor": {
|
||||
"title": "Two-factor authentication",
|
||||
"intro": "A one-time code from an authenticator app on top of your password. Knowing your password alone is no longer enough.",
|
||||
"on": "Active",
|
||||
"off": "Not set up",
|
||||
"start": "Set up",
|
||||
"scan": "Scan the code with your authenticator app (e.g. Aegis, 1Password, Google Authenticator).",
|
||||
"qrAlt": "QR code for setting up the authenticator app",
|
||||
"manual": "If scanning does not work, enter this key manually:",
|
||||
"code": "Code from the app",
|
||||
"codeHint": "Enter the six-digit code from your authenticator app.",
|
||||
"activate": "Activate",
|
||||
"verify": "Confirm",
|
||||
"useRecovery": "Use a recovery code",
|
||||
"useApp": "Use the app code instead",
|
||||
"recoveryCode": "Recovery code",
|
||||
"recoveryHint": "Enter one of your recovery codes. Each one works only once.",
|
||||
"codesTitle": "Recovery codes",
|
||||
"codesHint": "Keep these codes somewhere safe – printed, for example. They are the only way back in if you lose your device. Each code works exactly once and they are shown here only this one time.",
|
||||
"codesSaved": "I have saved them",
|
||||
"codesLeft": "{{n}} unused recovery codes left.",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied to clipboard.",
|
||||
"download": "Save as file",
|
||||
"newCodes": "Generate new codes",
|
||||
"newCodesTitle": "New recovery codes",
|
||||
"newCodesHint": "The previous codes stop working. Enter your password to confirm.",
|
||||
"disable": "Turn off two-factor",
|
||||
"disableTitle": "Turn off two-factor",
|
||||
"disableHint": "Your password alone will be enough again. Enter your password to confirm.",
|
||||
"disabled": "Two-factor turned off.",
|
||||
"requiredHint": "The policy requires two-factor. The rest of the application stays locked until you set it up."
|
||||
},
|
||||
"policy": {
|
||||
"title": "Password policy",
|
||||
"hint": "Applies to every user of this instance.",
|
||||
"minLength": "Minimum length",
|
||||
"require_upper": "Require an uppercase letter",
|
||||
"require_lower": "Require a lowercase letter",
|
||||
"require_digit": "Require a digit",
|
||||
"require_special": "Require a special character",
|
||||
"mfaRequired": "Require two-factor for everyone",
|
||||
"mfaHint": "Users without a second factor must set one up at their next sign-in.",
|
||||
"tightenWarning": "Tightening: users whose password no longer meets the policy will have to change it at their next sign-in.",
|
||||
"mfaWarning": "Every user without two-factor will be taken to the setup at their next sign-in – including you."
|
||||
},
|
||||
"restore": {
|
||||
"title": "Restore a backup",
|
||||
"hint": "Pick a backup file. It is only inspected first; nothing is written until you confirm.",
|
||||
"file": "Backup file",
|
||||
"empty": "The file contains no data.",
|
||||
"scopeUser": "Backup of the data of “{{name}}”.",
|
||||
"scopeSystem": "System-wide backup with {{n}} users.",
|
||||
"exportedAt": "Created on {{when}}",
|
||||
"mode": "How to proceed",
|
||||
"modeReplace": "Replace",
|
||||
"modeMerge": "Add missing",
|
||||
"modeReplaceHint": "Existing data is deleted and replaced by the backup. The server writes a database snapshot beforehand.",
|
||||
"modeMergeHint": "Nothing is deleted. Only missing entries are added; budget, rules and settings stay untouched if anything is already there.",
|
||||
"start": "Restore",
|
||||
"confirmTitle": "Really restore?",
|
||||
"confirmReplace": "All your accounts, transactions, categories and settings will be deleted and replaced by the backup.",
|
||||
"confirmSystem": "All users and their data will be deleted and replaced by the backup. Every session ends afterwards.",
|
||||
"done": "Restore finished.",
|
||||
"doneSystem": "Restore finished. Please sign in again."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user