import type { IdentitySession, IdentityUser } from "./contracts.ts"; interface RawIdentitySession { session: { id: string; token: string; expiresAt: Date | string }; user: { id: string; email: string; emailVerified: boolean; twoFactorEnabled?: boolean; name: string; image?: string | null; role?: string | null; }; } export interface IdentityServerSession extends IdentitySession { sessionId: string; sessionToken: string; } export interface IdentitySessionReader { getSession(input: { headers: Headers }): Promise; } export class IdentityAuthorizationError extends Error { constructor( message: string, readonly status: 401 | 403, ) { super(message); this.name = "IdentityAuthorizationError"; } } function parseRoles(role: string | null | undefined): string[] { const roles = (role ?? "user") .split(",") .map((value) => value.trim()) .filter(Boolean); return [...new Set(roles.length ? roles : ["user"])]; } export async function readIdentityServerSession( reader: IdentitySessionReader, requestHeaders: Headers, ): Promise { const value = await reader.getSession({ headers: requestHeaders }); if (!value) return null; const expiresAt = new Date(value.session.expiresAt); if (!Number.isFinite(expiresAt.getTime())) { throw new Error("identity session has an invalid expiry"); } return { sessionId: value.session.id, sessionToken: value.session.token, expiresAt, user: { id: value.user.id, email: value.user.email.trim().toLowerCase(), emailVerified: value.user.emailVerified, twoFactorEnabled: value.user.twoFactorEnabled === true, name: value.user.name, image: value.user.image ?? null, role: parseRoles(value.user.role), }, }; } export async function readIdentitySession( reader: IdentitySessionReader, requestHeaders: Headers, ): Promise { const session = await readIdentityServerSession(reader, requestHeaders); return session ? { user: session.user, expiresAt: session.expiresAt } : null; } export async function requireIdentityServerSession( reader: IdentitySessionReader, requestHeaders: Headers, ): Promise { const session = await readIdentityServerSession(reader, requestHeaders); if (!session) throw new IdentityAuthorizationError("Authentication required", 401); return session; } export async function requireIdentityUser( reader: IdentitySessionReader, requestHeaders: Headers, ): Promise { const session = await readIdentitySession(reader, requestHeaders); if (!session) throw new IdentityAuthorizationError("Authentication required", 401); return session.user; } export async function requireIdentityAdmin( reader: IdentitySessionReader, requestHeaders: Headers, ): Promise { const user = await requireIdentityUser(reader, requestHeaders); if (!user.role.includes("admin")) { throw new IdentityAuthorizationError("Administrator access required", 403); } return user; }