import { Router } from 'express'; import { randomBytes } from 'node:crypto'; import { listRecipients, createRecipient, deleteRecipient, deleteGrant, getAccessLog, logAccess, listVaults, listGrantsForVault } from './db.js'; import { requireAdminAuth } from './auth.js'; export const router = Router(); router.use(requireAdminAuth); function newRecipientId(label: string): string { const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 24); const suffix = randomBytes(3).toString('hex'); return slug ? `${slug}-${suffix}` : suffix; } router.get('/recipients', (_req, res) => { res.json(listRecipients().map(r => ({ recipientId: r.recipient_id, label: r.label, publicKey: r.public_key, createdAt: r.created_at }))); }); router.post('/recipients', (req, res) => { const body = req.body as { label?: string; publicKey?: string }; if (!body.label || !body.publicKey) { res.status(400).json({ error: 'label and publicKey are required' }); return; } const recipientId = newRecipientId(body.label); createRecipient(recipientId, body.label, body.publicKey); res.json({ recipientId }); }); router.delete('/recipients/:id', (req, res) => { deleteRecipient(req.params.id); res.json({ ok: true }); }); // Revocation is pure metadata deletion — no crypto material is touched, // which is exactly why admin alone (no vault access of their own // required) can perform it, unlike `grant`. router.delete('/vaults/:key/grants/:recipientId', (req, res) => { deleteGrant(req.params.key, req.params.recipientId); logAccess(req.params.key, req.params.recipientId, 'revoke'); res.json({ ok: true }); }); router.get('/vaults/:key/log', (req, res) => { res.json(getAccessLog(req.params.key)); }); // Cross-vault view — "which things have access to what" in one call, // instead of walking every vault by hand with /vaults/:key/recipients // (which is itself grant-gated per vault, and only shows one vault at a // time). Admin-only since it reveals every vault's full grant list at // once, not just the ones the caller happens to have a grant on. router.get('/overview', (_req, res) => { const byId = new Map(listRecipients().map(r => [r.recipient_id, r])); const vaults = listVaults().map(v => ({ vaultKey: v.vault_key, updatedAt: v.updated_at, updatedBy: v.updated_by, updatedByLabel: byId.get(v.updated_by)?.label ?? null, grants: listGrantsForVault(v.vault_key).map(g => ({ recipientId: g.recipient_id, label: byId.get(g.recipient_id)?.label ?? null, canWrite: g.can_write === 1, })), })); res.json({ recipients: listRecipients().map(r => ({ recipientId: r.recipient_id, label: r.label, publicKey: r.public_key, createdAt: r.created_at })), vaults, }); });