Hey everyone, How can I create RPC, not callable b...
# help
n
Hey everyone, How can I create RPC, not callable by the js client but only "internally"? Is something about the grants? If yes, then which one should I remove?
Copy code
ALTER FUNCTION public.myinternalfunc(jsonb) OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.myinternalfunc(jsonb) TO PUBLIC;
GRANT EXECUTE ON FUNCTION public.myinternalfunc(jsonb) TO anon;
GRANT EXECUTE ON FUNCTION public.myinternalfunc(jsonb) TO authenticated;
GRANT EXECUTE ON FUNCTION public.myinternalfunc(jsonb) TO postgres;
GRANT EXECUTE ON FUNCTION public.myinternalfunc(jsonb) TO service_role;
n
Hello @NARCISO! This thread has been automatically created from your message in #843999948717555735 a ``few seconds ago``. Pinging @User so that they see this as well! Want to unsubscribe from this thread? Right-click the thread in Discord (or use the ... menu) and select Leave Thread to unsubscribe from future updates. Want to change the title? Use the
/title
command! We have solved your problem? Click the button below to archive it.
🆕 How to create a RPC not callable by the js client
g
I create a schema called security and put functions I don’t want the API to have access to there.
n
How to create a RPC not callable by the js client
n
Hi @garyaustin, thanks for the feedback! That's a good solution but I guess it's not applicable to the problem I have now. Let me explain a bit further. I have a function "connected" to a trigger that adds a role to a user
after inserting
a row in a certain table. My concern at the moment is that the user could add the role he wants to himself calling the function as "rpc". And that's something should not happen. What do you think would be the best way to deny users to execute the function? Is there a way to permit to execute this function just to the "database" itself? The function and trigger looks like this:
Copy code
-- Add worker role to user
create function public.add_worker_role()
  returns trigger as $$
begin
  -- Check if the user is the owner of the user profile
  if (new.user_id <> auth.uid()) then
    raise exception 'not_owner';
  end if;

  -- Check if the user profile just created exists
  if not exists(select 1 from public.user_profile where user_id = new.user_id) then
    raise exception 'user_profile_not_exists';
  end if;

  -- Add worker role to the user
  insert into public.user_role (user_id, role_id) values (new.user_id, 'WORKER'::role);

  return null;
end;
$$ language plpgsql security definer set search_path = public, pg_temp;

create trigger update_user_profile_trigger
  after insert on public.user_profile
  for each row
  execute procedure public.add_worker_role();
Thank you so much in advance!
g
I’m not sure why putting in a special schema would not work. The API and rpc call would not have access to it. Your trigger would by calling security.myfunction()
n
@garyaustin You are right. Now I see you solution. I have the last question tho. I have these grants at the moment:
Copy code
pgsql
ALTER FUNCTION public.add_worker_role() OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.add_worker_role() TO PUBLIC;
GRANT EXECUTE ON FUNCTION public.add_worker_role() TO anon;
GRANT EXECUTE ON FUNCTION public.add_worker_role() TO authenticated;
GRANT EXECUTE ON FUNCTION public.add_worker_role() TO postgres;
GRANT EXECUTE ON FUNCTION public.add_worker_role() TO service_role;
Do you think it will work even if I'll just REVOKE the execution from PUBLIC, anon and authenticated? to deny the users the direct invocation of the function?
g
If your trigger function is happening from a table insert/update coming from the API then no. If a super user only does the initial table operation then probably.
n
You mean that, having a trigger like the one above that works
after insert
on the table
public.user_profile
, if a user from the js-client
insert
some data in the table, it would not work revoking the execution? But if I would have a function (as security definer) that inserts the data, that would work?
g
The user is set for the entire session. Seq def won’t work cause you can’t even start the function without execute permission.
Oh wait. Yes if a seq def function did the first insert then probably
n
Uh okay, well I think at this point the best way to proceed is your method. About creating a "security" schema, should there be something I need to consider?
Or just running
CREATE SCHEMA security
and then creating the functions with security.add_worker_role would work?
g
I just created it, no special grants. I use them in my RLS.
n
Sorry I did not understand what do you mean by "I use them in my RLS"
What are you referring to?
g
I put my security definer functions I use in RLS policies in that schema ( hence the name) so api can’t access them.
n
Could you give me a code example if you have it there? I'm not sure I understood correctly. You set RLS for the function? or in the schema for what? Sorry I'm pretty new to all this RLS and Postgres specific stuff 😁
g
I’m just saying I use this approach for functions I use in my policies so that users can’t access them thru rpc call. Like you want here. The last comment here is from a SB dev recommending same approach: https://github.com/supabase/supabase/discussions/3269
n
That's awesome! I created a
private
schema and put there all my private functions, but I was wondering about the grants. Which grants should I give to it? Just the
postgres
one? and REVOKE all the others
PostgREST
roles?
Copy code
ALTER FUNCTION private.handle_new_user() OWNER TO postgres;
GRANT EXECUTE ON FUNCTION private.handle_new_user() TO PUBLIC;
GRANT EXECUTE ON FUNCTION private.handle_new_user() TO anon;
GRANT EXECUTE ON FUNCTION private.handle_new_user() TO authenticated;
GRANT EXECUTE ON FUNCTION private.handle_new_user() TO postgres;
GRANT EXECUTE ON FUNCTION private.handle_new_user() TO service_role;
You think that also the functions connected to triggers for "data validation" should be moved to
private
schema too?
g
I leave all grants. But then I want them usable as part of my RLS. Only you can create a call to the function by using schema.function(). Trigger function can’t be called by rpc.
n
So basically leaving all the grants would give the PostgREST user to access them but the API would not access that schema, so it's safe to leave them, right?
Ah, so if my
private.function()
has none of the above GRANTS the RLS would not be able to use that in the policy?
g
Yes. An API user can be any of them but postgres and RLS would be run as that user. Also if you have a normal rpc function and want to call a security function you want that to work too.
n
I moved a
trigger function
handle_new_user
to the
private schema
that populate the user profile table based on
after insert
on
auth.users
. As you said, I could leave that in the
public
schema because it cannot be called as
rpc
, right?
Copy code
pgsql
CREATE TRIGGER handle_new_user_trigger
    AFTER INSERT
    ON auth.users
    FOR EACH ROW
    EXECUTE FUNCTION private.handle_new_user();
g
That is my understanding. I’ve not personally tried it.
n
Allright, thank you so much for time!