mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Encrypt MedicationSideEffect.notes, MedicationDoseChange.note and MedicationInventoryItem.notes with AES-256-GCM, closing the last plaintext PHI columns left after the v1.23 note-encryption rollout. Add the three *Encrypted Bytes columns (migration 0208), register them in the encrypted- column rotation set, route every read/write through the note cipher, and add a transactional fail-closed boot-time backfill for legacy rows. Plaintext columns drop in a later release once the backfill reports zero remaining rows. Require a fresh second factor on password change for MFA-enrolled accounts, matching the existing step-up on MFA-disable and recovery-code regen. Redact free-text note values from the Zod validation excerpt that can reach a wide event.
36 lines
1.9 KiB
SQL
36 lines
1.9 KiB
SQL
-- v1.25 — encrypt medication free-text notes at rest.
|
|
--
|
|
-- Adds the AES-256-GCM ciphertext columns for the three medication
|
|
-- free-text fields that were still plaintext: the side-effect note
|
|
-- (`medication_side_effects.notes`), the dose-change titration note
|
|
-- (`medication_dose_changes.note`), and the inventory-item note
|
|
-- (`medication_inventory_items.notes`). These were the last plaintext
|
|
-- PHI columns left after the v1.23 note-encryption rollout.
|
|
--
|
|
-- STORAGE CONVENTION: Bytes (`bytea`), matching the dominant free-text
|
|
-- encrypted-note convention already in the schema (mood_entries.note_
|
|
-- encrypted, measurements.notes_encrypted, illness_day_logs.note_encrypted,
|
|
-- labs.note_encrypted, coach_*.{...}_encrypted) — the `encrypt()`
|
|
-- ciphertext string stored UTF-8 as `bytea`, the shape the rotation
|
|
-- script's `rotateBytesColumn` already covers. All three columns are
|
|
-- registered in ENCRYPTED_COLUMNS + the rotation script in the same change.
|
|
--
|
|
-- This migration is ADDITIVE ONLY. The legacy plaintext columns
|
|
-- (`medication_side_effects.notes`, `medication_dose_changes.note`,
|
|
-- `medication_inventory_items.notes`) are intentionally NOT dropped here:
|
|
-- an idempotent, transactional, fail-closed boot-time job encrypts the
|
|
-- existing rows (plaintext -> ciphertext, then nulls the plaintext) so the
|
|
-- read path can fall back to plaintext for not-yet-migrated rows. Dropping
|
|
-- the plaintext columns is a deliberate FOLLOW-UP release, once the backfill
|
|
-- reports zero remaining plaintext-but-no-ciphertext rows — the same
|
|
-- boundary the encryption-key-rotation playbook uses before dropping a
|
|
-- legacy key.
|
|
ALTER TABLE "medication_side_effects"
|
|
ADD COLUMN IF NOT EXISTS "notes_encrypted" BYTEA;
|
|
|
|
ALTER TABLE "medication_dose_changes"
|
|
ADD COLUMN IF NOT EXISTS "note_encrypted" BYTEA;
|
|
|
|
ALTER TABLE "medication_inventory_items"
|
|
ADD COLUMN IF NOT EXISTS "notes_encrypted" BYTEA;
|