mirror of
https://github.com/tiennm99/llmapikey.git
synced 2026-06-17 12:48:54 +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.
63 lines
1.9 KiB
JavaScript
63 lines
1.9 KiB
JavaScript
import { SignJWT, jwtVerify } from "jose";
|
|
|
|
/**
|
|
* Pure JWT sign/verify for the session token — no cookies, no `server-only`, so
|
|
* it is unit-testable in node:test. `session.js` wraps these with the cookie
|
|
* store. HS256 over the GitHub identity; subject = numeric `provider_id`.
|
|
*/
|
|
|
|
export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 7; // 7 days
|
|
|
|
/**
|
|
* Encode + validate the signing secret. Throws (config error) when missing or
|
|
* shorter than 32 bytes.
|
|
*
|
|
* @param {string | undefined} raw
|
|
* @returns {Uint8Array}
|
|
*/
|
|
export function encodeSecret(raw) {
|
|
if (!raw || Buffer.byteLength(raw, "utf8") < 32) {
|
|
throw new Error(
|
|
"AUTH_SESSION_SECRET must be set and at least 32 bytes long",
|
|
);
|
|
}
|
|
return new TextEncoder().encode(raw);
|
|
}
|
|
|
|
/**
|
|
* Sign the identity into a JWT.
|
|
*
|
|
* @param {{ githubUserId: string, githubUsername: string }} identity
|
|
* @param {Uint8Array} secret
|
|
* @returns {Promise<string>}
|
|
*/
|
|
export function signSessionToken({ githubUserId, githubUsername }, secret) {
|
|
return new SignJWT({ login: githubUsername })
|
|
.setProtectedHeader({ alg: "HS256" })
|
|
.setSubject(githubUserId)
|
|
.setIssuedAt()
|
|
.setExpirationTime("7d")
|
|
.sign(secret);
|
|
}
|
|
|
|
/**
|
|
* Verify a JWT and extract the identity. Returns null on any token error
|
|
* (missing/tampered/expired) or when the subject is not a numeric provider_id.
|
|
*
|
|
* @param {string | undefined | null} token
|
|
* @param {Uint8Array} secret
|
|
* @returns {Promise<{ githubUserId: string, githubUsername: string } | null>}
|
|
*/
|
|
export async function verifySessionToken(token, secret) {
|
|
if (!token) return null;
|
|
try {
|
|
const { payload } = await jwtVerify(token, secret);
|
|
const githubUserId = String(payload.sub ?? "");
|
|
// Re-assert the numeric provider_id invariant (defense in depth).
|
|
if (!/^\d+$/.test(githubUserId)) return null;
|
|
return { githubUserId, githubUsername: String(payload.login ?? "") };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|