Fixed bugs in memberships management

This commit is contained in:
giancarlo
2024-04-20 18:12:04 +08:00
parent efd27aa7de
commit bf0d2e1c87
10 changed files with 2114 additions and 2255 deletions

View File

@@ -113,6 +113,9 @@ jobs:
- name: Run Playwright tests
run: pnpm run test
- name: Run Supabase tests
run: pnpm --filter web supabase:test
- uses: actions/upload-artifact@v4
if: always()
with:

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ./.env.local --",
"with-env:test": "dotenv -e ./.env.test --",
"supabase": "supabase",
"supabase:start": "supabase status || supabase start",
"supabase:stop": "supabase stop",
"supabase:reset": "supabase db reset",

View File

@@ -670,9 +670,9 @@ create policy roles_read on public.roles
or public.has_role_on_account(account_id)
);
-- Function to check if a user can remove a member from an account
-- Function to check if a user can perform management actions on an account member
create or replace function
kit.can_remove_account_member(target_team_account_id uuid,
public.can_action_account_member(target_team_account_id uuid,
user_id uuid)
returns boolean
as $$
@@ -680,22 +680,34 @@ declare
permission_granted boolean;
target_user_hierarchy_level int;
current_user_hierarchy_level int;
is_account_owner boolean;
begin
select public.is_account_owner(target_team_account_id) into is_account_owner;
-- a primary owner of the account can never be actioned
if is_account_owner and user_id = auth.uid() then
raise exception 'You cannot action the primary owner of the account';
end if;
-- an account owner can action any member of the account
if is_account_owner then
return true;
end if;
-- validate the auth user has the required permission on the account
-- to manage members of the account
-- to manage members of the account
select
public.has_permission(auth.uid(), target_team_account_id,
'members.manage'::app_permissions) into
permission_granted;
if not permission_granted then
raise exception 'You do not have permission to remove a member from this account';
raise exception 'You do not have permission to action a member from this account';
end if;
-- users cannot remove themselves from the account with this function
if can_remove_account_member.user_id = auth.uid() then
raise exception 'You cannot remove yourself from the account';
if can_action_account_member.user_id = auth.uid() then
raise exception 'You cannot update your own account membership with this function';
end if;
select
@@ -718,13 +730,15 @@ begin
where
account_id = target_team_account_id
and user_id = auth.uid());
-- check if the current user has a higher hierarchy level than the
-- target user
-- target user. Lower hierarchy levels have higher permissions than higher hierarchy levels
if current_user_hierarchy_level <= target_user_hierarchy_level then
raise exception 'You do not have permission to remove this user from the account';
raise exception 'You do not have permission to action this user';
end if;
-- return true if the user has the required permission
return true;
end;
@@ -732,11 +746,11 @@ end;
$$
language plpgsql;
grant execute on function kit.can_remove_account_member(uuid, uuid)
grant execute on function public.can_action_account_member(uuid, uuid)
to authenticated, service_role;
-- RLS
-- SELECT: Users can read their team members account memberships
-- SELECT: Users can read their account memberships
create policy accounts_memberships_read_self on public.accounts_memberships
for select to authenticated
using (user_id = auth.uid());
@@ -760,7 +774,7 @@ create policy accounts_memberships_delete_self on public.accounts_memberships
-- DELETE: Users with the required role can remove members from an account
create policy accounts_memberships_delete on public.accounts_memberships
for delete to authenticated
using (kit.can_remove_account_member(account_id, user_id));
using (public.can_action_account_member(account_id, user_id));
-- SELECT (public.accounts): Team members can read accounts of the team
-- they are a member of
@@ -939,7 +953,7 @@ grant select, insert, update, delete on table public.invitations to
-- Enable RLS on the invitations table
alter table public.invitations enable row level security;
create or replace function check_team_account()
create or replace function kit.check_team_account()
returns trigger
as $$
begin
@@ -963,7 +977,7 @@ language plpgsql;
create trigger only_team_accounts_check
before insert or update on public.invitations for each row
execute procedure check_team_account();
execute procedure kit.check_team_account();
-- RLS
-- SELECT: Users can read invitations to users of an account they
@@ -971,7 +985,7 @@ create trigger only_team_accounts_check
-- a member of
create policy invitations_read_self on public.invitations
for select to authenticated
using (has_role_on_account(account_id));
using (public.has_role_on_account(account_id));
-- INSERT: Users can create invitations to users of an account they are
-- a member of
@@ -982,7 +996,7 @@ create policy invitations_create_self on public.invitations
for insert to authenticated
with check (
public.is_set('enable_team_accounts') and
public.has_permission(auth.uid(), account_id, 'invites.manage' ::app_permissions)
public.has_permission(auth.uid(), account_id, 'invites.manage' ::app_permissions)
and public.has_more_elevated_role(
auth.uid(), account_id, role));

