feat(platform): replace Supabase Auth with Better Auth - #13330
Conversation
…rvice deps - autogpt_libs.auth: verify asymmetric (ES256/RS256/EdDSA) JWTs against a JWKS endpoint (JWT_JWKS_URL) while keeping the legacy HS256 shared-secret path active for the migration window, so sessions issued by Supabase GoTrue stay valid - add Better Auth tables (user/session/account/verification/jwks) to the Prisma schema + migration; drop the legacy auth.users sync trigger - feature flags: build LaunchDarkly user context from the auth user table instead of the Supabase admin API - e2e test data: seed Better Auth credential accounts (bcrypt) instead of creating users via supabase.auth.admin - remove supabase python SDK, SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e frontend - Better Auth server at /api/auth/* (cookie DB sessions, ES256 JWT plugin with JWKS for the Python backend, admin plugin for roles, bcrypt password hashing so migrated GoTrue hashes keep verifying) - supabase-bridge plugin: silently upgrades valid legacy Supabase session cookies into Better Auth sessions so logged-in users survive the cutover - new src/lib/auth module (server config, actions, hooks, middleware) with a Supabase-compatible User shape; all ~50 consumers swept from useSupabase/useSupabaseStore to useAuth/useAuthStore - auth flows rewritten: login, signup, OAuth provider + callback, password reset (token-based), email change, logout; legacy confirm/code-exchange routes removed - docker compose: kong/gotrue/studio/meta removed; db is now plain pgvector/pgvector:pg16 with an init shim for historical migrations that referenced auth.users - one-time data migration script (frontend/scripts/migrate-supabase-auth.ts) copying auth.users/auth.identities into the Better Auth tables with UUIDs and bcrypt hashes preserved - @supabase/ssr and @supabase/supabase-js removed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Better Auth - backend CI: replace Supabase CLI with pgvector/pgvector:pg16 container (init shim provides the legacy auth schema for historical migrations) - fullstack CI: start only db (no gotrue), drop auth.users from the e2e data cache dump (Better Auth tables live in the platform schema dump) - agent sandbox workflows: pre-pull pgvector instead of supabase images - unit tests for the new auth module (bridge cookie parsing, user mapping, route-protection middleware) - docs: getting-started, advanced setup, oauth flow, READMEs, TESTING.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pin kysely to 0.28.17: @better-auth/kysely-adapter 1.6.x imports DEFAULT_MIGRATION_LOCK_TABLE from the kysely root, which 0.29 moved to kysely/migration (runtime export removed), crashing every auth route - the orval mutator chain put src/lib/auth/server/token.ts in the client component graph: browser bundles now resolve it to a no-op stub via webpack/turbopack aliases (browser calls go through /api/proxy, which mints the JWT server-side), and the server layers defer the next/headers and Better Auth imports to call time, mirroring the lazy-require pattern the Supabase client used here before Verified e2e locally: fresh-DB migrations, signup, JWKS, ES256 JWT accepted by the Python backend (platform User created with same UUID), legacy HS256 token dual-validation, supabase-bridge upgrade of an expired legacy session cookie, bcrypt sign-in, middleware redirects, proxy token minting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PR migrates platform authentication from Supabase to Better Auth, adds JWKS-based JWT verification and service-token authorization, introduces transactional auth-email delivery, replaces the local Supabase stack with PostgreSQL/pgvector, adds user migration paths, and updates frontend consumers, CI, tests, configuration, and documentation. ChangesBetter Auth foundation
Backend and database integration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #13330 +/- ##
==========================================
+ Coverage 76.77% 76.90% +0.13%
==========================================
Files 2750 2761 +11
Lines 209139 209516 +377
Branches 20059 20078 +19
==========================================
+ Hits 160559 161131 +572
+ Misses 44198 43946 -252
- Partials 4382 4439 +57
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
- mint backend JWTs via an HTTP call to /api/auth/token instead of
importing the Better Auth server instance: keeps pg/nodemailer out of
the client component graph (fixes the prod webpack build) and reads
cookies via next/headers cookies() so a session created earlier in the
same server action is visible immediately (cursor: high)
- revoke all sessions on password reset, matching the previous
signOut({ scope: global }) behavior
- never log one-time auth links in production when SMTP is unconfigured
- regenerate poetry.lock with Poetry 2.2.1 to match CI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… helpers, bridge) 73 new unit tests: server actions, auth config closures (bcrypt round-trip, JWT claim mapping, social provider env wiring), email fallback behavior, page-protection helpers, hook helpers, getServerUser, and legacy-token verification (jose-signed fixtures incl. tolerance-window cases). Also restructures the lazy next/headers require so prettier can't orphan its eslint suppression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The legacy Supabase auth.users trigger created a Profile row for every new user; with the trigger gone, nothing did. Store submissions (and the /profile page) hard-require a Profile, which broke the marketplace, publish, and settings e2e suites — and would have broken every new production signup. get_or_create_user now creates (or backfills) a default Profile with a generated username, mirroring the old trigger. Also keys the e2e data cache on backend/data/user.py so the previously cached profile-less dataset can't be restored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The signup action and onboarding page can both trigger get-or-create for a brand-new user at once; the loser of the Profile insert race now no-ops instead of bubbling a 500. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A silent return let the reset-password UI report "Email sent" while nothing was delivered. Throwing surfaces the misconfiguration as a flow error instead; it throws for every address equally, so account existence is not leaked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🧹 Auto-undeploying: PR closed with active deployment. Cleaning up development environment for PR #13330. |
|
🧹 Preview Environment Cleaned Up All resources for PR #13330 have been removed:
Cleanup completed successfully. |
Resolve org-batch vs dev conflicts (union both sides): - v1.py update_graph: keep team_id Query param + adopt dev UpdateGraphResponse - chat/routes.py SessionSummaryResponse: keep organization_id/team_id + expert_id - chat/routes_test.py _make_session_info: union org/team + expert_id params - library/model_test.py _make_library_agent: union org/team + name/description Auth-path verified against Better Auth: RequestContext/get_request_context unchanged by migration; orgs routes/regression + library model tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
Resolve tool_schema_test.py _CHAR_BUDGET conflict: dev's OPEN-3188 structured agent-graph tools (50915) + our tiered-memory tool params stack in the merged registry (measured 51980 chars) — neither branch's budget fit, so raise to 52500 (~520 headroom). Memory-tier + graphiti tests pass post-merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…emory-hold-buffer-api Forward-merge inherits dev + tool_schema budget resolution from parent. orgs/memory_routes_test (hold-buffer governance + auth path) passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
Clean merge. grant_db_test + grants_test pass against Better Auth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…rant-credential-modes Forward-merge inherits dev. grant_db + grants + credentials-owner + auto-credentials tests pass against Better Auth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…edentials Clean merge. credentials_store_test (team/org creds read path) passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
Clean merge. scoped_credentials_test (team-scoped credential write) passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…m-spend Clean merge. spend_test (per-team spend breakdown + MANAGE_BILLING gate) passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
) into feat/org-avatars-v2 Forward-merge inherits dev + spend. Resolved generated openapi.json union: keep HeldMemory/HeldMemoryListResponse (rollup-aligned) + dev's HireRequest/ HireResult; SessionSummaryResponse gets organization_id/team_id + expert_id. orgs/routes_test (avatar upload + org auth path) passes against Better Auth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
Clean merge — frontend chain root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…ecut/teams-crud Resolve OrgTeamProvider.tsx: take Better Auth useAuth() (useSupabase removed), keep our org/team-fetching store logic (setTeams for loadTeams). Union. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…nvites Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…sfer Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…ling-toggle Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…ut/tabs Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…-dropdown Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…eat/team-picker-creates Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
… feat/team-badges-lists Clean forward-merge (inherits OrgTeamProvider resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…eat/marketplace-team-splitbutton Resolve AddToLibraryButton.tsx: keep our SplitButton + X-Team-Id team-target transport (handleAdd per team); Button/className still used by logged-out and error renders. Union with dev. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
) into feat/invitation-resend-button Clean forward-merge (inherits OrgTeamProvider + AddToLibraryButton resolutions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
… into feat/agent-share-picker Clean forward-merge (inherits OrgTeamProvider + AddToLibraryButton resolutions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
Why / What / How
Why: The platform's only hard dependency on the Supabase stack was auth (GoTrue + Kong + the
auth.usersschema). That dependency forced every deployment — including local/self-hosted — to run the full docker-compose Supabase sidecar, which blocks the longer-term goal of shipping the platform as a single container or packaged app.What: Replaces Supabase Auth with Better Auth (1.6.x) embedded in the Next.js frontend, with zero session loss for existing users and zero password resets.
How:
/api/auth/*(cookie-based DB sessions, tables in theplatformschema of the existing Postgres, owned by Prisma migrations — one migration authority)./api/auth/jwks;autogpt_libs.authnow validates asymmetric tokens againstJWT_JWKS_URLwhile keeping the legacy HS256 shared-secret path active, dispatched by token algorithm. JWT claims are shaped identically to GoTrue's (sub,email,role,user_metadata.name,aud: "authenticated"), so no route/handler changes were needed.supabase-bridgeBetter Auth plugin silently upgrades a legacy Supabase cookie (signature-verified, accepted up toSUPABASE_BRIDGE_MAX_TOKEN_AGE_DAYSpast expiry since refresh tokens have no issuer anymore) into a Better Auth session, triggered by middleware exactly once per browser,scripts/migrate-supabase-auth.tscopiesauth.users/auth.identitiesinto the Better Auth tables preserving UUIDs (no FK orphans) and bcrypt hashes (Better Auth is configured with bcrypt hashing, so GoTrue passwords keep verifying)./api/proxy, so backend JWTs are minted server-side from the cookie session (with a per-session mint cache) — the browser never holds a JWT.kong,auth(GoTrue),studio,metaremoved;dbis now plainpgvector/pgvector:pg16with an init shim that creates a minimalauth.usersso the historical migration chain applies on fresh databases.Changes 🏗️
autogpt_libs.auth: JWKS (ES256/RS256/EdDSA) validation viaJWT_JWKS_URL+ legacyJWT_VERIFY_KEYHS256 path; config validation requires at least oneUserAuthIdentity,UserAuthSession,UserAuthAccount,UserAuthVerification,UserAuthJwks) + migration that also drops theauth.userssync triggersupabaseSDK; feature-flag LD context now reads the auth user table; e2e test data seeds bcrypt credential accountssrc/lib/authmodule (server config w/ admin+jwt+bridge plugins, actions, hooks, middleware); all ~50useSupabaseconsumers migrated touseAuth(same surface); login/signup/OAuth/password-reset/email-change flows rewritten;@supabase/*packages removed@better-auth/kysely-adapterimports a constant kysely 0.29 removed from the root export)Configuration changes:
+JWT_JWKS_URL,SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEYremoved,JWT_VERIFY_KEYretained for the migration window+BETTER_AUTH_SECRET,+BETTER_AUTH_URL,+DATABASE_URL,+AUTH_DB_SCHEMA, optionalAUTH_{GOOGLE,GITHUB,DISCORD}_CLIENT_ID/SECRET, optionalSMTP_*, optionalSUPABASE_JWT_SECRET(bridge window only);NEXT_PUBLIC_SUPABASE_*removed/auth/v1/callback) tohttps://<frontend>/api/auth/callback/{provider}Cloud rollout order (automated on merge): the deploy workflow's
migratejob runs the schema migrations including the bulk GoTrue→Better Auth copy (20260716120000_copy_supabase_users_to_better_auth), and the backend deploy dispatches only after it succeeds. The Vercel frontend build runs in parallel off the same push — if it finishes first, auth 500s briefly until the migrate job lands (self-heals; no action needed). After the frontend flip, run the “AutoGPT Platform - Supabase Auth Sweep” workflow (workflow_dispatch, environment picker) once to copy any users who signed up via GoTrue in between. KeepSUPABASE_JWT_SECRETset (JWT_VERIFY_KEYfalls back to it) through the bridge window → after the window, drop the legacy secrets and the GoTrue infra.Checklist 📋
For code changes:
/api/auth/jwksserves ES256 key; minted JWT carriessub/email/role/user_metadata/aud=authenticatedPOST /api/auth/user→ 200)sb-*cookie on a protected page → Better Auth session minted, sb cookies cleared, redirected to original destination/api/proxymints backend JWT (credits endpoint 200)pnpm types/lintclean; 3215+ unit tests green incl. new bridge/middleware/mapping testsFor configuration changes:
.env.defaultis updated or already compatible with my changesdocker-compose.ymlis updated or already compatible with my changes🤖 Generated with Claude Code
Cloud deploy checklist ☁️
Things that live outside this repo's code and must be handled at rollout (in order):
auth.users; new signups insert intoplatform."user"where no trigger exists. Port the waitlist check (trigger onplatform."user"or a Better AuthdatabaseHooks.user.create.beforehook) before deploy, or signups are open. The frontend'snot_allowederror handling is preserved and ready.BETTER_AUTH_SECRET(the compose default is dev-only), frontendDATABASE_URL+AUTH_DB_SCHEMA=platform, backendJWT_JWKS_URL, SMTP credentials (Postmark SMTP works; without SMTP, password reset/verification/email-change flows fail loudly by design); keepJWT_VERIFY_KEY+SUPABASE_JWT_SECRETset for the bridge window; remove GoTrue/Kong workloads after the window./auth/v1/callbacktohttps://<frontend>/api/auth/callback/{provider}, then smoke-test each provider in staging — including a sign-in by a migrated OAuth user (identity rows are migrated and account linking is enabled, but no live provider flow runs in CI).frontend/scripts/migrate-supabase-auth.tsunder the hood).SUPABASE_BRIDGE_MAX_TOKEN_AGE_DAYS); open SPA tabs at cutover 401 until one page reload; migrated temporary bans become permanent; GoTrue 2FA enrollments don't migrate (no 2FA UI exists today).role='admin'onplatform."user"; delete the inertdb/docker/tree in a cleanup PR.Rollback ⏮️
Precondition — the rollback window: keep the GoTrue/Kong workloads and
JWT_VERIFY_KEY+SUPABASE_JWT_SECRETrunning until the cutover is confirmed healthy. Dropping the legacy infra/secrets (final step of the rollout order) is the point of no return — don't do it until you're confident.auth.userscredential still exists).One-way caveat: users who signed up or changed their password on Better Auth during the cutover window exist only in
platform."user"/account, notauth.users, so after rollback they can't log in via GoTrue. Mitigate by picking one: (a) keep the window short and accept the few re-signups, (b) freeze new signups during the window, or (c) reverse-sync newplatform."user"rows intoauth.usersbefore rolling back.Note
High Risk
This is a full authentication and deployment-model change (JWT verification, session migration, CI database setup) touching security-critical paths; rollout order and env secrets (
JWT_JWKS_URL, bridge window) matter for production access.Overview
Replaces the Supabase auth stack (Kong, GoTrue, Studio, related env) with plain
pgvectorPostgres and Better Auth on the frontend. Platform.env.defaultis trimmed to database credentials; local DB data moves todata/db/data.autogpt_libs.authis reworked for Better Auth:JWT_JWKS_URLis required at startup (with cleartext remotehttp://blocked unlessJWKS_ALLOW_INSECURE_TRANSPORT). Tokens verify via JWKS (ES256, etc.) or legacy HS256 (JWT_VERIFY_KEY/SUPABASE_JWT_SECRET) by algorithm. Newrequires_frontend_serviceguards pre-login backend calls with JWKS-signed service tokens (separate audience from user JWTs). Missingroleclaims fail closed (403 admin, default"user"elsewhere) instead ofKeyError/500.CI and workflows: Docker image caches swap Supabase images for
pgvector/pgvector:pg15; backend tests use a pgvector container + init shim instead ofsupabase start; pytest dropsSUPABASE_*env; newautogpt-libs-testjob. Full-stack E2E startsdbonly (no GoTrue), caches/dumps platform schema (Better Auth tables), and fails ifrest_serveris unhealthy after 180s. Adds manual “Supabase Auth Sweep” workflow runningmigrate-supabase-auth.tspost-cutover.Also:
SECURITY.mdclarifies JWKS transport warnings are operator guidance;.gitignoreforsupabase/.temp/; removal ofsupabase_integration_credentials_store/types.pyfrom the diff hunk.Reviewed by Cursor Bugbot for commit ffc0264. Bugbot is set up for automated code reviews on this repo. Configure here.