mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* fix(frontend): surface model loading failures * refactor(frontend): reuse model error UI primitives * fix(frontend): address model banner review feedback * refactor(frontend): remove unused model fetch state
107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
import { buildLoginUrl } from "@/core/auth/types";
|
|
|
|
import { UnauthorizedError } from "./errors";
|
|
|
|
/** HTTP methods that the gateway's CSRFMiddleware checks. */
|
|
export type StateChangingMethod = "POST" | "PUT" | "DELETE" | "PATCH";
|
|
|
|
export const STATE_CHANGING_METHODS: ReadonlySet<StateChangingMethod> = new Set(
|
|
["POST", "PUT", "DELETE", "PATCH"],
|
|
);
|
|
|
|
/** Mirror of the gateway's ``should_check_csrf`` decision. */
|
|
export function isStateChangingMethod(method: string): boolean {
|
|
return (STATE_CHANGING_METHODS as ReadonlySet<string>).has(
|
|
method.toUpperCase(),
|
|
);
|
|
}
|
|
|
|
const CSRF_COOKIE_PREFIX = "csrf_token=";
|
|
|
|
/**
|
|
* Read the ``csrf_token`` cookie set by the gateway at login.
|
|
*
|
|
* SSR-safe: returns ``null`` when ``document`` is undefined so the same
|
|
* helper can be imported from server components without a guard.
|
|
*
|
|
* Uses `String.split` instead of a regex to side-step ESLint's
|
|
* `prefer-regexp-exec` rule and the cookie value's reliable `; `
|
|
* separator (set by the gateway, not the browser, so format is stable).
|
|
*/
|
|
export function readCsrfCookie(): string | null {
|
|
if (typeof document === "undefined") return null;
|
|
for (const pair of document.cookie.split("; ")) {
|
|
if (pair.startsWith(CSRF_COOKIE_PREFIX)) {
|
|
return decodeURIComponent(pair.slice(CSRF_COOKIE_PREFIX.length));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Fetch with credentials and automatic CSRF protection.
|
|
*
|
|
* Two centralized contracts every API call needs:
|
|
*
|
|
* 1. ``credentials: "include"`` so the HttpOnly access_token cookie
|
|
* accompanies cross-origin SSR-routed requests.
|
|
* 2. ``X-CSRF-Token`` header on state-changing methods (POST/PUT/
|
|
* DELETE/PATCH), echoed from the ``csrf_token`` cookie. The gateway's
|
|
* CSRFMiddleware enforces Double Submit Cookie comparison and returns
|
|
* 403 if the header is missing — silently breaking every call site
|
|
* that uses raw ``fetch()`` instead of this wrapper.
|
|
*
|
|
* Auto-redirects to ``/login`` on 401. Caller-supplied headers are
|
|
* preserved; the helper only ADDS the CSRF header when it isn't already
|
|
* present, so explicit overrides win.
|
|
*/
|
|
export async function fetch(
|
|
input: RequestInfo | string,
|
|
init?: RequestInit,
|
|
): Promise<Response> {
|
|
const url = typeof input === "string" ? input : input.url;
|
|
|
|
// Inject CSRF for state-changing methods. GET/HEAD/OPTIONS/TRACE skip
|
|
// it to mirror the gateway's ``should_check_csrf`` logic exactly.
|
|
let headers = init?.headers;
|
|
if (isStateChangingMethod(init?.method ?? "GET")) {
|
|
const token = readCsrfCookie();
|
|
if (token) {
|
|
// Fresh Headers instance so we don't mutate caller-supplied objects.
|
|
const merged = new Headers(headers);
|
|
if (!merged.has("X-CSRF-Token")) {
|
|
merged.set("X-CSRF-Token", token);
|
|
}
|
|
headers = merged;
|
|
}
|
|
}
|
|
|
|
const res = await globalThis.fetch(url, {
|
|
...init,
|
|
headers,
|
|
credentials: "include",
|
|
});
|
|
|
|
if (res.status === 401) {
|
|
window.location.href = buildLoginUrl(window.location.pathname);
|
|
throw new UnauthorizedError();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Build headers for CSRF-protected requests.
|
|
*
|
|
* **Prefer :func:`fetchWithAuth`** for new code — it injects the header
|
|
* automatically on state-changing methods. This helper exists for legacy
|
|
* call sites that need to compose headers manually (e.g. inside
|
|
* `next/server` route handlers that build their own ``Headers`` object).
|
|
*
|
|
* Per RFC-001: Double Submit Cookie pattern.
|
|
*/
|
|
export function getCsrfHeaders(): HeadersInit {
|
|
const token = readCsrfCookie();
|
|
return token ? { "X-CSRF-Token": token } : {};
|
|
}
|