mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
The cookie was the Session row's cuid. cuid is built for collision- resistant identity across distributed writers, not for unguessability — it encodes a millisecond timestamp and a process-local counter and never touches a CSPRNG. A session cookie is a bearer credential and has to be unguessable, so it now carries 32 random bytes and only the HMAC of that value is stored, matching how api_tokens and refresh_tokens already hold their secrets. Compatibility is passive, and deliberately so. A backfill cannot help: the cookie already sitting in a browser is the cuid, and nothing we generate server-side can be pushed into it — backfilling would sign everyone out at deploy. Upgrading a row in place on first use loses to the parallel requests a single page load fires: each mints its own secret, one write wins, and the losers cannot re-emit the winner's secret because only a hash is stored, so the browser can end up holding a value the row does not honour. Instead, rows minted before the column keep a NULL token_hash and stay resolvable by id, and are denied the sliding-expiry extension so they drain within the 30-day session lifetime. Every login from this release forward is on a secret at once, and an id stops authenticating the moment its row holds one. Note for operators: API_TOKEN_HMAC_KEY now also keys session cookies, so rotating it signs browser sessions out as well as invalidating tokens. It is already Core-required, so no deployment gains a new prerequisite.
22 lines
1.1 KiB
SQL
22 lines
1.1 KiB
SQL
-- Session cookies carry a purpose-generated secret rather than the row's cuid
|
|
-- primary key.
|
|
--
|
|
-- A cuid is built for collision-resistant identity, not for unguessability: it
|
|
-- embeds a timestamp and a monotonic counter and does not come from a CSPRNG.
|
|
-- A session cookie is a bearer credential and must. The cookie now carries a
|
|
-- 32-byte random token (`hls_<hex>`) and only its HMAC-SHA256 hash is stored,
|
|
-- matching how api_tokens and refresh_tokens already hold their secrets.
|
|
--
|
|
-- Additive and nullable ON PURPOSE: every session row that exists at deploy
|
|
-- time keeps NULL here and stays resolvable by its id, so the release signs
|
|
-- nobody out. Those rows cannot have their sliding expiry extended, so the
|
|
-- id-as-credential path drains itself within the 30-day session lifetime with
|
|
-- no operator action. Postgres permits many NULLs under a unique index, so the
|
|
-- constraint below does not conflict with the backfill-free transition.
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "sessions" ADD COLUMN "token_hash" TEXT;
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "sessions_token_hash_key" ON "sessions"("token_hash");
|