8f752ed86b
Each Reader's userscript credential is derived from TOKEN_KEY, their Discord id and a token epoch (HMAC-SHA256, hex); only its SHA-256 sits in readers.token_sha256, so install URLs survive restarts while a database leak yields nothing but hashes. One credential authenticates the script download path and the API bearer header. - internal/token: derivation + hashing; migration 0006 adds token_epoch - seed refreshes the owner's epoch-0 hash only before first rotation - httpmw.Auth resolves the acting Reader from the credential hash and stashes it in the request context; the retired API_TOKEN resolves to the owner until API_TOKEN_GRACE_UNTIL, logged per use, on both the bearer and script-download paths - userscript handler renders the bindmounted file with the resolved Reader's credential substituted for __API_TOKEN__; a legacy-path request during grace serves the derived credential, so devices self-migrate on their next update poll - web UI: Userscripts panel with session-gated install endpoints that render the script directly (credential never in markup, address bar or a redirect) and confirm-gated rotation; atomic epoch bump + hash rewrite in the store - both userscripts carry __API_TOKEN__ placeholders; the committed global-token literal is removed (rotating at deploy retires it for real — it survives in git history) - env: TOKEN_KEY required, API_TOKEN/API_TOKEN_GRACE_UNTIL retire the legacy credential; docs and compose updated
8 lines
478 B
SQL
8 lines
478 B
SQL
-- Rotation is an epoch bump: a Reader's credential is derived from the
|
|
-- deployment secret, their Discord id and this epoch, so bumping it issues a
|
|
-- new credential and the rewritten token_sha256 invalidates the old one the
|
|
-- moment the transaction commits. The seed's ON CONFLICT refresh (Store.Open)
|
|
-- is gated on this being 0, so a restart can never undo a rotation by
|
|
-- restoring the epoch-0 hash.
|
|
ALTER TABLE readers ADD COLUMN token_epoch bigint NOT NULL DEFAULT 0;
|