feat(broker): api key schema and helpers
Foundation for v0.2.0 REST + external WS auth. Bearer tokens stored as SHA-256 hashes; secrets are 256-bit CSPRNG so Argon2 would waste cost without security gain. Adds mesh.api_key table, migration 0023 applied manually to prod, and helpers: createApiKey, listApiKeys, revokeApiKey, verifyApiKey. Next slices: CLI apikey verbs and REST endpoints in apps/web router. Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
packages/db/migrations/0023_api_keys.sql
Normal file
34
packages/db/migrations/0023_api_keys.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- API keys for REST + external WS access (v0.2.0).
|
||||
--
|
||||
-- Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md
|
||||
--
|
||||
-- Bearer-token auth for non-WS clients (humans on the dashboard, scripts,
|
||||
-- bots, mobile apps). The secret is shown once at creation, then only
|
||||
-- the Argon2id hash is stored. Capabilities + topic_scopes constrain
|
||||
-- what each key can do — a CI bot key gets `send/read` on `#deploys`
|
||||
-- only, never the whole mesh.
|
||||
--
|
||||
-- Additive — no breaking changes. CLI/web can ignore the table until
|
||||
-- the issuance verbs ship in 0.2.0.
|
||||
|
||||
CREATE TYPE "mesh"."api_key_capability" AS ENUM (
|
||||
'send', 'read', 'state_write', 'admin'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "mesh"."api_key" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"mesh_id" text NOT NULL REFERENCES "mesh"."mesh"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"label" text NOT NULL,
|
||||
"secret_hash" text NOT NULL,
|
||||
"secret_prefix" text NOT NULL,
|
||||
"capabilities" jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
"topic_scopes" jsonb,
|
||||
"issued_by_member_id" text REFERENCES "mesh"."member"("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"last_used_at" timestamp,
|
||||
"revoked_at" timestamp,
|
||||
"expires_at" timestamp
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "api_key_by_mesh" ON "mesh"."api_key" ("mesh_id");
|
||||
CREATE INDEX IF NOT EXISTS "api_key_by_prefix" ON "mesh"."api_key" ("secret_prefix");
|
||||
Reference in New Issue
Block a user