- Migration 082: api_keys table (user_id, name, prefix, hashed_key, scopes, last_used_at, expires_at) - APIKey model, APIKeyService (Create, List, Delete, ValidateAPIKey) - APIKeyHandler: GET/POST/DELETE /api/v1/developer/api-keys - AuthMiddleware: X-API-Key and Bearer vza_* accepted as alternative to JWT - CSRF: skip for API key auth (stateless) - Key format: vza_ prefix, SHA-256 hashed storage
27 lines
1.2 KiB
SQL
27 lines
1.2 KiB
SQL
-- 082_create_api_keys.sql
|
|
-- User API keys for developer portal (v0.102 Lot C)
|
|
-- Distinct from webhook API keys (whk_); user keys use vza_ prefix
|
|
|
|
CREATE TABLE IF NOT EXISTS public.api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
prefix VARCHAR(16) NOT NULL,
|
|
hashed_key VARCHAR(128) NOT NULL,
|
|
|
|
scopes TEXT[] NOT NULL DEFAULT ARRAY['read'],
|
|
|
|
last_used_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON public.api_keys(prefix);
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON public.api_keys(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_expires_at ON public.api_keys(expires_at) WHERE expires_at IS NOT NULL;
|
|
|
|
COMMENT ON TABLE public.api_keys IS 'User API keys for developer portal (X-API-Key auth)';
|
|
COMMENT ON COLUMN public.api_keys.prefix IS 'First 8 chars of key for display (e.g. vza_abc1)';
|
|
COMMENT ON COLUMN public.api_keys.hashed_key IS 'SHA-256 hash of full key, never stored in plaintext';
|
|
COMMENT ON COLUMN public.api_keys.scopes IS 'Array of scopes: read, write, admin';
|