textmachine/platform/internal/pgstore/migrations/00001_identity.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;