mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(auth): 重构认证系统,引入 session 会话管理
- 新增 user_sessions 数据库表及 Alembic 迁移 - 实现 SessionService 会话生命周期管理(创建/刷新/撤销/清理) - 认证流程改用 refresh token cookie + access token 双令牌模式 - 前端实现自动静默刷新、跨标签页同步及设备指纹 - 用户设置页新增会话管理和密码修改功能 - 管理员用户管理新增强制登出和会话查看 - 密码策略增强,支持强度校验和泄露检测 - OAuth 登录流程适配新会话机制 - 新增完整的单元测试和 API 测试覆盖 Closes #232 Co-authored-by: LewisPen <LewisPen@nyadoo.com>
This commit is contained in:
@@ -76,7 +76,6 @@ export const MOCK_NORMAL_USER: User = {
|
||||
|
||||
export const MOCK_LOGIN_RESPONSE_ADMIN: LoginResponse = {
|
||||
access_token: 'demo-access-token-admin',
|
||||
refresh_token: 'demo-refresh-token-admin',
|
||||
token_type: 'bearer',
|
||||
expires_in: 3600,
|
||||
user_id: MOCK_ADMIN_USER.id,
|
||||
@@ -87,7 +86,6 @@ export const MOCK_LOGIN_RESPONSE_ADMIN: LoginResponse = {
|
||||
|
||||
export const MOCK_LOGIN_RESPONSE_USER: LoginResponse = {
|
||||
access_token: 'demo-access-token-user',
|
||||
refresh_token: 'demo-refresh-token-user',
|
||||
token_type: 'bearer',
|
||||
expires_in: 3600,
|
||||
user_id: MOCK_NORMAL_USER.id,
|
||||
|
||||
@@ -531,6 +531,76 @@ const mockHandlers: Record<string, (config: AxiosRequestConfig) => Promise<Axios
|
||||
return createMockResponse({ message: '密码修改成功(演示模式)' })
|
||||
},
|
||||
|
||||
'GET /api/users/me/sessions': async () => {
|
||||
await delay()
|
||||
return createMockResponse([
|
||||
{
|
||||
id: 'session-current',
|
||||
device_label: 'Chrome / macOS',
|
||||
device_type: 'desktop',
|
||||
browser_name: 'Chrome',
|
||||
browser_version: '134.0',
|
||||
os_name: 'macOS',
|
||||
os_version: '15.3',
|
||||
device_model: null,
|
||||
ip_address: '192.168.1.100',
|
||||
last_seen_at: new Date().toISOString(),
|
||||
created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(),
|
||||
is_current: true,
|
||||
revoked_at: null,
|
||||
revoke_reason: null
|
||||
},
|
||||
{
|
||||
id: 'session-other',
|
||||
device_label: 'Safari / iPhone',
|
||||
device_type: 'mobile',
|
||||
browser_name: 'Safari',
|
||||
browser_version: '18.0',
|
||||
os_name: 'iOS',
|
||||
os_version: '18.3',
|
||||
device_model: 'iPhone',
|
||||
ip_address: '10.0.0.12',
|
||||
last_seen_at: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
|
||||
created_at: new Date(Date.now() - 5 * 24 * 3600 * 1000).toISOString(),
|
||||
is_current: false,
|
||||
revoked_at: null,
|
||||
revoke_reason: null
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
'DELETE /api/users/me/sessions/others': async () => {
|
||||
await delay()
|
||||
return createMockResponse({ message: '其他设备已退出登录(演示模式)', revoked_count: 1 })
|
||||
},
|
||||
|
||||
'PATCH /api/users/me/sessions/:sessionId': async (config) => {
|
||||
await delay()
|
||||
const sessionId = config.url?.split('/').pop() || 'session'
|
||||
const body = JSON.parse(config.data || '{}')
|
||||
return createMockResponse({
|
||||
id: sessionId,
|
||||
device_label: body.device_label || '已重命名设备',
|
||||
device_type: 'desktop',
|
||||
browser_name: 'Chrome',
|
||||
browser_version: '134.0',
|
||||
os_name: 'macOS',
|
||||
os_version: '15.3',
|
||||
device_model: null,
|
||||
ip_address: '192.168.1.100',
|
||||
last_seen_at: new Date().toISOString(),
|
||||
created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(),
|
||||
is_current: sessionId === 'session-current',
|
||||
revoked_at: null,
|
||||
revoke_reason: null
|
||||
})
|
||||
},
|
||||
|
||||
'DELETE /api/users/me/sessions/:sessionId': async () => {
|
||||
await delay()
|
||||
return createMockResponse({ message: '设备已退出登录(演示模式)' })
|
||||
},
|
||||
|
||||
'GET /api/users/me/api-keys': async () => {
|
||||
await delay()
|
||||
return createMockResponse(MOCK_USER_API_KEYS)
|
||||
@@ -2086,6 +2156,44 @@ registerDynamicRoute('GET', '/api/admin/users/:userId/api-keys', async (_config,
|
||||
return createMockResponse(MOCK_USER_API_KEYS)
|
||||
})
|
||||
|
||||
// 管理员 - 用户会话列表
|
||||
registerDynamicRoute('GET', '/api/admin/users/:userId/sessions', async (_config, _params) => {
|
||||
await delay()
|
||||
requireAdmin()
|
||||
return createMockResponse([
|
||||
{
|
||||
id: 'admin-session-1',
|
||||
device_label: 'Chrome / macOS',
|
||||
device_type: 'desktop',
|
||||
browser_name: 'Chrome',
|
||||
browser_version: '134.0',
|
||||
os_name: 'macOS',
|
||||
os_version: '15.3',
|
||||
device_model: null,
|
||||
ip_address: '192.168.1.100',
|
||||
last_seen_at: new Date().toISOString(),
|
||||
created_at: new Date(Date.now() - 2 * 24 * 3600 * 1000).toISOString(),
|
||||
is_current: false,
|
||||
revoked_at: null,
|
||||
revoke_reason: null
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// 管理员 - 撤销用户单个会话
|
||||
registerDynamicRoute('DELETE', '/api/admin/users/:userId/sessions/:sessionId', async (_config, _params) => {
|
||||
await delay()
|
||||
requireAdmin()
|
||||
return createMockResponse({ message: '会话已撤销(演示模式)' })
|
||||
})
|
||||
|
||||
// 管理员 - 撤销用户全部会话
|
||||
registerDynamicRoute('DELETE', '/api/admin/users/:userId/sessions', async (_config, _params) => {
|
||||
await delay()
|
||||
requireAdmin()
|
||||
return createMockResponse({ message: '全部会话已撤销(演示模式)', revoked_count: 1 })
|
||||
})
|
||||
|
||||
// API Key 详情
|
||||
registerDynamicRoute('GET', '/api/admin/api-keys/:keyId', async (_config, params) => {
|
||||
await delay()
|
||||
|
||||
Reference in New Issue
Block a user