Is there something like RLS for stored procedures?...
# off-topic
b
Is there something like RLS for stored procedures? Also is there a way to opt out procedures from rpc?
g
You can put your procedures in a schema the API does not have access to (see API settings). You could also do postgres permissions to control access at a broad level. For RLS type things you would just do that in the actual stored procedure.
m
When you create the procedure you can use SECURITY INVOKER or SECURITY DEFINER, both of which will respect RLS I think. https://www.postgresql.org/docs/13/sql-createprocedure.html
> SECURITY INVOKER indicates that the procedure is to be executed with the privileges of the user that calls it. That is the default. SECURITY DEFINER specifies that the procedure is to be executed with the privileges of the user that owns it.
b
So if SECURITY INVOKER is set, all operations on tables inside the procedure check against the RLS as if the user who invoked the procedure was acting on them directly, correct? That means I have to manually make sure that procedures with SECURITY DEFINER set are inaccessible from outside the database
m
That’s my understanding. I have cases where I want a function to run with more permissions that the user invoking it has on the table, so I use DEFINER. But anything you don’t want called via rpc should be in a different schema.
g
Security invoker is default if you don't specify anything. You can use security definer to do things on RLS protected tables, but then you need to make sure they do their own protection. You don't always want SD procedures hidden as they are used on RLS tables many times, just control the result on their own.
m
An example of when you might want DEFINER: you want to write something to a table but you don’t want to grant the person invoking it INSERT permissions
b
> You don't always want SD procedures hidden as they are used on RLS tables many times, just control the result on their own. Waht does this mean?
g
Like MikeJ said, or say you want to get a count from a protected table, the user needs access to the function, but does not have read RLS on the table.
b
I'm currently rocking the insert into the profiles table when a new user signs up-trigger, but the stored procedure is in the public schema - so I think I could just create a new schema, and recreate the procedure there and then it would be able to be called by the trigger, but not by users via RPC, right?
Ah, ok from your original answer I wasn't sure if you ment that RLS itself relies on functions being inside the public schema or smth like that
g
I'm not positive whether you can run a trigger function or not with rpc, interesting... It will certainly not have any data passed to it.
m
It’s not quite the same - but I set a users “name” to their email when they first sign up, like this:
Create a trigger so that when a profile is associated with a user we set a default value for name based on their email address. -------------------------------------------- CREATE OR REPLACE FUNCTION set_default_profile_name_from_user () RETURNS TRIGGER AS $$ BEGIN IF NEW.user_id IS NOT NULL AND NEW.name IS NULL THEN SELECT INTO NEW.name split_part(email, '@', 1) AS name FROM auth.users WHERE id = NEW.user_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER VOLATILE; --- AND CREATE TRIGGER create_profile_for_response BEFORE INSERT OR UPDATE ON profiles FOR EACH ROW EXECUTE PROCEDURE set_default_profile_name_from_user (); Don't allow users to null out their name ---------------------------------------- ALTER TABLE profiles ADD CONSTRAINT profiles_with_users_cannot_have_null_or_blank_name CHECK ( user_id IS NULL OR COALESCE(TRIM(name), '') <> '' )
g
So MikeJ's function certainly won't work if called from rpc as he checks new. which won't be there.
b
ty @User for the example
m
Correct - this was just to show you need DEFINER else RLS would not allow the INSERT
g
FYI supabase example has the trigger function in public....
b
yeah i was following that tbh I completely forgot that schemas exist and that's what I'm going to use now, I think
but it's good to see Mikes example that does input checking etc
m
My app is a little backwards in that I have profiles for anonymous people that later get assigned to users - they can interact with the app before choosing to sign up.
b
ha same
though I'm really just generating a random username and password client side and giving them later the option to pick their own
m
If that’s what you’re doing, this might be useful to you:
Function to return a profile as json (or a new id if passed null) ----------------------------------------------------------------- CREATE OR REPLACE FUNCTION get_profile (profile_id uuid DEFAULT NULL, user_id uuid DEFAULT NULL) RETURNS json AS $$ # variable_conflict use_variable DECLARE profile record; BEGIN IF profile_id IS NULL AND user_id IS NULL THEN -- If both profile_id and user_id are null, return a new (unsaved) profile SELECT INTO profile uuid_generate_v4 () AS id; END IF; IF profile_id IS NOT NULL AND user_id IS NULL THEN -- If the profile_id is not null, we return the profile (which may or may not be saved) SELECT INTO profile * FROM profiles WHERE id = profile_id; IF NOT found THEN SELECT INTO profile profile_id AS id; END IF; END IF; IF user_id IS NOT NULL THEN -- If there is a user and they have a profile, return that profile SELECT INTO profile * FROM profiles WHERE profiles.user_id = user_id; IF NOT found THEN -- If the user didn't have a profile, -- coalesce a NULL profile_id into a new random value. -- Attempt to insert the profile; if it exists then update it INSERT INTO profiles (id, user_id) VALUES (COALESCE(profile_id, uuid_generate_v4 ()), user_id) ON CONFLICT (id) DO UPDATE SET user_id = user_id RETURNING * INTO profile; END IF; END IF; RETURN row_to_json(profile); END; $$ LANGUAGE plpgsql VOLATILE;
b
ty, but with the trigger I have guaranteed that each user has a profile, so that is thankfully not an issue
m
What I’m doing is recording responses to surveys like this one. For the first response, I need a profile (which is then stored in localstorage). For the next response, I use the profile id. Then if the person signs up with their email, I add the profile to the user. https://consensus.j4e.name/poll/967065f5-1145-45da-865d-246bc719a6fc
Was “fun” having to think it through backwards
b
Yeah, I first thought about doing something like this too, but I concluded that only using the supabase native user table is probably less of a hassle