a3245b249a
- API Key: run.md/.env/seed_model_providers.sql 中明文Key替换为占位符 - 认证: JWT从localStorage迁移到HttpOnly Cookie,移除后端body返回token - 前端: api.ts/knowledge/page.tsx/auth.ts 全面改用Cookie认证 - postcss XSS: package.json添加overrides强制升级到8.5.15 - gitignore: 添加server/.env和.env排除规则 - lint: 移除tenant.go中未使用的roleKey常量 - 文档: 新增docs/security-audit-report.md和hardware-requirements.md
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { create } from "zustand";
|
|
import api from "@/lib/api";
|
|
import type { User, Organization } from "@/lib/types";
|
|
|
|
export type { Organization };
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
isAuthenticated: boolean;
|
|
_hasFetched: boolean;
|
|
fetchUser: () => Promise<void>;
|
|
login: (email: string, password: string, orgId?: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
setAuth: (user: User) => void;
|
|
switchOrg: (orgId: string) => Promise<void>;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
user: null,
|
|
isLoading: true,
|
|
isAuthenticated: false,
|
|
_hasFetched: false,
|
|
|
|
fetchUser: async () => {
|
|
if (get()._hasFetched) return;
|
|
set({ _hasFetched: true });
|
|
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
|
|
try {
|
|
const user = await api.get<User>("/api/v1/auth/me", { signal: controller.signal });
|
|
set({ user, isAuthenticated: true, isLoading: false });
|
|
} catch {
|
|
set({ user: null, isAuthenticated: false, isLoading: false });
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
},
|
|
|
|
login: async (email: string, password: string, orgId?: string) => {
|
|
const data = await api.post<{ user: User }>("/api/v1/auth/login", {
|
|
email,
|
|
password,
|
|
org_id: orgId,
|
|
});
|
|
set({ user: data.user, isAuthenticated: true, isLoading: false });
|
|
},
|
|
|
|
setAuth: (user: User) => {
|
|
set({ user, isAuthenticated: true, isLoading: false });
|
|
},
|
|
|
|
logout: async () => {
|
|
try {
|
|
await api.post("/api/v1/auth/logout");
|
|
} catch {
|
|
// ignore
|
|
} finally {
|
|
set({ user: null, isAuthenticated: false, _hasFetched: false });
|
|
}
|
|
},
|
|
|
|
switchOrg: async (orgId: string) => {
|
|
const data = await api.post<{
|
|
message: string;
|
|
org: Organization;
|
|
user?: User;
|
|
}>("/api/v1/auth/switch-org", { org_id: orgId });
|
|
if (data.user && data.org) {
|
|
set({ user: { ...data.user, org_id: orgId, org: data.org }, isAuthenticated: true });
|
|
} else {
|
|
const user = get().user;
|
|
if (user && data.org) {
|
|
set({ user: { ...user, org_id: orgId, org: data.org } });
|
|
}
|
|
}
|
|
},
|
|
})); |