Files
deer-flow/frontend/src/core/api/fetcher.ts
T
greatmengqi f942e4e597 feat(auth): wire auth end-to-end (middleware + frontend replacement)
Backend:
- Port auth_middleware, csrf_middleware, langgraph_auth, routers/auth
- Port authz decorator (owner_filter_key defaults to 'owner_id')
- Merge app.py: register AuthMiddleware + CSRFMiddleware + CORS, add
  _ensure_admin_user lifespan hook, _migrate_orphaned_threads helper,
  register auth router
- Merge deps.py: add get_local_provider, get_current_user_from_request,
  get_optional_user_from_request; keep get_current_user as thin str|None
  adapter for feedback router
- langgraph.json: add auth path pointing to langgraph_auth.py:auth
- Rename metadata['user_id'] -> metadata['owner_id'] in langgraph_auth
  (both metadata write and LangGraph filter dict) + test fixtures

Frontend:
- Delete better-auth library and api catch-all route
- Remove better-auth npm dependency and env vars (BETTER_AUTH_SECRET,
  BETTER_AUTH_GITHUB_*) from env.js
- Port frontend/src/core/auth/* (AuthProvider, gateway-config,
  proxy-policy, server-side getServerSideUser, types)
- Port frontend/src/core/api/fetcher.ts
- Port (auth)/layout, (auth)/login, (auth)/setup pages
- Rewrite workspace/layout.tsx as server component that calls
  getServerSideUser and wraps in AuthProvider
- Port workspace/workspace-content.tsx for the client-side sidebar logic

Tests:
- Port 5 auth test files (test_auth, test_auth_middleware,
  test_auth_type_system, test_ensure_admin, test_langgraph_auth)
- 176 auth tests PASS

After this commit: login/logout/registration flow works, but persistence
layer does not yet filter by owner_id. Commit 4 closes that gap.
2026-04-08 09:41:56 +08:00

40 lines
938 B
TypeScript

import { buildLoginUrl } from "@/core/auth/types";
/**
* Fetch with credentials. Automatically redirects to login on 401.
*/
export async function fetchWithAuth(
input: RequestInfo | string,
init?: RequestInit,
): Promise<Response> {
const url = typeof input === "string" ? input : input.url;
const res = await fetch(url, {
...init,
credentials: "include",
});
if (res.status === 401) {
window.location.href = buildLoginUrl(window.location.pathname);
throw new Error("Unauthorized");
}
return res;
}
/**
* Build headers for CSRF-protected requests
* Per RFC-001: Double Submit Cookie pattern
*/
export function getCsrfHeaders(): HeadersInit {
const token = getCsrfToken();
return token ? { "X-CSRF-Token": token } : {};
}
/**
* Get CSRF token from cookie
*/
function getCsrfToken(): string | null {
const match = /csrf_token=([^;]+)/.exec(document.cookie);
return match?.[1] ?? null;
}