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

59 lines
1.8 KiB
TypeScript

import Link from "next/link";
import { redirect } from "next/navigation";
import { AuthProvider } from "@/core/auth/AuthProvider";
import { getServerSideUser } from "@/core/auth/server";
import { assertNever } from "@/core/auth/types";
import { WorkspaceContent } from "./workspace-content";
export const dynamic = "force-dynamic";
export default async function WorkspaceLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
const result = await getServerSideUser();
switch (result.tag) {
case "authenticated":
return (
<AuthProvider initialUser={result.user}>
<WorkspaceContent>{children}</WorkspaceContent>
</AuthProvider>
);
case "needs_setup":
redirect("/setup");
case "unauthenticated":
redirect("/login");
case "gateway_unavailable":
return (
<div className="flex h-screen flex-col items-center justify-center gap-4">
<p className="text-muted-foreground">
Service temporarily unavailable.
</p>
<p className="text-muted-foreground text-xs">
The backend may be restarting. Please wait a moment and try again.
</p>
<div className="flex gap-3">
<Link
href="/workspace"
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2 text-sm"
>
Retry
</Link>
<Link
href="/api/v1/auth/logout"
className="text-muted-foreground hover:bg-muted rounded-md border px-4 py-2 text-sm"
>
Logout &amp; Reset
</Link>
</div>
</div>
);
case "config_error":
throw new Error(result.message);
default:
assertNever(result);
}
}