chore(rbac): user link and utils (#14320)
This commit is contained in:
committed by
GitHub
parent
7161cf1903
commit
b2245cc672
@@ -1,5 +1,6 @@
|
||||
export * from "./cart-payment-collection"
|
||||
export * from "./cart-promotion"
|
||||
export * from "./customer-account-holder"
|
||||
export * from "./fulfillment-provider-location"
|
||||
export * from "./fulfillment-set-location"
|
||||
export * from "./order-cart"
|
||||
@@ -8,6 +9,7 @@ export * from "./order-payment-collection"
|
||||
export * from "./order-promotion"
|
||||
export * from "./order-return-fulfillment"
|
||||
export * from "./product-sales-channel"
|
||||
export * from "./product-shipping-profile"
|
||||
export * from "./product-variant-inventory-item"
|
||||
export * from "./product-variant-price-set"
|
||||
export * from "./publishable-api-key-sales-channel"
|
||||
@@ -15,5 +17,4 @@ export * from "./readonly"
|
||||
export * from "./region-payment-provider"
|
||||
export * from "./sales-channel-location"
|
||||
export * from "./shipping-option-price-set"
|
||||
export * from "./product-shipping-profile"
|
||||
export * from "./customer-account-holder"
|
||||
export * from "./user-rbac-role"
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ModuleJoinerConfig } from "@medusajs/framework/types"
|
||||
import { LINKS, Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export const UserRbacRole: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.UserRbacRole,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "user_rbac_role",
|
||||
idPrefix: "userrole",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "user_rbac_role",
|
||||
},
|
||||
{
|
||||
name: "user_rbac_roles",
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "user_id", "rbac_role_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.USER,
|
||||
entity: "User",
|
||||
primaryKey: "id",
|
||||
foreignKey: "user_id",
|
||||
alias: "user",
|
||||
args: {
|
||||
methodSuffix: "Users",
|
||||
},
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
serviceName: Modules.RBAC,
|
||||
entity: "RbacRole",
|
||||
primaryKey: "id",
|
||||
foreignKey: "rbac_role_id",
|
||||
alias: "rbac_role",
|
||||
args: {
|
||||
methodSuffix: "RbacRoles",
|
||||
},
|
||||
hasMany: true,
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.USER,
|
||||
entity: "User",
|
||||
fieldAlias: {
|
||||
rbac_roles: {
|
||||
path: "rbac_roles_link.rbac_role",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.UserRbacRole,
|
||||
primaryKey: "user_id",
|
||||
foreignKey: "id",
|
||||
alias: "rbac_roles_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.RBAC,
|
||||
entity: "RbacRole",
|
||||
relationship: {
|
||||
serviceName: LINKS.UserRbacRole,
|
||||
primaryKey: "rbac_role_id",
|
||||
foreignKey: "id",
|
||||
alias: "users_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from "@medusajs/framework/utils"
|
||||
import { Module, Modules } from "@medusajs/framework/utils"
|
||||
import { RbacModuleService } from "@services"
|
||||
|
||||
export default Module("rbac", {
|
||||
export default Module(Modules.RBAC, {
|
||||
service: RbacModuleService,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"namespaces": ["public"],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
@@ -143,9 +141,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_policy_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
@@ -250,9 +246,7 @@
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
@@ -283,8 +277,8 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"inherited_role_id": {
|
||||
"name": "inherited_role_id",
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
@@ -334,50 +328,48 @@
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "rbac_role_inheritance",
|
||||
"name": "rbac_role_parent",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_role_id",
|
||||
"keyName": "IDX_rbac_role_parent_role_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_inheritance_role_id\" ON \"rbac_role_inheritance\" (\"role_id\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_parent_role_id\" ON \"rbac_role_parent\" (\"role_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_inherited_role_id",
|
||||
"keyName": "IDX_rbac_role_parent_parent_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_inheritance_inherited_role_id\" ON \"rbac_role_inheritance\" (\"inherited_role_id\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_parent_parent_id\" ON \"rbac_role_parent\" (\"parent_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_deleted_at",
|
||||
"keyName": "IDX_rbac_role_parent_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_inheritance_deleted_at\" ON \"rbac_role_inheritance\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_parent_deleted_at\" ON \"rbac_role_parent\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_role_id_inherited_role_id_unique",
|
||||
"keyName": "IDX_rbac_role_parent_role_id_parent_id_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_role_inheritance_role_id_inherited_role_id_unique\" ON \"rbac_role_inheritance\" (\"role_id\", \"inherited_role_id\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_role_parent_role_id_parent_id_unique\" ON \"rbac_role_parent\" (\"role_id\", \"parent_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_inheritance_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"keyName": "rbac_role_parent_pkey",
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
@@ -386,27 +378,19 @@
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"rbac_role_inheritance_role_id_foreign": {
|
||||
"constraintName": "rbac_role_inheritance_role_id_foreign",
|
||||
"columnNames": [
|
||||
"role_id"
|
||||
],
|
||||
"localTableName": "public.rbac_role_inheritance",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"rbac_role_parent_role_id_foreign": {
|
||||
"constraintName": "rbac_role_parent_role_id_foreign",
|
||||
"columnNames": ["role_id"],
|
||||
"localTableName": "public.rbac_role_parent",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rbac_role",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"rbac_role_inheritance_inherited_role_id_foreign": {
|
||||
"constraintName": "rbac_role_inheritance_inherited_role_id_foreign",
|
||||
"columnNames": [
|
||||
"inherited_role_id"
|
||||
],
|
||||
"localTableName": "public.rbac_role_inheritance",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"rbac_role_parent_parent_id_foreign": {
|
||||
"constraintName": "rbac_role_parent_parent_id_foreign",
|
||||
"columnNames": ["parent_id"],
|
||||
"localTableName": "public.rbac_role_parent",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rbac_role",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
@@ -433,8 +417,8 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"scope_id": {
|
||||
"name": "scope_id",
|
||||
"policy_id": {
|
||||
"name": "policy_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
@@ -497,13 +481,13 @@
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_role_id\" ON \"rbac_role_policy\" (\"role_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_policy_scope_id",
|
||||
"keyName": "IDX_rbac_role_policy_policy_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_scope_id\" ON \"rbac_role_policy\" (\"scope_id\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_policy_id\" ON \"rbac_role_policy\" (\"policy_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_policy_deleted_at",
|
||||
@@ -515,19 +499,17 @@
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_deleted_at\" ON \"rbac_role_policy\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_policy_role_id_scope_id_unique",
|
||||
"keyName": "IDX_rbac_role_policy_role_id_policy_id_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_role_id_scope_id_unique\" ON \"rbac_role_policy\" (\"role_id\", \"scope_id\") WHERE deleted_at IS NULL"
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_role_policy_role_id_policy_id_unique\" ON \"rbac_role_policy\" (\"role_id\", \"policy_id\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_policy_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"columnNames": ["id"],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
@@ -538,25 +520,17 @@
|
||||
"foreignKeys": {
|
||||
"rbac_role_policy_role_id_foreign": {
|
||||
"constraintName": "rbac_role_policy_role_id_foreign",
|
||||
"columnNames": [
|
||||
"role_id"
|
||||
],
|
||||
"columnNames": ["role_id"],
|
||||
"localTableName": "public.rbac_role_policy",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rbac_role",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"rbac_role_policy_scope_id_foreign": {
|
||||
"constraintName": "rbac_role_policy_scope_id_foreign",
|
||||
"columnNames": [
|
||||
"scope_id"
|
||||
],
|
||||
"rbac_role_policy_policy_id_foreign": {
|
||||
"constraintName": "rbac_role_policy_policy_id_foreign",
|
||||
"columnNames": ["policy_id"],
|
||||
"localTableName": "public.rbac_role_policy",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedTableName": "public.rbac_policy",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
import { Migration } from "@medusajs/framework/mikro-orm/migrations";
|
||||
|
||||
export class Migration20251215113723 extends Migration {
|
||||
export class Migration20251219163509 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table if exists "rbac_role_policy" drop constraint if exists "rbac_role_policy_role_id_scope_id_unique";`);
|
||||
this.addSql(`alter table if exists "rbac_role_inheritance" drop constraint if exists "rbac_role_inheritance_role_id_inherited_role_id_unique";`);
|
||||
this.addSql(`alter table if exists "rbac_role_policy" drop constraint if exists "rbac_role_policy_role_id_policy_id_unique";`);
|
||||
this.addSql(`alter table if exists "rbac_role_parent" drop constraint if exists "rbac_role_parent_role_id_parent_id_unique";`);
|
||||
this.addSql(`alter table if exists "rbac_role" drop constraint if exists "rbac_role_name_unique";`);
|
||||
this.addSql(`alter table if exists "rbac_policy" drop constraint if exists "rbac_policy_key_unique";`);
|
||||
this.addSql(`create table if not exists "rbac_policy" ("id" text not null, "key" text not null, "resource" text not null, "operation" text not null, "name" text null, "description" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "rbac_policy_pkey" primary key ("id"));`);
|
||||
@@ -17,23 +17,23 @@ export class Migration20251215113723 extends Migration {
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_deleted_at" ON "rbac_role" ("deleted_at") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_role_name_unique" ON "rbac_role" ("name") WHERE deleted_at IS NULL;`);
|
||||
|
||||
this.addSql(`create table if not exists "rbac_role_inheritance" ("id" text not null, "role_id" text not null, "inherited_role_id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "rbac_role_inheritance_pkey" primary key ("id"));`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_inheritance_role_id" ON "rbac_role_inheritance" ("role_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_inheritance_inherited_role_id" ON "rbac_role_inheritance" ("inherited_role_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_inheritance_deleted_at" ON "rbac_role_inheritance" ("deleted_at") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_role_inheritance_role_id_inherited_role_id_unique" ON "rbac_role_inheritance" ("role_id", "inherited_role_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`create table if not exists "rbac_role_parent" ("id" text not null, "role_id" text not null, "parent_id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "rbac_role_parent_pkey" primary key ("id"));`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_parent_role_id" ON "rbac_role_parent" ("role_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_parent_parent_id" ON "rbac_role_parent" ("parent_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_parent_deleted_at" ON "rbac_role_parent" ("deleted_at") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_role_parent_role_id_parent_id_unique" ON "rbac_role_parent" ("role_id", "parent_id") WHERE deleted_at IS NULL;`);
|
||||
|
||||
this.addSql(`create table if not exists "rbac_role_policy" ("id" text not null, "role_id" text not null, "scope_id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "rbac_role_policy_pkey" primary key ("id"));`);
|
||||
this.addSql(`create table if not exists "rbac_role_policy" ("id" text not null, "role_id" text not null, "policy_id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "rbac_role_policy_pkey" primary key ("id"));`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_policy_role_id" ON "rbac_role_policy" ("role_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_policy_scope_id" ON "rbac_role_policy" ("scope_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_policy_policy_id" ON "rbac_role_policy" ("policy_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_role_policy_deleted_at" ON "rbac_role_policy" ("deleted_at") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_role_policy_role_id_scope_id_unique" ON "rbac_role_policy" ("role_id", "scope_id") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_role_policy_role_id_policy_id_unique" ON "rbac_role_policy" ("role_id", "policy_id") WHERE deleted_at IS NULL;`);
|
||||
|
||||
this.addSql(`alter table if exists "rbac_role_inheritance" add constraint "rbac_role_inheritance_role_id_foreign" foreign key ("role_id") references "rbac_role" ("id") on update cascade;`);
|
||||
this.addSql(`alter table if exists "rbac_role_inheritance" add constraint "rbac_role_inheritance_inherited_role_id_foreign" foreign key ("inherited_role_id") references "rbac_role" ("id") on update cascade;`);
|
||||
this.addSql(`alter table if exists "rbac_role_parent" add constraint "rbac_role_parent_role_id_foreign" foreign key ("role_id") references "rbac_role" ("id") on update cascade;`);
|
||||
this.addSql(`alter table if exists "rbac_role_parent" add constraint "rbac_role_parent_parent_id_foreign" foreign key ("parent_id") references "rbac_role" ("id") on update cascade;`);
|
||||
|
||||
this.addSql(`alter table if exists "rbac_role_policy" add constraint "rbac_role_policy_role_id_foreign" foreign key ("role_id") references "rbac_role" ("id") on update cascade;`);
|
||||
this.addSql(`alter table if exists "rbac_role_policy" add constraint "rbac_role_policy_scope_id_foreign" foreign key ("scope_id") references "rbac_policy" ("id") on update cascade;`);
|
||||
this.addSql(`alter table if exists "rbac_role_policy" add constraint "rbac_role_policy_policy_id_foreign" foreign key ("policy_id") references "rbac_policy" ("id") on update cascade;`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export { default as RbacPolicy } from "./rbac-policy"
|
||||
export { default as RbacRole } from "./rbac-role"
|
||||
export { default as RbacRoleInheritance } from "./rbac-role-inheritance"
|
||||
export { default as RbacRoleParent } from "./rbac-role-parent"
|
||||
export { default as RbacRolePolicy } from "./rbac-role-policy"
|
||||
|
||||
27
packages/modules/rbac/src/models/rbac-role-parent.ts
Normal file
27
packages/modules/rbac/src/models/rbac-role-parent.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import RbacRole from "./rbac-role"
|
||||
|
||||
const RbacRoleParent = model
|
||||
.define("rbac_role_parent", {
|
||||
id: model.id({ prefix: "rlin" }).primaryKey(),
|
||||
role: model.belongsTo(() => RbacRole, { mappedBy: "parents" }),
|
||||
parent: model.belongsTo(() => RbacRole),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["role_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["parent_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["role_id", "parent_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default RbacRoleParent
|
||||
@@ -6,7 +6,7 @@ const RbacRolePolicy = model
|
||||
.define("rbac_role_policy", {
|
||||
id: model.id({ prefix: "rlpl" }).primaryKey(),
|
||||
role: model.belongsTo(() => RbacRole),
|
||||
scope: model.belongsTo(() => RbacPolicy),
|
||||
policy: model.belongsTo(() => RbacPolicy),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
@@ -15,11 +15,11 @@ const RbacRolePolicy = model
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["scope_id"],
|
||||
on: ["policy_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["role_id", "scope_id"],
|
||||
on: ["role_id", "policy_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import RbacRoleParent from "./rbac-role-parent"
|
||||
import RbacRolePolicy from "./rbac-role-policy"
|
||||
|
||||
const RbacRole = model
|
||||
.define("rbac_role", {
|
||||
@@ -6,6 +8,12 @@ const RbacRole = model
|
||||
name: model.text().searchable(),
|
||||
description: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
policies: model.hasMany(() => RbacRolePolicy, {
|
||||
mappedBy: "role",
|
||||
}),
|
||||
parents: model.hasMany(() => RbacRoleParent, {
|
||||
mappedBy: "role",
|
||||
}),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
|
||||
@@ -35,17 +35,19 @@ export class RbacRepository extends MikroOrmBase {
|
||||
|
||||
const query = `
|
||||
WITH RECURSIVE role_hierarchy AS (
|
||||
SELECT id, name, id as original_role_id
|
||||
SELECT id, name, id as original_role_id, ARRAY[id] as path
|
||||
FROM rbac_role
|
||||
WHERE id IN (${placeholders}) AND deleted_at IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT r.id, r.name, rh.original_role_id
|
||||
SELECT r.id, r.name, rh.original_role_id, rh.path || r.id
|
||||
FROM rbac_role r
|
||||
INNER JOIN rbac_role_inheritance ri ON ri.inherited_role_id = r.id
|
||||
INNER JOIN rbac_role_parent ri ON ri.parent_id = r.id
|
||||
INNER JOIN role_hierarchy rh ON rh.id = ri.role_id
|
||||
WHERE r.deleted_at IS NULL AND ri.deleted_at IS NULL
|
||||
WHERE r.deleted_at IS NULL
|
||||
AND ri.deleted_at IS NULL
|
||||
AND NOT (r.id = ANY(rh.path))
|
||||
)
|
||||
SELECT DISTINCT
|
||||
rh.original_role_id,
|
||||
@@ -60,7 +62,7 @@ export class RbacRepository extends MikroOrmBase {
|
||||
p.updated_at,
|
||||
CASE WHEN rp.role_id = rh.original_role_id THEN NULL ELSE rp.role_id END as inherited_from_role_id
|
||||
FROM rbac_policy p
|
||||
INNER JOIN rbac_role_policy rp ON rp.scope_id = p.id
|
||||
INNER JOIN rbac_role_policy rp ON rp.policy_id = p.id
|
||||
INNER JOIN role_hierarchy rh ON rh.id = rp.role_id
|
||||
WHERE p.deleted_at IS NULL AND rp.deleted_at IS NULL
|
||||
ORDER BY rh.original_role_id, p.resource, p.operation, p.key
|
||||
@@ -85,4 +87,40 @@ export class RbacRepository extends MikroOrmBase {
|
||||
|
||||
return policiesByRole
|
||||
}
|
||||
|
||||
async checkForCycle(
|
||||
roleId: string,
|
||||
parentId: string,
|
||||
sharedContext: Context = {}
|
||||
): Promise<boolean> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(sharedContext)
|
||||
const knex = manager.getKnex()
|
||||
|
||||
// Check if adding this parent would create a circular dependency
|
||||
// A cycle exists if role_id is already an ancestor of parent_id
|
||||
// (i.e., if we traverse up from parent_id, we reach role_id)
|
||||
const query = `
|
||||
WITH RECURSIVE role_hierarchy AS (
|
||||
SELECT id, ARRAY[id] as path
|
||||
FROM rbac_role
|
||||
WHERE id = ? AND deleted_at IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT r.id, rh.path || r.id
|
||||
FROM role_hierarchy rh
|
||||
INNER JOIN rbac_role_parent ri ON ri.role_id = rh.id
|
||||
INNER JOIN rbac_role r ON r.id = ri.parent_id
|
||||
WHERE r.deleted_at IS NULL
|
||||
AND ri.deleted_at IS NULL
|
||||
AND NOT (r.id = ANY(rh.path))
|
||||
)
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM role_hierarchy WHERE id = ?
|
||||
) as has_cycle
|
||||
`
|
||||
|
||||
const result = await knex.raw(query, [parentId, roleId])
|
||||
return result.rows[0]?.has_cycle || false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
import { Context, FindConfig } from "@medusajs/framework/types"
|
||||
import {
|
||||
Context,
|
||||
FilterableRbacRoleProps,
|
||||
FindConfig,
|
||||
RbacRoleDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaService,
|
||||
Policy,
|
||||
promiseAll,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
RbacPolicy,
|
||||
RbacRole,
|
||||
RbacRoleInheritance,
|
||||
RbacRolePolicy,
|
||||
} from "@models"
|
||||
CreateRbacRoleParentDTO,
|
||||
IRbacModuleService,
|
||||
RbacRoleParentDTO,
|
||||
UpdateRbacRoleParentDTO,
|
||||
} from "@medusajs/types"
|
||||
import { RbacPolicy, RbacRole, RbacRoleParent, RbacRolePolicy } from "@models"
|
||||
import { RbacRepository } from "../repositories"
|
||||
|
||||
type InjectedDependencies = {
|
||||
rbacRepository: RbacRepository
|
||||
}
|
||||
|
||||
export default class RbacModuleService extends MedusaService<{
|
||||
RbacRole: { dto: any }
|
||||
RbacPolicy: { dto: any }
|
||||
RbacRoleInheritance: { dto: any }
|
||||
RbacRolePolicy: { dto: any }
|
||||
}>({
|
||||
RbacRole,
|
||||
RbacPolicy,
|
||||
RbacRoleInheritance,
|
||||
RbacRolePolicy,
|
||||
}) {
|
||||
export default class RbacModuleService
|
||||
extends MedusaService({
|
||||
RbacRole,
|
||||
RbacPolicy,
|
||||
RbacRoleParent,
|
||||
RbacRolePolicy,
|
||||
})
|
||||
implements IRbacModuleService
|
||||
{
|
||||
protected readonly rbacRepository_: RbacRepository
|
||||
|
||||
constructor({ rbacRepository }: InjectedDependencies) {
|
||||
@@ -35,6 +41,93 @@ export default class RbacModuleService extends MedusaService<{
|
||||
this.rbacRepository_ = rbacRepository
|
||||
}
|
||||
|
||||
__hooks = {
|
||||
onApplicationStart: async () => {
|
||||
this.onApplicationStart()
|
||||
},
|
||||
}
|
||||
|
||||
async onApplicationStart(): Promise<void> {
|
||||
await this.syncRegisteredPolicies()
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
private async syncRegisteredPolicies(
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
const registeredPolicies = Object.entries(Policy).map(
|
||||
([name, { resource, operation, description }]) => ({
|
||||
key: `${resource}:${operation}`,
|
||||
name,
|
||||
resource,
|
||||
operation,
|
||||
description,
|
||||
})
|
||||
)
|
||||
|
||||
const registeredKeys = registeredPolicies.map((p) => p.key)
|
||||
|
||||
// Fetch all existing policies (including soft-deleted ones)
|
||||
const existingPolicies = await this.listRbacPolicies(
|
||||
{},
|
||||
{ withDeleted: true },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingPoliciesMap = new Map(existingPolicies.map((p) => [p.key, p]))
|
||||
|
||||
const policiesToCreate: any[] = []
|
||||
const policiesToUpdate: any[] = []
|
||||
const policiesToRestore: string[] = []
|
||||
|
||||
// Process registered policies
|
||||
for (const registeredPolicy of registeredPolicies) {
|
||||
const existing = existingPoliciesMap.get(registeredPolicy.key)
|
||||
|
||||
const hasChanges =
|
||||
existing &&
|
||||
(existing.name !== registeredPolicy.name ||
|
||||
existing.description !== registeredPolicy.description)
|
||||
|
||||
if (!existing) {
|
||||
policiesToCreate.push(registeredPolicy)
|
||||
} else if (existing.deleted_at) {
|
||||
policiesToRestore.push(existing.id)
|
||||
if (hasChanges) {
|
||||
policiesToUpdate.push({
|
||||
id: existing.id,
|
||||
name: registeredPolicy.name,
|
||||
description: registeredPolicy.description,
|
||||
})
|
||||
}
|
||||
} else if (hasChanges) {
|
||||
policiesToUpdate.push({
|
||||
id: existing.id,
|
||||
name: registeredPolicy.name,
|
||||
description: registeredPolicy.description,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const policiesToSoftDelete = existingPolicies
|
||||
.filter((p) => !p.deleted_at && !registeredKeys.includes(p.key))
|
||||
.map((p) => p.id)
|
||||
|
||||
// First restore any soft-deleted policies
|
||||
if (policiesToRestore.length > 0) {
|
||||
await this.restoreRbacPolicies(policiesToRestore, {}, sharedContext)
|
||||
}
|
||||
|
||||
await promiseAll([
|
||||
policiesToCreate.length > 0 &&
|
||||
this.createRbacPolicies(policiesToCreate, sharedContext),
|
||||
policiesToUpdate.length > 0 &&
|
||||
this.updateRbacPolicies(policiesToUpdate, sharedContext),
|
||||
policiesToSoftDelete.length > 0 &&
|
||||
this.softDeleteRbacPolicies(policiesToSoftDelete, {}, sharedContext),
|
||||
])
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async listPoliciesForRole(
|
||||
roleId: string,
|
||||
@@ -46,41 +139,13 @@ export default class RbacModuleService extends MedusaService<{
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async listRbacRoles(
|
||||
filters: any = {},
|
||||
config: FindConfig<any> = {},
|
||||
filters: FilterableRbacRoleProps = {},
|
||||
config: FindConfig<RbacRoleDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<any[]> {
|
||||
const roles = await super.listRbacRoles(filters, config, sharedContext)
|
||||
|
||||
const shouldIncludePolicies =
|
||||
config.relations?.includes("policies") ||
|
||||
config.select?.includes("policies")
|
||||
|
||||
if (shouldIncludePolicies && roles.length > 0) {
|
||||
const roleIds = roles.map((role) => role.id)
|
||||
const policiesByRole = await this.rbacRepository_.listPoliciesForRoles(
|
||||
roleIds,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
for (const role of roles) {
|
||||
role.policies = policiesByRole.get(role.id) || []
|
||||
}
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async listAndCountRbacRoles(
|
||||
filters: any = {},
|
||||
config: FindConfig<any> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[any[], number]> {
|
||||
const [roles, count] = await super.listAndCountRbacRoles(
|
||||
): Promise<RbacRoleDTO[]> {
|
||||
const roles = await super.listRbacRoles(
|
||||
filters,
|
||||
config,
|
||||
config as any,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -100,6 +165,102 @@ export default class RbacModuleService extends MedusaService<{
|
||||
}
|
||||
}
|
||||
|
||||
return [roles, count]
|
||||
return roles as unknown as RbacRoleDTO[]
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async listAndCountRbacRoles(
|
||||
filters: FilterableRbacRoleProps = {},
|
||||
config: FindConfig<RbacRoleDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[RbacRoleDTO[], number]> {
|
||||
const [roles, count] = await super.listAndCountRbacRoles(
|
||||
filters,
|
||||
config as any,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const shouldIncludePolicies =
|
||||
config.relations?.includes("policies") ||
|
||||
config.select?.includes("policies")
|
||||
|
||||
if (shouldIncludePolicies && roles.length > 0) {
|
||||
const roleIds = roles.map((role) => role.id)
|
||||
const policiesByRole = await this.rbacRepository_.listPoliciesForRoles(
|
||||
roleIds,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
for (const role of roles) {
|
||||
role.policies = policiesByRole.get(role.id) || []
|
||||
}
|
||||
}
|
||||
|
||||
return [roles as unknown as RbacRoleDTO[], count]
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async createRbacRoleParents(
|
||||
data: CreateRbacRoleParentDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<RbacRoleParentDTO[]> {
|
||||
for (const parent of data) {
|
||||
const { role_id, parent_id } = parent
|
||||
|
||||
if (role_id === parent_id) {
|
||||
throw new Error(
|
||||
`Cannot create role parent relationship: a role cannot be its own parent (role_id: ${role_id})`
|
||||
)
|
||||
}
|
||||
|
||||
const wouldCreateCycle = await this.rbacRepository_.checkForCycle(
|
||||
role_id,
|
||||
parent_id,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (wouldCreateCycle) {
|
||||
throw new Error(
|
||||
`Cannot create role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return await super.createRbacRoleParents(data, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async updateRbacRoleParents(
|
||||
data: UpdateRbacRoleParentDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<RbacRoleParentDTO[]> {
|
||||
for (const parent of data) {
|
||||
const { role_id, parent_id } = parent
|
||||
|
||||
if (parent_id) {
|
||||
if (role_id === parent_id) {
|
||||
throw new Error(
|
||||
`Cannot update role parent relationship: a role cannot be its own parent (role_id: ${role_id})`
|
||||
)
|
||||
}
|
||||
|
||||
const wouldCreateCycle = await this.rbacRepository_.checkForCycle(
|
||||
role_id!,
|
||||
parent_id,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (wouldCreateCycle) {
|
||||
throw new Error(
|
||||
`Cannot update role parent relationship: this would create a circular dependency (role_id: ${role_id}, parent_id: ${parent_id})`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await super.updateRbacRoleParents(data, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user