40 lines
1.8 KiB
SQL
40 lines
1.8 KiB
SQL
-- +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;
|