import { PERMISSION_CODES } from '@lingxi/shared'; import { create } from 'zustand'; export interface AuthUser { id: string; name: string; email: string; roleName?: string; } export interface AuthSession { accessToken: string | null; refreshToken: string | null; user: AuthUser | null; permissions: string[]; } export interface SetSessionInput { accessToken: string; refreshToken?: string | null; user: AuthUser; permissions?: readonly string[]; } export interface AuthState extends AuthSession { setSession: (input: SetSessionInput) => void; setTokens: (tokens: { accessToken: string; refreshToken?: string | null }) => void; clearSession: () => void; signInDemo: () => void; hasPermission: (code: string) => boolean; } const STORAGE_KEY = 'lingxi.auth.session'; const EMPTY_SESSION: AuthSession = { accessToken: null, refreshToken: null, user: null, permissions: [], }; export const DEMO_USER: AuthUser = { id: '01J0DEMO000000000000000000', name: '灵犀管理员', email: 'admin@lingxi.local', roleName: '平台管理员', }; function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; } function isAuthUser(value: unknown): value is AuthUser { return ( isRecord(value) && typeof value.id === 'string' && typeof value.name === 'string' && typeof value.email === 'string' ); } function isAuthSession(value: unknown): value is AuthSession { return ( isRecord(value) && (typeof value.accessToken === 'string' || value.accessToken === null) && (typeof value.refreshToken === 'string' || value.refreshToken === null) && (isAuthUser(value.user) || value.user === null) && Array.isArray(value.permissions) && value.permissions.every((permission) => typeof permission === 'string') ); } function readPersistedSession(): AuthSession { if (typeof window === 'undefined') { return EMPTY_SESSION; } try { const raw = window.localStorage.getItem(STORAGE_KEY); if (raw === null) { return EMPTY_SESSION; } const parsed: unknown = JSON.parse(raw); return isAuthSession(parsed) ? parsed : EMPTY_SESSION; } catch { return EMPTY_SESSION; } } function persistSession(session: AuthSession): void { if (typeof window === 'undefined') { return; } try { if (session.accessToken === null) { window.localStorage.removeItem(STORAGE_KEY); return; } window.localStorage.setItem(STORAGE_KEY, JSON.stringify(session)); } catch { // 本地存储不可用(隐私模式等)时退化为内存会话,不影响登录流程 } } export const useAuthStore = create()((set, get) => ({ ...readPersistedSession(), setSession: (input) => { const session: AuthSession = { accessToken: input.accessToken, refreshToken: input.refreshToken ?? null, user: input.user, permissions: [...(input.permissions ?? PERMISSION_CODES)], }; persistSession(session); set(session); }, setTokens: (tokens) => { const current = get(); const session: AuthSession = { ...current, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken ?? current.refreshToken, }; persistSession(session); set({ accessToken: session.accessToken, refreshToken: session.refreshToken }); }, clearSession: () => { persistSession(EMPTY_SESSION); set(EMPTY_SESSION); }, signInDemo: () => { const session: AuthSession = { accessToken: 'demo-access-token', refreshToken: 'demo-refresh-token', user: DEMO_USER, permissions: [...PERMISSION_CODES], }; persistSession(session); set(session); }, hasPermission: (code) => get().permissions.includes(code), }));