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);