add rate limiting and 2fa hardening

This commit is contained in:
2026-06-10 01:35:36 +02:00
parent ae41b96fa4
commit 58f098d4ca
11 changed files with 410 additions and 59 deletions

View File

@@ -486,6 +486,7 @@ async function loadProfile() {
// ---- ACCOUNT SETTINGS ----
let latestRecoveryCodes = [];
let pendingTwoFactorSetupToken = "";
function showAccountSettingsMessage(message, type = 'success') {
const box = document.getElementById('account-settings-message');
@@ -518,6 +519,18 @@ function setTwoFactorPanels(enabled) {
}
}
function updateRecoveryCodeWarning(remaining, warning) {
const warningBox = document.getElementById('recovery-codes-warning');
if (!warningBox) return;
if (warning) {
warningBox.textContent = `You only have ${remaining} recovery code${remaining === 1 ? '' : 's'} left. Generate and download new codes soon.`;
warningBox.style.display = 'block';
} else {
warningBox.style.display = 'none';
}
}
function renderRecoveryCodes(codes) {
latestRecoveryCodes = codes || [];
const panel = document.getElementById('recovery-codes-panel');
@@ -545,6 +558,7 @@ async function loadAccountSettings() {
if (usernameInput) usernameInput.value = data.username || '';
if (avatarPreview && data.username) avatarPreview.innerText = data.username[0].toLocaleUpperCase();
if (remaining) remaining.innerText = data.recovery_codes_remaining || 0;
updateRecoveryCodeWarning(data.recovery_codes_remaining || 0, !!data.recovery_codes_warning);
setTwoFactorPanels(!!data.two_factor_enabled);
renderRecoveryCodes([]);
@@ -614,6 +628,8 @@ async function startTwoFactorSetup() {
const secret = document.getElementById('two-factor-secret');
const otpauth = document.getElementById('two-factor-otpauth');
pendingTwoFactorSetupToken = data.setup_token || '';
if (panel) panel.style.display = 'block';
if (qr) {
qr.src = data.qr_code;
@@ -636,16 +652,24 @@ async function enableTwoFactor(event) {
try {
const data = await apiRequest('/api/2fa/enable', 'POST', {
code: document.getElementById('two-factor-enable-code').value.trim()
code: document.getElementById('two-factor-enable-code').value.trim(),
setup_token: pendingTwoFactorSetupToken
});
document.getElementById('two-factor-enable-form').reset();
document.getElementById('two-factor-setup-panel').style.display = 'none';
pendingTwoFactorSetupToken = '';
setTwoFactorPanels(true);
renderRecoveryCodes(data.recovery_codes || []);
const remaining = document.getElementById('recovery-codes-remaining');
if (remaining) remaining.innerText = (data.recovery_codes || []).length;
if (remaining) remaining.innerText = data.recovery_codes_remaining || (data.recovery_codes || []).length;
updateRecoveryCodeWarning(data.recovery_codes_remaining || (data.recovery_codes || []).length, !!data.recovery_codes_warning);
if (data.access_token && data.refresh_token) {
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
}
showAccountSettingsMessage('2FA enabled. Download your recovery codes now.');
loadProfile();
@@ -670,6 +694,7 @@ async function disableTwoFactor(event) {
localStorage.removeItem('refresh_token');
setTwoFactorPanels(false);
renderRecoveryCodes([]);
updateRecoveryCodeWarning(0, false);
showAccountSettingsMessage('2FA disabled. Redirecting to login because sessions were revoked.');
setTimeout(() => {
window.location.href = '/login';
@@ -695,6 +720,7 @@ async function regenerateRecoveryCodes(event) {
const remaining = document.getElementById('recovery-codes-remaining');
if (remaining) remaining.innerText = (data.recovery_codes || []).length;
updateRecoveryCodeWarning((data.recovery_codes || []).length, false);
showAccountSettingsMessage('New recovery codes generated. Download them now.');
} catch (err) {