Skip to content
File

Blob: src/worker/objects/auth-object.ts

typescript96 lines
1import { DurableObject } from "cloudflare:workers";
2 
3import { constantTimeEqual } from "@/worker/auth/bytes";
4import { createAuthDoDb, type AuthDoDb } from "@/worker/db/auth-do/client";
5import {
6 createPat,
7 createSession,
8 getPat,
9 getSession,
10 listPats,
11 revokeSession,
12 touchSession,
13 updatePat,
14} from "@/worker/db/auth-do/repository";
15import type { NewPatRow, NewSessionRow, PatRow, SessionRow } from "@/worker/db/auth-do/schema";
16import { migrateAuthObject } from "@/worker/objects/common/migrations";
17 
18export interface SessionValidation {
19 ok: true;
20 session: SessionRow;
21}
22 
23export interface PatVerification {
24 ok: true;
25 pat: PatRow;
26}
27 
28export class AuthObject extends DurableObject<Env> {
29 readonly db: AuthDoDb;
30 
31 constructor(ctx: DurableObjectState, env: Env) {
32 super(ctx, env);
33 this.db = createAuthDoDb(ctx.storage);
34 
35 ctx.blockConcurrencyWhile(async () => {
36 await migrateAuthObject(this.db);
37 });
38 }
39 
40 async ping(): Promise<{ ok: true; object: "auth" }> {
41 return { ok: true, object: "auth" };
42 }
43 
44 async createSession(row: NewSessionRow): Promise<SessionRow> {
45 createSession(this.db, row);
46 return getSession(this.db, row.id)!;
47 }
48 
49 async validateSession(input: { sessionId: string; nowMs: number }): Promise<SessionValidation | { ok: false }> {
50 const session = getSession(this.db, input.sessionId);
51 if (!session || session.revokedAtMs !== null || session.expiresAtMs <= input.nowMs) return { ok: false };
52 touchSession(this.db, input.sessionId, input.nowMs);
53 return { ok: true, session: getSession(this.db, input.sessionId) ?? session };
54 }
55 
56 async revokeSession(input: { sessionId: string; nowMs: number }): Promise<{ ok: true }> {
57 revokeSession(this.db, input.sessionId, input.nowMs);
58 return { ok: true };
59 }
60 
61 async createPat(row: NewPatRow): Promise<PatRow> {
62 createPat(this.db, row);
63 return getPat(this.db, row.id)!;
64 }
65 
66 async listPats(): Promise<PatRow[]> {
67 return listPats(this.db);
68 }
69 
70 async getPat(input: { patId: string }): Promise<PatRow | null> {
71 return getPat(this.db, input.patId) ?? null;
72 }
73 
74 async updatePat(input: {
75 patId: string;
76 name?: string;
77 expiresAtMs?: number | null;
78 revokedAtMs?: number | null;
79 }): Promise<PatRow | null> {
80 return updatePat(this.db, input.patId, input) ?? null;
81 }
82 
83 async verifyPat(input: {
84 patId: string;
85 tokenDigest: string;
86 nowMs: number;
87 }): Promise<PatVerification | { ok: false }> {
88 const pat = getPat(this.db, input.patId);
89 if (!pat || pat.revokedAtMs !== null || (pat.expiresAtMs !== null && pat.expiresAtMs <= input.nowMs))
90 return { ok: false };
91 if (!constantTimeEqual(pat.tokenDigest, input.tokenDigest)) return { ok: false };
92 const updated = updatePat(this.db, input.patId, { lastUsedAtMs: input.nowMs }) ?? pat;
93 return { ok: true, pat: updated };
94 }
95}