| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- -- PII envelope encryption, expansion step (plan todo 8).
- --
- -- Forward-only: deterministic, additive expansion only. This migration adds the
- -- encrypted (`_enc`) and keyed blind-index (`_index`) columns the PII backfill
- -- fills, and drops NOT NULL on the plaintext columns that backfill blanks to
- -- NULL. Nothing is dropped or renamed: the old plaintext columns, the `citext`
- -- extension, and the legacy unique constraints all remain, so version N-1
- -- replicas keep working through the rolling upgrade (docs/DATABASE.md,
- -- "Expand and contract").
- --
- -- Maintenance window: `ALTER TABLE ... DROP NOT NULL` takes ACCESS EXCLUSIVE on
- -- users / redirect_uris / oauth_clients, blocking both reads and writes for the
- -- duration; each non-concurrent `CREATE INDEX` takes SHARE, blocking writes.
- -- sqlx runs every migration inside ONE transaction, so a non-transactional
- -- `CREATE INDEX CONCURRENTLY` is NOT used (PostgreSQL rejects it in a
- -- transaction). Apply this migration from the dedicated migration Job during a
- -- maintenance window with the app tier quiesced; see RUNBOOK.md.
- --
- -- The legacy `ux_users_email UNIQUE (email)` and
- -- `ux_redirect_uris_uri_kind UNIQUE (uri, kind)` constraints are deliberately
- -- NOT dropped. PostgreSQL treats NULLs as distinct, so once the backfill blanks
- -- the plaintext each constraint goes inert (any number of NULL rows allowed)
- -- while still serving old replicas.
- -- (a) Plaintext columns the backfill blanks become nullable.
- ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
- ALTER TABLE redirect_uris ALTER COLUMN uri DROP NOT NULL;
- ALTER TABLE oauth_clients ALTER COLUMN client_name DROP NOT NULL;
- -- (b) users: encrypted email ciphertext + keyed blind index.
- ALTER TABLE users
- ADD COLUMN email_enc bytea NULL,
- ADD COLUMN email_index text NULL;
- CREATE UNIQUE INDEX ux_users_email_index
- ON users (email_index)
- WHERE email_index IS NOT NULL;
- -- (c) oauth_clients: encrypted PII metadata and blind-indexed mTLS identities.
- ALTER TABLE oauth_clients
- ADD COLUMN client_name_enc bytea NULL,
- ADD COLUMN contacts_enc bytea NULL,
- ADD COLUMN request_uris_enc bytea NULL,
- ADD COLUMN jwks_uri_enc bytea NULL,
- ADD COLUMN software_id_enc bytea NULL,
- ADD COLUMN tls_client_auth_subject_dn_enc bytea NULL,
- ADD COLUMN tls_client_auth_cert_thumbprint_enc bytea NULL,
- ADD COLUMN tls_client_auth_subject_dn_index text NULL,
- ADD COLUMN tls_client_auth_cert_thumbprint_index text NULL;
- -- Mirrors ux_oauth_clients_tls_subject_dn / ux_oauth_clients_tls_cert_thumbprint
- -- on the blind-index columns; the legacy plaintext indexes stay in place.
- CREATE UNIQUE INDEX ux_oauth_clients_tls_subject_dn_index
- ON oauth_clients (tls_client_auth_subject_dn_index)
- WHERE tls_client_auth_subject_dn_index IS NOT NULL;
- CREATE UNIQUE INDEX ux_oauth_clients_tls_cert_thumbprint_index
- ON oauth_clients (tls_client_auth_cert_thumbprint_index)
- WHERE tls_client_auth_cert_thumbprint_index IS NOT NULL;
- -- (d) redirect_uris: encrypted URI ciphertext + keyed blind index per kind.
- ALTER TABLE redirect_uris
- ADD COLUMN uri_enc bytea NULL,
- ADD COLUMN uri_index text NULL;
- CREATE UNIQUE INDEX ux_redirect_uris_uri_index_kind
- ON redirect_uris (uri_index, kind)
- WHERE uri_index IS NOT NULL;
|