Files
llmapikey/tests/session-roundtrip.test.js
tiennm99 559bac8104 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.
2026-06-14 12:19:40 +07:00

60 lines
2.0 KiB
JavaScript

import assert from "node:assert/strict";
import { test } from "node:test";
import {
encodeSecret,
signSessionToken,
verifySessionToken,
} from "../lib/auth/session-token.js";
const SECRET = encodeSecret("x".repeat(48));
const IDENTITY = { githubUserId: "12345", githubUsername: "octocat" };
test("session token round-trips identity", async () => {
const token = await signSessionToken(IDENTITY, SECRET);
const decoded = await verifySessionToken(token, SECRET);
assert.deepEqual(decoded, IDENTITY);
});
test("tampered token verifies to null", async () => {
const token = await signSessionToken(IDENTITY, SECRET);
const tampered = token.slice(0, -2) + (token.endsWith("a") ? "bb" : "aa");
assert.equal(await verifySessionToken(tampered, SECRET), null);
});
test("token signed with a different secret verifies to null", async () => {
const token = await signSessionToken(IDENTITY, SECRET);
const otherSecret = encodeSecret("y".repeat(48));
assert.equal(await verifySessionToken(token, otherSecret), null);
});
test("non-numeric subject is rejected", async () => {
const token = await signSessionToken(
{ githubUserId: "octocat", githubUsername: "octocat" },
SECRET,
);
assert.equal(await verifySessionToken(token, SECRET), null);
});
test("expired token verifies to null", async () => {
const { SignJWT } = await import("jose");
const expired = await new SignJWT({ login: "octocat" })
.setProtectedHeader({ alg: "HS256" })
.setSubject("12345")
.setIssuedAt(0)
.setExpirationTime(1) // epoch+1s — long past
.sign(SECRET);
assert.equal(await verifySessionToken(expired, SECRET), null);
});
test("missing token verifies to null", async () => {
assert.equal(await verifySessionToken(undefined, SECRET), null);
assert.equal(await verifySessionToken("", SECRET), null);
});
test("encodeSecret rejects short/missing secrets", () => {
assert.throws(() => encodeSecret(undefined));
assert.throws(() => encodeSecret("tooshort"));
assert.doesNotThrow(() => encodeSecret("z".repeat(32)));
});