Files
claudemesh/packages/db/migrations/0023_api_keys.sql
Alejandro Gutiérrez f45380d231
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
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>
2026-05-02 02:09:44 +01:00

35 lines
1.3 KiB
SQL

-- 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");