refactor: auth expiry - sync JWT exp check, remove async validation

Previous async validateAuth() approach caused: recursion loop,
wrong /me path (404 -> session cleared), init() fetching auth-required
settings without token (cleared session every load).

New synchronous approach:
- Auth.expired(): decodes JWT payload locally, checks exp timestamp
- Auth.logged(): token+user exist AND not expired; auto-clears stale
  session on expiry (no network, no async, no loops)
- route(): simple sync guard - if !Auth.logged() reset + redirect to
  #/login; home page stays public (clears stale cookie only)
- API.req: 401 response -> Auth.reset() + redirect to login unless on
  public page (fallback for server-side revocation/invalid signature)
- Removed validateAuth/_validating/_doRoute entirely
This commit is contained in:
2026-08-21 21:01:30 +08:00
parent edf561729f
commit 188e083719
3 changed files with 27 additions and 43 deletions

View File

@@ -86,28 +86,6 @@ const App = {
closeModal() { document.getElementById('modal-overlay').classList.add('hidden'); },
navigate(page, param) { location.hash = `#/${page}` + (param ? `/${param}` : ''); },
_validating: false,
// 验证 token 有效性(轻量级,失败则清除登录态)
async validateAuth() {
if (this._validating) return null; // 防止递归
this._validating = true;
if (!Auth.logged()) { this._validating = false; return false; }
try {
await API.get('/auth/me');
this._validating = false;
return true;
} catch (e) {
this._validating = false;
if (e && e.message && e.message.includes('登录')) {
Auth.reset();
return false;
}
// 非登录错误(网络/404/500)不视为登录失效,放行路由
return true;
}
},
route() {
const hash = location.hash || '#/home';
const parts = hash.replace('#/', '').split('/');
@@ -123,31 +101,13 @@ const App = {
if (page === 'home') { this.showPublic('home', param); return; }
if (page === 'login' || page === 'register' || page === 'forgot' || page === 'reset') { this.showPublic(page); return; }
// 访问受保护页面前先验证 token 有效性
if (Auth.logged() && !this._validating) {
this._validating = true; // 标记正在验证
this.validateAuth().then(valid => {
this._validating = false;
if (!valid) {
location.hash = '#/login';
return;
}
this._doRoute(page, param);
}).catch(() => {
this._validating = false;
Auth.reset();
location.hash = '#/login';
});
return;
}
// 受保护页面: Auth.logged() 内部解码 JWT exp,过期自动清除登录态(同步,无异步循环)
if (!Auth.logged()) {
Auth.reset();
location.hash = '#/login';
return;
}
// 正在验证中,不做任何操作(等待验证完成)
},
_doRoute(page, param) {
this.showLayout();
const sidebarPage = (page === 'tickets' && param.startsWith('create')) ? 'tickets-create' : page;
this.renderSidebar(sidebarPage);