-- Migration 003: face images, and the credential a shop PC uses to upload one. -- -- Until now the system stored no images anywhere, which was a deliberate -- privacy position rather than a missing feature. Turning images on changes -- that position, so the schema makes the new obligations explicit rather than -- leaving them to whoever writes the next query: -- -- * an image is an object KEY, never a URL - a stored URL is permanent and -- unrevocable, and these are pictures of customers' faces -- * erasure has to delete the object, so the keys must stay findable -- * a site uploads through a short-lived presigned URL and never holds -- bucket credentials BEGIN; -- The credential a store PC uses for HTTPS calls it makes on its own behalf - -- today, asking for an upload URL. -- -- Separate from the broker password because they authenticate different -- things: the broker password says "this site may publish events", this says -- "this site may ask the API for something". Reusing one secret for both means -- rotating either one breaks the other. -- -- Hashed, not encrypted: unlike the broker password this is never handed back -- out. It is shown once at enrolment and the agent keeps it. ALTER TABLE agents ADD COLUMN api_token_hash bytea; CREATE UNIQUE INDEX agents_api_token_idx ON agents (api_token_hash) WHERE api_token_hash IS NOT NULL; -- When the image for this visit was erased, and by which request. Kept as a -- record rather than just blanking image_key: "we deleted it" is the thing an -- auditor asks to see, and an empty column cannot tell you whether an image was -- deleted or never captured. ALTER TABLE visits ADD COLUMN image_deleted_at timestamptz; -- Finding every object belonging to one person is the erasure path's first -- step, and without an index it is a full scan of every visit the client has -- ever recorded. CREATE INDEX visits_image_key_idx ON visits (visitor_id) WHERE image_key <> '' AND image_deleted_at IS NULL; COMMIT;