All authorization helpers live in lib/api/server.ts.
Guard functions
Guards are applied in order of increasing restriction.
1. requireApiSession
requireApiSession(request, options?) → { ok: true, value: session } | { ok: false, response: NextResponse }Checks for a valid session via Authorization: Bearer <sessionId> header or gh_session cookie. Returns 401 if absent or expired.
2. requireOrganizationAccess
requireOrganizationAccess(session, orgLogin);Checks session.user.organizations.some(o => o.login === orgLogin). Returns 403 if the user is not a member of the organization.
3. requireOrganizationAdmin
requireOrganizationAdmin(session, orgLogin);Checks session.user.organizations.some(o => o.login === orgLogin && o.viewerCanAdminister). Returns 403 if the user is not an admin.
4. resolveInstallationForOrganization
resolveInstallationForOrganization(session, orgLogin, { requireAdmin?: boolean })
// returns { installationId } | { error, status }Verifies that the session has an active GitHub App installation linked to the org. Pass requireAdmin: true to combine with the admin check (used by mass-invite).
Admin check mechanism
viewerCanAdminister is read from GitHub’s viewer { organizations { viewerCanAdminister } } query at sign-in time and stored in the organization_memberships table.
Staleness caveat: if a user is promoted to admin after signing in, the flag will not update until they sign out and sign in again. There is no background refresh.
The mass-invite admin check uses the in-session value directly:
session.user.organizations.some(org => org.login === login && org.viewerCanAdminister);Debug route gate
All /api/debug/* routes call requireDebugAccess (lib/auth/debug.ts):
export async function requireDebugAccess(request: NextRequest) {
if (!appEnv.isProduction) return null; // open — no auth in non-prod
if (!appEnv.enableDebugRoutes) return 403;
const session = await getRequestSession(request);
if (!session) return 401;
return null;
}| Environment | ENABLE_DEBUG_ROUTES | Result |
|---|---|---|
| non-production | any | Open — no auth check |
| production | unset / false | 403 |
| production | true | Requires valid session → 401 if absent |
ENABLE_DEBUG_ROUTES maps to appEnv.enableDebugRoutes. It does not grant admin; it only unlocks the endpoints.
Typical guard composition
requireApiSession
└─ requireOrganizationAccess
└─ requireOrganizationAdmin (write/mutate paths)
└─ resolveInstallationForOrganization (mass-invite)Routes that only read data (e.g. GET /api/organizations) need only requireApiSession. Routes that mutate org-scoped data require the full chain.