import { DurableObject } from "cloudflare:workers"; import * as Effect from "effect/Effect"; import { loadControlPlaneSecrets } from "../configuration/control-plane.ts"; import type { Env } from "./config.ts"; import { decrypt, encrypt } from "./crypto.ts"; import { transaction } from "./storage.ts"; export interface Transaction { bindingHash: string; verifier: string; previousSession: string | null; returnTo: "/connect"; bridgeContinuation?: string; providerInstallationId?: string; domainInstallationId?: string; expiresAt: number; } export interface Principal { subject: string; grantRef: string; selectedAccountId: string | null; expiresAt: number; } export interface Grant { subject: string; accessToken: string; scopes: string[]; expiresAt: number; } export interface BridgeRequest { installationId: string; state: string; challenge: string; audience?: string; expiresAt: number; } export interface BridgeContinuation extends BridgeRequest { bindingHash: string; } export interface BridgeCode extends BridgeRequest { subject: string; audience: string; } export interface OperationBinding { subject: string; accountId: string; installationId: string; operationId: string; } export interface ProtectedOperation extends OperationBinding { grantRef: string; expiresAt: number; bootstrapSecret: string | null; } const matchesOperation = ( value: OperationBinding, expected: OperationBinding, ) => value.subject === expected.subject && value.accountId === expected.accountId && value.installationId === expected.installationId && value.operationId === expected.operationId; type RecordValue = | { kind: "transaction"; value: Transaction } | { kind: "session"; value: Principal } | { kind: "grant"; value: Grant } | { kind: "continuation"; value: BridgeContinuation } | { kind: "code"; value: BridgeCode } | { kind: "operation"; value: ProtectedOperation }; interface Stored { kind: RecordValue["kind"]; expiresAt: number; iv: string; ciphertext: string; } // No public fetch router, listing RPC, metadata database, or token-returning HTTP // endpoint. Only this control-plane Worker's trusted binding can invoke RPC. // Each random transaction/session/grant gets its own strongly consistent object. export class AuthVault extends DurableObject { #encryptionKey() { return loadControlPlaneSecrets(this.env).credentialEncryptionKey.reveal(); } #open(record: Stored) { return Effect.gen({ self: this }, function* () { const secret = this.#encryptionKey(); return yield* decrypt( secret, record, `${this.ctx.id}:${record.kind}:${record.expiresAt}`, ).pipe( Effect.catchDefect((error) => { if ( error instanceof DOMException && ["OperationError", "DataError", "InvalidCharacterError"].includes( error.name, ) ) return Effect.succeed(null); return Effect.die(error); }), ); }); } #put(record: RecordValue) { return Effect.gen({ self: this }, function* () { const expiresAt = record.value.expiresAt; const sealed = yield* encrypt( this.#encryptionKey(), record.value, `${this.ctx.id}:${record.kind}:${expiresAt}`, ); yield* transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { if (yield* Effect.promise(() => tx.get("record"))) return yield* Effect.die(new Error("Vault record already exists")); yield* Effect.promise(() => tx.put("record", { kind: record.kind, expiresAt, ...sealed }), ); yield* Effect.promise(() => tx.setAlarm(expiresAt)); }), ); }); } createContinuation(value: BridgeContinuation) { return Effect.runPromise(this.#put({ kind: "continuation", value })); } createCode(value: BridgeCode) { return Effect.runPromise(this.#put({ kind: "code", value })); } claimContinuation(bindingHash: string): Promise { return Effect.runPromise( transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if ( !stored || stored.kind !== "continuation" || stored.expiresAt <= Date.now() ) return null; const value = yield* this.#open(stored); if (!value || value.bindingHash !== bindingHash) return null; yield* Effect.promise(() => tx.delete("record")); return value; }), ), ); } claimCode(expected: { installationId: string; audience: string; state: string; challenge: string; }): Promise { return Effect.runPromise( transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if ( !stored || stored.kind !== "code" || stored.expiresAt <= Date.now() ) return null; const value = yield* this.#open(stored); if ( !value || Object.entries(expected).some( ([key, item]) => value[key as keyof BridgeCode] !== item, ) ) return null; yield* Effect.promise(() => tx.delete("record")); return value; }), ), ); } createOperation(value: ProtectedOperation): Promise { return Effect.runPromise( Effect.gen({ self: this }, function* () { if ( !value.subject || value.subject.length > 512 || !/^[a-f0-9]{32}$/.test(value.accountId) || !/^[a-f0-9]{32}$/.test(value.installationId) || !/^[a-f0-9]{32}$/.test(value.operationId) || !/^[A-Za-z0-9_-]{43}$/.test(value.grantRef) || !Number.isSafeInteger(value.expiresAt) || value.expiresAt <= Date.now() || value.expiresAt > Date.now() + 8 * 60 * 60_000 || (value.bootstrapSecret !== null && !/^[A-Za-z0-9_-]{43}$/.test(value.bootstrapSecret)) ) return yield* Effect.die(new Error("Invalid protected operation")); const grant = yield* Effect.promise(() => this.env.AUTH_VAULT.get( this.env.AUTH_VAULT.idFromName(`grant:${value.grantRef}`), ).grant(value.subject), ); if (!grant || value.expiresAt > grant.expiresAt) return yield* Effect.die( new Error("Protected operation authorization unavailable"), ); const sealed = yield* encrypt( this.#encryptionKey(), value, `${this.ctx.id}:operation:${value.expiresAt}`, ); return yield* transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const existing = yield* Effect.promise(() => tx.get("record"), ); if (existing) { if ( existing.kind !== "operation" || existing.expiresAt <= Date.now() ) return yield* Effect.die( new Error("Protected operation unavailable"), ); const previous = yield* this.#open(existing); if ( !previous || !matchesOperation(previous, value) || previous.grantRef !== value.grantRef || previous.expiresAt !== value.expiresAt ) return yield* Effect.die( new Error("Protected operation conflict"), ); return previous; } yield* Effect.promise(() => tx.put("record", { kind: "operation", expiresAt: value.expiresAt, ...sealed, }), ); yield* Effect.promise(() => tx.setAlarm(value.expiresAt)); return value; }), ); }), ); } operation(expected: OperationBinding): Promise { return Effect.runPromise( Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => this.ctx.storage.get("record"), ); if ( !stored || stored.kind !== "operation" || stored.expiresAt <= Date.now() ) return null; const value = yield* this.#open(stored); return value && matchesOperation(value, expected) ? value : null; }), ); } retireBootstrap(expected: OperationBinding): Promise { return Effect.runPromise( transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if ( !stored || stored.kind !== "operation" || stored.expiresAt <= Date.now() ) return false; const value = yield* this.#open(stored); if (!value || !matchesOperation(value, expected)) return false; value.bootstrapSecret = null; const sealed = yield* encrypt( this.#encryptionKey(), value, `${this.ctx.id}:operation:${value.expiresAt}`, ); yield* Effect.promise(() => tx.put("record", { kind: "operation", expiresAt: value.expiresAt, ...sealed, }), ); return true; }), ), ); } createTransaction(value: Transaction) { return Effect.runPromise(this.#put({ kind: "transaction", value })); } createSession(value: Principal) { return Effect.runPromise(this.#put({ kind: "session", value })); } createGrant(value: Grant) { return Effect.runPromise(this.#put({ kind: "grant", value })); } claimTransaction( bindingHash: string, previousSession: string | null, ): Promise { return Effect.runPromise( // Read/compare/delete are one native storage transaction. The code exchange // happens only after this commit; failures cannot reopen a claimed state. transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if ( !stored || stored.kind !== "transaction" || stored.expiresAt <= Date.now() ) return null; const value = yield* this.#open(stored); if ( !value || value.bindingHash !== bindingHash || value.previousSession !== previousSession ) return null; yield* Effect.promise(() => tx.delete("record")); return value; }), ), ); } session(): Promise { return Effect.runPromise( Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => this.ctx.storage.get("record"), ); if ( !stored || stored.kind !== "session" || stored.expiresAt <= Date.now() ) return null; return yield* this.#open(stored); }), ); } grant(subject: string): Promise { return Effect.runPromise( Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => this.ctx.storage.get("record"), ); if ( !stored || stored.kind !== "grant" || stored.expiresAt <= Date.now() ) return null; const grant = yield* this.#open(stored); return grant?.subject === subject ? grant : null; }), ); } selectAccount( subject: string, grantRef: string, accountId: string, ): Promise { return Effect.runPromise( transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if ( !stored || stored.kind !== "session" || stored.expiresAt <= Date.now() ) return false; const value = yield* this.#open(stored); if ( !value || value.subject !== subject || value.grantRef !== grantRef ) return false; value.selectedAccountId = accountId; const sealed = yield* encrypt( this.#encryptionKey(), value, `${this.ctx.id}:session:${stored.expiresAt}`, ); yield* Effect.promise(() => tx.put("record", { kind: "session", expiresAt: stored.expiresAt, ...sealed, }), ); return true; }), ), ); } retireSession(): Promise { return Effect.runPromise( transaction(this.ctx.storage, (tx) => Effect.gen({ self: this }, function* () { const stored = yield* Effect.promise(() => tx.get("record")); if (!stored || stored.kind !== "session") return null; const principal = yield* this.#open(stored); yield* Effect.promise(() => tx.delete("record")); return principal; }), ), ); } destroy() { return Effect.runPromise( Effect.promise(() => this.ctx.storage.deleteAll()), ); } alarm() { return Effect.runPromise( Effect.promise(() => this.ctx.storage.deleteAll()), ); } }