Flask Session -> Double Token

This commit is contained in:
喆OvO
2026-08-11 19:57:21 +08:00
parent 0fef309972
commit cbbb2e6d34
18 changed files with 1375 additions and 607 deletions
+37 -72
View File
@@ -141,6 +141,7 @@
.theme-light .badge-done { background: #e8f5e9; color: #2e7d32; }
.theme-light .badge-pending { background: #fff3e0; color: #e65100; }
</style>
<script src="/static/js/token.js"></script>
<script src="/static/js/passkey_bridge.js"></script>
<script src="/static/js/theme.js"></script>
</head>
@@ -207,29 +208,21 @@
// ─── Check MFA status ──────────────────
function checkMFAStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/auth/mfa/status');
xhr.onload = function () {
if (xhr.status === 200) {
var s = JSON.parse(xhr.responseText);
// Temp accounts must configure ONE login method to activate the account.
if (s.is_temp_account) {
document.querySelector('.setup-box h1').textContent = '🔐 激活你的账号';
document.querySelector('.subtitle').textContent =
'请配置至少一种登录方式(Passkey 或 TOTP)。配置并验证完成后,你的临时验证码将失效,账号立即正式生效。';
}
if (s.passkey_enabled) { passkeyDone = true; updatePasskeyUI(true); }
if (s.totp_enabled) { totpDone = true; updateTOTPUI(true); }
if (s.recovery_codes_remaining > 0) {
showRecoveryCodes();
}
updateFinishBtn();
} else if (xhr.status === 401) {
// Not logged in — redirect to login
location.href = '/login?view=control&next=/mfa-setup';
Auth.request('GET', '/api/auth/mfa/status', null, function (s, status) {
if (status !== 200) return;
// Temp accounts must configure ONE login method to activate the account.
if (s.is_temp_account) {
document.querySelector('.setup-box h1').textContent = '🔐 激活你的账号';
document.querySelector('.subtitle').textContent =
'请配置至少一种登录方式(Passkey 或 TOTP)。配置并验证完成后,你的临时验证码将失效,账号立即正式生效。';
}
};
xhr.send();
if (s.passkey_enabled) { passkeyDone = true; updatePasskeyUI(true); }
if (s.totp_enabled) { totpDone = true; updateTOTPUI(true); }
if (s.recovery_codes_remaining > 0) {
showRecoveryCodes();
}
updateFinishBtn();
});
}
checkMFAStatus();
@@ -251,33 +244,26 @@
btn.disabled = true;
statusEl.textContent = '正在准备注册...';
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/auth/webauthn/register/begin');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (xhr.status !== 200) {
Auth.request('POST', '/api/auth/webauthn/register/begin', null, function (options, st) {
if (st !== 200) {
statusEl.textContent = '❌ 获取注册参数失败';
btn.disabled = false;
return;
}
var options = JSON.parse(xhr.responseText);
// Passkey 中间件:浏览器走 WebAuthn;iOS App 走原生 ASAuthorizationController
PasskeyBridge.creation(options)
.then(function (credential) {
credential.user_name = options.user && options.user.name ? options.user.name : 'user';
var verify = new XMLHttpRequest();
verify.open('POST', '/api/auth/webauthn/register/complete');
verify.setRequestHeader('Content-Type', 'application/json');
verify.onload = function () {
var res = JSON.parse(verify.responseText || '{}');
if (verify.status === 200) {
Auth.request('POST', '/api/auth/webauthn/register/complete', { credential: credential }, function (res, st2) {
if (st2 === 200) {
statusEl.textContent = '✅ Passkey 注册成功!';
passkeyDone = true;
updatePasskeyUI(true);
checkRecoveryAndFinish();
if (res.finalized) {
if (res.access_token) Auth.setAccess(res.access_token);
statusEl.textContent = '✅ 账号已激活!即将进入控制面板...';
setTimeout(function () { location.href = '/'; }, 2500);
}
@@ -285,15 +271,13 @@
statusEl.textContent = '❌ ' + (res.error || '注册失败');
btn.disabled = false;
}
};
verify.send(JSON.stringify({ credential: credential }));
});
})
.catch(function (err) {
statusEl.textContent = '⏸️ ' + (err.message || '操作取消');
btn.disabled = false;
});
};
xhr.send();
});
});
// ─── TOTP ──────────────────────────────
@@ -318,16 +302,11 @@
document.getElementById('setup-totp-btn').textContent = '取消';
// Request TOTP setup from server
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/auth/totp/setup');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (xhr.status !== 200) return;
var data = JSON.parse(xhr.responseText);
Auth.request('POST', '/api/auth/totp/setup', null, function (data, status) {
if (status !== 200) return;
document.getElementById('totp-qr-wrap').innerHTML = '<img src="data:image/png;base64,' + data.qr_base64 + '" alt="TOTP QR Code">';
document.getElementById('totp-secret-display').textContent = '密钥: ' + data.secret;
};
xhr.send();
});
});
document.getElementById('totp-verify-btn').addEventListener('click', function () {
@@ -335,12 +314,8 @@
var msg = document.getElementById('totp-msg');
msg.textContent = '';
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/auth/totp/verify');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
var res = JSON.parse(xhr.responseText || '{}');
if (xhr.status === 200) {
Auth.request('POST', '/api/auth/totp/verify', { code: code }, function (res, st) {
if (st === 200) {
msg.className = 'msg success';
msg.textContent = '✅ TOTP 已启用!';
totpDone = true;
@@ -354,14 +329,14 @@
}
checkRecoveryAndFinish();
if (res.finalized) {
if (res.access_token) Auth.setAccess(res.access_token);
msg.textContent = '✅ 账号已激活!即将进入控制面板...';
setTimeout(function () { location.href = '/'; }, 2500);
}
} else {
msg.textContent = res.error || '验证失败';
}
};
xhr.send(JSON.stringify({ code: code }));
});
});
// ─── Recovery codes ────────────────────
@@ -373,17 +348,11 @@
}
function showRecoveryCodes() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/auth/recovery-codes');
xhr.onload = function () {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
if (data.codes && data.codes.length > 0) {
showRecoveryCodesList(data.codes);
}
Auth.request('GET', '/api/auth/recovery-codes', null, function (data, status) {
if (status === 200 && data.codes && data.codes.length > 0) {
showRecoveryCodesList(data.codes);
}
};
xhr.send();
});
}
function checkRecoveryAndFinish() {
@@ -417,18 +386,14 @@
document.getElementById('finish-btn').addEventListener('click', function () {
var msg = document.getElementById('finish-msg');
msg.textContent = '正在完成设置...';
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/auth/mfa/complete');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (xhr.status === 200) {
Auth.request('POST', '/api/auth/mfa/complete', null, function (res, status) {
if (status === 200) {
if (res.access_token) Auth.setAccess(res.access_token);
location.href = '/';
} else {
var res = JSON.parse(xhr.responseText || '{}');
msg.textContent = res.error || '完成失败,请重试';
}
};
xhr.send();
});
});
// ─── Helpers ────────────────────────────