diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs index 67c9e44..32f2984 100644 --- a/crates/db/src/lib.rs +++ b/crates/db/src/lib.rs @@ -132,5 +132,27 @@ mod tests { Some("oauth_session"), "run_migrations must apply 0011_oauth (embedded migration set is stale?)" ); + + // Latest expected object: 0012 adds device linking + grant-scoped OAuth sessions. + let device_link: Option = + sqlx::query_scalar("select to_regclass('public.device_link')::text") + .fetch_one(&pool) + .await + .expect("query to_regclass"); + assert_eq!( + device_link.as_deref(), + Some("device_link"), + "run_migrations must apply 0012_device_link (embedded migration set is stale?)" + ); + let oauth_grant: Option = + sqlx::query_scalar("select to_regclass('public.oauth_grant')::text") + .fetch_one(&pool) + .await + .expect("query to_regclass"); + assert_eq!( + oauth_grant.as_deref(), + Some("oauth_grant"), + "run_migrations must apply 0012_device_link (embedded migration set is stale?)" + ); } } diff --git a/migrations/0012_device_link.sql b/migrations/0012_device_link.sql new file mode 100644 index 0000000..2b88766 --- /dev/null +++ b/migrations/0012_device_link.sql @@ -0,0 +1,24 @@ +-- Device linking (link-once OAuth over a trusted channel) + grant-scoped OAuth sessions. +-- +-- device_link: short-lived link codes minted for an assertion-verified DID, flipping to +-- 'linked' when the browser OAuth flow proves the SAME DID at its PDS. `receipts_opt_in` +-- is the standing opt-in captured at link time (drives buyer-repo purchase-claim writes). +-- oauth_grant: atrium session store for the LINK OAuth client (DPoP key + token set keyed +-- by DID) — separate from oauth_session so a later web sign-in (identity/email scopes) +-- cannot overwrite the repo-scoped grant. +create table if not exists device_link ( + code text primary key, + did text not null, + receipts_opt_in bool not null default false, + status text not null default 'pending', -- pending | linked + created_at timestamptz not null default now(), + expires_at timestamptz not null, + linked_at timestamptz +); +create index if not exists device_link_did_idx on device_link (did); + +create table if not exists oauth_grant ( + did text primary key, + data jsonb not null, + updated_at timestamptz not null default now() +);