[refactor](alarm): retire subscribe-token rotation
The subscribe token is minted once per alert and lives as long as it does; subscribers are never evicted, so the rotation counter had no reader. Drop token_version from the schema and stop serialising tokenVersion on the wire. The QR payload is now built from alert id + token only, which also makes it instance-relative.
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
\c dcntrlized;
|
||||||
|
|
||||||
|
-- 002_drop_token_version.sql — remove the rotation counter.
|
||||||
|
--
|
||||||
|
-- Contract decision (2026-10-18): `rotateSubscribeToken` is gone. The subscribe
|
||||||
|
-- token is minted once at creation and lives as long as the alert; subscribers
|
||||||
|
-- are never evicted and there is no rotation lifecycle. `token_version` was the
|
||||||
|
-- rotation counter and has no remaining reader: no query, view or wire field
|
||||||
|
-- depends on it (the API stopped serialising `tokenVersion` in the same change).
|
||||||
|
--
|
||||||
|
-- `001_alarm_schema.sql` is already applied to the dev database and is never
|
||||||
|
-- edited in place, so the column is dropped here instead of being removed from
|
||||||
|
-- the CREATE TABLE. A fresh database applies 001 (which still creates the
|
||||||
|
-- column) and immediately drops it below; that is intentional and keeps the
|
||||||
|
-- migration history append-only. The CHECK constraint
|
||||||
|
-- `alerts_token_version_positive` disappears with the column.
|
||||||
|
--
|
||||||
|
-- Idempotent: guarded on the schema/column existing, so re-applying the whole
|
||||||
|
-- sequence (or applying this file alone to a virgin database) is a clean no-op.
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'alarm') THEN
|
||||||
|
ALTER TABLE alarm.alerts DROP COLUMN IF EXISTS token_version;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
@@ -22,7 +22,8 @@
|
|||||||
// constant-time over the digest.
|
// constant-time over the digest.
|
||||||
// - the SUBSCRIBE token is 24 random bytes, base64url (32 chars). It is
|
// - the SUBSCRIBE token is 24 random bytes, base64url (32 chars). It is
|
||||||
// stored in plaintext on purpose: it is shared through the QR code with
|
// stored in plaintext on purpose: it is shared through the QR code with
|
||||||
// every subscriber, and rotation is a cheap UPDATE (token_version). It is
|
// every subscriber and is minted once per alert — there is no rotation
|
||||||
|
// (subscribers are permanent for the lifetime of the alert). It is
|
||||||
// still compared in constant time so a timing oracle cannot be used to
|
// still compared in constant time so a timing oracle cannot be used to
|
||||||
// recover it byte by byte.
|
// recover it byte by byte.
|
||||||
package alarmtoken
|
package alarmtoken
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
//
|
//
|
||||||
// The fragment (not the query string) carries the token so the secret is never
|
// The fragment (not the query string) carries the token so the secret is never
|
||||||
// sent to the host if a scanner ever resolves the URL in a browser. Build is
|
// sent to the host if a scanner ever resolves the URL in a browser. Build is
|
||||||
// used by createAlert/getAlert/rotateSubscribeToken, and Parse is used by any
|
// used by createAlert/getAlert, and Parse is used by any
|
||||||
// component that accepts a scanned payload (the Android client's scanner and
|
// component that accepts a scanned payload (the Android client's scanner and
|
||||||
// the service's own tests).
|
// the service's own tests).
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user