Gates /admin and /spotify-import behind a single owner's GitHub account. There is no multi-user support — this is an allow-list-of-one, not a real auth system.
GET /auth/login(+server.ts) builds a GitHub OAuth authorize URL (github-auth.ts:buildAuthorizeUrl) with scoperead:user, generates a randomstate, and stashes{ state, redirectTo }in a short-livedoauth_statecookie before redirecting to GitHub.- GitHub redirects back to
GET /auth/callback(+server.ts) withcode+state. - The callback verifies
statematches the cookie (CSRF protection), exchangescodefor an access token, then callsfetchGithubLoginto get the authenticated user's GitHub login. - The login is compared case-insensitively against
site.githubUsernamein config.ts. Any mismatch redirects to/auth/denied?reason=not-owner— no session is issued for anyone else, full stop. - On match, a signed session token is minted (session.ts) and set as an
httpOnly,sameSite=laxcookie (rg_session, 30-day max age), then redirects toredirectTo(defaults to/admin).
Sessions are a self-contained signed token, not a server-side session store:
base64url(json({ sub: login, exp: timestamp })) + "." + HMAC-SHA256(payload, SESSION_SECRET)
verifySessionToken re-derives the signature and does a timingSafeEqual comparison before trusting the payload, then checks expiry. There's no revocation list — rotating SESSION_SECRET is the only way to invalidate all sessions at once (see environment.md for how to generate one).
GitHub OAuth Apps require an exact callback URL match, so you need a second OAuth App for local dev with callback http://localhost:5173/auth/callback — you can't reuse the production app's registration with two callback URLs.
- No CSRF token on state beyond the
oauth_statecookie round-trip; acceptable for a single-owner login but worth knowing if this ever needs to support more than one user. /auth/loginand/auth/callbackare rate limited to 10 requests/minute/IP (in-memory, per-process — seecheckRateLimitin src/hooks.server.ts).