import { desc, eq } from "drizzle-orm"; import { randomHex } from "@/worker/auth/bytes"; import { generateHostLabel } from "@/worker/auth/host-labels"; import type { DavScope, JsonObject } from "@/worker/db/types"; import type { ControlPlaneDb } from "./client"; import { auditEvents, patProjections, subjects, type NewAuditEventRow, type NewPatProjectionRow, type PatProjectionRow, type SubjectRow, } from "./schema"; export interface TesseraProfile { sub: string; email?: string | null; displayName?: string | null; } export interface BootstrappedSubject { subject: SubjectRow; created: boolean; } function storageId(): string { return `stg_${randomHex(16)}`; } function auditId(): string { return `aud_${randomHex(16)}`; } function auditRow(input: { subjectId?: string | null; actorSubjectId?: string | null; eventType: string; createdAtMs: number; data?: JsonObject; }): NewAuditEventRow { return { id: auditId(), subjectId: input.subjectId ?? null, actorSubjectId: input.actorSubjectId ?? null, eventType: input.eventType, createdAtMs: input.createdAtMs, data: input.data ?? {}, }; } export async function createAuditEvent( db: ControlPlaneDb, input: { subjectId?: string | null; actorSubjectId?: string | null; eventType: string; createdAtMs: number; data?: JsonObject; }, ): Promise { await db.insert(auditEvents).values(auditRow(input)); } export async function getSubjectById(db: ControlPlaneDb, subjectId: string): Promise { return await db.query.subjects.findFirst({ where: eq(subjects.id, subjectId) }); } export async function getSubjectByHostLabel(db: ControlPlaneDb, hostLabel: string): Promise { return await db.query.subjects.findFirst({ where: eq(subjects.hostLabel, hostLabel) }); } export async function bootstrapSubject( db: ControlPlaneDb, profile: TesseraProfile, nowMs = Date.now(), ): Promise { const existing = await getSubjectById(db, profile.sub); if (existing) { await db .update(subjects) .set({ email: profile.email ?? null, displayName: profile.displayName ?? null, lastLoginAtMs: nowMs, }) .where(eq(subjects.id, profile.sub)); return { subject: (await getSubjectById(db, profile.sub)) ?? existing, created: false }; } for (let attempt = 0; attempt < 8; attempt += 1) { const subject = { id: profile.sub, storageId: storageId(), hostLabel: generateHostLabel(), email: profile.email ?? null, displayName: profile.displayName ?? null, createdAtMs: nowMs, lastLoginAtMs: nowMs, hostRotatedAtMs: null, disabledAtMs: null, }; try { await db.insert(subjects).values(subject); return { subject, created: true }; } catch (error) { const racedSubject = await getSubjectById(db, profile.sub); if (racedSubject) return { subject: racedSubject, created: false }; if (attempt === 7) throw error; } } throw new Error("Unable to bootstrap subject"); } export async function listPatProjections(db: ControlPlaneDb, subjectId: string): Promise { return await db .select() .from(patProjections) .where(eq(patProjections.subjectId, subjectId)) .orderBy(desc(patProjections.createdAtMs)); } export async function getPatProjection( db: ControlPlaneDb, subjectId: string, patId: string, ): Promise { const row = await db.query.patProjections.findFirst({ where: eq(patProjections.id, patId) }); return row?.subjectId === subjectId ? row : undefined; } export async function createPatProjection(db: ControlPlaneDb, row: NewPatProjectionRow): Promise { await db.insert(patProjections).values(row); } export async function updatePatProjection( db: ControlPlaneDb, subjectId: string, patId: string, patch: { name?: string; scopes?: DavScope[]; expiresAtMs?: number | null; revokedAtMs?: number | null; lastUsedAtMs?: number | null; }, ): Promise { const existing = await getPatProjection(db, subjectId, patId); if (!existing) return; await db .update(patProjections) .set({ name: patch.name ?? existing.name, scopes: patch.scopes ?? existing.scopes, expiresAtMs: patch.expiresAtMs === undefined ? existing.expiresAtMs : patch.expiresAtMs, revokedAtMs: patch.revokedAtMs === undefined ? existing.revokedAtMs : patch.revokedAtMs, lastUsedAtMs: patch.lastUsedAtMs === undefined ? existing.lastUsedAtMs : patch.lastUsedAtMs, }) .where(eq(patProjections.id, patId)); }