fix(auth): 修复登录后反复报未配置账号并跳回登录页的死循环
根因:ny_save_config 只写不校验,Db::settingsSet 内部吞异常且返回 void。 data/ 目录不可写时登录页照常显示登录成功,但下一请求读到的账号仍是空的 → 抛未配置卷王登录账号 → 前端 auth 打标 → 跳登录页 → 无限循环。 修复: - Db::settingsSet 改返回 bool 并回读校验;新增 settingsDelete - ny_save_config 双写后均做回读校验,失败写入 ny_config_save_error() - 新增 ny_config_diag()/ny_session_usable(),暴露来源/可写性/是否已存账号 - ny_load_config 把 null 账号强制成空字符串(脏数据不再伪装成未配置) - login.php 存凭据后回读确认,存不住/会话不可写直接报错而不是假成功 - login.php 账号为空时页面直接给出 data 目录写权限提示 - config_save 合并逻辑抽成 ny_apply_config_input:账号/密码不许被改空 - 鉴权失败响应带 extra.diag,前端 401 也算 auth - 前端防死循环:刚登录 120s 内或已跳 2 次则停止自动跳转,弹窗给出真实原因+诊断 测试:新增 tests/test-config-persist.php(29) 与 tools/test-login-loop.js(19); run-all.sh 纳入两者。全套 148+48=196 项通过。
This commit is contained in:
+42
-3
@@ -161,10 +161,43 @@ async function api(action, params){
|
||||
const j = await res.json();
|
||||
if (j && j.ok) return j.data;
|
||||
const err = new Error((j && (j.msg || j.error)) || '请求失败');
|
||||
err.auth = !!(j && j.extra && j.extra.auth);
|
||||
// 401 = 本系统会话已失效,同样需要重新登录(防死循环闸门见 handleAuthFail)
|
||||
err.auth = !!(j && j.extra && j.extra.auth) || res.status === 401;
|
||||
err.extra = (j && j.extra) || null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
/* ---------- 鉴权失败:防反复跳登录页的死循环 ----------
|
||||
* 若刚登录完(120 秒内)又鉴权失败,说明"重新登录"解决不了问题(多半是凭据没存住),
|
||||
* 此时不再跳转,直接把服务端原因 + 落盘诊断显示出来,让人能自查。 */
|
||||
function authJustLoggedIn(){
|
||||
try {
|
||||
const t = parseInt(sessionStorage.getItem('ny_login_at') || '0', 10);
|
||||
return t > 0 && (Date.now() - t) < 120000;
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
function authJumpCount(){
|
||||
try { return parseInt(sessionStorage.getItem('ny_auth_jumps') || '0', 10) || 0; } catch (e) { return 0; }
|
||||
}
|
||||
function showAuthFailureBox(e){
|
||||
const d = (e.extra && e.extra.diag) || null;
|
||||
const yn = v => (v ? '是' : '否');
|
||||
const lines = d ? [
|
||||
'配置来源:' + (d.storage || '-') + '(配置文件可写=' + yn(d.cfg_writable) + ')',
|
||||
'数据目录:' + (d.data_dir || '-') + '(可写=' + yn(d.dir_writable) + ')',
|
||||
'数据库:' + (d.db_connected ? ('已连接(可写=' + yn(d.db_writable) + ')') : ('未连接 ' + (d.db_error || ''))),
|
||||
'已存账号:' + (d.has_username ? (d.username || '已设置') : '空 ← 这就是「未配置账号」的原因'),
|
||||
'已存密码:' + (d.has_password ? '已保存' : '空'),
|
||||
d.save_error ? ('保存异常:' + d.save_error) : '',
|
||||
].filter(Boolean) : [];
|
||||
$('#boardBody').innerHTML =
|
||||
'<div class="empty" style="text-align:left;line-height:1.9">' +
|
||||
'<b>无法访问卷王</b><br>' + esc(e.message || '') +
|
||||
(lines.length ? '<pre style="white-space:pre-wrap;margin:10px 0 0;text-align:left;font-size:12px">' + esc(lines.join('\n')) + '</pre>' : '') +
|
||||
'<div style="margin-top:12px"><a class="btn btn-primary" href="login.php">重新登录</a></div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
/* ---------- 问卷列表(双策略文件夹识别 + 缓存) ---------- */
|
||||
function renderProjects(list){
|
||||
const sel = $('#projectSel');
|
||||
@@ -249,6 +282,9 @@ async function refreshClassSel(){
|
||||
renderBoard();
|
||||
} catch (e) {
|
||||
if (e.auth) {
|
||||
const jumps = authJumpCount();
|
||||
if (authJustLoggedIn() || jumps >= 2) { showAuthFailureBox(e); return; }
|
||||
try { sessionStorage.setItem('ny_auth_jumps', String(jumps + 1)); } catch (err) {}
|
||||
// 卷王账号未配置或登录失效:跳转登录页自愈
|
||||
$('#boardBody').innerHTML = '<div class="empty">卷王账号未配置或登录已失效,<a href="login.php">点击重新登录</a></div>';
|
||||
setTimeout(() => { location.href = 'login.php'; }, 1200);
|
||||
@@ -372,7 +408,10 @@ function markStale(msg){
|
||||
// 具体原因放进 title(鼠标悬停可见),正文保持简短
|
||||
if (lu) { lu.textContent = '刷新中断(仍显示上次数据)'; if (msg) lu.title = String(msg); }
|
||||
}
|
||||
function handleAuthFail(){
|
||||
function handleAuthFail(e){
|
||||
const jumps = authJumpCount();
|
||||
if (authJustLoggedIn() || jumps >= 2) { showAuthFailureBox(e || new Error('鉴权失败')); return; }
|
||||
try { sessionStorage.setItem('ny_auth_jumps', String(jumps + 1)); } catch (err) {}
|
||||
$('#boardBody').innerHTML = '<div class="empty">卷王账号未配置或登录已失效,<a href="login.php">点击重新登录</a></div>';
|
||||
setTimeout(() => { location.href = 'login.php'; }, 1200);
|
||||
}
|
||||
@@ -524,7 +563,7 @@ async function refreshBoard(manual){
|
||||
$('#lastUpdate').textContent = fresh.updated_at || '-';
|
||||
markLive();
|
||||
} catch (e) {
|
||||
if (e.auth) { handleAuthFail(); return; }
|
||||
if (e.auth) { handleAuthFail(e); return; }
|
||||
// 静默续期:保留旧数据,但把实时点变红提示中断
|
||||
markStale(e.message);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user