feat: 新增管理后台前端项目及安全加固
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

refactor(saas): 重构认证中间件与限流策略
- 登录限流调整为5次/分钟/IP
- 注册限流调整为3次/小时/IP
- GET请求不计入限流

fix(saas): 修复调度器时间戳处理
- 使用NOW()替代文本时间戳
- 兼容TEXT和TIMESTAMPTZ列类型

feat(saas): 实现环境变量插值
- 支持${ENV_VAR}语法解析
- 数据库密码支持环境变量注入

chore: 新增前端管理界面
- 基于React+Ant Design Pro
- 包含路由守卫/错误边界
- 对接58个API端点

docs: 更新安全加固文档
- 新增密钥管理规范
- 记录P0安全项审计结果
- 补充TLS终止说明

test: 完善配置解析单元测试
- 新增环境变量插值测试用例
This commit is contained in:
iven
2026-03-31 00:11:33 +08:00
parent 6821df5f44
commit eb956d0dce
129 changed files with 11913 additions and 863 deletions

View File

@@ -0,0 +1,96 @@
// ============================================================
// ZCLAW Admin V2 — Zustand 认证状态管理
// ============================================================
//
// 安全策略: JWT token 通过 HttpOnly cookie 传递,前端 JS 无法读取。
// account 信息(显示名/角色)仍存 localStorage 用于页面刷新后恢复 UI。
// 内存中的 token/refreshToken 仅用于 Authorization header fallbackAPI 客户端兼容)。
import { create } from 'zustand'
import type { AccountPublic } from '@/types'
/** 权限常量 — 与后端 db.rs SEED_ROLES 保持同步 */
const ROLE_PERMISSIONS: Record<string, string[]> = {
super_admin: [
'admin:full', 'account:admin', 'provider:manage', 'model:manage',
'relay:admin', 'config:write', 'prompt:read', 'prompt:write',
'prompt:publish', 'prompt:admin',
],
admin: [
'account:read', 'account:admin', 'provider:manage', 'model:read',
'model:manage', 'relay:use', 'config:read',
'config:write', 'prompt:read', 'prompt:write', 'prompt:publish',
],
user: ['model:read', 'relay:use', 'config:read', 'prompt:read'],
}
const ACCOUNT_KEY = 'zclaw_admin_account'
/** 从 localStorage 恢复 account 信息token 通过 HttpOnly cookie 管理) */
function loadFromStorage(): { account: AccountPublic | null } {
const raw = localStorage.getItem(ACCOUNT_KEY)
let account: AccountPublic | null = null
if (raw) {
try { account = JSON.parse(raw) } catch { /* ignore */ }
}
return { account }
}
interface AuthState {
token: string | null
refreshToken: string | null
account: AccountPublic | null
permissions: string[]
setToken: (token: string) => void
setRefreshToken: (refreshToken: string) => void
login: (token: string, refreshToken: string, account: AccountPublic) => void
logout: () => void
hasPermission: (permission: string) => boolean
}
export const useAuthStore = create<AuthState>((set, get) => {
const stored = loadFromStorage()
const perms = stored.account?.role
? (ROLE_PERMISSIONS[stored.account.role] ?? [])
: []
return {
token: null,
refreshToken: null,
account: stored.account,
permissions: perms,
setToken: (token: string) => {
set({ token })
},
setRefreshToken: (refreshToken: string) => {
set({ refreshToken })
},
login: (token: string, refreshToken: string, account: AccountPublic) => {
// account 保留 localStorage仅用于 UI 显示,非敏感)
localStorage.setItem(ACCOUNT_KEY, JSON.stringify(account))
// token 仅存内存(实际认证通过 HttpOnly cookie
set({
token,
refreshToken,
account,
permissions: ROLE_PERMISSIONS[account.role] ?? [],
})
},
logout: () => {
localStorage.removeItem(ACCOUNT_KEY)
set({ token: null, refreshToken: null, account: null, permissions: [] })
// 调用后端 logout 清除 HttpOnly cookiesfire-and-forget
fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {})
},
hasPermission: (permission: string) => {
const { permissions } = get()
return permissions.includes(permission) || permissions.includes('admin:full')
},
}
})