A community based topic aggregation platform built on atproto
at main 92 lines 4.1 kB view raw
1-- +goose Up 2-- Encrypt aggregator OAuth tokens at rest using pgp_sym_encrypt 3-- This addresses the security issue where OAuth tokens were stored in plaintext 4-- despite migration 024 claiming "encrypted at application layer before storage" 5 6-- +goose StatementBegin 7 8-- Step 1: Add new encrypted columns for OAuth tokens and DPoP private key 9ALTER TABLE aggregators 10 ADD COLUMN oauth_access_token_encrypted BYTEA, 11 ADD COLUMN oauth_refresh_token_encrypted BYTEA, 12 ADD COLUMN oauth_dpop_private_key_encrypted BYTEA; 13 14-- Step 2: Migrate existing plaintext data to encrypted columns 15-- Uses the same encryption key table as community credentials (migration 006) 16UPDATE aggregators 17SET 18 oauth_access_token_encrypted = CASE 19 WHEN oauth_access_token IS NOT NULL AND oauth_access_token != '' 20 THEN pgp_sym_encrypt(oauth_access_token, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 21 ELSE NULL 22 END, 23 oauth_refresh_token_encrypted = CASE 24 WHEN oauth_refresh_token IS NOT NULL AND oauth_refresh_token != '' 25 THEN pgp_sym_encrypt(oauth_refresh_token, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 26 ELSE NULL 27 END, 28 oauth_dpop_private_key_encrypted = CASE 29 WHEN oauth_dpop_private_key_multibase IS NOT NULL AND oauth_dpop_private_key_multibase != '' 30 THEN pgp_sym_encrypt(oauth_dpop_private_key_multibase, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 31 ELSE NULL 32 END 33WHERE oauth_access_token IS NOT NULL 34 OR oauth_refresh_token IS NOT NULL 35 OR oauth_dpop_private_key_multibase IS NOT NULL; 36 37-- Step 3: Drop the old plaintext columns 38ALTER TABLE aggregators 39 DROP COLUMN oauth_access_token, 40 DROP COLUMN oauth_refresh_token, 41 DROP COLUMN oauth_dpop_private_key_multibase; 42 43-- Step 4: Add security comments 44COMMENT ON COLUMN aggregators.oauth_access_token_encrypted IS 'SENSITIVE: Encrypted OAuth access token (pgp_sym_encrypt) for PDS operations'; 45COMMENT ON COLUMN aggregators.oauth_refresh_token_encrypted IS 'SENSITIVE: Encrypted OAuth refresh token (pgp_sym_encrypt) for session renewal'; 46COMMENT ON COLUMN aggregators.oauth_dpop_private_key_encrypted IS 'SENSITIVE: Encrypted DPoP private key (pgp_sym_encrypt) for token refresh'; 47 48-- +goose StatementEnd 49 50-- +goose Down 51-- +goose StatementBegin 52 53-- Restore plaintext columns 54ALTER TABLE aggregators 55 ADD COLUMN oauth_access_token TEXT, 56 ADD COLUMN oauth_refresh_token TEXT, 57 ADD COLUMN oauth_dpop_private_key_multibase TEXT; 58 59-- Decrypt data back to plaintext (for rollback) 60UPDATE aggregators 61SET 62 oauth_access_token = CASE 63 WHEN oauth_access_token_encrypted IS NOT NULL 64 THEN pgp_sym_decrypt(oauth_access_token_encrypted, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 65 ELSE NULL 66 END, 67 oauth_refresh_token = CASE 68 WHEN oauth_refresh_token_encrypted IS NOT NULL 69 THEN pgp_sym_decrypt(oauth_refresh_token_encrypted, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 70 ELSE NULL 71 END, 72 oauth_dpop_private_key_multibase = CASE 73 WHEN oauth_dpop_private_key_encrypted IS NOT NULL 74 THEN pgp_sym_decrypt(oauth_dpop_private_key_encrypted, (SELECT encode(key_data, 'hex') FROM encryption_keys WHERE id = 1)) 75 ELSE NULL 76 END 77WHERE oauth_access_token_encrypted IS NOT NULL 78 OR oauth_refresh_token_encrypted IS NOT NULL 79 OR oauth_dpop_private_key_encrypted IS NOT NULL; 80 81-- Drop encrypted columns 82ALTER TABLE aggregators 83 DROP COLUMN oauth_access_token_encrypted, 84 DROP COLUMN oauth_refresh_token_encrypted, 85 DROP COLUMN oauth_dpop_private_key_encrypted; 86 87-- Restore comments 88COMMENT ON COLUMN aggregators.oauth_access_token IS 'SENSITIVE: OAuth access token for PDS operations'; 89COMMENT ON COLUMN aggregators.oauth_refresh_token IS 'SENSITIVE: OAuth refresh token for session renewal'; 90COMMENT ON COLUMN aggregators.oauth_dpop_private_key_multibase IS 'SENSITIVE: DPoP private key in multibase format for token refresh'; 91 92-- +goose StatementEnd