fabro/apps/arc-web/app/lib/session.server.ts
Bryan Helmkamp 1fd9a8ebfe SQLite-backed sessions with GitHub email and app manifest fix
Replace cookie-based sessions with SQLite-backed storage using
better-sqlite3 and React Router's createSessionStorage. Sessions are
now stored in ~/.arc/arc-web.db with a session ID cookie, enabling
larger payloads and server-side revocation.

- Add db.server.ts (lazy singleton, WAL mode, web_sessions table)
- Add session-storage.server.ts (CRUD ops, probabilistic cleanup)
- Fetch primary verified email from /user/emails during OAuth
- Add emails:read to GitHub App manifest default_permissions
- Expand session data: userUrl, githubId, githubNodeId, email
- Default ARC_API_BASE_URL to localhost:3000
- Whitelist better-sqlite3 in trustedDependencies
- Externalize better-sqlite3 from Vite SSR bundling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:05:59 -05:00

55 lines
1.5 KiB
TypeScript

import { redirect } from "react-router";
import { createSqliteSessionStorage } from "./session-storage.server";
interface SessionData {
userUrl: string;
githubId: number;
githubNodeId: string;
githubLogin: string;
name: string;
email: string;
avatarUrl: string;
accessToken: string;
}
function getSessionStorage() {
const secret = process.env.SESSION_SECRET;
if (!secret) {
throw new Error("SESSION_SECRET is not set");
}
return createSqliteSessionStorage(secret);
}
export async function getSession(request: Request) {
const storage = getSessionStorage();
return storage.getSession(request.headers.get("Cookie"));
}
export async function commitSession(session: Awaited<ReturnType<typeof getSession>>) {
const storage = getSessionStorage();
return storage.commitSession(session);
}
export async function destroySession(session: Awaited<ReturnType<typeof getSession>>) {
const storage = getSessionStorage();
return storage.destroySession(session);
}
export async function getUser(request: Request) {
const session = await getSession(request);
const githubLogin = session.get("githubLogin");
if (!githubLogin) return null;
return {
userUrl: session.get("userUrl") ?? "",
githubLogin,
name: session.get("name") ?? githubLogin,
email: session.get("email") ?? "",
avatarUrl: session.get("avatarUrl") ?? "",
};
}
export async function requireUser(request: Request) {
const user = await getUser(request);
if (!user) throw redirect("/auth/login");
return user;
}