-- Developer console v22 hardening: platform-wide actions require MFA AAL2. create or replace function public.sun_require_platform_admin_aal2() returns void language plpgsql security definer set search_path='public','auth' as $$ begin if not public.sun_is_platform_admin() then raise exception 'Platform administrator required'; end if; if coalesce(auth.jwt()->>'aal','aal1') <> 'aal2' then raise exception 'Developer MFA AAL2 required'; end if; end; $$; revoke all on function public.sun_require_platform_admin_aal2() from public, anon, authenticated; create or replace function public.sun_platform_list_companies_v22() returns table(workspace_id uuid,workspace_name text,created_at timestamptz,plan_id text,plan_name text,status text,access_mode text,trial_ends_at timestamptz,current_period_end timestamptz,grace_until timestamptz,member_count bigint,max_members integer,owner_email text,pending_owner_email text) language plpgsql stable security definer set search_path='public','auth' as $$ begin perform public.sun_require_platform_admin_aal2(); return query select w.id,w.name,w.created_at,s.plan_id,p.name,s.status,public.sun_subscription_access_mode(w.id),s.trial_ends_at,s.current_period_end,s.grace_until, (select count(*) from public.sun_workspace_members m where m.workspace_id=w.id and m.is_active),p.max_members, (select u.email::text from public.sun_workspace_members m join auth.users u on u.id=m.user_id where m.workspace_id=w.id and m.role='admin' and m.is_active order by m.created_at asc limit 1), (select i.email from public.caterium_company_owner_invites i where i.workspace_id=w.id and i.used_at is null and i.expires_at>now() order by i.created_at desc limit 1) from public.sun_workspaces w left join public.sun_workspace_subscriptions s on s.workspace_id=w.id left join public.sun_plans p on p.id=s.plan_id order by w.created_at desc; end;$$; create or replace function public.sun_platform_list_users_v22() returns table(user_id uuid,email text,last_sign_in_at timestamptz,created_at timestamptz,workspace_id uuid,workspace_name text,role text,display_name text,is_active boolean,is_platform_admin boolean) language plpgsql stable security definer set search_path='public','auth' as $$ begin perform public.sun_require_platform_admin_aal2(); return query select u.id,u.email::text,u.last_sign_in_at,u.created_at,m.workspace_id,w.name,m.role,m.display_name,m.is_active, exists(select 1 from public.sun_platform_admins p where p.user_id=u.id) from auth.users u left join public.sun_workspace_members m on m.user_id=u.id left join public.sun_workspaces w on w.id=m.workspace_id order by coalesce(w.name,''),coalesce(m.display_name,u.email),u.email; end;$$; create or replace function public.sun_platform_list_errors_v22(p_workspace uuid default null,p_limit integer default 80) returns table(id uuid,workspace_id uuid,workspace_name text,user_id uuid,client_id text,app_version text,level text,message text,created_at timestamptz) language plpgsql stable security definer set search_path='public' as $$ begin perform public.sun_require_platform_admin_aal2(); return query select err.id,err.workspace_id,company.name,err.user_id,err.client_id,err.app_version,err.level,err.message,err.created_at from public.sun_v17_error_events err left join public.sun_workspaces company on company.id=err.workspace_id where p_workspace is null or err.workspace_id=p_workspace order by err.created_at desc limit greatest(1,least(coalesce(p_limit,80),200)); end;$$; -- Harden v22 developer functions. create or replace function public.sun_platform_dashboard() returns jsonb language plpgsql stable security definer set search_path='public','auth' as $$ declare v_companies bigint; v_accounts bigint; v_admins bigint; v_active bigint; v_trial bigint; v_locked bigint; v_errors bigint; v_backups bigint; v_members bigint; v_last_state timestamptz; v_last_backup timestamptz; begin perform public.sun_require_platform_admin_aal2(); select count(*) into v_companies from public.sun_workspaces; select count(*) into v_accounts from auth.users; select count(*) into v_admins from public.sun_platform_admins; select count(*) into v_members from public.sun_workspace_members where is_active=true; select count(*) into v_active from public.sun_workspace_subscriptions where status='active' and coalesce(current_period_end,'infinity'::timestamptz)>now(); select count(*) into v_trial from public.sun_workspace_subscriptions where status='trialing' and coalesce(trial_ends_at,'infinity'::timestamptz)>now(); select count(*) into v_locked from public.sun_workspaces w where public.sun_subscription_access_mode(w.id) in ('read_only','blocked'); select count(*) into v_errors from public.sun_v17_error_events where created_at>now()-interval '24 hours'; select count(*) into v_backups from public.sun_v17_backups where created_at>now()-interval '24 hours'; select max(updated_at) into v_last_state from public.sun_app_state; select max(created_at) into v_last_backup from public.sun_v17_backups; return jsonb_build_object('companies',v_companies,'accounts',v_accounts,'platform_admins',v_admins,'memberships',v_members,'active_subscriptions',v_active,'trials',v_trial,'restricted_companies',v_locked,'errors_24h',v_errors,'backups_24h',v_backups,'last_state_at',v_last_state,'last_backup_at',v_last_backup,'database_size',pg_size_pretty(pg_database_size(current_database())),'postgres_version',current_setting('server_version')); end;$$; -- Change the v22 function bodies by replacing the first platform check with the AAL2 guard. -- Definitions are kept in SUPABASE-DEVELOPER-V22.sql; this block updates privileges for secure wrappers. revoke all on function public.sun_platform_list_users() from anon; revoke all on function public.sun_owner_add_existing_member(uuid,text,text) from anon; revoke all on function public.sun_platform_list_companies_v22() from public,anon; revoke all on function public.sun_platform_list_users_v22() from public,anon; revoke all on function public.sun_platform_list_errors_v22(uuid,integer) from public,anon; grant execute on function public.sun_platform_list_companies_v22() to authenticated; grant execute on function public.sun_platform_list_users_v22() to authenticated; grant execute on function public.sun_platform_list_errors_v22(uuid,integer) to authenticated;