From 1741b57a0b97f55559aff02390b051fd662e294c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 20 Apr 2026 19:37:26 -0400 Subject: [PATCH] feat(db): add sessions table for /api/* auth Stores sha256(token) plus user_agent + last_seen_at so future active-sessions UI doesn't need another migration. Co-Authored-By: Claude Opus 4.7 --- internal/db/migrations/0004_sessions.down.sql | 2 ++ internal/db/migrations/0004_sessions.up.sql | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 internal/db/migrations/0004_sessions.down.sql create mode 100644 internal/db/migrations/0004_sessions.up.sql diff --git a/internal/db/migrations/0004_sessions.down.sql b/internal/db/migrations/0004_sessions.down.sql new file mode 100644 index 00000000..0d6115ac --- /dev/null +++ b/internal/db/migrations/0004_sessions.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS sessions_user_id_idx; +DROP TABLE IF EXISTS sessions; diff --git a/internal/db/migrations/0004_sessions.up.sql b/internal/db/migrations/0004_sessions.up.sql new file mode 100644 index 00000000..ebffcece --- /dev/null +++ b/internal/db/migrations/0004_sessions.up.sql @@ -0,0 +1,18 @@ +-- Session tokens authenticate /api/* requests. We store sha256(token) so a +-- read-only DB leak doesn't grant active sessions; the raw token lives only +-- in the client's cookie (web) or bearer header (Flutter). +-- +-- last_seen_at enables an "active sessions" UI later (not wired in this plan) +-- without schema churn. user_agent is captured at issue time for the same +-- reason — free metadata now, no migration later. + +CREATE TABLE sessions ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash bytea NOT NULL UNIQUE, + user_agent text NOT NULL DEFAULT '', + created_at timestamptz NOT NULL DEFAULT now(), + last_seen_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX sessions_user_id_idx ON sessions (user_id);