caterium-app/ops/sql/SUPABASE-ADMIN-RIGHTS-V30.sql
2026-09-07 15:29:20 +03:00

191 lines
7.6 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Caterium v17.5.30 - granular administrator permissions and correct last-admin checks.
create or replace function public.sun_has_permission(p_workspace uuid, p_permission text)
returns boolean
language plpgsql
stable
security definer
set search_path='public'
as $$
declare
v_role text;
v_permissions jsonb;
v_active boolean;
begin
select role,permissions,is_active
into v_role,v_permissions,v_active
from public.sun_workspace_members
where workspace_id=p_workspace and user_id=auth.uid()
limit 1;
if not coalesce(v_active,false) then return false; end if;
-- Explicit member permissions override the role template for every role,
-- including administrators. Missing keys fall back to the role defaults.
if coalesce(v_permissions,'{}'::jsonb) ? p_permission then
return coalesce((v_permissions->>p_permission)::boolean,false);
end if;
return coalesce((public.sun_role_default_permissions(v_role)->>p_permission)::boolean,false);
end;
$$;
create or replace function public.sun_admin_update_member(
p_workspace uuid,
p_user uuid,
p_display_name text,
p_role text,
p_is_active boolean,
p_permissions jsonb
)
returns void
language plpgsql
security definer
set search_path='public'
as $$
declare
v_old_role text;
v_old_active boolean;
v_role text:=lower(coalesce(p_role,''));
v_other_admins integer:=0;
v_other_managing_admins integer:=0;
v_target_will_manage boolean:=false;
v_max integer;
v_active_count integer;
begin
-- Serialize membership administration inside one workspace so two admins
-- cannot simultaneously remove/demote the last administrators.
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if;
if v_role not in ('admin','manager','kitchen','courier','viewer') then raise exception 'Некорректная роль'; end if;
if p_permissions is null or jsonb_typeof(p_permissions)<>'object' then raise exception 'Некорректные права пользователя'; end if;
select role,is_active into v_old_role,v_old_active
from public.sun_workspace_members
where workspace_id=p_workspace and user_id=p_user
for update;
if not found then raise exception 'Пользователь не найден'; end if;
select count(*)::int into v_other_admins
from public.sun_workspace_members
where workspace_id=p_workspace
and user_id<>p_user
and role='admin'
and is_active=true;
if v_old_role='admin' and coalesce(v_old_active,false)
and (v_role<>'admin' or not coalesce(p_is_active,false))
and v_other_admins=0 then
raise exception 'Нельзя отключить или понизить последнего активного администратора';
end if;
select count(*)::int into v_other_managing_admins
from public.sun_workspace_members
where workspace_id=p_workspace
and user_id<>p_user
and role='admin'
and is_active=true
and coalesce(
case when coalesce(permissions,'{}'::jsonb) ? 'users.manage'
then (permissions->>'users.manage')::boolean
else null end,
true
)=true;
v_target_will_manage := v_role='admin'
and coalesce(p_is_active,false)
and coalesce(
case when p_permissions ? 'users.manage'
then (p_permissions->>'users.manage')::boolean
else null end,
true
);
if not v_target_will_manage and v_other_managing_admins=0 then
raise exception 'У хотя бы одного активного администратора должно оставаться право «Пользователи и права»';
end if;
if coalesce(p_is_active,false) and not coalesce(v_old_active,false) then
select p.max_members into v_max
from public.sun_workspace_subscriptions s
join public.sun_plans p on p.id=s.plan_id
where s.workspace_id=p_workspace;
select count(*)::int into v_active_count
from public.sun_workspace_members
where workspace_id=p_workspace and is_active=true;
if v_max is not null and v_active_count>=v_max then
raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max;
end if;
end if;
update public.sun_workspace_members
set display_name=nullif(trim(coalesce(p_display_name,'')),''),
role=v_role,
is_active=coalesce(p_is_active,false),
permissions=p_permissions,
updated_at=now()
where workspace_id=p_workspace and user_id=p_user;
end;
$$;
create or replace function public.sun_admin_remove_member(p_workspace uuid,p_user uuid)
returns void
language plpgsql
security definer
set search_path='public'
as $$
declare
v_role text;
v_active boolean;
v_other_admins integer:=0;
v_other_managing_admins integer:=0;
begin
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if;
if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if;
if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if;
select role,is_active into v_role,v_active
from public.sun_workspace_members
where workspace_id=p_workspace and user_id=p_user
for update;
if not found then return; end if;
if v_role='admin' and coalesce(v_active,false) then
select count(*)::int into v_other_admins
from public.sun_workspace_members
where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true;
if v_other_admins=0 then raise exception 'Нельзя удалить последнего активного администратора'; end if;
select count(*)::int into v_other_managing_admins
from public.sun_workspace_members
where workspace_id=p_workspace
and user_id<>p_user
and role='admin'
and is_active=true
and coalesce(
case when coalesce(permissions,'{}'::jsonb) ? 'users.manage'
then (permissions->>'users.manage')::boolean
else null end,
true
)=true;
if v_other_managing_admins=0 then
raise exception 'Нельзя удалить администратора: после удаления никто не сможет управлять пользователями';
end if;
end if;
delete from public.sun_workspace_members
where workspace_id=p_workspace and user_id=p_user;
end;
$$;
revoke all on function public.sun_has_permission(uuid,text) from public,anon;
revoke all on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) from public,anon;
revoke all on function public.sun_admin_remove_member(uuid,uuid) from public,anon;
grant execute on function public.sun_has_permission(uuid,text) to authenticated;
grant execute on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) to authenticated;
grant execute on function public.sun_admin_remove_member(uuid,uuid) to authenticated;