View File

@@ -44,51 +44,6 @@ execute function "supabase_functions"."http_request"(
-- DATA SEED
-- This is a data dump for testing purposes. It should be used to seed the database with data for testing.
SET session_replication_role = replica;
pg_dump: warning: there are circular foreign-key constraints on this table:
pg_dump: detail: key
pg_dump: hint: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.
pg_dump: hint: Consider using a full dump instead of a --data-only dump to avoid this problem.
--
-- PostgreSQL database dump
--
-- Dumped from database version 15.1 (Ubuntu 15.1-1.pgdg20.04+1)
-- Dumped by pg_dump version 15.5 (Ubuntu 15.5-1.pgdg20.04+1)
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: audit_log_entries; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."audit_log_entries" ("instance_id", "id", "payload", "created_at", "ip_address") VALUES
('00000000-0000-0000-0000-000000000000', '45d9f5dc-ae78-45aa-8dcf-7777639eafb3', '{"action":"user_confirmation_requested","actor_id":"31a03e74-1639-45b6-bfa7-77447f1a4762","actor_username":"test@makerkit.dev","actor_via_sso":false,"log_type":"user","traits":{"provider":"email"}}', '2024-04-20 08:20:34.463864+00', ''),
('00000000-0000-0000-0000-000000000000', 'a15ab66b-f291-427b-9f15-5364840b7cfb', '{"action":"user_signedup","actor_id":"31a03e74-1639-45b6-bfa7-77447f1a4762","actor_username":"test@makerkit.dev","actor_via_sso":false,"log_type":"team"}', '2024-04-20 08:20:38.163624+00', ''),
('00000000-0000-0000-0000-000000000000', 'c625f494-d041-44a9-8487-88f71cec3ca2', '{"action":"login","actor_id":"31a03e74-1639-45b6-bfa7-77447f1a4762","actor_username":"test@makerkit.dev","actor_via_sso":false,"log_type":"account","traits":{"provider_type":"email"}}', '2024-04-20 08:20:39.086702+00', ''),
('00000000-0000-0000-0000-000000000000', 'c579eedc-4089-4752-b5da-869d62b63c94', '{"action":"user_confirmation_requested","actor_id":"5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf","actor_username":"owner@makerkit.dev","actor_via_sso":false,"log_type":"user","traits":{"provider":"email"}}', '2024-04-20 08:36:27.638468+00', ''),
('00000000-0000-0000-0000-000000000000', '9f0349fd-fd4a-4a78-926d-3045c086055d', '{"action":"user_signedup","actor_id":"5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf","actor_username":"owner@makerkit.dev","actor_via_sso":false,"log_type":"team"}', '2024-04-20 08:36:37.517553+00', ''),
('00000000-0000-0000-0000-000000000000', '0a7673c0-8a4c-4984-a1f2-2b02d829fc85', '{"action":"login","actor_id":"5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf","actor_username":"owner@makerkit.dev","actor_via_sso":false,"log_type":"account","traits":{"provider_type":"email"}}', '2024-04-20 08:36:37.613295+00', ''),
('00000000-0000-0000-0000-000000000000', '4f1b635c-f00b-46a4-80de-1edb48250f8b', '{"action":"logout","actor_id":"5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf","actor_username":"owner@makerkit.dev","actor_via_sso":false,"log_type":"account"}', '2024-04-20 08:36:50.193801+00', ''),
('00000000-0000-0000-0000-000000000000', 'f0ec2dd0-b870-4822-a606-4208b7e17add', '{"action":"logout","actor_id":"31a03e74-1639-45b6-bfa7-77447f1a4762","actor_username":"test@makerkit.dev","actor_via_sso":false,"log_type":"account"}', '2024-04-20 08:37:13.584462+00', ''),
('00000000-0000-0000-0000-000000000000', '9b0174f5-c278-4e28-ba16-d3a845345abb', '{"action":"user_confirmation_requested","actor_id":"b73eb03e-fb7a-424d-84ff-18e2791ce0b4","actor_username":"custom@makerkit.dev","actor_via_sso":false,"log_type":"user","traits":{"provider":"email"}}', '2024-04-20 08:37:43.343197+00', ''),
('00000000-0000-0000-0000-000000000000', '26275d69-7031-45e3-a0b4-9d48dbeab82a', '{"action":"user_signedup","actor_id":"b73eb03e-fb7a-424d-84ff-18e2791ce0b4","actor_username":"custom@makerkit.dev","actor_via_sso":false,"log_type":"team"}', '2024-04-20 08:38:00.859507+00', ''),
('00000000-0000-0000-0000-000000000000', '498c38bf-318b-4653-b26b-d4e6daaca4f1', '{"action":"login","actor_id":"b73eb03e-fb7a-424d-84ff-18e2791ce0b4","actor_username":"custom@makerkit.dev","actor_via_sso":false,"log_type":"account","traits":{"provider_type":"email"}}', '2024-04-20 08:38:00.937756+00', ''),
('00000000-0000-0000-0000-000000000000', 'f53e4bd9-87d3-42f7-8b83-5fa9a9b914fe', '{"action":"login","actor_id":"31a03e74-1639-45b6-bfa7-77447f1a4762","actor_username":"test@makerkit.dev","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-04-20 08:40:08.032833+00', ''),
('00000000-0000-0000-0000-000000000000', '24e13912-f40c-457c-a380-ee079b904df8', '{"action":"user_confirmation_requested","actor_id":"6b83d656-e4ab-48e3-a062-c0c54a427368","actor_username":"member@makerkit.dev","actor_via_sso":false,"log_type":"user","traits":{"provider":"email"}}', '2024-04-20 08:41:08.68908+00', ''),
('00000000-0000-0000-0000-000000000000', '4341c565-778d-4ea1-8ccc-1b99674c1c40', '{"action":"user_signedup","actor_id":"6b83d656-e4ab-48e3-a062-c0c54a427368","actor_username":"member@makerkit.dev","actor_via_sso":false,"log_type":"team"}', '2024-04-20 08:41:15.376428+00', ''),
('00000000-0000-0000-0000-000000000000', 'bb38e0c7-4cb1-4eea-8a06-5ee4b34b261a', '{"action":"login","actor_id":"6b83d656-e4ab-48e3-a062-c0c54a427368","actor_username":"member@makerkit.dev","actor_via_sso":false,"log_type":"account","traits":{"provider_type":"email"}}', '2024-04-20 08:41:15.484323+00', '');
--
-- Data for Name: flow_state; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
@@ -102,47 +57,32 @@ INSERT INTO "auth"."audit_log_entries" ("instance_id", "id", "payload", "created
INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", "phone_change_token", "phone_change_sent_at", "email_change_token_current", "email_change_confirm_status", "banned_until", "reauthentication_token", "reauthentication_sent_at", "is_sso_user", "deleted_at", "is_anonymous") VALUES
('00000000-0000-0000-0000-000000000000', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'authenticated', 'authenticated', 'custom@makerkit.dev', '$2a$10$b3ZPpU6TU3or30QzrXnZDuATPAx2pPq3JW.sNaneVY3aafMSuR4yi', '2024-04-20 08:38:00.860548+00', NULL, '', '2024-04-20 08:37:43.343769+00', '', NULL, '', '', NULL, '2024-04-20 08:38:00.93864+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "b73eb03e-fb7a-424d-84ff-18e2791ce0b4", "email": "custom@makerkit.dev", "email_verified": false, "phone_verified": false}', NULL, '2024-04-20 08:37:43.3385+00', '2024-04-20 08:38:00.942809+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false),
('00000000-0000-0000-0000-000000000000', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'authenticated', 'authenticated', 'test@makerkit.dev', '$2a$10$uC911UKLmGbkp2Di7K2UfO9QYUQFz5et.gPpjhxWEob1jeXkLrNZu', '2024-04-20 08:20:38.165331+00', NULL, '', '2024-04-20 08:20:34.464746+00', '', NULL, '', '', NULL, '2024-04-20 08:40:08.033665+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', NULL, '2024-04-20 08:20:34.459113+00', '2024-04-20 08:40:08.035046+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false),
('00000000-0000-0000-0000-000000000000', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'authenticated', 'authenticated', 'test@makerkit.dev', '$2a$10$NaMVRrI7NyfwP.AfAVWt6O/abulGnf9BBqwa6DqdMwXMvOCGpAnVO', '2024-04-20 08:20:38.165331+00', NULL, '', NULL, '', NULL, '', '', NULL, '2024-04-20 09:36:02.521776+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', NULL, '2024-04-20 08:20:34.459113+00', '2024-04-20 10:07:48.554125+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false),
('00000000-0000-0000-0000-000000000000', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', 'authenticated', 'authenticated', 'owner@makerkit.dev', '$2a$10$D6arGxWJShy8q4RTW18z7eW0vEm2hOxEUovUCj5f3NblyHfamm5/a', '2024-04-20 08:36:37.517993+00', NULL, '', '2024-04-20 08:36:27.639648+00', '', NULL, '', '', NULL, '2024-04-20 08:36:37.614337+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf", "email": "owner@makerkit.dev", "email_verified": false, "phone_verified": false}', NULL, '2024-04-20 08:36:27.630379+00', '2024-04-20 08:36:37.617955+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false),
('00000000-0000-0000-0000-000000000000', '6b83d656-e4ab-48e3-a062-c0c54a427368', 'authenticated', 'authenticated', 'member@makerkit.dev', '$2a$10$6h/x.AX.6zzphTfDXIJMzuYx13hIYEi/Iods9FXH19J2VxhsLycfa', '2024-04-20 08:41:15.376778+00', NULL, '', '2024-04-20 08:41:08.689674+00', '', NULL, '', '', NULL, '2024-04-20 08:41:15.484606+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "6b83d656-e4ab-48e3-a062-c0c54a427368", "email": "member@makerkit.dev", "email_verified": false, "phone_verified": false}', NULL, '2024-04-20 08:41:08.683395+00', '2024-04-20 08:41:15.485494+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false);
--
-- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."identities" ("provider_id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", "updated_at", "id") VALUES
('31a03e74-1639-45b6-bfa7-77447f1a4762', '31a03e74-1639-45b6-bfa7-77447f1a4762', '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:20:34.46275+00', '2024-04-20 08:20:34.462773+00', '2024-04-20 08:20:34.462773+00', '9bb58bad-24a4-41a8-9742-1b5b4e2d8abd'),
('5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '{"sub": "5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf", "email": "owner@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:36:27.637388+00', '2024-04-20 08:36:27.637409+00', '2024-04-20 08:36:27.637409+00', '090598a1-ebba-4879-bbe3-38d517d5066f'),
('31a03e74-1639-45b6-bfa7-77447f1a4762', '31a03e74-1639-45b6-bfa7-77447f1a4762', '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:20:34.46275+00', '2024-04-20 08:20:34.462773+00', '2024-04-20 08:20:34.462773+00', '9bb58bad-24a4-41a8-9742-1b5b4e2d8abd'), ('5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '{"sub": "5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf", "email": "owner@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:36:27.637388+00', '2024-04-20 08:36:27.637409+00', '2024-04-20 08:36:27.637409+00', '090598a1-ebba-4879-bbe3-38d517d5066f'),
('b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', '{"sub": "b73eb03e-fb7a-424d-84ff-18e2791ce0b4", "email": "custom@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:37:43.342194+00', '2024-04-20 08:37:43.342218+00', '2024-04-20 08:37:43.342218+00', '4392e228-a6d8-4295-a7d6-baed50c33e7c'),
('6b83d656-e4ab-48e3-a062-c0c54a427368', '6b83d656-e4ab-48e3-a062-c0c54a427368', '{"sub": "6b83d656-e4ab-48e3-a062-c0c54a427368", "email": "member@makerkit.dev", "email_verified": false, "phone_verified": false}', 'email', '2024-04-20 08:41:08.687948+00', '2024-04-20 08:41:08.687982+00', '2024-04-20 08:41:08.687982+00', 'd122aca5-4f29-43f0-b1b1-940b000638db');
--
-- Data for Name: instances; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
--
-- Data for Name: sessions; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."sessions" ("id", "user_id", "created_at", "updated_at", "factor_id", "aal", "not_after", "refreshed_at", "user_agent", "ip", "tag") VALUES
('6110bfeb-31e9-4c70-9c54-d723abd52048', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', '2024-04-20 08:38:00.938815+00', '2024-04-20 08:38:00.938815+00', NULL, 'aal1', NULL, NULL, 'node', '192.168.228.1', NULL),
('d5086cc6-9897-47e0-874b-7f8f11649560', '31a03e74-1639-45b6-bfa7-77447f1a4762', '2024-04-20 08:40:08.033701+00', '2024-04-20 08:40:08.033701+00', NULL, 'aal1', NULL, NULL, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', '192.168.228.1', NULL),
('469be37b-b21f-46aa-b30b-52f828b1baad', '6b83d656-e4ab-48e3-a062-c0c54a427368', '2024-04-20 08:41:15.484635+00', '2024-04-20 08:41:15.484635+00', NULL, 'aal1', NULL, NULL, 'node', '192.168.228.1', NULL);
--
-- Data for Name: mfa_amr_claims; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."mfa_amr_claims" ("session_id", "created_at", "updated_at", "authentication_method", "id") VALUES
('6110bfeb-31e9-4c70-9c54-d723abd52048', '2024-04-20 08:38:00.943514+00', '2024-04-20 08:38:00.943514+00', 'email/signup', '42337df0-b3d0-42f5-bff5-3740ccad191b'),
('d5086cc6-9897-47e0-874b-7f8f11649560', '2024-04-20 08:40:08.035464+00', '2024-04-20 08:40:08.035464+00', 'password', 'e0d54b55-9813-44b5-b502-06b081c3a44b'),
('469be37b-b21f-46aa-b30b-52f828b1baad', '2024-04-20 08:41:15.485666+00', '2024-04-20 08:41:15.485666+00', 'email/signup', '55704b9a-6a31-48e6-ae2b-662f6b7ce302');
--
-- Data for Name: mfa_factors; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
@@ -160,12 +100,6 @@ INSERT INTO "auth"."mfa_amr_claims" ("session_id", "created_at", "updated_at", "
-- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."refresh_tokens" ("instance_id", "id", "token", "user_id", "revoked", "created_at", "updated_at", "parent", "session_id") VALUES
('00000000-0000-0000-0000-000000000000', 3, 'lRWBgOYDQHq2GfqAc5iPpw', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', false, '2024-04-20 08:38:00.940523+00', '2024-04-20 08:38:00.940523+00', NULL, '6110bfeb-31e9-4c70-9c54-d723abd52048'),
('00000000-0000-0000-0000-000000000000', 4, 'XuRjZ1Ipdh8Eb19lPRUJhw', '31a03e74-1639-45b6-bfa7-77447f1a4762', false, '2024-04-20 08:40:08.03433+00', '2024-04-20 08:40:08.03433+00', NULL, 'd5086cc6-9897-47e0-874b-7f8f11649560'),
('00000000-0000-0000-0000-000000000000', 5, 'iQ0e1JiLv3R29gkHZYZggw', '6b83d656-e4ab-48e3-a062-c0c54a427368', false, '2024-04-20 08:41:15.484992+00', '2024-04-20 08:41:15.484992+00', NULL, '469be37b-b21f-46aa-b30b-52f828b1baad');
--
-- Data for Name: sso_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
@@ -201,23 +135,15 @@ INSERT INTO "auth"."refresh_tokens" ("instance_id", "id", "token", "user_id", "r
--
INSERT INTO "public"."accounts" ("id", "primary_owner_user_id", "name", "slug", "email", "is_personal_account", "updated_at", "created_at", "created_by", "updated_by", "picture_url", "public_data") VALUES
('31a03e74-1639-45b6-bfa7-77447f1a4762', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'test', NULL, 'test@makerkit.dev', true, NULL, NULL, NULL, NULL, NULL, '{}'),
('5deaa894-2094-4da3-b4fd-1fada0809d1c', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'Makerkit', 'makerkit', NULL, false, NULL, NULL, NULL, NULL, NULL, '{}'),
('5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', 'owner', NULL, 'owner@makerkit.dev', true, NULL, NULL, NULL, NULL, NULL, '{}'),
('b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'custom', NULL, 'custom@makerkit.dev', true, NULL, NULL, NULL, NULL, NULL, '{}'),
('6b83d656-e4ab-48e3-a062-c0c54a427368', '6b83d656-e4ab-48e3-a062-c0c54a427368', 'member', NULL, 'member@makerkit.dev', true, NULL, NULL, NULL, NULL, NULL, '{}');
('5deaa894-2094-4da3-b4fd-1fada0809d1c', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'Makerkit', 'makerkit', NULL, false, NULL, NULL, NULL, NULL, NULL, '{}');
--
-- Data for Name: roles; Type: TABLE DATA; Schema: public; Owner: postgres
--
INSERT INTO "public"."roles" ("name", "hierarchy_level", "account_id") VALUES
('owner', 1, NULL),
('member', 2, NULL),
('custom-role', 4, '5deaa894-2094-4da3-b4fd-1fada0809d1c');
--
-- Data for Name: accounts_memberships; Type: TABLE DATA; Schema: public; Owner: postgres
--
@@ -235,14 +161,6 @@ INSERT INTO "public"."accounts_memberships" ("user_id", "account_id", "account_r
--
-- Data for Name: config; Type: TABLE DATA; Schema: public; Owner: postgres
--
INSERT INTO "public"."config" ("enable_team_accounts", "enable_account_billing", "enable_team_account_billing", "billing_provider") VALUES
(true, true, true, 'stripe');
--
-- Data for Name: invitations; Type: TABLE DATA; Schema: public; Owner: postgres
--
@@ -260,21 +178,6 @@ INSERT INTO "public"."config" ("enable_team_accounts", "enable_account_billing",
--
--
-- Data for Name: role_permissions; Type: TABLE DATA; Schema: public; Owner: postgres
--
INSERT INTO "public"."role_permissions" ("id", "role", "permission") VALUES
(1, 'owner', 'roles.manage'),
(2, 'owner', 'billing.manage'),
(3, 'owner', 'settings.manage'),
(4, 'owner', 'members.manage'),
(5, 'owner', 'invites.manage'),
(6, 'member', 'settings.manage'),
(7, 'member', 'invites.manage');
--
-- Data for Name: subscriptions; Type: TABLE DATA; Schema: public; Owner: postgres
--
@@ -286,15 +189,10 @@ INSERT INTO "public"."role_permissions" ("id", "role", "permission") VALUES
--
--
-- Data for Name: buckets; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
--
INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_at", "public", "avif_autodetection", "file_size_limit", "allowed_mime_types", "owner_id") VALUES
('account_image', 'account_image', NULL, '2024-04-20 08:18:41.364926+00', '2024-04-20 08:18:41.364926+00', true, false, NULL, NULL, NULL);
--
-- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
--
@@ -317,34 +215,10 @@ INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_a
-- Data for Name: hooks; Type: TABLE DATA; Schema: supabase_functions; Owner: supabase_functions_admin
--
INSERT INTO "supabase_functions"."hooks" ("id", "hook_table_id", "hook_name", "created_at", "request_id") VALUES
(1, 17811, 'invitations_insert', '2024-04-20 08:22:15.403223+00', 1),
(2, 17811, 'invitations_insert', '2024-04-20 08:22:15.403223+00', 2),
(3, 17811, 'invitations_insert', '2024-04-20 08:22:15.403223+00', 3),
(4, 17811, 'invitations_insert', '2024-04-20 08:25:04.593848+00', 4),
(5, 17811, 'invitations_insert', '2024-04-20 08:25:04.593848+00', 5),
(6, 17811, 'invitations_insert', '2024-04-20 08:25:04.593848+00', 6),
(7, 17811, 'invitations_insert', '2024-04-20 08:26:43.163367+00', 7),
(8, 17811, 'invitations_insert', '2024-04-20 08:26:43.163367+00', 8),
(9, 17811, 'invitations_insert', '2024-04-20 08:26:43.163367+00', 9),
(10, 17811, 'invitations_insert', '2024-04-20 08:28:46.753609+00', 10),
(11, 17811, 'invitations_insert', '2024-04-20 08:28:46.753609+00', 11),
(12, 17811, 'invitations_insert', '2024-04-20 08:28:46.753609+00', 12),
(13, 17811, 'invitations_insert', '2024-04-20 08:30:12.356719+00', 13),
(14, 17811, 'invitations_insert', '2024-04-20 08:33:11.210097+00', 14),
(15, 17811, 'invitations_insert', '2024-04-20 08:33:52.113026+00', 15),
(16, 17811, 'invitations_insert', '2024-04-20 08:35:21.557382+00', 16),
(17, 17811, 'invitations_insert', '2024-04-20 08:35:21.557382+00', 17),
(18, 17811, 'invitations_insert', '2024-04-20 08:35:21.557382+00', 18),
(19, 17811, 'invitations_insert', '2024-04-20 08:40:20.99569+00', 19);
--
-- Data for Name: secrets; Type: TABLE DATA; Schema: vault; Owner: supabase_admin
--
--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE SET; Schema: auth; Owner: supabase_auth_admin
--
@@ -385,10 +259,3 @@ SELECT pg_catalog.setval('"public"."role_permissions_id_seq"', 7, true);
--
SELECT pg_catalog.setval('"supabase_functions"."hooks_id_seq"', 19, true);
--
-- PostgreSQL database dump complete
--
RESET ALL;

View File

@@ -56,16 +56,23 @@ export function AccountMembersTable({
const { t } = useTranslation('teams');
const permissions = {
canUpdateRole: (targetRole: number) =>
canManageRoles && targetRole < userRoleHierarchy,
canRemoveFromAccount: (targetRole: number) =>
canManageRoles && targetRole < userRoleHierarchy,
canUpdateRole: (targetRole: number) => {
return (
isPrimaryOwner || (canManageRoles && userRoleHierarchy < targetRole)
);
},
canRemoveFromAccount: (targetRole: number) => {
return (
isPrimaryOwner || (canManageRoles && userRoleHierarchy < targetRole)
);
},
canTransferOwnership: isPrimaryOwner,
};
const columns = useGetColumns(permissions, {
currentUserId,
currentAccountId,
currentRoleHierarchy: userRoleHierarchy,
});
const filteredMembers = members.filter((member) => {
@@ -96,6 +103,7 @@ function useGetColumns(
params: {
currentUserId: string;
currentAccountId: string;
currentRoleHierarchy: number;
},
): ColumnDef<Members[0]>[] {
const { t } = useTranslation('teams');
@@ -173,6 +181,7 @@ function useGetColumns(
member={row.original}
currentUserId={params.currentUserId}
accountId={params.currentAccountId}
currentRoleHierarchy={params.currentRoleHierarchy}
/>
),
},
@@ -185,12 +194,13 @@ function ActionsDropdown({
permissions,
member,
currentUserId,
accountId,
currentRoleHierarchy,
}: {
permissions: Permissions;
member: Members[0];
currentUserId: string;
accountId: string;
currentRoleHierarchy: number;
}) {
const [isRemoving, setIsRemoving] = useState(false);
const [isTransferring, setIsTransferring] = useState(false);
@@ -262,10 +272,10 @@ function ActionsDropdown({
<UpdateMemberRoleDialog
isOpen
setIsOpen={setIsUpdatingRole}
accountId={member.id}
userId={member.user_id}
userRole={member.role}
userRoleHierarchy={memberRoleHierarchy}
teamAccountId={member.account_id}
userRoleHierarchy={currentRoleHierarchy}
/>
</If>
@@ -274,7 +284,7 @@ function ActionsDropdown({
isOpen
setIsOpen={setIsTransferring}
targetDisplayName={member.name ?? member.email}
accountId={accountId}
accountId={member.account_id}
userId={member.user_id}
/>
</If>

View File

@@ -36,14 +36,14 @@ export const UpdateMemberRoleDialog: React.FC<{
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
userId: string;
accountId: string;
teamAccountId: string;
userRole: Role;
userRoleHierarchy: number;
}> = ({
isOpen,
setIsOpen,
userId,
accountId,
teamAccountId,
userRole,
userRoleHierarchy,
}) => {
@@ -61,14 +61,14 @@ export const UpdateMemberRoleDialog: React.FC<{
</DialogHeader>
<RolesDataProvider
accountId={accountId}
accountId={teamAccountId}
maxRoleHierarchy={userRoleHierarchy}
>
{(data) => (
<UpdateMemberForm
setIsOpen={setIsOpen}
userId={userId}
accountId={accountId}
teamAccountId={teamAccountId}
userRole={userRole}
roles={data}
/>
@@ -82,13 +82,13 @@ export const UpdateMemberRoleDialog: React.FC<{
function UpdateMemberForm({
userId,
userRole,
accountId,
teamAccountId,
setIsOpen,
roles,
}: React.PropsWithChildren<{
userId: string;
userRole: Role;
accountId: string;
teamAccountId: string;
setIsOpen: (isOpen: boolean) => void;
roles: Role[];
}>) {
@@ -99,7 +99,11 @@ function UpdateMemberForm({
const onSubmit = ({ role }: { role: Role }) => {
startTransition(async () => {
try {
await updateMemberRoleAction({ accountId, userId, role });
await updateMemberRoleAction({
accountId: teamAccountId,
userId,
role,
});
setIsOpen(false);
} catch (e) {

View File

@@ -33,6 +33,9 @@ export async function removeMemberFromAccountAction(
userId,
});
// revalidate all pages that depend on the account
revalidatePath('/home/[account]', 'layout');
return { success: true };
}
@@ -44,12 +47,20 @@ export async function updateMemberRoleAction(
await assertSession(client);
const service = new AccountMembersService(client);
const adminClient = getSupabaseServerActionClient({ admin: true });
await service.updateMemberRole({
accountId: params.accountId,
userId: params.userId,
role: params.role,
});
// update the role of the member
await service.updateMemberRole(
{
accountId: params.accountId,
userId: params.userId,
role: params.role,
},
adminClient,
);
// revalidate all pages that depend on the account
revalidatePath('/home/[account]', 'layout');
return { success: true };
}

View File

@@ -59,7 +59,10 @@ export class AccountMembersService {
return data;
}
async updateMemberRole(params: z.infer<typeof UpdateMemberRoleSchema>) {
async updateMemberRole(
params: z.infer<typeof UpdateMemberRoleSchema>,
adminClient: SupabaseClient<Database>,
) {
const logger = await getLogger();
const ctx = {
@@ -67,9 +70,33 @@ export class AccountMembersService {
...params,
};
logger.info(ctx, `Updating member role...`);
logger.info(ctx, `Validating permissions to update member role...`);
const { data, error } = await this.client
const { data: canActionAccountMember, error: accountError } =
await this.client.rpc('can_action_account_member', {
user_id: params.userId,
target_team_account_id: params.accountId,
});
if (accountError ?? !canActionAccountMember) {
logger.error(
{
...ctx,
accountError,
},
`Failed to validate permissions to update member role`,
);
throw new Error(`Failed to validate permissions to update member role`);
}
logger.info(ctx, `Permissions validated. Updating member role...`);
// we use the Admin client to update the role
// since we do not set any RLS policies on the accounts_memberships table
// for updating accounts_memberships. Instead, we use the can_action_account_member
// RPC to validate permissions to update the role
const { data, error } = await adminClient
.from('accounts_memberships')
.update({
account_role: params.role,

File diff suppressed because it is too large Load Diff