Skip to content

refactor(auth): #183 remove the unused custom oauth_states CSRF layer#184

Merged
TortoiseWolfe merged 1 commit into
mainfrom
chore/183-remove-oauth-states
Jul 5, 2026
Merged

refactor(auth): #183 remove the unused custom oauth_states CSRF layer#184
TortoiseWolfe merged 1 commit into
mainfrom
chore/183-remove-oauth-states

Conversation

@TortoiseWolfe

Copy link
Copy Markdown
Owner

Closes #183. Removes a custom OAuth-state CSRF layer that had zero production callers and a table with fully-public RLS — unused attack surface.

Why

The real OAuth CSRF defense is Supabase's built-in OAuth2 state parameter (the client uses flowType: 'implicit' — this is a static export, so no server-side code exchange). The custom src/lib/auth/oauth-state.ts + oauth_states table duplicated that (more weakly: session_id self-reported in sessionStorage, fail-open) and nothing ever called it — both OAuth entry points comment that Supabase handles CSRF. The table's RLS was WITH CHECK (true) / USING (true) on INSERT/SELECT/UPDATE: anyone with the anon key could flood/read/overwrite it.

Prod-verified before dropping: 0 rows (never written to), 4 public policies. Dropped live via the Management API; confirmed table + policies gone.

Removed

  • src/lib/auth/oauth-state.ts + __tests__/oauth-state.test.ts (537 lines, 0 non-test importers)
  • oauth_states CREATE TABLE + 3 indexes + 4 RLS policies + comments from the monolithic migration (edited in place per CLAUDE.md — no separate migration file)
  • deleteAllFromTable('oauth_states', ...) in scripts/reset-database.ts
  • the generated oauth_states type block in src/lib/supabase/types.ts
  • live DROP TABLE oauth_states CASCADE applied to prod + verified

Docs correction

The OAuth blog post taught the removed manual-state pattern and mislabeled the flow as PKCE. Corrected to the actual implicit-flow + Supabase-state story (a static export has no exchangeCodeForSession in the OAuth callback), and fixed the matching OAuthButtons.tsx comment. Regenerated only the OAuth post's blog-data.json entry.

⚠️ Noted, not fixed here (scope): blog-data.json has a separate pre-existing drift — several other posts' committed titles/ids are stale vs their source .md (e.g. the offline-payment + E2E-encryption posts). Left untouched to keep this PR scoped; worth a standalone pnpm run generate:blog resync. Also left the email-verification callback's exchangeCodeForSession example (different flow, not part of this removal).

Verification

  • Prod: oauth_states table + 4 policies dropped (verified via information_schema/pg_policies); OAuth sign-in unaffected (Supabase state was always the real path).
  • pnpm type-check + pnpm lint clean. Full suite 3558 passed / 0 failed (−19 = the removed oauth-state tests).

🤖 Generated with Claude Code

The custom OAuth-state CSRF machinery had ZERO production callers — the real
CSRF defense is Supabase's built-in OAuth2 `state` parameter (the client uses
flowType:'implicit' for this static export). The `oauth_states` table also
carried fully-public RLS (WITH CHECK/USING (true) on INSERT/SELECT/UPDATE), so
it was unused attack surface. Prod-verified before dropping: 0 rows (never
written to — nothing ever called generateOAuthState), 4 public policies.

Removed:
- src/lib/auth/oauth-state.ts + its test (generateOAuthState/validateOAuthState/
  cleanupExpiredStates — 0 non-test importers)
- oauth_states CREATE TABLE + 3 indexes + 4 RLS policies + comments from the
  monolithic migration (edited in place per CLAUDE.md — no separate migration)
- the deleteAllFromTable('oauth_states', ...) call in scripts/reset-database.ts
- the generated oauth_states type block from src/lib/supabase/types.ts
- Applied a live DROP TABLE oauth_states CASCADE to prod via the Management API;
  verified the table + its 4 policies are gone. Full suite 3558 passed.

Docs: the OAuth blog post taught the removed manual-state pattern AND
mislabeled the flow as PKCE. Corrected it to the actual implicit-flow +
Supabase-state story (this app is a static export, so no exchangeCodeForSession
in the OAuth callback), fixed the matching OAuthButtons comment, and
regenerated ONLY the OAuth post's blog-data.json entry (other posts have a
separate pre-existing blog-data drift, left untouched to keep this PR scoped).

999_drop_all_tables.sql keeps its idempotent DROP as a no-op for older DBs.

Closes #183

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TortoiseWolfe TortoiseWolfe merged commit 4ad49ca into main Jul 5, 2026
19 checks passed
@TortoiseWolfe TortoiseWolfe deleted the chore/183-remove-oauth-states branch July 5, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove the unused custom oauth_states CSRF layer (dead code + public-write RLS)

2 participants