-- +goose Up -- ONE server-side session, two presentations: a __Host cookie (browser) or a Bearer token -- (desktop/CLI). D39.84 — the principal is established in middleware only, so nothing below -- knows which presentation was used. create table users ( id text primary key, email text not null, created_at timestamptz not null default now(), -- The library read spans books, so it cannot borrow a book's counter: it needs its own -- monotonic scope. Revisions are compared WITHIN a scope only (K-4 proposal). library_revision bigint not null default 0 ); -- Case-insensitive identity without the citext extension: an extension is a deploy-time -- privilege we would need on every environment for one column. create unique index users_email_key on users (lower(email)); create table sessions ( -- The token itself is never stored. A leaked dump must not yield a usable credential, so the -- lookup key IS the digest: 256 random bits are not brute-forcible, no salt is needed. token_sha256 bytea primary key, user_id text not null references users (id) on delete cascade, created_at timestamptz not null default now(), last_used_at timestamptz not null default now(), -- Two clocks: idle expiry slides on use, absolute expiry never does — a stolen token cannot -- be kept alive forever by touching it. idle_expires_at timestamptz not null, absolute_expires_at timestamptz not null, revoked_at timestamptz ); create index sessions_user_idx on sessions (user_id); -- The expiry sweep deletes by this; expired rows are removed, not archived. create index sessions_absolute_expiry_idx on sessions (absolute_expires_at); -- +goose Down drop table sessions; drop table users;