Skip to content

Commit 1b364ce

Browse files
Enhance security migrations
- Restricted project_stars data access to authenticated users - Enforced accepted status in is_team_member checks - Added guard for handle_new_user trigger to prevent misuse X-Lovable-Edit-ID: edt-c2aa50e1-bf64-4e60-a409-1a85135ded93 Co-authored-by: TopProjectsCreator <230782617+TopProjectsCreator@users.noreply.github.com>
2 parents a6c6932 + a23fa98 commit 1b364ce

3 files changed

Lines changed: 106 additions & 1 deletion

File tree

bun.lock

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

supabase/functions/manage-env-secrets/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,48 @@ serve(async (req) => {
1717
}
1818

1919
try {
20+
const authHeader = req.headers.get('Authorization');
21+
if (!authHeader) {
22+
return new Response(JSON.stringify({ error: 'Missing authorization header' }), {
23+
status: 401,
24+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
25+
});
26+
}
27+
28+
const supabaseAuth = createClient(
29+
Deno.env.get('SUPABASE_URL') ?? '',
30+
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
31+
{ global: { headers: { Authorization: authHeader } } }
32+
);
33+
const { data: { user }, error: authError } = await supabaseAuth.auth.getUser();
34+
if (authError || !user) {
35+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
36+
status: 401,
37+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
38+
});
39+
}
40+
2041
const supabase = createClient(
2142
Deno.env.get('SUPABASE_URL') ?? '',
2243
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
2344
);
2445
const { projectId, key, value, scope = 'shared' } = await req.json();
2546

47+
// Verify user owns the project
48+
const { data: project, error: projectError } = await supabase
49+
.from('projects')
50+
.select('id')
51+
.eq('id', projectId)
52+
.eq('user_id', user.id)
53+
.single();
54+
55+
if (projectError || !project) {
56+
return new Response(JSON.stringify({ error: 'Forbidden: you do not own this project' }), {
57+
status: 403,
58+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
59+
});
60+
}
61+
2662
if (!projectId || !key || !value) {
2763
return new Response(JSON.stringify({ error: 'projectId, key, and value are required' }), {
2864
status: 400,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- Fix 1: Restrict project_stars SELECT to authenticated users only
2+
DROP POLICY IF EXISTS "Users can view all stars" ON public.project_stars;
3+
CREATE POLICY "Authenticated users can view stars"
4+
ON public.project_stars FOR SELECT
5+
TO authenticated
6+
USING (auth.uid() IS NOT NULL);
7+
8+
-- Fix 2: Fix is_team_member to require accepted = true
9+
CREATE OR REPLACE FUNCTION public.is_team_member(_user_id uuid, _team_id uuid)
10+
RETURNS boolean
11+
LANGUAGE sql
12+
STABLE
13+
SECURITY DEFINER
14+
SET search_path = 'public'
15+
AS $$
16+
SELECT EXISTS (
17+
SELECT 1
18+
FROM public.team_members
19+
WHERE user_id = _user_id
20+
AND team_id = _team_id
21+
AND accepted = true
22+
)
23+
$$;
24+
25+
-- Fix 3: Add trigger context guard to handle_new_user
26+
CREATE OR REPLACE FUNCTION public.handle_new_user()
27+
RETURNS trigger
28+
LANGUAGE plpgsql
29+
SECURITY DEFINER
30+
SET search_path = 'public'
31+
AS $$
32+
BEGIN
33+
IF TG_OP != 'INSERT' OR TG_TABLE_NAME != 'users' THEN
34+
RAISE EXCEPTION 'Unauthorized function call';
35+
END IF;
36+
37+
INSERT INTO public.profiles (user_id, display_name)
38+
VALUES (NEW.id, COALESCE(NEW.raw_user_meta_data->>'display_name', split_part(NEW.email, '@', 1)));
39+
40+
INSERT INTO public.user_roles (user_id, role)
41+
VALUES (NEW.id, 'user');
42+
43+
RETURN NEW;
44+
END;
45+
$$;

0 commit comments

Comments
 (0)