import { DurableObject } from "cloudflare:workers"; import { constantTimeEqual } from "@/worker/auth/bytes"; import { createAuthDoDb, type AuthDoDb } from "@/worker/db/auth-do/client"; import { createPat, createSession, getPat, getSession, listPats, revokeSession, touchSession, updatePat, } from "@/worker/db/auth-do/repository"; import type { NewPatRow, NewSessionRow, PatRow, SessionRow } from "@/worker/db/auth-do/schema"; import { migrateAuthObject } from "@/worker/objects/common/migrations"; export interface SessionValidation { ok: true; session: SessionRow; } export interface PatVerification { ok: true; pat: PatRow; } export class AuthObject extends DurableObject { readonly db: AuthDoDb; constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.db = createAuthDoDb(ctx.storage); ctx.blockConcurrencyWhile(async () => { await migrateAuthObject(this.db); }); } async ping(): Promise<{ ok: true; object: "auth" }> { return { ok: true, object: "auth" }; } async createSession(row: NewSessionRow): Promise { createSession(this.db, row); return getSession(this.db, row.id)!; } async validateSession(input: { sessionId: string; nowMs: number }): Promise { const session = getSession(this.db, input.sessionId); if (!session || session.revokedAtMs !== null || session.expiresAtMs <= input.nowMs) return { ok: false }; touchSession(this.db, input.sessionId, input.nowMs); return { ok: true, session: getSession(this.db, input.sessionId) ?? session }; } async revokeSession(input: { sessionId: string; nowMs: number }): Promise<{ ok: true }> { revokeSession(this.db, input.sessionId, input.nowMs); return { ok: true }; } async createPat(row: NewPatRow): Promise { createPat(this.db, row); return getPat(this.db, row.id)!; } async listPats(): Promise { return listPats(this.db); } async getPat(input: { patId: string }): Promise { return getPat(this.db, input.patId) ?? null; } async updatePat(input: { patId: string; name?: string; expiresAtMs?: number | null; revokedAtMs?: number | null; }): Promise { return updatePat(this.db, input.patId, input) ?? null; } async verifyPat(input: { patId: string; tokenDigest: string; nowMs: number; }): Promise { const pat = getPat(this.db, input.patId); if (!pat || pat.revokedAtMs !== null || (pat.expiresAtMs !== null && pat.expiresAtMs <= input.nowMs)) return { ok: false }; if (!constantTimeEqual(pat.tokenDigest, input.tokenDigest)) return { ok: false }; const updated = updatePat(this.db, input.patId, { lastUsedAtMs: input.nowMs }) ?? pat; return { ok: true, pat: updated }; } }