fix(auth): close the MFA policy lockout and add self-service enrollment (#531)

Fixes #529 (opened investigating #515).

Setting MFA policy to "required"/"admins only" saved regardless of whether the mfa enterprise feature was licensed, and there was no enrollment UI at all, so any instance that flipped the toggle locked every unenrolled user out with no way back in. The login page and Settings save also both collapsed the resulting error into a generic message, hiding the real reason.

- Reject saving mfaPolicy to admins_only/required server-side unless mfa is licensed
- Surface the specific server error on login and on a failed settings save instead of a generic fallback
- Add a self-service two-factor authentication enrollment flow (QR code, manual entry, recovery codes, verify, disable) so a licensed admin can actually satisfy the policy before it's enforced
- Fix a pending-enrollment dead end, silent error swallowing in verify/disable, and a silent clipboard-copy failure on the recovery codes screen
- Add the integration test that actually proves the fix: a real login attempt returns 403 MFA_ENROLLMENT_REQUIRED
This commit is contained in:
SnapOtter
2026-07-16 18:07:27 +08:00
committed by GitHub
parent d88999e7a9
commit 190d4c2a00
34 changed files with 1624 additions and 19 deletions
+25
View File
@@ -3304,6 +3304,31 @@ export const ru: TranslationKeys = {
currentPasswordIncorrect: "Текущий пароль неверен",
changePasswordButton: "Изменить пароль",
loginAttemptLimitNote: "Лимиты попыток входа можно настроить в Системных настройках.",
twoFactorHeading: "Двухфакторная аутентификация",
twoFactorDescription:
"Добавьте дополнительный уровень защиты аккаунта с помощью приложения-аутентификатора.",
twoFactorEnabledStatus: "Двухфакторная аутентификация включена.",
twoFactorDisabledStatus: "Двухфакторная аутентификация отключена.",
enableTwoFactorButton: "Включить двухфакторную аутентификацию",
disableTwoFactorButton: "Отключить двухфакторную аутентификацию",
twoFactorScanQr:
"Отсканируйте этот QR-код приложением-аутентификатором (например, Google Authenticator, Authy или 1Password).",
twoFactorManualEntry: "Не удаётся отсканировать код? Введите этот ключ вручную:",
twoFactorRecoveryCodesHeading: "Коды восстановления",
twoFactorRecoveryCodesDescription:
"Сохраните эти коды в надёжном месте. Каждый из них можно использовать один раз для входа, если вы потеряете доступ к приложению-аутентификатору.",
twoFactorCopyRecoveryCodes: "Копировать коды",
twoFactorCodesCopied: "Скопировано",
twoFactorCopyFailed:
"Не удалось скопировать автоматически. Выделите коды выше и скопируйте их вручную.",
twoFactorEnterCode: "Введите 6-значный код из приложения, чтобы подтвердить настройку.",
twoFactorCodePlaceholder: "000000",
twoFactorConfirmButton: "Подтвердить и включить",
twoFactorCancelButton: "Отмена",
twoFactorEnableSuccess: "Двухфакторная аутентификация включена.",
twoFactorDisableSuccess: "Двухфакторная аутентификация отключена.",
twoFactorDisablePrompt:
"Введите текущий код из приложения-аутентификатора, чтобы отключить двухфакторную аутентификацию.",
adminHeading: "Настройки безопасности администратора",
adminDescription: "Корпоративные политики безопасности. Применяются ко всем пользователям.",
sessionIdleTimeout: "Тайм-аут бездействия сессии (минуты)",