DEV Community

Ming
Ming

Posted on

2

User Authorization with Postgres Row Level Security Policy

Supabase has a storage gateway that uses RLS for authorization.

It requires a JWT that provides the role information to perform the SQL, here is an example of the JWT payload:

{
  "sub": "authenticated",
  "iat": 1516239022,
  "role": "f918ffd9-a611-4b2a-b4bb-df8f25d7569f"
}
Enter fullscreen mode Exit fullscreen mode

The storage bucket table is:

                        Table "storage.buckets"
   Column   |           Type           | Collation | Nullable | Default 
------------+--------------------------+-----------+----------+---------
 id         | text                     |           | not null | 
 name       | text                     |           | not null | 
 owner      | uuid                     |           |          | 
 created_at | timestamp with time zone |           |          | now()
 updated_at | timestamp with time zone |           |          | now()
Enter fullscreen mode Exit fullscreen mode

You only need to set up the correct RLS policy in the database. Here is an example:

-- generate a UUID as the role name since it needs to match the owner type
SELECT gen_random_uuid(); -- f918ffd9-a611-4b2a-b4bb-df8f25d7569f

CREATE ROLE "f918ffd9-a611-4b2a-b4bb-df8f25d7569f";
GRANT all ON schema storage TO "f918ffd9-a611-4b2a-b4bb-df8f25d7569f";
GRANT all on buckets TO "f918ffd9-a611-4b2a-b4bb-df8f25d7569f";
-- generate another role
CREATE ROLE "11b795e0-a566-491b-9ee7-62c025175dd8";
GRANT all ON schema storage TO "11b795e0-a566-491b-9ee7-62c025175dd8";
GRANT all on buckets TO "11b795e0-a566-491b-9ee7-62c025175dd8";

CREATE OR REPLACE FUNCTION user_record_count(uuid) RETURNS integer AS $$
DECLARE
    count integer;
BEGIN
    SELECT COUNT(*) INTO count
    FROM buckets
    WHERE owner = $1;

    RETURN count;
END;
$$ LANGUAGE plpgsql;

CREATE POLICY limit_user_crud ON buckets
    USING (owner = current_user::uuid)
    WITH CHECK (
        (SELECT user_record_count(current_user::uuid) < 3)
    );

SET ROLE "f918ffd9-a611-4b2a-b4bb-df8f25d7569f";

INSERT INTO buckets (id, name, owner) VALUES ('1', 'one', current_user::uuid);
INSERT INTO buckets (id, name, owner) VALUES ('2', 'two', current_user::uuid);
INSERT INTO buckets (id, name, owner) VALUES ('3', 'three', current_user::uuid);
-- check before the insertion
INSERT INTO buckets (id, name, owner) VALUES ('4', 'four', current_user::uuid); -- ERROR:  new row violates row-level security policy for table "buckets"
SELECT * FROM buckets; -- this returns 3 rows

SET role "11b795e0-a566-491b-9ee7-62c025175dd8";
SELECT * FROM buckets; -- this returns nothing
INSERT INTO buckets (id, name, owner) VALUES ('4', 'four', current_user::uuid); -- success
DELETE FROM buckets where id = '4'; -- success
DELETE FROM buckets where id = '1'; -- delete 0
Enter fullscreen mode Exit fullscreen mode

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more