feat(device): OAuth2 device-pairing flow for HA add-on (code/approve/token/tunnel + /activate)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
-- 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.';
|
||||
Reference in New Issue
Block a user