feat(auth): 登出留痕审计
- 原登出为纯前端清 token,不发请求,故无记录 - 新增 POST /api/auth/logout 端点(无状态,仅供审计留痕),deriveActionLabel 加「登出」标签 - 前端 logout 先带 token 通知后端再清本地,失败不阻塞 - 审计中间件经 authMiddleware 解析操作人,记录谁/何时登出
This commit is contained in:
@@ -150,6 +150,7 @@ function deriveActionLabel(method: string, path: string): string {
|
|||||||
const p = path.replace(/\/+$/, '');
|
const p = path.replace(/\/+$/, '');
|
||||||
const rules: ReadonlyArray<[RegExp, string]> = [
|
const rules: ReadonlyArray<[RegExp, string]> = [
|
||||||
[/^\/api\/auth\/login$/, '登录'],
|
[/^\/api\/auth\/login$/, '登录'],
|
||||||
|
[/^\/api\/auth\/logout$/, '登出'],
|
||||||
[/^\/api\/assessments\/run$/, '运行评估(创建/重评)'],
|
[/^\/api\/assessments\/run$/, '运行评估(创建/重评)'],
|
||||||
[/^\/api\/assessments\/[^/]+\/submit$/, '申报报送风控'],
|
[/^\/api\/assessments\/[^/]+\/submit$/, '申报报送风控'],
|
||||||
[/^\/api\/assessments\/[^/]+\/resubmit$/, '驳回后重新提交'],
|
[/^\/api\/assessments\/[^/]+\/resubmit$/, '驳回后重新提交'],
|
||||||
@@ -319,6 +320,15 @@ app.post('/api/auth/login', async (c) => {
|
|||||||
return c.json({ token, username, role });
|
return c.json({ token, username, role });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/auth/logout
|
||||||
|
* 登出:无状态 JWT 由客户端丢弃 token 即可,本端点仅用于留痕审计(记录谁/何时登出)。
|
||||||
|
* 需携带有效 token,审计中间件据此解析操作人。
|
||||||
|
*/
|
||||||
|
app.post('/api/auth/logout', (c) => {
|
||||||
|
return c.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
/** LLM 启用状态(不泄露密钥),供前端/调试查询。 */
|
/** LLM 启用状态(不泄露密钥),供前端/调试查询。 */
|
||||||
app.get('/api/llm/status', (c) => {
|
app.get('/api/llm/status', (c) => {
|
||||||
const cfg = getLlmConfig();
|
const cfg = getLlmConfig();
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
|
// 先通知后端留痕(登出审计),再清除本地令牌;失败不阻塞登出。
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('risk-agent-token');
|
||||||
|
void fetch(`${API_BASE}/api/auth/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||||
|
}).catch(() => undefined);
|
||||||
|
} catch { /* ignore */ }
|
||||||
saveToStorage(null);
|
saveToStorage(null);
|
||||||
try { localStorage.removeItem('risk-agent-token'); localStorage.removeItem('risk-agent-uid'); } catch { /* ignore */ }
|
try { localStorage.removeItem('risk-agent-token'); localStorage.removeItem('risk-agent-uid'); } catch { /* ignore */ }
|
||||||
set({ isAuthenticated: false, user: null, error: null });
|
set({ isAuthenticated: false, user: null, error: null });
|
||||||
|
|||||||
Reference in New Issue
Block a user