-- 0003_device_pairing.sql -- Additive, idempotent migration for the OAuth2 device-authorization-grant -- style "device pairing" flow used by the Home Assistant add-on. -- Safe to run multiple times. -- 1. Device pairings ------------------------------------------------------ -- One row per device-pairing attempt. The opaque device_code is only ever -- stored as a SHA-256 hash; the short, human-typed user_code is stored in the -- clear so it can be looked up when the user approves on the web. create table if not exists public.device_pairings ( id bigint generated always as identity primary key, device_code_hash text not null, user_code text not null, user_id uuid, subdomain citext, status text not null default 'pending', interval_seconds int not null default 5, created_at timestamptz not null default now(), expires_at timestamptz not null, approved_at timestamptz, claimed_at timestamptz, last_polled_at timestamptz ); create unique index if not exists device_pairings_device_code_hash_uidx on public.device_pairings (device_code_hash); create unique index if not exists device_pairings_user_code_uidx on public.device_pairings (user_code); create index if not exists device_pairings_expires_at_idx on public.device_pairings (expires_at); -- Cascade-delete pairings when the owning auth user is removed. do $$ begin if not exists ( select 1 from pg_constraint where conname = 'device_pairings_user_id_fkey' and conrelid = 'public.device_pairings'::regclass ) then alter table public.device_pairings add constraint device_pairings_user_id_fkey foreign key (user_id) references auth.users (id) on delete cascade; end if; end $$; -- RLS enabled with NO policies: service-role only. alter table public.device_pairings enable row level security; comment on table public.device_pairings is 'Device-authorization-grant pairings (device_code SHA-256 hashed). RLS enabled with no policies: service-role only.'; -- 2. Device tokens -------------------------------------------------------- -- Long-lived bearer tokens minted to a paired device. Only the SHA-256 hash -- is stored; the plaintext is returned to the device exactly once at mint. create table if not exists public.device_tokens ( id bigint generated always as identity primary key, token_hash text not null, user_id uuid not null, label text, created_at timestamptz not null default now(), last_used_at timestamptz, revoked_at timestamptz ); create unique index if not exists device_tokens_token_hash_uidx on public.device_tokens (token_hash); create index if not exists device_tokens_user_id_idx on public.device_tokens (user_id); -- Cascade-delete device tokens when the owning auth user is removed. do $$ begin if not exists ( select 1 from pg_constraint where conname = 'device_tokens_user_id_fkey' and conrelid = 'public.device_tokens'::regclass ) then alter table public.device_tokens add constraint device_tokens_user_id_fkey foreign key (user_id) references auth.users (id) on delete cascade; end if; end $$; -- RLS enabled with NO policies: service-role only. alter table public.device_tokens enable row level security; comment on table public.device_tokens is 'Device bearer tokens (SHA-256 hashed). RLS enabled with no policies: service-role only.';