diff --git a/supabase/migrations/20241223164233_update_tag_search_public_phone_number.sql b/supabase/migrations/20241223164233_update_tag_search_public_phone_number.sql new file mode 100644 index 000000000..35a83a7b5 --- /dev/null +++ b/supabase/migrations/20241223164233_update_tag_search_public_phone_number.sql @@ -0,0 +1,46 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.tag_search(query text, limit_val integer, offset_val integer) + RETURNS TABLE(send_id_matches tag_search_result[], tag_matches tag_search_result[], phone_matches tag_search_result[]) + LANGUAGE plpgsql + IMMUTABLE SECURITY DEFINER +AS $function$ +begin + if limit_val is null or (limit_val <= 0 or limit_val > 100) then + raise exception 'limit_val must be between 1 and 100'; + end if; + if offset_val is null or offset_val < 0 then + raise exception 'offset_val must be greater than or equal to 0'; + end if; + return query -- + select ( select array_agg(row (sub.avatar_url, sub.tag_name, sub.send_id, sub.phone)::public.tag_search_result) + from ( select p.avatar_url, t.name as tag_name, p.send_id, null::text as phone + from profiles p + left join tags t on t.user_id = p.id and t.status = 'confirmed' + where query similar to '\d+' + and p.send_id::varchar like '%' || query || '%' + order by p.send_id + limit limit_val offset offset_val ) sub ) as send_id_matches, + ( select array_agg(row (sub.avatar_url, sub.tag_name, sub.send_id, sub.phone)::public.tag_search_result) + from ( select p.avatar_url, t.name as tag_name, p.send_id, null::text as phone + from profiles p + join tags t on t.user_id = p.id + where t.status = 'confirmed' + and (t.name <<-> query < 0.7 or t.name ilike '%' || query || '%') + order by (t.name <-> query) + limit limit_val offset offset_val ) sub ) as tag_matches, + ( select array_agg(row (sub.avatar_url, sub.tag_name, sub.send_id, sub.phone)::public.tag_search_result) + from ( select p.avatar_url, t.name as tag_name, p.send_id, u.phone + from profiles p + left join tags t on t.user_id = p.id and t.status = 'confirmed' + join auth.users u on u.id = p.id + where p.is_public + and query ~ '^\d{8,}$' + and u.phone like query || '%' + order by u.phone + limit limit_val offset offset_val ) sub ) as phone_matches; +end; +$function$ +; + +revoke all on function public.tag_search(q text, limit_val int, offset_val int) from anon; diff --git a/supabase/tests/tags_search_test.sql b/supabase/tests/tags_search_test.sql index 1b034ae92..09be8064e 100644 --- a/supabase/tests/tags_search_test.sql +++ b/supabase/tests/tags_search_test.sql @@ -1,7 +1,7 @@ -- Tag Search begin; -select plan(6); +select plan(8); create extension "basejump-supabase_test_helpers"; -- noqa: RF05 @@ -99,5 +99,35 @@ select results_eq($$ )::tag_search_result] ) $$, 'You can search by send_id'); +-- Test phone number privacy +select tests.authenticate_as_service_role(); +-- Make Bob's profile private +update profiles set is_public = false where id = tests.get_supabase_uid('bob'); + +select tests.authenticate_as('neo'); + +-- Verify that private profile phone numbers are not searchable +select results_eq($$ + SELECT coalesce(array_length(phone_matches,1), 0) from tag_search( $$ || :bobs_phone_number || $$::text, 1, 0); $$, $$ + values (0) $$, 'Private profile phone numbers should not be searchable'); + +-- Make Bob's profile public again +select tests.authenticate_as_service_role(); +update profiles set is_public = true where id = tests.get_supabase_uid('bob'); + +select tests.authenticate_as('neo'); + +-- Verify that public profile phone numbers are searchable +select results_eq($$ + SELECT phone_matches from tag_search( $$ || :bobs_phone_number || $$::text, 1, 0); $$, $$ + values ( + ARRAY[ROW( + 'bob_avatar', -- avatar_url + null, -- tag_name + $$ || :bob_send_id || $$, -- bob's send_id + $$ || :bobs_phone_number || $$ -- bob's phone number + )::tag_search_result] + ) $$, 'Public profile phone numbers should be searchable'); + select finish(); rollback;