From f466ebf082670a81eb8856efa8cf8ed91da77294 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 22:22:53 +0000 Subject: [PATCH] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- ...0_787ab50d-22c3-4d25-a5d9-098c6291ffa6.sql | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 supabase/migrations/20260528222250_787ab50d-22c3-4d25-a5d9-098c6291ffa6.sql diff --git a/supabase/migrations/20260528222250_787ab50d-22c3-4d25-a5d9-098c6291ffa6.sql b/supabase/migrations/20260528222250_787ab50d-22c3-4d25-a5d9-098c6291ffa6.sql new file mode 100644 index 0000000..e37527f --- /dev/null +++ b/supabase/migrations/20260528222250_787ab50d-22c3-4d25-a5d9-098c6291ffa6.sql @@ -0,0 +1,100 @@ +-- Harden default skills so platform-provided skills do not weaken quota/RLS guarantees. + +CREATE OR REPLACE VIEW public.user_skill_limits +WITH (security_invoker = true) +AS +SELECT + p.id AS user_id, + pl.slug AS plan_slug, + CASE + WHEN pl.slug IS NULL THEN NULL::integer + WHEN pl.slug = 'basic' THEN 5 + WHEN pl.slug = 'starter' THEN 15 + WHEN pl.slug = 'professional' THEN 50 + WHEN pl.slug = 'enterprise' THEN 999999 + ELSE NULL::integer + END AS max_skills, + ( + SELECT count(*)::integer + FROM public.skills sk + WHERE sk.user_id = p.id + AND sk.status <> 'archived' + AND COALESCE(sk.is_default, false) = false + ) AS current_skills_count +FROM public.profiles p +LEFT JOIN public.subscriptions s + ON s.user_id = p.id + AND s.status = ANY (ARRAY['active'::text, 'trialing'::text]) +LEFT JOIN public.plans pl ON pl.id = s.plan_id; + +GRANT SELECT ON public.user_skill_limits TO authenticated; +GRANT SELECT ON public.user_skill_limits TO service_role; + +CREATE OR REPLACE FUNCTION public.enforce_skill_limit() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_max int; + v_count int; + v_request_role text := COALESCE(current_setting('request.jwt.claim.role', true), ''); +BEGIN + IF COALESCE(NEW.is_default, false) THEN + IF v_request_role = 'service_role' THEN + RETURN NEW; + END IF; + + NEW.is_default := false; + END IF; + + SELECT max_skills, current_skills_count + INTO v_max, v_count + FROM public.user_skill_limits + WHERE user_id = NEW.user_id; + + IF v_max IS NULL THEN + RAISE EXCEPTION 'Sem assinatura ativa para criar skills' + USING ERRCODE = 'P0001'; + END IF; + + IF v_count >= v_max THEN + RAISE EXCEPTION 'Limite de skills atingido para o plano' + USING ERRCODE = 'P0002'; + END IF; + + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION public.protect_skill_default_flag() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_request_role text := COALESCE(current_setting('request.jwt.claim.role', true), ''); +BEGIN + IF v_request_role = 'service_role' THEN + RETURN NEW; + END IF; + + IF TG_OP = 'INSERT' THEN + NEW.is_default := false; + ELSIF TG_OP = 'UPDATE' THEN + NEW.is_default := OLD.is_default; + END IF; + + RETURN NEW; +END; +$$; + +DROP TRIGGER IF EXISTS trigger_protect_skill_default_flag ON public.skills; +CREATE TRIGGER trigger_protect_skill_default_flag + BEFORE INSERT OR UPDATE ON public.skills + FOR EACH ROW EXECUTE FUNCTION public.protect_skill_default_flag(); + +REVOKE EXECUTE ON FUNCTION public.enforce_skill_limit() FROM anon, authenticated, public; +REVOKE EXECUTE ON FUNCTION public.protect_skill_default_flag() FROM anon, authenticated, public; \ No newline at end of file