feat(auth): replace Supabase Auth with app-native GitHub OAuth

Self-contained GitHub OAuth (Arctic) with a stateless HS256 signed-cookie
session (jose); Supabase is downgraded to the Postgres host only.

- Origin-derived callback (no redirect-uri env); read:user scope; access
  token read once at callback and discarded (no token storage).
- CSRF via single-use state cookie; open-redirect guard on next.
- getCurrentGithubIdentity() now reads the session cookie, preserving the
  numeric provider_id identity contract for admin/dashboard/mint.
- Remove @supabase/ssr + @supabase/supabase-js, middleware, and the
  supabase-dependent rls test; delete lib/supabase clients.
This commit is contained in:
2026-06-14 12:19:40 +07:00
parent 616f133989
commit 559bac8104
23 changed files with 797 additions and 395 deletions
+7 -29
View File
@@ -1,6 +1,6 @@
import "server-only";
import { createServerAuthClient } from "@/lib/supabase/server-client";
import { readSession } from "@/lib/auth/session";
/**
* @typedef {Object} GithubIdentity
@@ -9,37 +9,15 @@ import { createServerAuthClient } from "@/lib/supabase/server-client";
*/
/**
* Resolve the current GitHub identity from the validated server session.
* Resolve the current GitHub identity from the signed session cookie.
*
* Identity anchor: `provider_id` — the numeric, immutable GitHub id. NOT
* `user_name` (the mutable login a rename could otherwise mint a second key)
* and NOT `sub` (the Supabase user UUID). `user_metadata` is end-user-mutable,
* so it scopes queries server-side only and is never used as an RLS/auth claim.
*
* Uses `getUser()` (validates the token with Supabase), not `getSession()`.
* Identity anchor: `provider_id` — the numeric, immutable GitHub id. NOT the
* mutable login (a rename could otherwise mint a second key). The numeric
* invariant is enforced inside `readSession` (defense in depth) and again at
* mint time in the OAuth callback. `githubUsername` is display-only.
*
* @returns {Promise<GithubIdentity | null>} null when unauthenticated.
* @throws if a session exists but its GitHub metadata is missing/non-numeric.
*/
export async function getCurrentGithubIdentity() {
const supabase = await createServerAuthClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) return null;
const meta = user.user_metadata ?? {};
if (meta.provider_id == null || meta.user_name == null) {
throw new Error(
"GitHub identity missing provider_id/user_name in user_metadata",
);
}
const githubUserId = String(meta.provider_id);
// Assert numeric — guards against accidentally anchoring on the mutable login.
if (!/^\d+$/.test(githubUserId)) {
throw new Error(`Expected numeric GitHub provider_id, got: ${githubUserId}`);
}
return { githubUserId, githubUsername: String(meta.user_name) };
return readSession();
}