Remove account id dependencies from role management

This commit refactor the role management, removing the dependencies on account ids throughout our system. Changes include removing account id related attributes in the roles table schema and functions, modifying seed data and simplifying the role data provider code. This makes role structure more generic and simplifies the role handling process.
This commit is contained in:
giancarlo
2024-04-29 21:14:33 +07:00
parent e09a10a7f9
commit faaa26c4e8
10 changed files with 20 additions and 150 deletions

View File

@@ -78,7 +78,6 @@ async function TeamAccountMembersPage({ params }: Params) {
<If condition={canManageInvitations && canAddMember}> <If condition={canManageInvitations && canAddMember}>
<InviteMembersDialogContainer <InviteMembersDialogContainer
userRoleHierarchy={currentUserRoleHierarchy} userRoleHierarchy={currentUserRoleHierarchy}
accountId={account.id}
accountSlug={account.slug} accountSlug={account.slug}
> >
<Button size={'sm'} data-test={'invite-members-form-trigger'}> <Button size={'sm'} data-test={'invite-members-form-trigger'}>

View File

@@ -512,43 +512,18 @@ export type Database = {
} }
roles: { roles: {
Row: { Row: {
account_id: string | null
hierarchy_level: number hierarchy_level: number
name: string name: string
} }
Insert: { Insert: {
account_id?: string | null
hierarchy_level: number hierarchy_level: number
name: string name: string
} }
Update: { Update: {
account_id?: string | null
hierarchy_level?: number hierarchy_level?: number
name?: string name?: string
} }
Relationships: [ Relationships: []
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "accounts"
referencedColumns: ["id"]
},
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_account_workspace"
referencedColumns: ["id"]
},
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_accounts"
referencedColumns: ["id"]
},
]
} }
subscription_items: { subscription_items: {
Row: { Row: {

View File

@@ -490,8 +490,7 @@ declare
role varchar(50); role varchar(50);
begin begin
select name from public.roles select name from public.roles
where account_id is null and where hierarchy_level = 1 into role;
hierarchy_level = 1 into role;
return role; return role;
end; end;
@@ -568,9 +567,8 @@ create table if not exists
public.roles ( public.roles (
name varchar(50) not null, name varchar(50) not null,
hierarchy_level int not null check (hierarchy_level > 0), hierarchy_level int not null check (hierarchy_level > 0),
account_id uuid references public.accounts (id) on delete cascade, primary key (name),
unique (name, account_id), unique (hierarchy_level)
primary key (name)
); );
-- Revoke all on roles table from authenticated and service_role -- Revoke all on roles table from authenticated and service_role
@@ -582,69 +580,9 @@ from
-- Open up access to roles table for authenticated users and service_role -- Open up access to roles table for authenticated users and service_role
grant grant
select select
, on table public.roles to authenticated,
insert,
delete,
update on table public.roles to authenticated,
service_role; service_role;
-- define the system role uuid as a static UUID to be used as a default
-- account_id for system roles when the account_id is null. Useful for constraints.
create
or replace function kit.get_system_role_uuid () returns uuid
set
search_path = '' as $$
begin
return 'fd4f287c-762e-42b7-8207-b1252f799670';
end; $$ language plpgsql immutable;
grant
execute on function kit.get_system_role_uuid () to authenticated,
service_role;
-- we create a unique index on the roles table to ensure that the
-- can there be a unique hierarchy_level per account (or system role)
create unique index idx_unique_hierarchy_per_account on public.roles (
hierarchy_level,
coalesce(account_id, kit.get_system_role_uuid ())
);
-- we create a unique index on the roles table to ensure that the
-- can there be a unique name per account (or system role)
create unique index idx_unique_name_per_account on public.roles (
name,
coalesce(account_id, kit.get_system_role_uuid ())
);
-- Indexes on the roles table
create index idx_roles_account_id on public.roles (account_id);
-- Function "kit.check_non_personal_account_roles"
-- Trigger to prevent roles from being created for personal accounts
create
or replace function kit.check_non_personal_account_roles () returns trigger
set
search_path = '' as $$
begin
if new.account_id is not null and(
select
is_personal_account
from
public.accounts
where
id = new.account_id) then
raise exception 'Roles cannot be created for personal accounts';
end if;
return new;
end; $$ language plpgsql;
create constraint trigger tr_check_non_personal_account_roles
after insert
or
update on public.roles for each row
execute procedure kit.check_non_personal_account_roles ();
-- RLS -- RLS
alter table public.roles enable row level security; alter table public.roles enable row level security;
@@ -796,12 +734,11 @@ service_role;
-- RLS -- RLS
-- SELECT(roles) -- SELECT(roles)
-- authenticated users can query roles if the role is public or the user has a role on the account the role is for -- authenticated users can query roles
create policy roles_read on public.roles for create policy roles_read on public.roles for
select select
to authenticated using ( to authenticated using (
account_id is null true
or public.has_role_on_account (account_id)
); );
-- Function "public.can_action_account_member" -- Function "public.can_action_account_member"
@@ -1094,8 +1031,7 @@ begin
from from
public.roles public.roles
where where
name = role_name name = role_name;
and (account_id = target_account_id or account_id is null);
-- If the target role does not exist, the user cannot perform the action -- If the target role does not exist, the user cannot perform the action
if target_role_hierarchy_level is null then if target_role_hierarchy_level is null then
@@ -1171,8 +1107,7 @@ begin
from from
public.roles public.roles
where where
name = role_name name = role_name;
and (account_id = target_account_id or account_id is null);
-- If the target role does not exist, the user cannot perform the action -- If the target role does not exist, the user cannot perform the action
if target_role_hierarchy_level is null then if target_role_hierarchy_level is null then

View File

@@ -141,8 +141,8 @@ INSERT INTO "public"."accounts" ("id", "primary_owner_user_id", "name", "slug",
-- Data for Name: roles; Type: TABLE DATA; Schema: public; Owner: postgres -- Data for Name: roles; Type: TABLE DATA; Schema: public; Owner: postgres
-- --
INSERT INTO "public"."roles" ("name", "hierarchy_level", "account_id") VALUES INSERT INTO "public"."roles" ("name", "hierarchy_level") VALUES
('custom-role', 4, '5deaa894-2094-4da3-b4fd-1fada0809d1c'); ('custom-role', 4);
-- --
-- Data for Name: accounts_memberships; Type: TABLE DATA; Schema: public; Owner: postgres -- Data for Name: accounts_memberships; Type: TABLE DATA; Schema: public; Owner: postgres

View File

@@ -45,7 +45,7 @@ select row_eq(
-- Custom roles -- Custom roles
-- New roles created for the app -- New roles created for the app
set local role service_role; set local role postgres;
-- the name should be unique -- the name should be unique
@@ -57,17 +57,16 @@ select throws_ok(
-- the hierarchy level should be unique -- the hierarchy level should be unique
select throws_ok( select throws_ok(
$$ insert into public.roles (name, hierarchy_level) values ('custom-role-2', 1) $$, $$ insert into public.roles (name, hierarchy_level) values ('custom-role-2', 1) $$,
'duplicate key value violates unique constraint "idx_unique_hierarchy_per_account"' 'duplicate key value violates unique constraint "roles_hierarchy_level_key"'
); );
-- Custom Account Role -- Custom Account Role
-- Roles created specifically for the account
set local role service_role; set local role postgres;
-- the names should be unique -- the names should be unique
select throws_ok( select throws_ok(
$$ insert into public.roles (name, hierarchy_level, account_id) values ('owner', 1, makerkit.get_account_id_by_slug('test')) $$, $$ insert into public.roles (name, hierarchy_level) values ('owner', 1) $$,
'duplicate key value violates unique constraint "roles_pkey"' 'duplicate key value violates unique constraint "roles_pkey"'
); );

View File

@@ -144,10 +144,7 @@ function UpdateInvitationForm({
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<RolesDataProvider <RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
accountId={account}
maxRoleHierarchy={userRoleHierarchy}
>
{(roles) => ( {(roles) => (
<MembershipRoleSelector <MembershipRoleSelector
roles={roles} roles={roles}

View File

@@ -52,12 +52,10 @@ const MAX_INVITES = 5;
export function InviteMembersDialogContainer({ export function InviteMembersDialogContainer({
accountSlug, accountSlug,
accountId,
userRoleHierarchy, userRoleHierarchy,
children, children,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
accountSlug: string; accountSlug: string;
accountId: string;
userRoleHierarchy: number; userRoleHierarchy: number;
}>) { }>) {
const [pending, startTransition] = useTransition(); const [pending, startTransition] = useTransition();
@@ -79,10 +77,7 @@ export function InviteMembersDialogContainer({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<RolesDataProvider <RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
accountId={accountId}
maxRoleHierarchy={userRoleHierarchy}
>
{(roles) => ( {(roles) => (
<InviteMembersForm <InviteMembersForm
pending={pending} pending={pending}

View File

@@ -5,7 +5,6 @@ import { LoadingOverlay } from '@kit/ui/loading-overlay';
export function RolesDataProvider(props: { export function RolesDataProvider(props: {
maxRoleHierarchy: number; maxRoleHierarchy: number;
accountId: string;
children: (roles: string[]) => React.ReactNode; children: (roles: string[]) => React.ReactNode;
}) { }) {
const rolesQuery = useFetchRoles(props); const rolesQuery = useFetchRoles(props);
@@ -21,7 +20,7 @@ export function RolesDataProvider(props: {
return <>{props.children(rolesQuery.data ?? [])}</>; return <>{props.children(rolesQuery.data ?? [])}</>;
} }
function useFetchRoles(props: { maxRoleHierarchy: number; accountId: string }) { function useFetchRoles(props: { maxRoleHierarchy: number }) {
const supabase = useSupabase(); const supabase = useSupabase();
return useQuery({ return useQuery({
@@ -31,7 +30,6 @@ function useFetchRoles(props: { maxRoleHierarchy: number; accountId: string }) {
.from('roles') .from('roles')
.select('name') .select('name')
.gte('hierarchy_level', props.maxRoleHierarchy) .gte('hierarchy_level', props.maxRoleHierarchy)
.or(`account_id.eq.${props.accountId}, account_id.is.null`)
.order('hierarchy_level', { ascending: true }); .order('hierarchy_level', { ascending: true });
if (error) { if (error) {

View File

@@ -60,10 +60,7 @@ export const UpdateMemberRoleDialog: React.FC<{
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<RolesDataProvider <RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
accountId={teamAccountId}
maxRoleHierarchy={userRoleHierarchy}
>
{(data) => ( {(data) => (
<UpdateMemberForm <UpdateMemberForm
setIsOpen={setIsOpen} setIsOpen={setIsOpen}

View File

@@ -512,43 +512,18 @@ export type Database = {
} }
roles: { roles: {
Row: { Row: {
account_id: string | null
hierarchy_level: number hierarchy_level: number
name: string name: string
} }
Insert: { Insert: {
account_id?: string | null
hierarchy_level: number hierarchy_level: number
name: string name: string
} }
Update: { Update: {
account_id?: string | null
hierarchy_level?: number hierarchy_level?: number
name?: string name?: string
} }
Relationships: [ Relationships: []
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "accounts"
referencedColumns: ["id"]
},
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_account_workspace"
referencedColumns: ["id"]
},
{
foreignKeyName: "roles_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_accounts"
referencedColumns: ["id"]
},
]
} }
subscription_items: { subscription_items: {
Row: { Row: {