mirror of
https://github.com/tiennm99/llmapikey.git
synced 2026-06-17 20:48:27 +00:00
559bac8104
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.
59 lines
2.0 KiB
JavaScript
59 lines
2.0 KiB
JavaScript
import { NextResponse } from "next/server";
|
|
import { cookies } from "next/headers";
|
|
|
|
import { getGithubOAuth } from "@/lib/auth/github-oauth";
|
|
import { createSession } from "@/lib/auth/session";
|
|
import { sanitizeNext } from "@/lib/auth/sanitize-next";
|
|
|
|
/**
|
|
* GitHub OAuth callback. Verify CSRF state, exchange the code, read the public
|
|
* profile once (`id` + `login`), mint the session cookie, then redirect to the
|
|
* stashed `next`. The access token is never stored.
|
|
*
|
|
* @param {Request} request
|
|
*/
|
|
export async function GET(request) {
|
|
const { searchParams, origin } = new URL(request.url);
|
|
const code = searchParams.get("code");
|
|
const state = searchParams.get("state");
|
|
|
|
const cookieStore = await cookies();
|
|
const storedState = cookieStore.get("oauth_state")?.value;
|
|
const next = sanitizeNext(cookieStore.get("oauth_next")?.value);
|
|
|
|
// Clear the transient cookies regardless of outcome.
|
|
cookieStore.delete("oauth_state");
|
|
cookieStore.delete("oauth_next");
|
|
|
|
if (!code || !state || !storedState || state !== storedState) {
|
|
return NextResponse.redirect(`${origin}/?auth_error=1`);
|
|
}
|
|
|
|
try {
|
|
const oauth = getGithubOAuth(origin);
|
|
const tokens = await oauth.validateAuthorizationCode(code);
|
|
const accessToken = tokens.accessToken();
|
|
|
|
const res = await fetch("https://api.github.com/user", {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"User-Agent": "llmapikey",
|
|
Accept: "application/vnd.github+json",
|
|
},
|
|
});
|
|
if (!res.ok) return NextResponse.redirect(`${origin}/?auth_error=1`);
|
|
|
|
const profile = await res.json();
|
|
const githubUserId = String(profile.id);
|
|
// Anchor on the numeric provider_id, never the mutable login.
|
|
if (!/^\d+$/.test(githubUserId)) {
|
|
return NextResponse.redirect(`${origin}/?auth_error=1`);
|
|
}
|
|
|
|
await createSession({ githubUserId, githubUsername: String(profile.login) });
|
|
return NextResponse.redirect(`${origin}${next}`);
|
|
} catch {
|
|
return NextResponse.redirect(`${origin}/?auth_error=1`);
|
|
}
|
|
}
|