-- Subscription RLS and permissions -- Purpose: Ensure admins can read/write ml_user_subscriptions and ml_subscription_plans; -- consumers can only access their own subscriptions; everyone can read active plans. -- Notes: -- - Designed for Supabase (auth.uid(), auth.jwt()). -- - Adjust table/column names if they differ in your DB. -- 1) Helper: identify admin users -- Prefer JWT app_metadata.role = 'admin' if you set it; fallback to ak_users.user_type = 5 -- (5 corresponds to ADMIN per MALL_USER_TYPE). create or replace function public.is_admin() returns boolean language sql stable as $$ select coalesce( -- Check custom claim from JWT: { app_metadata: { role: 'admin' } } ((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin') -- Fallback: ak_users.user_type = 5 (cast to text for compatibility), match user by id as text or exists ( select 1 from public.ak_users u where u.id::text = auth.uid()::text and u.user_type::text = '5' ) , false); $$; comment on function public.is_admin is 'Returns true if current JWT/app user is admin by claim or ak_users.user_type=5.'; -- 2) Enable RLS on subscription tables alter table if exists public.ml_subscription_plans enable row level security; alter table if exists public.ml_user_subscriptions enable row level security; grant select on table public.ml_subscription_plans to anon, authenticated; grant select, insert, update, delete on table public.ml_subscription_plans to authenticated; -- limited by RLS grant select, insert, update, delete on table public.ml_user_subscriptions to authenticated; -- limited by RLS -- 4) Policies for ml_subscription_plans -- 4.1 Everyone can read active plans drop policy if exists ml_plans_select_active on public.ml_subscription_plans; create policy ml_plans_select_active on public.ml_subscription_plans for select to anon, authenticated using (is_active = true); -- 4.2 Admin can do anything drop policy if exists ml_plans_admin_all on public.ml_subscription_plans; create policy ml_plans_admin_all on public.ml_subscription_plans for all to authenticated using (public.is_admin()) with check (public.is_admin()); -- 5) Policies for ml_user_subscriptions -- 5.1 Users can see their own subscriptions drop policy if exists ml_user_subs_select_own on public.ml_user_subscriptions; create policy ml_user_subs_select_own on public.ml_user_subscriptions for select to authenticated using (user_id = auth.uid()); -- 5.2 Users can create their own subscriptions (checkout) drop policy if exists ml_user_subs_insert_own on public.ml_user_subscriptions; create policy ml_user_subs_insert_own on public.ml_user_subscriptions for insert to authenticated with check (user_id = auth.uid()); -- 5.3 Users may update their own records (e.g., auto_renew, cancel_at_period_end) -- NOTE: This allows updating any columns; for stricter control, add a BEFORE UPDATE trigger -- that restricts column changes for non-admins. drop policy if exists ml_user_subs_update_own on public.ml_user_subscriptions; create policy ml_user_subs_update_own on public.ml_user_subscriptions for update to authenticated using (user_id = auth.uid()) with check (user_id = auth.uid()); -- 5.4 Admin can do anything on user subscriptions drop policy if exists ml_user_subs_admin_all on public.ml_user_subscriptions; create policy ml_user_subs_admin_all on public.ml_user_subscriptions for all to authenticated using (public.is_admin()) with check (public.is_admin()); -- 6) Optional: Trigger to limit non-admin updates to specific fields -- Uncomment if you want to enforce column-level restrictions -- create or replace function public.enforce_user_sub_update() -- returns trigger language plpgsql as $$ -- begin -- if public.is_admin() then -- return new; -- admins can change anything -- end if; -- -- Only allow toggling auto_renew and cancel_at_period_end for owners -- if new.user_id = auth.uid() then -- new.status := old.status; -- new.plan_id := old.plan_id; -- new.start_date := old.start_date; -- new.end_date := old.end_date; -- new.next_billing_date := old.next_billing_date; -- -- allow: auto_renew, cancel_at_period_end -- return new; -- end if; -- raise exception 'Forbidden'; -- end $$; -- drop trigger if exists trg_enforce_user_sub_update on public.ml_user_subscriptions; -- create trigger trg_enforce_user_sub_update -- before update on public.ml_user_subscriptions -- for each row execute function public.enforce_user_sub_update(); -- 7) Safety: ensure no rows are exposed to non-auth users except active plans via select policy above. -- Admins authenticate as normal users with admin claim or ak_users.user_type=5.