-- 003_vault.sql — API Key Encryption + User Vault -- v0.9.4: Two-tier encryption for API keys -- -- Phase 1 (this migration): Add new columns alongside existing plaintext. -- Phase 2 (Go startup): Backfill — encrypt plaintext keys, generate UEKs. -- Phase 3 (future): Drop api_key_plain after confirmed stable. -- ========================================= -- 1. USERS — Vault support -- ========================================= -- Per-user encryption key (UEK) wrapped with password-derived key (Argon2id). -- UEK protects personal BYOK API keys. Platform admin cannot recover without -- the user's password. ALTER TABLE users ADD COLUMN IF NOT EXISTS encrypted_uek BYTEA; ALTER TABLE users ADD COLUMN IF NOT EXISTS uek_salt BYTEA; ALTER TABLE users ADD COLUMN IF NOT EXISTS uek_nonce BYTEA; ALTER TABLE users ADD COLUMN IF NOT EXISTS vault_set BOOLEAN NOT NULL DEFAULT false; COMMENT ON COLUMN users.encrypted_uek IS 'UEK encrypted with Argon2id(password)-derived key'; COMMENT ON COLUMN users.uek_salt IS 'Argon2id salt for UEK derivation'; COMMENT ON COLUMN users.uek_nonce IS 'AES-GCM nonce for UEK wrapping'; COMMENT ON COLUMN users.vault_set IS 'true once UEK has been generated and wrapped'; -- ========================================= -- 2. PROVIDER_CONFIGS — Encrypted key storage -- ========================================= -- Rename current plaintext column, add encrypted BYTEA columns. -- Backfill happens in Go (needs ENCRYPTION_KEY env var). -- Keep plaintext temporarily for backfill; Go startup reads it, encrypts, -- writes to new columns, then NULLs it out. ALTER TABLE provider_configs RENAME COLUMN api_key_enc TO api_key_plain; ALTER TABLE provider_configs ADD COLUMN IF NOT EXISTS api_key_enc BYTEA; ALTER TABLE provider_configs ADD COLUMN IF NOT EXISTS key_nonce BYTEA; ALTER TABLE provider_configs ADD COLUMN IF NOT EXISTS key_scope TEXT; -- Backfill key_scope from existing scope column (safe default) UPDATE provider_configs SET key_scope = scope WHERE key_scope IS NULL; -- Now make it NOT NULL with default ALTER TABLE provider_configs ALTER COLUMN key_scope SET NOT NULL; ALTER TABLE provider_configs ALTER COLUMN key_scope SET DEFAULT 'global'; COMMENT ON COLUMN provider_configs.api_key_plain IS 'DEPRECATED: plaintext key, cleared after vault backfill'; COMMENT ON COLUMN provider_configs.api_key_enc IS 'AES-256-GCM encrypted API key (BYTEA)'; COMMENT ON COLUMN provider_configs.key_nonce IS 'AES-GCM nonce for api_key_enc'; COMMENT ON COLUMN provider_configs.key_scope IS 'Encryption tier: global/team use env key, personal uses UEK';