Reference source · 0.1.0-dev.11
directory.ts
src/reference/directory.ts · Download original file
import { DatabaseSync } from 'node:sqlite';
import type { Authority, Credential } from '../identity.ts';
import { TrustStore } from '../identity.ts';
import { patientAddress, delegate, webfinger } from '../discovery.ts';
import type { Handoff } from '../migration-proof.ts';
import { verifyHandoff, secureEndpoint } from '../migration-proof.ts';
import { id, integer, fingerprint, jsonBytes, requireThat } from '../core.ts';
// An independently operated address provider. Enrollment is a trusted address-owner integration.
export class ReferenceDirectory {
db: DatabaseSync; authority: Authority; domains: string[]; trust: TrustStore;
constructor(path: string, authority: Authority, domains: string[], trust: TrustStore) {
this.db = new DatabaseSync(path); this.authority = authority; this.domains = domains; this.trust = trust;
this.db.exec(`CREATE TABLE IF NOT EXISTS bindings (address TEXT PRIMARY KEY, patient TEXT NOT NULL, host TEXT NOT NULL, credential TEXT NOT NULL, endpoint TEXT NOT NULL, generation INTEGER NOT NULL, handoff_digest TEXT);`);
}
address(address: string): string {
const parsed = patientAddress(address); requireThat(this.domains.includes(parsed.domain), 'untrusted_address_authority'); return parsed.address;
}
enroll(address: string, patient: string, credential: Credential, endpoint: string, now: number, generation = 1): void {
const normalized = this.address(address); id(patient); integer(generation); requireThat(generation >= 1); secureEndpoint(endpoint);
const host = this.trust.validate(credential, now, undefined, 'host');
requireThat(!this.db.prepare('SELECT address FROM bindings WHERE address=?').get(normalized), 'address_already_registered');
this.db.prepare('INSERT INTO bindings VALUES (?, ?, ?, ?, ?, ?, NULL)').run(normalized, patient, host.subject, JSON.stringify(credential), endpoint, generation);
}
document(address: string, now: number): Record<string, any> {
const normalized = this.address(address); const row = this.db.prepare('SELECT * FROM bindings WHERE address=?').get(normalized) as any; requireThat(row, 'not_found');
const credential = JSON.parse(row.credential); this.trust.validate(credential, now, row.host, 'host');
return webfinger(normalized, delegate(this.authority, { address: normalized, record_id: row.patient, host: row.host, credential, endpoint: row.endpoint, generation: row.generation, issued_at: now, expires_at: now + 300 }));
}
// Admission requires the complete cryptographic handoff; this cannot publish a bare new URL.
publish(handoff: Handoff, now: number, destinationCredential = handoff.activation.credential): void {
const { plan } = verifyHandoff(handoff, this.trust, now), address = this.address(plan.address), digest = fingerprint(jsonBytes(handoff));
const destination = this.trust.validate(destinationCredential, now, plan.destination, 'host');
requireThat(destination.signingKid === plan.destination_signing_kid && destination.encryptionKid === plan.destination_encryption_kid, 'wrong_transfer_host');
this.db.exec('BEGIN IMMEDIATE');
try {
const row = this.db.prepare('SELECT * FROM bindings WHERE address=?').get(address) as any;
requireThat(row && row.patient === plan.patient, 'migration_context_mismatch');
if (row.generation === plan.to_generation && row.host === plan.destination && row.handoff_digest === digest) {
this.db.prepare('UPDATE bindings SET credential=? WHERE address=?').run(JSON.stringify(destinationCredential), address); this.db.exec('COMMIT'); return;
}
requireThat(row.generation === plan.from_generation && row.host === plan.source, 'discovery_cutover_conflict');
this.db.prepare('UPDATE bindings SET host=?,credential=?,endpoint=?,generation=?,handoff_digest=? WHERE address=?').run(plan.destination, JSON.stringify(destinationCredential), plan.destination_endpoint, plan.to_generation, digest, address);
this.db.exec('COMMIT');
} catch (e) { this.db.exec('ROLLBACK'); throw e; }
}
close(): void { this.db.close(); }
}