diff --git a/prisma/migrations/0181_v1187_share_link_passphrase/migration.sql b/prisma/migrations/0181_v1187_share_link_passphrase/migration.sql new file mode 100644 index 000000000..1ba75e46a --- /dev/null +++ b/prisma/migrations/0181_v1187_share_link_passphrase/migration.sql @@ -0,0 +1,7 @@ +-- v1.18.7 — passphrase second factor for clinician share links. +-- A leaked share URL on its own no longer opens the record: every new +-- link carries a high-entropy passphrase, stored only as its HMAC-SHA256 +-- hash (same key as token_hash). The column is NULLABLE so every link +-- minted before this change keeps the possession-only contract and the +-- public view renders for it without a gate. +ALTER TABLE "clinician_share_links" ADD COLUMN "passphrase_hash" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f6d3092f4..7cac927df 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3856,6 +3856,13 @@ model ClinicianShareLink { userId String @map("user_id") /// HMAC-SHA256 of the raw `hls_` token (same scheme as `ApiToken.tokenHash`). tokenHash String @unique @map("token_hash") + /// v1.18.7 — HMAC-SHA256 of the passphrase second factor (same key as + /// `tokenHash`). NULL for legacy links minted before the passphrase gate; + /// those keep the possession-only contract (the public view renders without + /// a gate). A non-null value forces the unlock gate before the record paints. + /// The raw passphrase is returned exactly once at create time and stored only + /// as this hash — there is no recovery path. + passphraseHash String? @map("passphrase_hash") /// Human label the owner sets (e.g. a clinic/specialty note). Plaintext, bounded. label String