fix(auth): store BankID personnummer ciphertext as raw bytea, not JSON-serialized Buffer (#1233)
Both writers of bankid_identities.personal_number_enc passed a raw Buffer
to supabase-js, which PostgREST serializes as JSON: every row stored the
literal text {"type":"Buffer","data":[...]} instead of iv|tag|ciphertext
bytes, so decryptPersonalNumber could never have read them (issue #1232).
- encryptPersonalNumberForStorage(): hex-encode for PostgREST bytea input
- decryptStoredPersonalNumber(): tolerant decode (raw bytea read-back,
legacy JSON-Buffer text, Buffer, serialized object)
- migration 20260727170000 rewrites existing rows to raw bytes; prefix
guard keeps it idempotent and skips already-raw rows. Conversion SQL
verified read-only against prod: converted bytes decrypt with the live
key (GCM tag valid, 12-digit result).
Closes #1232
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
88099f5390
commit
ff205951b1
@@ -0,0 +1,27 @@
|
||||
-- Fix bankid_identities.personal_number_enc rows written through supabase-js
|
||||
-- with a raw Buffer (issue #1232). PostgREST serialized the Buffer as JSON,
|
||||
-- so the bytea column holds the literal UTF-8 text
|
||||
-- {"type":"Buffer","data":[...]}
|
||||
-- instead of the raw iv|tag|ciphertext bytes that decryptPersonalNumber()
|
||||
-- expects. Rewrite those rows to the raw bytes recovered from the JSON
|
||||
-- "data" array.
|
||||
--
|
||||
-- The WHERE guard uses CASE, not AND: Postgres does not guarantee AND
|
||||
-- evaluation order, and convert_from() must never run on a row already
|
||||
-- holding raw ciphertext (not valid UTF-8), such as one written by the
|
||||
-- fixed code between deploy and apply, or any row on a re-run. CASE
|
||||
-- guarantees the byte-prefix check gates the convert_from call, keeping
|
||||
-- the migration idempotent and race-safe.
|
||||
|
||||
UPDATE bankid_identities
|
||||
SET personal_number_enc = (
|
||||
SELECT decode(string_agg(lpad(to_hex(elem::int), 2, '0'), '' ORDER BY ord), 'hex')
|
||||
FROM jsonb_array_elements_text(
|
||||
convert_from(personal_number_enc, 'UTF8')::jsonb -> 'data'
|
||||
) WITH ORDINALITY AS t(elem, ord)
|
||||
)
|
||||
WHERE CASE
|
||||
WHEN substring(personal_number_enc FROM 1 FOR 16) = convert_to('{"type":"Buffer"', 'UTF8')
|
||||
THEN jsonb_array_length(convert_from(personal_number_enc, 'UTF8')::jsonb -> 'data') > 0
|
||||
ELSE false
|
||||
END;
|
||||
Reference in New Issue
Block a user