Brand Studio docs
Engineering manualDatabase declarations

supabase-brand-studio-mcp-oauth

Exact checked-in database declaration: docs/supabase-brand-studio-mcp-oauth.sql

Source: docs/supabase-brand-studio-mcp-oauth.sql. Declared objects: function:brand_studio_mcp_oauth_authorize, function:brand_studio_mcp_oauth_consume_code, function:brand_studio_mcp_oauth_issue_tokens, function:brand_studio_mcp_oauth_revoke_token, index:brand_studio_mcp_oauth_grants_owner_idx, index:brand_studio_mcp_oauth_tokens_grant_idx, table:brand_studio_mcp_oauth_codes, table:brand_studio_mcp_oauth_grants, table:brand_studio_mcp_oauth_tokens. This is repository DDL, not a claim about live deployment.

All database declarations · Data structures and lineage

-- Read-only MCP OAuth adapter. Apply separately; this file is not an automatic
-- migration. Access/refresh/code values are NEVER stored, only SHA-256 hashes.
-- All mutation RPCs lock the grant first, so owner revocation, code issuance,
-- and refresh rotation serialize on one row. No browser/client RLS policies.
begin;

create table if not exists public.brand_studio_mcp_oauth_grants (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  client_id text not null check (length(client_id) between 1 and 200),
  scopes text[] not null check (scopes = array['read_reports']::text[]),
  resource text not null check (resource ~ '^https://[^/?#]+/api/mcp$'),
  created_at timestamptz not null default now(),
  expires_at timestamptz not null,
  revoked_at timestamptz,
  check (expires_at > created_at and expires_at <= created_at + interval '14 days')
);
create index if not exists brand_studio_mcp_oauth_grants_owner_idx
  on public.brand_studio_mcp_oauth_grants (user_id, created_at desc, id desc);

create table if not exists public.brand_studio_mcp_oauth_codes (
  code_hash text primary key check (code_hash ~ '^[a-f0-9]{64}$'),
  grant_id uuid not null unique references public.brand_studio_mcp_oauth_grants(id) on delete cascade,
  redirect_uri text not null check (length(redirect_uri) <= 2048 and redirect_uri like 'https://%'),
  code_challenge text not null check (code_challenge ~ '^[A-Za-z0-9_-]{43}$'),
  code_challenge_method text not null check (code_challenge_method = 'S256'),
  expires_at timestamptz not null,
  consumed_at timestamptz,
  claim_hash text check (claim_hash ~ '^[a-f0-9]{64}$'),
  issued_at timestamptz,
  check ((consumed_at is null) = (claim_hash is null)),
  check (issued_at is null or consumed_at is not null)
);

create table if not exists public.brand_studio_mcp_oauth_tokens (
  access_hash text primary key check (access_hash ~ '^[a-f0-9]{64}$'),
  refresh_hash text not null unique check (refresh_hash ~ '^[a-f0-9]{64}$'),
  grant_id uuid not null references public.brand_studio_mcp_oauth_grants(id) on delete cascade,
  access_expires_at timestamptz not null,
  refresh_expires_at timestamptz not null,
  rotated_at timestamptz,
  check (access_expires_at <= refresh_expires_at)
);
create index if not exists brand_studio_mcp_oauth_tokens_grant_idx
  on public.brand_studio_mcp_oauth_tokens (grant_id);

alter table public.brand_studio_mcp_oauth_grants enable row level security;
alter table public.brand_studio_mcp_oauth_codes enable row level security;
alter table public.brand_studio_mcp_oauth_tokens enable row level security;
revoke all on public.brand_studio_mcp_oauth_grants, public.brand_studio_mcp_oauth_codes,
  public.brand_studio_mcp_oauth_tokens from public, anon, authenticated;
grant select, insert, update, delete on public.brand_studio_mcp_oauth_grants,
  public.brand_studio_mcp_oauth_codes, public.brand_studio_mcp_oauth_tokens to service_role;

create or replace function public.brand_studio_mcp_oauth_authorize(p_grant jsonb, p_code jsonb)
returns boolean language plpgsql security invoker set search_path = '' as $$
declare
  v_now timestamptz := clock_timestamp();
  v_expiry timestamptz := (p_grant->>'expires_at')::timestamptz;
  v_code_expiry timestamptz := (p_code->>'expires_at')::timestamptz;
begin
  if v_expiry is null or v_expiry <= v_now or v_expiry > v_now + interval '14 days'
      or v_code_expiry is null or v_code_expiry <= v_now or v_code_expiry > v_now + interval '5 minutes'
      or p_code->>'grant_id' is distinct from p_grant->>'id' then return false; end if;
  -- Serialize consent creation per owner, including concurrent approvals from
  -- different clients. Revoked/expired grants remain paginated history only.
  perform pg_advisory_xact_lock(hashtextextended((p_grant->>'user_id')::uuid::text, 0));
  if (select count(*) from public.brand_studio_mcp_oauth_grants
      where user_id = (p_grant->>'user_id')::uuid and revoked_at is null
        and expires_at > clock_timestamp()) >= 20 then return false; end if;
  insert into public.brand_studio_mcp_oauth_grants
    (id, user_id, client_id, scopes, resource, created_at, expires_at)
    values ((p_grant->>'id')::uuid, (p_grant->>'user_id')::uuid, p_grant->>'client_id',
      array(select jsonb_array_elements_text(p_grant->'scopes')), p_grant->>'resource', v_now, v_expiry);
  insert into public.brand_studio_mcp_oauth_codes
    (code_hash, grant_id, redirect_uri, code_challenge, code_challenge_method, expires_at)
    values (p_code->>'code_hash', (p_grant->>'id')::uuid, p_code->>'redirect_uri',
      p_code->>'code_challenge', p_code->>'code_challenge_method', v_code_expiry);
  return true;
