-- AZ-531 (Epic AZ-529): refresh-token sessions with rotation + reuse detection. -- One row per issued refresh token. Rows in the same session family share -- family_id; rotation marks the previous row as `revoked_reason='rotated'` and -- inserts a new row in the same family. Reuse detection keys off detecting a -- second presentation of an already-rotated row. CREATE TABLE IF NOT EXISTS public.sessions ( id uuid PRIMARY KEY, user_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, refresh_hash text NOT NULL, family_id uuid NOT NULL, issued_at timestamp NOT NULL DEFAULT now(), last_used_at timestamp NOT NULL DEFAULT now(), expires_at timestamp NOT NULL, revoked_at timestamp NULL, revoked_reason varchar(64) NULL, parent_session_id uuid NULL REFERENCES public.sessions(id) ON DELETE SET NULL, family_started_at timestamp NOT NULL DEFAULT now() ); -- O(1) lookup by the hash carried in the refresh token. CREATE UNIQUE INDEX IF NOT EXISTS sessions_refresh_hash_idx ON public.sessions (refresh_hash); -- Family-wide revoke (reuse-detection / logout-all) reads only active rows. CREATE INDEX IF NOT EXISTS sessions_family_active_idx ON public.sessions (family_id) WHERE revoked_at IS NULL; GRANT INSERT, SELECT, UPDATE ON public.sessions TO azaion_admin; GRANT SELECT ON public.sessions TO azaion_reader;