feat: Add two-factor authentication via TOTP

Adds TOTP setup with QR code, login challenge, recovery codes and
trusted devices that let a browser skip the second factor for 30
days. Admins can reset another user's second factor by confirming
with their own password.
This commit is contained in:
Daniel Hiller
2026-08-07 20:11:55 +02:00
parent 36aa732fec
commit 2e4904f77d
74 changed files with 4173 additions and 4 deletions
+53
View File
@@ -120,6 +120,7 @@ export default {
closed: 'Closed',
color: 'Color',
comments: 'Comments',
confirmCodesSaved: 'I have saved these codes in a safe place.',
contentExceedsLimit: 'Content exceeds {{limit}}',
contentOfThisAttachmentIsTooBigToDisplay: 'Content of this attachment is too big to display.',
copy_inline: 'copy',
@@ -138,9 +139,51 @@ export default {
customFields_title: 'Custom Fields',
customerPanel_title: 'Customer Panel',
dangerZone_title: 'Danger Zone',
disable2fa_title: 'Disable Two-Factor Authentication',
disable2faWarning:
'You will need to confirm your current password and a TOTP code. Existing sessions stay signed in.',
discoverPlankaPro: '✨ More features for your boards: Discover PLANKA Pro',
discoverPlankaPro_title: 'Discover PLANKA Pro',
dismissProBannerFor30Days: 'Dismiss for 30 days',
enable2fa_title: 'Enable Two-Factor Authentication',
enabledOn: 'Enabled on {{date}}',
enterCodeFromApp: 'Enter the 6-digit code from your authenticator app.',
enterPasswordToContinue: 'Confirm your current password to continue.',
enterTotpOrRecoveryCode:
'Enter the 6-digit code from your authenticator app, or one of your recovery codes (xxxxx-xxxxx).',
expires: 'Expires',
invalidTotpCode: 'Invalid TOTP or recovery code',
lastUsed: 'Last used',
never: 'Never',
noTrustedDevices: 'No trusted devices.',
orEnterSecretManually: 'Or enter this secret manually:',
recoveryCodesExhausted:
'All recovery codes have been used. Regenerate a new set to keep a fallback in case you lose your authenticator app.',
recoveryCodesIntro:
'Store these codes in a safe place — each can be used once if you lose access to your authenticator app.',
recoveryCodesLow_one: 'Only {{count}} recovery code remaining. Consider regenerating soon.',
recoveryCodesLow_other:
'Only {{count}} recovery codes remaining. Consider regenerating soon.',
regenerateRecoveryCodes_title: 'Regenerate Recovery Codes',
regenerateRecoveryCodesIntro:
'Generating new codes will invalidate all existing recovery codes.',
reset2fa_title: 'Reset 2FA',
reset2faWarning:
'Resetting 2FA signs the user out of all sessions and removes their TOTP setup. They will need to set it up again on next login.',
saveTheseCodes_title: 'Save These Recovery Codes',
scanQrCodeWithApp:
'Scan this QR code with an authenticator app such as Google Authenticator or Authy.',
security_title: 'Security',
totpCode: 'TOTP code',
totpOrRecoveryCode: 'TOTP or recovery code',
trustedDevices_title: 'Trusted Browsers',
trustedDevicesHint:
'Browsers that have been marked as trusted skip the second factor for 30 days. Revoke any you no longer recognize.',
twoFactor_enabled: 'Two-factor authentication is enabled.',
twoFactor_intro:
'Add an extra layer of security to your account using a time-based one-time code from an authenticator app.',
twoFactorAuthentication: 'Two-Factor Authentication',
unknownDevice: 'Unknown device',
upgradeTeamToPro_title: 'Upgrade Team to Pro',
proFeatureCalendar: '✨ Calendar View for your boards',
proFeatureRecurringCards: '✨ Recurring Cards',
@@ -397,8 +440,10 @@ export default {
archiveCards: 'Archive cards',
archiveCards_title: 'Archive Cards',
assignAsOwner: 'Assign as owner',
back: 'Back',
cancel: 'Cancel',
copy: 'Copy',
copyAll: 'Copy all',
copyCard_title: 'Copy Card',
createApiKey: 'Create API key',
createBoard: 'Create board',
@@ -438,7 +483,9 @@ export default {
deleteUser: 'Delete user',
deleteUser_title: 'Delete User',
deleteWebhook: 'Delete webhook',
disable2fa: 'Disable 2FA',
dismissAll: 'Dismiss all',
done: 'Done',
download: 'Download',
duplicateCard_title: 'Duplicate Card',
edit: 'Edit',
@@ -458,6 +505,7 @@ export default {
editUsername_title: 'Edit Username',
emptyTrash: 'Empty trash',
emptyTrash_title: 'Empty Trash',
enable2fa: 'Enable 2FA',
import: 'Import',
join: 'Join',
leave: 'Leave',
@@ -472,7 +520,9 @@ export default {
move: 'Move',
moveCard_title: 'Move Card',
moveList_title: 'Move List',
regenerate: 'Regenerate',
regenerateApiKey: 'Regenerate API key',
regenerateRecoveryCodes: 'Regenerate recovery codes',
remove: 'Remove',
removeAssignee: 'Remove assignee',
removeColor: 'Remove color',
@@ -481,8 +531,10 @@ export default {
removeFromProject: 'Remove from project',
removeManager: 'Remove manager',
removeMember: 'Remove member',
reset2fa: 'Reset 2FA',
restoreToList: 'Restore to {{list}}',
returnToBoard: 'Return to board',
revoke: 'Revoke',
save: 'Save',
sendTestEmail: 'Send test email',
showActive: 'Show active',
@@ -499,6 +551,7 @@ export default {
unsubscribe: 'Unsubscribe',
uploadNewAvatar: 'Upload new avatar',
uploadNewImage: 'Upload new image',
verify: 'Verify',
},
},
};