// ============================================================ // ZCLAW Admin V2 — Zustand 认证状态管理 // ============================================================ // // 安全策略: JWT token 通过 HttpOnly cookie 传递,前端 JS 无法读取。 // account 信息(显示名/角色)仍存 localStorage 用于页面刷新后恢复 UI。 // 内存中的 token/refreshToken 仅用于 Authorization header fallback(API 客户端兼容)。 import { create } from 'zustand' import type { AccountPublic } from '@/types' /** 权限常量 — 与后端 db.rs SEED_ROLES 保持同步 */ const ROLE_PERMISSIONS: Record = { 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((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 cookies(fire-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') }, } })