-- ============================================================================ -- Secure Active Subscriptions Access -- ============================================================================ -- This migration fixes a security issue with the active_subscriptions view -- which could expose all users' subscription data. -- -- Changes: -- 1. Drop the public.active_subscriptions view (insecure) -- 2. Create a secure function that only returns the current user's subscription -- 3. Add RLS-compatible function for admin access if needed -- ============================================================================ -- ============================================================================ -- Drop the insecure view -- ============================================================================ drop view if exists public.active_subscriptions; -- ============================================================================ -- Create secure function to get current user's active subscription -- ============================================================================ -- Function to get the authenticated user's active subscription create or replace function public.get_my_active_subscription() returns table ( subscription_id text, user_id uuid, user_email text, first_name text, last_name text, status text, current_period_start timestamp with time zone, current_period_end timestamp with time zone, cancel_at_period_end boolean, product_name text, currency text, unit_amount integer, billing_interval text, plan subscription_plan ) language plpgsql security definer -- Set search path for security set search_path = public, stripe as $$ begin -- Only return data for the authenticated user return query select s.id as subscription_id, (c.metadata->>'user_id')::uuid as user_id, p.email as user_email, p.first_name, p.last_name, s.status::text, to_timestamp(si.current_period_start) as current_period_start, to_timestamp(si.current_period_end) as current_period_end, s.cancel_at_period_end, pr.name as product_name, pc.currency, pc.unit_amount, pc.recurring->>'interval' as billing_interval, p.plan from stripe.subscriptions s inner join stripe.customers c on c.id = s.customer inner join stripe.subscription_items si on si.subscription = s.id inner join public.profiles p on p.id = (c.metadata->>'user_id')::uuid left join stripe.prices pc on pc.id = si.price left join stripe.products pr on pr.id = pc.product where (c.metadata->>'user_id')::uuid = auth.uid() -- Filter by authenticated user only! and s.status::text in ('active', 'trialing') and si.current_period_end is not null and to_timestamp(si.current_period_end) > now() order by si.current_period_end desc limit 1; end; $$; -- ============================================================================ -- Grant appropriate permissions -- ============================================================================ -- Grant access to authenticated users for their own subscription grant execute on function public.get_my_active_subscription() to authenticated; -- ============================================================================ -- Comments for documentation -- ============================================================================ comment on function public.get_my_active_subscription is 'Returns the current authenticated user''s active subscription (secure, RLS-compliant)';