end;
$$;

create or replace function public.brand_studio_mcp_oauth_consume_code(
  p_hash text, p_client_id text, p_resource text, p_claim_hash text)
returns boolean language plpgsql security invoker set search_path = '' as $$
declare
  v_grant public.brand_studio_mcp_oauth_grants%rowtype;
begin
  select g.* into v_grant from public.brand_studio_mcp_oauth_grants g
    join public.brand_studio_mcp_oauth_codes c on c.grant_id = g.id
    where c.code_hash = p_hash for update of g;
  if not found or v_grant.revoked_at is not null or v_grant.expires_at <= clock_timestamp()
      or v_grant.client_id is distinct from p_client_id or v_grant.resource is distinct from p_resource
      or v_grant.scopes <> array['read_reports']::text[] then return false; end if;
  update public.brand_studio_mcp_oauth_codes set consumed_at = clock_timestamp(), claim_hash = p_claim_hash
    where code_hash = p_hash and consumed_at is null and expires_at > clock_timestamp();
  return found;
end;
$$;

-- Never raise an exception for refresh replay: returning {ok:false} commits the
-- family revocation. Raising would roll it back and keep stolen tokens alive.
create or replace function public.brand_studio_mcp_oauth_issue_tokens(
  p_kind text, p_hash text, p_claim_hash text, p_client_id text, p_resource text, p_token jsonb)
returns jsonb language plpgsql security invoker set search_path = '' as $$
declare
  v_grant_id uuid;
  v_grant public.brand_studio_mcp_oauth_grants%rowtype;
  v_code public.brand_studio_mcp_oauth_codes%rowtype;
  v_old public.brand_studio_mcp_oauth_tokens%rowtype;
  v_now timestamptz;
  v_access_expiry timestamptz := (p_token->>'access_expires_at')::timestamptz;
  v_refresh_expiry timestamptz := (p_token->>'refresh_expires_at')::timestamptz;
begin
  if p_kind = 'code' then
    select grant_id into v_grant_id from public.brand_studio_mcp_oauth_codes where code_hash = p_hash;
  elsif p_kind = 'refresh' then
    select grant_id into v_grant_id from public.brand_studio_mcp_oauth_tokens where refresh_hash = p_hash;
  else return jsonb_build_object('ok', false); end if;
  select * into v_grant from public.brand_studio_mcp_oauth_grants where id = v_grant_id for update;
  v_now := clock_timestamp();
  if not found or v_grant.revoked_at is not null or v_grant.expires_at <= v_now
      or v_grant.client_id is distinct from p_client_id or v_grant.resource is distinct from p_resource
      or v_grant.scopes <> array['read_reports']::text[] then return jsonb_build_object('ok', false); end if;
  if p_kind = 'code' then
    select * into v_code from public.brand_studio_mcp_oauth_codes where code_hash = p_hash;
    if v_code.consumed_at is null or v_code.issued_at is not null or v_code.expires_at <= v_now
        or p_claim_hash is null or v_code.claim_hash is distinct from p_claim_hash then return jsonb_build_object('ok', false); end if;
  else
    select * into v_old from public.brand_studio_mcp_oauth_tokens where refresh_hash = p_hash;
    if v_old.rotated_at is not null then
      update public.brand_studio_mcp_oauth_grants set revoked_at = v_now where id = v_grant_id;
      return jsonb_build_object('ok', false);
    end if;
    if v_old.refresh_expires_at <= v_now then return jsonb_build_object('ok', false); end if;
  end if;
  if v_access_expiry is null or v_refresh_expiry is null
      or v_access_expiry <= v_now or v_access_expiry > v_now + interval '15 minutes'
      or v_refresh_expiry <= v_now or v_refresh_expiry > v_grant.expires_at
      or v_access_expiry > v_refresh_expiry then return jsonb_build_object('ok', false); end if;
  insert into public.brand_studio_mcp_oauth_tokens
    (access_hash, refresh_hash, grant_id, access_expires_at, refresh_expires_at)
    values (p_token->>'access_hash', p_token->>'refresh_hash', v_grant_id, v_access_expiry, v_refresh_expiry);
  if p_kind = 'code' then
    update public.brand_studio_mcp_oauth_codes set issued_at = v_now where code_hash = p_hash;
  else
    update public.brand_studio_mcp_oauth_tokens set rotated_at = v_now where refresh_hash = p_hash;
  end if;
  return jsonb_build_object('ok', true);
end;
$$;

create or replace function public.brand_studio_mcp_oauth_revoke_token(p_hash text, p_client_id text)
returns boolean language plpgsql security invoker set search_path = '' as $$
begin
  update public.brand_studio_mcp_oauth_grants g set revoked_at = clock_timestamp()
    where g.client_id = p_client_id and g.revoked_at is null and exists (
      select 1 from public.brand_studio_mcp_oauth_tokens t where t.grant_id = g.id
        and (t.access_hash = p_hash or t.refresh_hash = p_hash));
  return true;
end;
$$;

revoke all on function public.brand_studio_mcp_oauth_authorize(jsonb,jsonb),
  public.brand_studio_mcp_oauth_consume_code(text,text,text,text),
  public.brand_studio_mcp_oauth_issue_tokens(text,text,text,text,text,jsonb),
  public.brand_studio_mcp_oauth_revoke_token(text,text) from public, anon, authenticated;
grant execute on function public.brand_studio_mcp_oauth_authorize(jsonb,jsonb),
  public.brand_studio_mcp_oauth_consume_code(text,text,text,text),
  public.brand_studio_mcp_oauth_issue_tokens(text,text,text,text,text,jsonb),
  public.brand_studio_mcp_oauth_revoke_token(text,text) to service_role;
commit;