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
74 lines
2.4 KiB
JavaScript
74 lines
2.4 KiB
JavaScript
/*
|
|
* MC Report System
|
|
* Copyright (C) 2026 Sea Network Technology Studio
|
|
* Author: CangLan <admin@sea-studio.top>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
const Auth = {
|
|
TK: 'mc_token',
|
|
US: 'mc_user',
|
|
|
|
token() { return localStorage.getItem(this.TK); },
|
|
user() { try { return JSON.parse(localStorage.getItem(this.US)); } catch { return null; } },
|
|
|
|
// JWT payload 里的 exp(秒) 已过期?
|
|
expired() {
|
|
const t = this.token();
|
|
if (!t) return true;
|
|
try {
|
|
let b64 = t.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
b64 = b64.padEnd(b64.length + (4 - b64.length % 4) % 4, '=');
|
|
const payload = JSON.parse(atob(b64));
|
|
return payload.exp ? payload.exp * 1000 < Date.now() : false;
|
|
} catch { return true; }
|
|
},
|
|
|
|
// 登录态判断: token+user 存在 且 未过期;过期自动清除(不跳转)
|
|
logged() {
|
|
if (!this.token() || !this.user()) return false;
|
|
if (this.expired()) { this.reset(); return false; }
|
|
return true;
|
|
},
|
|
|
|
async login(username, password) {
|
|
const d = await API.post('/auth/login', { username, password });
|
|
localStorage.setItem(this.TK, d.token);
|
|
localStorage.setItem(this.US, JSON.stringify(d.user));
|
|
return d.user;
|
|
},
|
|
|
|
async register(data) {
|
|
return API.post('/auth/register', data);
|
|
},
|
|
|
|
logout() {
|
|
localStorage.removeItem(this.TK);
|
|
localStorage.removeItem(this.US);
|
|
location.hash = '#/login';
|
|
},
|
|
|
|
reset() {
|
|
// 清除本地登录态但不清除 URL(调用方决定是否跳转)
|
|
localStorage.removeItem(this.TK);
|
|
localStorage.removeItem(this.US);
|
|
},
|
|
|
|
role(...r) { const u = this.user(); return u && r.includes(u.role); },
|
|
isStaff() { return this.role('owner', 'admin'); },
|
|
isAdmin() { return this.role('owner', 'admin'); },
|
|
isOwner() { return this.role('owner'); },
|
|
};
|