security: 安全审计修复 - API Key清理 + JWT改为HttpOnly Cookie

- 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
This commit is contained in:
selfrelease
2026-06-26 10:44:04 +08:00
parent c011f2eae8
commit a3245b249a
15 changed files with 442 additions and 86 deletions
+1 -2
View File
@@ -45,10 +45,9 @@ export default function RegisterPage() {
email: string;
role: "user" | "super_admin" | "admin" | "creator";
};
access_token: string;
}>("/api/v1/auth/register", { name, email, password });
setAuth(res.user, res.access_token);
setAuth(res.user);
toast.success("注册成功");
router.push("/store");
} catch (err) {
+1 -1
View File
@@ -113,8 +113,8 @@ export default function KnowledgePage() {
formData.append("file", file);
const res = await fetch(`/api/v1/knowledge/${selectedKB!.id}/documents`, {
method: "POST",
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
body: formData,
credentials: "include",
});
if (!res.ok) throw new Error("上传失败");
return res.json();
+1 -10
View File
@@ -36,11 +36,8 @@ class APIError extends Error {
}
}
// 认证:token 通过 HttpOnly Cookie 自动携带,不再使用 localStorage
function getAuthHeaders(): Record<string, string> {
if (typeof window !== "undefined") {
const token = localStorage.getItem("token");
if (token) return { Authorization: `Bearer ${token}` };
}
return {};
}
@@ -81,12 +78,6 @@ async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
}
if (res.status === 401) {
if (typeof window !== "undefined") {
const currentPath = window.location.pathname;
if (currentPath !== "/login" && currentPath !== "/register") {
localStorage.removeItem("token");
}
}
throw new APIError(json.code || 40101, json.message || "未登录或登录已过期");
}
+4 -20
View File
@@ -12,7 +12,7 @@ interface AuthState {
fetchUser: () => Promise<void>;
login: (email: string, password: string, orgId?: string) => Promise<void>;
logout: () => Promise<void>;
setAuth: (user: User, token: string) => void;
setAuth: (user: User) => void;
switchOrg: (orgId: string) => Promise<void>;
}
@@ -26,12 +26,6 @@ export const useAuthStore = create<AuthState>((set, get) => ({
if (get()._hasFetched) return;
set({ _hasFetched: true });
const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
if (!token) {
set({ user: null, isAuthenticated: false, isLoading: false });
return;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
@@ -39,7 +33,6 @@ export const useAuthStore = create<AuthState>((set, get) => ({
const user = await api.get<User>("/api/v1/auth/me", { signal: controller.signal });
set({ user, isAuthenticated: true, isLoading: false });
} catch {
if (typeof window !== "undefined") localStorage.removeItem("token");
set({ user: null, isAuthenticated: false, isLoading: false });
} finally {
clearTimeout(timeout);
@@ -47,19 +40,15 @@ export const useAuthStore = create<AuthState>((set, get) => ({
},
login: async (email: string, password: string, orgId?: string) => {
const data = await api.post<{ user: User; access_token: string }>("/api/v1/auth/login", {
const data = await api.post<{ user: User }>("/api/v1/auth/login", {
email,
password,
org_id: orgId,
});
if (data.access_token) {
localStorage.setItem("token", data.access_token);
}
set({ user: data.user, isAuthenticated: true, isLoading: false });
},
setAuth: (user: User, token: string) => {
if (token) localStorage.setItem("token", token);
setAuth: (user: User) => {
set({ user, isAuthenticated: true, isLoading: false });
},
@@ -69,7 +58,6 @@ export const useAuthStore = create<AuthState>((set, get) => ({
} catch {
// ignore
} finally {
localStorage.removeItem("token");
set({ user: null, isAuthenticated: false, _hasFetched: false });
}
},
@@ -78,12 +66,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
const data = await api.post<{
message: string;
org: Organization;
token?: string;
user?: User;
}>("/api/v1/auth/switch-org", { org_id: orgId });
if (data.token) {
localStorage.setItem("token", data.token);
}
if (data.user && data.org) {
set({ user: { ...data.user, org_id: orgId, org: data.org }, isAuthenticated: true });
} else {
@@ -93,4 +77,4 @@ export const useAuthStore = create<AuthState>((set, get) => ({
}
}
},
}));
}));