2820823b36
Add an optional API-key layer to the public read-only API. Keys grant higher per-minute rate limits and attribute usage; anonymous callers are still allowed at a lower IP-based budget. - migration 0008_api_key: api_key table (sha256 hash only, plaintext shown once) - apikey pkg: key generation + hashing - ratelimit pkg: Redis fixed-window limiter + per-key usage counters; fails open - public API middleware: X-API-Key / Bearer auth, X-RateLimit-* headers, 429+Retry-After - admin: issue/list/revoke keys + usage view (API + UI tab) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
25 lines
1.3 KiB
SQL
25 lines
1.3 KiB
SQL
-- API keys for the public read-only API. Keys grant higher rate limits and let
|
|
-- usage be attributed to a caller; the API itself stays free and read-only.
|
|
-- Only the SHA-256 hash of a key is stored; the plaintext is shown once at
|
|
-- creation time. Keys are issued/revoked from the admin console. The public
|
|
-- server only ever SELECTs from this table (request counting lives in Redis),
|
|
-- preserving its read-only contract against PostgreSQL.
|
|
CREATE TABLE IF NOT EXISTS api_key (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
key_prefix VARCHAR(20) NOT NULL, -- shown for identification, e.g. og_live_AbC1
|
|
key_hash TEXT NOT NULL UNIQUE, -- hex SHA-256 of the full key
|
|
owner_email TEXT,
|
|
tier VARCHAR(16) NOT NULL DEFAULT 'free',
|
|
rate_limit_per_min INT NOT NULL DEFAULT 120,
|
|
revoked_at TIMESTAMPTZ,
|
|
created_by TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CONSTRAINT api_key_tier_chk CHECK (tier IN ('free', 'partner', 'internal')),
|
|
CONSTRAINT api_key_rate_chk CHECK (rate_limit_per_min > 0)
|
|
);
|
|
|
|
-- Fast lookup of active keys by their hash on every authenticated request.
|
|
CREATE INDEX IF NOT EXISTS idx_api_key_active_hash
|
|
ON api_key (key_hash) WHERE revoked_at IS NULL;
|