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:
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 || "未登录或登录已过期");
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => ({
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
}));
|
||||
Reference in New Issue
Block a user