feat(rbac): role-based access control module (#14310)
This commit is contained in:
committed by
GitHub
parent
d6d7d14a6a
commit
1bfde8dc57
@@ -0,0 +1,285 @@
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(60000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, api, getContainer }) => {
|
||||
let container
|
||||
|
||||
beforeEach(async () => {
|
||||
container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
})
|
||||
|
||||
describe("RBAC Policies - Admin API", () => {
|
||||
describe("POST /admin/rbac/policies", () => {
|
||||
it("should create a policy", async () => {
|
||||
const response = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
name: "Read Products",
|
||||
description: "Permission to read products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.policy).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
name: "Read Products",
|
||||
description: "Permission to read products",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a policy with metadata", async () => {
|
||||
const response = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "write:orders",
|
||||
resource: "order",
|
||||
operation: "write",
|
||||
name: "Write Orders",
|
||||
metadata: { category: "order_management" },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.policy).toEqual(
|
||||
expect.objectContaining({
|
||||
key: "write:orders",
|
||||
resource: "order",
|
||||
operation: "write",
|
||||
metadata: { category: "order_management" },
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/rbac/policies", () => {
|
||||
beforeEach(async () => {
|
||||
await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
name: "Read Products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
operation: "write",
|
||||
name: "Write Products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "read:orders",
|
||||
resource: "order",
|
||||
operation: "read",
|
||||
name: "Read Orders",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should list all policies", async () => {
|
||||
const response = await api.get("/admin/rbac/policies", adminHeaders)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(3)
|
||||
expect(response.data.policies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
operation: "write",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "read:orders",
|
||||
resource: "order",
|
||||
operation: "read",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should filter policies by resource", async () => {
|
||||
const response = await api.get(
|
||||
"/admin/rbac/policies?resource=product",
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(2)
|
||||
expect(response.data.policies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should filter policies by operation", async () => {
|
||||
const response = await api.get(
|
||||
"/admin/rbac/policies?operation=read",
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(2)
|
||||
expect(response.data.policies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "read:products",
|
||||
operation: "read",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "read:orders",
|
||||
operation: "read",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/rbac/policies/:id", () => {
|
||||
it("should retrieve a policy by id", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "delete:users",
|
||||
resource: "user",
|
||||
operation: "delete",
|
||||
name: "Delete Users",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const policyId = createResponse.data.policy.id
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/rbac/policies/${policyId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.policy).toEqual(
|
||||
expect.objectContaining({
|
||||
id: policyId,
|
||||
key: "delete:users",
|
||||
resource: "user",
|
||||
operation: "delete",
|
||||
name: "Delete Users",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/rbac/policies/:id", () => {
|
||||
it("should update a policy", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "admin:system",
|
||||
resource: "system",
|
||||
operation: "admin",
|
||||
name: "System Admin",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const policyId = createResponse.data.policy.id
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/rbac/policies/${policyId}`,
|
||||
{
|
||||
name: "System Administrator",
|
||||
description: "Full system access",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.policy).toEqual(
|
||||
expect.objectContaining({
|
||||
id: policyId,
|
||||
key: "admin:system",
|
||||
name: "System Administrator",
|
||||
description: "Full system access",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/rbac/policies/:id", () => {
|
||||
it("should delete a policy", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "test:delete",
|
||||
resource: "test",
|
||||
operation: "delete",
|
||||
name: "Test Delete",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const policyId = createResponse.data.policy.id
|
||||
|
||||
const deleteResponse = await api.delete(
|
||||
`/admin/rbac/policies/${policyId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(deleteResponse.status).toEqual(200)
|
||||
expect(deleteResponse.data).toEqual({
|
||||
id: policyId,
|
||||
object: "rbac_policy",
|
||||
deleted: true,
|
||||
})
|
||||
|
||||
const listResponse = await api.get(
|
||||
"/admin/rbac/policies",
|
||||
adminHeaders
|
||||
)
|
||||
expect(
|
||||
listResponse.data.policies.find((p) => p.id === policyId)
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
384
integration-tests/http/__tests__/rbac/admin/rbac-roles.spec.ts
Normal file
384
integration-tests/http/__tests__/rbac/admin/rbac-roles.spec.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(60000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, api, getContainer }) => {
|
||||
let container
|
||||
|
||||
beforeEach(async () => {
|
||||
container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
})
|
||||
|
||||
describe("RBAC Roles - Admin API", () => {
|
||||
describe("POST /admin/rbac/roles", () => {
|
||||
it("should create a role", async () => {
|
||||
const response = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Viewer",
|
||||
description: "Can view resources",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.role).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "Viewer",
|
||||
description: "Can view resources",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a role with metadata", async () => {
|
||||
const response = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Editor",
|
||||
description: "Can edit resources",
|
||||
metadata: { department: "sales" },
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.role).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "Editor",
|
||||
metadata: { department: "sales" },
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/rbac/roles", () => {
|
||||
beforeEach(async () => {
|
||||
await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Viewer",
|
||||
description: "Can view resources",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Editor",
|
||||
description: "Can edit resources",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Admin",
|
||||
description: "Full access",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should list all roles", async () => {
|
||||
const response = await api.get("/admin/rbac/roles", adminHeaders)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(3)
|
||||
expect(response.data.roles).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "Viewer",
|
||||
description: "Can view resources",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Editor",
|
||||
description: "Can edit resources",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Admin",
|
||||
description: "Full access",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should filter roles by name", async () => {
|
||||
const response = await api.get(
|
||||
"/admin/rbac/roles?name=Viewer",
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(1)
|
||||
expect(response.data.roles[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "Viewer",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/rbac/roles/:id", () => {
|
||||
it("should retrieve a role by id", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Manager",
|
||||
description: "Can manage resources",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const roleId = createResponse.data.role.id
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/rbac/roles/${roleId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.role).toEqual(
|
||||
expect.objectContaining({
|
||||
id: roleId,
|
||||
name: "Manager",
|
||||
description: "Can manage resources",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/rbac/roles/:id", () => {
|
||||
it("should update a role", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Support",
|
||||
description: "Support team",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const roleId = createResponse.data.role.id
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/rbac/roles/${roleId}`,
|
||||
{
|
||||
name: "Customer Support",
|
||||
description: "Customer support team with limited access",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.role).toEqual(
|
||||
expect.objectContaining({
|
||||
id: roleId,
|
||||
name: "Customer Support",
|
||||
description: "Customer support team with limited access",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/rbac/roles/:id", () => {
|
||||
it("should delete a role", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Temporary",
|
||||
description: "Temporary role",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const roleId = createResponse.data.role.id
|
||||
|
||||
const deleteResponse = await api.delete(
|
||||
`/admin/rbac/roles/${roleId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(deleteResponse.status).toEqual(200)
|
||||
expect(deleteResponse.data).toEqual({
|
||||
id: roleId,
|
||||
object: "rbac_role",
|
||||
deleted: true,
|
||||
})
|
||||
|
||||
const listResponse = await api.get("/admin/rbac/roles", adminHeaders)
|
||||
expect(
|
||||
listResponse.data.roles.find((r) => r.id === roleId)
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Role Policies", () => {
|
||||
let policies
|
||||
let viewerRole
|
||||
let editorRole
|
||||
|
||||
beforeEach(async () => {
|
||||
const policy1 = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
name: "Read Products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const policy2 = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
operation: "write",
|
||||
name: "Write Products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const policy3 = await api.post(
|
||||
"/admin/rbac/policies",
|
||||
{
|
||||
key: "delete:products",
|
||||
resource: "product",
|
||||
operation: "delete",
|
||||
name: "Delete Products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
policies = [
|
||||
policy1.data.policy,
|
||||
policy2.data.policy,
|
||||
policy3.data.policy,
|
||||
]
|
||||
|
||||
const viewer = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Product Viewer",
|
||||
description: "Can view products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
viewerRole = viewer.data.role
|
||||
|
||||
const editor = await api.post(
|
||||
"/admin/rbac/roles",
|
||||
{
|
||||
name: "Product Editor",
|
||||
description: "Can edit products",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
editorRole = editor.data.role
|
||||
})
|
||||
|
||||
it("should create role-policy associations", async () => {
|
||||
const response = await api.post(
|
||||
"/admin/rbac/role-policies",
|
||||
{
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[0].id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.role_policy).toEqual(
|
||||
expect.objectContaining({
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[0].id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should list role-policies for a specific role", async () => {
|
||||
await api.post(
|
||||
"/admin/rbac/role-policies",
|
||||
{
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[0].id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
"/admin/rbac/role-policies",
|
||||
{
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[1].id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/rbac/role-policies?role_id=${viewerRole.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(2)
|
||||
expect(response.data.role_policies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[0].id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role_id: viewerRole.id,
|
||||
scope_id: policies[1].id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should delete a role-policy association", async () => {
|
||||
const createResponse = await api.post(
|
||||
"/admin/rbac/role-policies",
|
||||
{
|
||||
role_id: editorRole.id,
|
||||
scope_id: policies[2].id,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const rolePolicyId = createResponse.data.role_policy.id
|
||||
|
||||
const deleteResponse = await api.delete(
|
||||
`/admin/rbac/role-policies/${rolePolicyId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(deleteResponse.status).toEqual(200)
|
||||
expect(deleteResponse.data).toEqual({
|
||||
id: rolePolicyId,
|
||||
object: "rbac_role_policy",
|
||||
deleted: true,
|
||||
})
|
||||
|
||||
const listResponse = await api.get(
|
||||
`/admin/rbac/role-policies?role_id=${editorRole.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(
|
||||
listResponse.data.role_policies.find((rp) => rp.id === rolePolicyId)
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -66,6 +66,9 @@ const modules = {
|
||||
resolve: "@medusajs/index",
|
||||
disable: process.env.ENABLE_INDEX_MODULE !== "true",
|
||||
},
|
||||
[Modules.RBAC]: {
|
||||
resolve: "@medusajs/rbac",
|
||||
},
|
||||
}
|
||||
|
||||
if (process.env.MEDUSA_FF_TRANSLATION === "true") {
|
||||
|
||||
516
integration-tests/modules/__tests__/rbac/rbac-workflows.spec.ts
Normal file
516
integration-tests/modules/__tests__/rbac/rbac-workflows.spec.ts
Normal file
@@ -0,0 +1,516 @@
|
||||
import {
|
||||
createRbacPoliciesWorkflow,
|
||||
createRbacRolesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import { IRbacModuleService, MedusaContainer } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/utils"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
env: {},
|
||||
testSuite: ({ getContainer }) => {
|
||||
describe("Workflows: RBAC", () => {
|
||||
let appContainer: MedusaContainer
|
||||
let rbacService: IRbacModuleService
|
||||
|
||||
beforeAll(async () => {
|
||||
appContainer = getContainer()
|
||||
rbacService = appContainer.resolve(Modules.RBAC)
|
||||
})
|
||||
|
||||
describe("Role Inheritance and Policy Management", () => {
|
||||
it("should create roles with inheritance and policies, then list all inherited policies", async () => {
|
||||
// Step 1: Create base policies
|
||||
const policiesWorkflow = createRbacPoliciesWorkflow(appContainer)
|
||||
const { result: createdPolicies } = await policiesWorkflow.run({
|
||||
input: {
|
||||
policies: [
|
||||
{
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
name: "Read Products",
|
||||
description: "Permission to read products",
|
||||
},
|
||||
{
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
operation: "write",
|
||||
name: "Write Products",
|
||||
description: "Permission to write products",
|
||||
},
|
||||
{
|
||||
key: "read:orders",
|
||||
resource: "order",
|
||||
operation: "read",
|
||||
name: "Read Orders",
|
||||
description: "Permission to read orders",
|
||||
},
|
||||
{
|
||||
key: "write:orders",
|
||||
resource: "order",
|
||||
operation: "write",
|
||||
name: "Write Orders",
|
||||
description: "Permission to write orders",
|
||||
},
|
||||
{
|
||||
key: "delete:users",
|
||||
resource: "user",
|
||||
operation: "delete",
|
||||
name: "Delete Users",
|
||||
description: "Permission to delete users",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(createdPolicies).toHaveLength(5)
|
||||
|
||||
// Step 2: Create base roles with their policies
|
||||
const rolesWorkflow = createRbacRolesWorkflow(appContainer)
|
||||
|
||||
// Create "Viewer" role with read permissions
|
||||
const { result: viewerRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Viewer",
|
||||
description: "Can view products and orders",
|
||||
policy_ids: [
|
||||
createdPolicies[0].id, // read:products
|
||||
createdPolicies[2].id, // read:orders
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(viewerRoles).toHaveLength(1)
|
||||
const viewerRole = viewerRoles[0]
|
||||
|
||||
// Create "Editor" role with write permissions
|
||||
const { result: editorRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Editor",
|
||||
description: "Can write products and orders",
|
||||
policy_ids: [
|
||||
createdPolicies[1].id, // write:products
|
||||
createdPolicies[3].id, // write:orders
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editorRoles).toHaveLength(1)
|
||||
const editorRole = editorRoles[0]
|
||||
|
||||
// Step 3: Create "Admin" role that inherits from both Viewer and Editor, plus additional permissions
|
||||
const { result: adminRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Admin",
|
||||
description:
|
||||
"Inherits from Viewer and Editor, plus can delete users",
|
||||
inherited_role_ids: [viewerRole.id, editorRole.id],
|
||||
policy_ids: [
|
||||
createdPolicies[4].id, // delete:users
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(adminRoles).toHaveLength(1)
|
||||
const adminRole = adminRoles[0]
|
||||
|
||||
// Step 4: Verify role inheritance was created
|
||||
const inheritances = await rbacService.listRbacRoleInheritances({
|
||||
role_id: adminRole.id,
|
||||
})
|
||||
|
||||
expect(inheritances).toHaveLength(2)
|
||||
expect(inheritances).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
role_id: adminRole.id,
|
||||
inherited_role_id: viewerRole.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role_id: adminRole.id,
|
||||
inherited_role_id: editorRole.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
// Step 5: Verify direct policies for Admin role
|
||||
const adminDirectPolicies = await rbacService.listRbacRolePolicies({
|
||||
role_id: adminRole.id,
|
||||
})
|
||||
|
||||
expect(adminDirectPolicies).toHaveLength(1)
|
||||
expect(adminDirectPolicies[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
role_id: adminRole.id,
|
||||
scope_id: createdPolicies[4].id, // delete:users
|
||||
})
|
||||
)
|
||||
|
||||
// Step 6: List ALL policies for Admin role (including inherited)
|
||||
const allAdminPolicies = await rbacService.listPoliciesForRole(
|
||||
adminRole.id
|
||||
)
|
||||
|
||||
// Admin should have:
|
||||
// - read:products (from Viewer)
|
||||
// - read:orders (from Viewer)
|
||||
// - write:products (from Editor)
|
||||
// - write:orders (from Editor)
|
||||
// - delete:users (direct)
|
||||
expect(allAdminPolicies).toHaveLength(5)
|
||||
|
||||
const policyKeys = allAdminPolicies.map((p) => p.key).sort()
|
||||
expect(policyKeys).toEqual([
|
||||
"delete:users",
|
||||
"read:orders",
|
||||
"read:products",
|
||||
"write:orders",
|
||||
"write:products",
|
||||
])
|
||||
|
||||
// Verify each policy has correct details
|
||||
expect(allAdminPolicies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "read:products",
|
||||
resource: "product",
|
||||
operation: "read",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "write:products",
|
||||
resource: "product",
|
||||
operation: "write",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "read:orders",
|
||||
resource: "order",
|
||||
operation: "read",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "write:orders",
|
||||
resource: "order",
|
||||
operation: "write",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "delete:users",
|
||||
resource: "user",
|
||||
operation: "delete",
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
// Step 7: Verify Viewer role only has its direct policies
|
||||
const viewerPolicies = await rbacService.listPoliciesForRole(
|
||||
viewerRole.id
|
||||
)
|
||||
|
||||
expect(viewerPolicies).toHaveLength(2)
|
||||
expect(viewerPolicies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "read:products",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "read:orders",
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
// Step 8: Verify Editor role only has its direct policies
|
||||
const editorPolicies = await rbacService.listPoliciesForRole(
|
||||
editorRole.id
|
||||
)
|
||||
|
||||
expect(editorPolicies).toHaveLength(2)
|
||||
expect(editorPolicies).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "write:products",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: "write:orders",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle multi-level role inheritance", async () => {
|
||||
// Create policies
|
||||
const policiesWorkflow = createRbacPoliciesWorkflow(appContainer)
|
||||
const { result: policies } = await policiesWorkflow.run({
|
||||
input: {
|
||||
policies: [
|
||||
{
|
||||
key: "read:catalog",
|
||||
resource: "catalog",
|
||||
operation: "read",
|
||||
name: "Read Catalog",
|
||||
},
|
||||
{
|
||||
key: "write:catalog",
|
||||
resource: "catalog",
|
||||
operation: "write",
|
||||
name: "Write Catalog",
|
||||
},
|
||||
{
|
||||
key: "admin:system",
|
||||
resource: "system",
|
||||
operation: "admin",
|
||||
name: "System Admin",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Create role hierarchy: Basic -> Manager -> SuperAdmin
|
||||
const rolesWorkflow = createRbacRolesWorkflow(appContainer)
|
||||
|
||||
// Level 1: Basic role
|
||||
const { result: basicRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Basic User",
|
||||
description: "Basic read access",
|
||||
policy_ids: [policies[0].id], // read:catalog
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const basicRole = basicRoles[0]
|
||||
|
||||
// Level 2: Manager inherits from Basic
|
||||
const { result: managerRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Manager",
|
||||
description: "Manager with write access",
|
||||
inherited_role_ids: [basicRole.id],
|
||||
policy_ids: [policies[1].id], // write:catalog
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const managerRole = managerRoles[0]
|
||||
|
||||
// Level 3: SuperAdmin inherits from Manager
|
||||
const { result: superAdminRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "SuperAdmin",
|
||||
description: "Super admin with all access",
|
||||
inherited_role_ids: [managerRole.id],
|
||||
policy_ids: [policies[2].id], // admin:system
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const superAdminRole = superAdminRoles[0]
|
||||
|
||||
// Verify SuperAdmin has all policies through inheritance chain
|
||||
const superAdminPolicies = await rbacService.listPoliciesForRole(
|
||||
superAdminRole.id
|
||||
)
|
||||
|
||||
expect(superAdminPolicies).toHaveLength(3)
|
||||
expect(superAdminPolicies.map((p) => p.key).sort()).toEqual([
|
||||
"admin:system",
|
||||
"read:catalog",
|
||||
"write:catalog",
|
||||
])
|
||||
|
||||
// Verify Manager has policies from Basic + its own
|
||||
const managerPolicies = await rbacService.listPoliciesForRole(
|
||||
managerRole.id
|
||||
)
|
||||
|
||||
expect(managerPolicies).toHaveLength(2)
|
||||
expect(managerPolicies.map((p) => p.key).sort()).toEqual([
|
||||
"read:catalog",
|
||||
"write:catalog",
|
||||
])
|
||||
|
||||
// Verify Basic only has its own policy
|
||||
const basicPolicies = await rbacService.listPoliciesForRole(
|
||||
basicRole.id
|
||||
)
|
||||
|
||||
expect(basicPolicies).toHaveLength(1)
|
||||
expect(basicPolicies[0].key).toBe("read:catalog")
|
||||
})
|
||||
|
||||
it("should propagate policy deletion from inherited role", async () => {
|
||||
// Create policies
|
||||
const policiesWorkflow = createRbacPoliciesWorkflow(appContainer)
|
||||
const { result: policies } = await policiesWorkflow.run({
|
||||
input: {
|
||||
policies: [
|
||||
{
|
||||
key: "read:inventory",
|
||||
resource: "inventory",
|
||||
operation: "read",
|
||||
name: "Read Inventory",
|
||||
},
|
||||
{
|
||||
key: "write:inventory",
|
||||
resource: "inventory",
|
||||
operation: "write",
|
||||
name: "Write Inventory",
|
||||
},
|
||||
{
|
||||
key: "admin:inventory",
|
||||
resource: "inventory",
|
||||
operation: "admin",
|
||||
name: "Admin Inventory",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Create role hierarchy: Basic -> Manager -> SuperAdmin
|
||||
const rolesWorkflow = createRbacRolesWorkflow(appContainer)
|
||||
|
||||
// Level 1: Basic role with read permission
|
||||
const { result: basicRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Basic Inventory User",
|
||||
description: "Basic inventory read access",
|
||||
policy_ids: [policies[0].id], // read:inventory
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const basicRole = basicRoles[0]
|
||||
|
||||
// Level 2: Manager inherits from Basic + write permission
|
||||
const { result: managerRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Inventory Manager",
|
||||
description: "Manager with write access",
|
||||
inherited_role_ids: [basicRole.id],
|
||||
policy_ids: [policies[1].id], // write:inventory
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const managerRole = managerRoles[0]
|
||||
|
||||
// Level 3: SuperAdmin inherits from Manager + admin permission
|
||||
const { result: superAdminRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Inventory SuperAdmin",
|
||||
description: "Super admin with all access",
|
||||
inherited_role_ids: [managerRole.id],
|
||||
policy_ids: [policies[2].id], // admin:inventory
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const superAdminRole = superAdminRoles[0]
|
||||
|
||||
// Verify initial state: SuperAdmin has all 3 policies
|
||||
let superAdminPolicies = await rbacService.listPoliciesForRole(
|
||||
superAdminRole.id
|
||||
)
|
||||
expect(superAdminPolicies).toHaveLength(3)
|
||||
expect(superAdminPolicies.map((p) => p.key).sort()).toEqual([
|
||||
"admin:inventory",
|
||||
"read:inventory",
|
||||
"write:inventory",
|
||||
])
|
||||
|
||||
// Verify Manager has 2 policies (Basic's + its own)
|
||||
let managerPolicies = await rbacService.listPoliciesForRole(
|
||||
managerRole.id
|
||||
)
|
||||
expect(managerPolicies).toHaveLength(2)
|
||||
expect(managerPolicies.map((p) => p.key).sort()).toEqual([
|
||||
"read:inventory",
|
||||
"write:inventory",
|
||||
])
|
||||
|
||||
// Delete the read:inventory policy from Basic role
|
||||
const basicRolePolicies = await rbacService.listRbacRolePolicies({
|
||||
role_id: basicRole.id,
|
||||
})
|
||||
const readPolicyAssociation = basicRolePolicies.find(
|
||||
(rp) => rp.scope_id === policies[0].id
|
||||
)
|
||||
await rbacService.deleteRbacRolePolicies([readPolicyAssociation!.id])
|
||||
|
||||
// Verify Basic role no longer has the read policy
|
||||
const basicPoliciesAfterDelete =
|
||||
await rbacService.listPoliciesForRole(basicRole.id)
|
||||
expect(basicPoliciesAfterDelete).toHaveLength(0)
|
||||
|
||||
// Verify Manager role no longer inherits the read policy
|
||||
managerPolicies = await rbacService.listPoliciesForRole(
|
||||
managerRole.id
|
||||
)
|
||||
expect(managerPolicies).toHaveLength(1)
|
||||
expect(managerPolicies[0].key).toBe("write:inventory")
|
||||
|
||||
// Verify SuperAdmin role also lost the read policy through inheritance chain
|
||||
superAdminPolicies = await rbacService.listPoliciesForRole(
|
||||
superAdminRole.id
|
||||
)
|
||||
expect(superAdminPolicies).toHaveLength(2)
|
||||
expect(superAdminPolicies.map((p) => p.key).sort()).toEqual([
|
||||
"admin:inventory",
|
||||
"write:inventory",
|
||||
])
|
||||
})
|
||||
|
||||
it("should handle role with no inherited roles or policies", async () => {
|
||||
const rolesWorkflow = createRbacRolesWorkflow(appContainer)
|
||||
|
||||
const { result: emptyRoles } = await rolesWorkflow.run({
|
||||
input: {
|
||||
roles: [
|
||||
{
|
||||
name: "Empty Role",
|
||||
description: "Role with no permissions",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const emptyRole = emptyRoles[0]
|
||||
|
||||
// Verify no policies
|
||||
const policies = await rbacService.listPoliciesForRole(emptyRole.id)
|
||||
expect(policies).toHaveLength(0)
|
||||
|
||||
// Verify no inheritance
|
||||
const inheritances = await rbacService.listRbacRoleInheritances({
|
||||
role_id: emptyRole.id,
|
||||
})
|
||||
expect(inheritances).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -189,5 +189,9 @@ module.exports = defineConfig({
|
||||
key: "brand",
|
||||
resolve: "src/modules/brand",
|
||||
},
|
||||
{
|
||||
key: Modules.RBAC,
|
||||
resolve: "@medusajs/rbac",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@ export * from "./pricing"
|
||||
export * from "./product"
|
||||
export * from "./product-category"
|
||||
export * from "./promotion"
|
||||
export * from "./rbac"
|
||||
export * from "./region"
|
||||
export * from "./reservation"
|
||||
export * from "./return-reason"
|
||||
|
||||
2
packages/core/core-flows/src/rbac/index.ts
Normal file
2
packages/core/core-flows/src/rbac/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type CreateRbacPolicyDTO = {
|
||||
key: string
|
||||
resource: string
|
||||
operation: string
|
||||
name?: string | null
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type CreateRbacPoliciesStepInput = {
|
||||
policies: CreateRbacPolicyDTO[]
|
||||
}
|
||||
|
||||
export const createRbacPoliciesStepId = "create-rbac-policies"
|
||||
|
||||
export const createRbacPoliciesStep = createStep(
|
||||
createRbacPoliciesStepId,
|
||||
async (data: CreateRbacPoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const created = await service.createRbacPolicies(data.policies)
|
||||
|
||||
return new StepResponse(
|
||||
created,
|
||||
(created ?? []).map((p) => p.id)
|
||||
)
|
||||
},
|
||||
async (createdIds: string[] | undefined, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacPolicies(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type CreateRbacRoleInheritanceDTO = {
|
||||
role_id: string
|
||||
inherited_role_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type CreateRbacRoleInheritancesStepInput = {
|
||||
role_inheritances: CreateRbacRoleInheritanceDTO[]
|
||||
}
|
||||
|
||||
export const createRbacRoleInheritancesStepId = "create-rbac-role-inheritances"
|
||||
|
||||
export const createRbacRoleInheritancesStep = createStep(
|
||||
createRbacRoleInheritancesStepId,
|
||||
async (data: CreateRbacRoleInheritancesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
if (!data.role_inheritances || data.role_inheritances.length === 0) {
|
||||
return new StepResponse([], [])
|
||||
}
|
||||
|
||||
const created = await service.createRbacRoleInheritances(
|
||||
data.role_inheritances
|
||||
)
|
||||
|
||||
return new StepResponse(
|
||||
created,
|
||||
(created ?? []).map((ri) => ri.id)
|
||||
)
|
||||
},
|
||||
async (createdIds: string[] | undefined, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacRoleInheritances(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type CreateRbacRolePolicyDTO = {
|
||||
role_id: string
|
||||
scope_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type CreateRbacRolePoliciesStepInput = {
|
||||
role_policies: CreateRbacRolePolicyDTO[]
|
||||
}
|
||||
|
||||
export const createRbacRolePoliciesStepId = "create-rbac-role-policies"
|
||||
|
||||
export const createRbacRolePoliciesStep = createStep(
|
||||
createRbacRolePoliciesStepId,
|
||||
async (data: CreateRbacRolePoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const created = await service.createRbacRolePolicies(data.role_policies)
|
||||
|
||||
return new StepResponse(
|
||||
created,
|
||||
(created ?? []).map((rp) => rp.id)
|
||||
)
|
||||
},
|
||||
async (createdIds: string[] | undefined, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacRolePolicies(createdIds)
|
||||
}
|
||||
)
|
||||
37
packages/core/core-flows/src/rbac/steps/create-rbac-roles.ts
Normal file
37
packages/core/core-flows/src/rbac/steps/create-rbac-roles.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type CreateRbacRoleDTO = {
|
||||
name: string
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type CreateRbacRolesStepInput = {
|
||||
roles: CreateRbacRoleDTO[]
|
||||
}
|
||||
|
||||
export const createRbacRolesStepId = "create-rbac-roles"
|
||||
|
||||
export const createRbacRolesStep = createStep(
|
||||
createRbacRolesStepId,
|
||||
async (data: CreateRbacRolesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const created = await service.createRbacRoles(data.roles)
|
||||
|
||||
return new StepResponse(
|
||||
created,
|
||||
(created ?? []).map((r) => r.id)
|
||||
)
|
||||
},
|
||||
async (createdIds: string[] | undefined, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacRoles(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type DeleteRbacPoliciesStepInput = string[]
|
||||
|
||||
export const deleteRbacPoliciesStepId = "delete-rbac-policies"
|
||||
|
||||
export const deleteRbacPoliciesStep = createStep(
|
||||
{ name: deleteRbacPoliciesStepId, noCompensation: true },
|
||||
async (ids: DeleteRbacPoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacPolicies(ids)
|
||||
return new StepResponse(void 0)
|
||||
},
|
||||
async () => {}
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type DeleteRbacRolePoliciesStepInput = string[]
|
||||
|
||||
export const deleteRbacRolePoliciesStepId = "delete-rbac-role-policies"
|
||||
|
||||
export const deleteRbacRolePoliciesStep = createStep(
|
||||
{ name: deleteRbacRolePoliciesStepId, noCompensation: true },
|
||||
async (ids: DeleteRbacRolePoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacRolePolicies(ids)
|
||||
return new StepResponse(void 0)
|
||||
},
|
||||
async () => {}
|
||||
)
|
||||
17
packages/core/core-flows/src/rbac/steps/delete-rbac-roles.ts
Normal file
17
packages/core/core-flows/src/rbac/steps/delete-rbac-roles.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type DeleteRbacRolesStepInput = string[]
|
||||
|
||||
export const deleteRbacRolesStepId = "delete-rbac-roles"
|
||||
|
||||
export const deleteRbacRolesStep = createStep(
|
||||
{ name: deleteRbacRolesStepId, noCompensation: true },
|
||||
async (ids: DeleteRbacRolesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
await service.deleteRbacRoles(ids)
|
||||
return new StepResponse(void 0)
|
||||
},
|
||||
async () => {}
|
||||
)
|
||||
14
packages/core/core-flows/src/rbac/steps/index.ts
Normal file
14
packages/core/core-flows/src/rbac/steps/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export * from "./create-rbac-roles"
|
||||
export * from "./delete-rbac-roles"
|
||||
export * from "./update-rbac-roles"
|
||||
|
||||
export * from "./create-rbac-policies"
|
||||
export * from "./delete-rbac-policies"
|
||||
export * from "./update-rbac-policies"
|
||||
|
||||
export * from "./create-rbac-role-policies"
|
||||
export * from "./delete-rbac-role-policies"
|
||||
export * from "./update-rbac-role-policies"
|
||||
|
||||
export * from "./create-rbac-role-inheritances"
|
||||
export * from "./set-role-inheritance"
|
||||
122
packages/core/core-flows/src/rbac/steps/set-role-inheritance.ts
Normal file
122
packages/core/core-flows/src/rbac/steps/set-role-inheritance.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService } from "@medusajs/types"
|
||||
|
||||
export type SetRoleInheritanceStepInput = Array<{
|
||||
role_id: string
|
||||
inherited_role_ids: string[]
|
||||
}>
|
||||
|
||||
export const setRoleInheritanceStepId = "set-role-inheritance"
|
||||
|
||||
export const setRoleInheritanceStep = createStep(
|
||||
setRoleInheritanceStepId,
|
||||
async (data: SetRoleInheritanceStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const allCompensationData: Array<{
|
||||
role_id: string
|
||||
previousInheritedRoleIds: string[]
|
||||
}> = []
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return new StepResponse(
|
||||
{ created: [], removedCount: 0 },
|
||||
allCompensationData
|
||||
)
|
||||
}
|
||||
|
||||
const allToRemoveIds: string[] = []
|
||||
const allToCreate: Array<{
|
||||
role_id: string
|
||||
inherited_role_id: string
|
||||
}> = []
|
||||
|
||||
for (const roleData of data) {
|
||||
const existingInheritance = await service.listRbacRoleInheritances({
|
||||
role_id: roleData.role_id,
|
||||
})
|
||||
|
||||
const existingInheritedRoleIds = existingInheritance.map(
|
||||
(ri) => ri.inherited_role_id
|
||||
)
|
||||
|
||||
allCompensationData.push({
|
||||
role_id: roleData.role_id,
|
||||
previousInheritedRoleIds: existingInheritedRoleIds,
|
||||
})
|
||||
|
||||
const toAdd = roleData.inherited_role_ids.filter(
|
||||
(id) => !existingInheritedRoleIds.includes(id)
|
||||
)
|
||||
const toRemove = existingInheritedRoleIds.filter(
|
||||
(id) => !roleData.inherited_role_ids.includes(id)
|
||||
)
|
||||
|
||||
if (toRemove.length > 0) {
|
||||
const toRemoveRecords = existingInheritance.filter((ri) =>
|
||||
toRemove.includes(ri.inherited_role_id)
|
||||
)
|
||||
allToRemoveIds.push(...toRemoveRecords.map((ri) => ri.id))
|
||||
}
|
||||
|
||||
if (toAdd.length > 0) {
|
||||
allToCreate.push(
|
||||
...toAdd.map((inherited_role_id) => ({
|
||||
role_id: roleData.role_id,
|
||||
inherited_role_id,
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (allToRemoveIds.length > 0) {
|
||||
await service.deleteRbacRoleInheritances(allToRemoveIds)
|
||||
}
|
||||
|
||||
let created: any[] = []
|
||||
if (allToCreate.length > 0) {
|
||||
created = await service.createRbacRoleInheritances(allToCreate)
|
||||
}
|
||||
|
||||
return new StepResponse(
|
||||
{ created, removedCount: allToRemoveIds.length },
|
||||
allCompensationData
|
||||
)
|
||||
},
|
||||
async (
|
||||
compensationData:
|
||||
| Array<{ role_id: string; previousInheritedRoleIds: string[] }>
|
||||
| undefined,
|
||||
{ container }
|
||||
) => {
|
||||
if (!compensationData || compensationData.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
for (const roleCompensation of compensationData) {
|
||||
const currentInheritance = await service.listRbacRoleInheritances({
|
||||
role_id: roleCompensation.role_id,
|
||||
})
|
||||
|
||||
if (currentInheritance.length > 0) {
|
||||
await service.deleteRbacRoleInheritances(
|
||||
currentInheritance.map((ri) => ri.id)
|
||||
)
|
||||
}
|
||||
|
||||
if (roleCompensation.previousInheritedRoleIds.length > 0) {
|
||||
await service.createRbacRoleInheritances(
|
||||
roleCompensation.previousInheritedRoleIds.map(
|
||||
(inherited_role_id) => ({
|
||||
role_id: roleCompensation.role_id,
|
||||
inherited_role_id,
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService, UpdateRbacPolicyDTO } from "@medusajs/types"
|
||||
|
||||
export type UpdateRbacPoliciesStepInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacPolicyDTO, "id">
|
||||
}
|
||||
|
||||
export const updateRbacPoliciesStepId = "update-rbac-policies"
|
||||
|
||||
export const updateRbacPoliciesStep = createStep(
|
||||
updateRbacPoliciesStepId,
|
||||
async (data: UpdateRbacPoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listRbacPolicies(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const updates = (prevData ?? []).map((p) => ({
|
||||
id: p.id,
|
||||
...data.update,
|
||||
})) as UpdateRbacPolicyDTO[]
|
||||
|
||||
const updated = await service.updateRbacPolicies(updates)
|
||||
|
||||
return new StepResponse(updated, {
|
||||
prevData,
|
||||
updateKeys: Object.keys(data.update ?? {}),
|
||||
})
|
||||
},
|
||||
async (
|
||||
compensationData: { prevData: any[]; updateKeys: string[] } | undefined,
|
||||
{ container }
|
||||
) => {
|
||||
if (!compensationData?.prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const updates = compensationData.prevData.map((p) => {
|
||||
const payload: Record<string, any> = { id: p.id }
|
||||
for (const key of compensationData.updateKeys) {
|
||||
payload[key] = p[key]
|
||||
}
|
||||
return payload
|
||||
}) as UpdateRbacPolicyDTO[]
|
||||
|
||||
await service.updateRbacPolicies(updates)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService, UpdateRbacRolePolicyDTO } from "@medusajs/types"
|
||||
|
||||
export type UpdateRbacRolePoliciesStepInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacRolePolicyDTO, "id">
|
||||
}
|
||||
|
||||
export const updateRbacRolePoliciesStepId = "update-rbac-role-policies"
|
||||
|
||||
export const updateRbacRolePoliciesStep = createStep(
|
||||
updateRbacRolePoliciesStepId,
|
||||
async (data: UpdateRbacRolePoliciesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listRbacRolePolicies(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const updates = (prevData ?? []).map((rp) => ({
|
||||
id: rp.id,
|
||||
...data.update,
|
||||
})) as UpdateRbacRolePolicyDTO[]
|
||||
|
||||
const updated = await service.updateRbacRolePolicies(updates)
|
||||
|
||||
return new StepResponse(updated, {
|
||||
prevData,
|
||||
updateKeys: Object.keys(data.update ?? {}),
|
||||
})
|
||||
},
|
||||
async (
|
||||
compensationData: { prevData: any[]; updateKeys: string[] } | undefined,
|
||||
{ container }
|
||||
) => {
|
||||
if (!compensationData?.prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const updates = compensationData.prevData.map((rp) => {
|
||||
const payload: Record<string, any> = { id: rp.id }
|
||||
for (const key of compensationData.updateKeys) {
|
||||
payload[key] = rp[key]
|
||||
}
|
||||
return payload
|
||||
}) as UpdateRbacRolePolicyDTO[]
|
||||
|
||||
await service.updateRbacRolePolicies(updates)
|
||||
}
|
||||
)
|
||||
61
packages/core/core-flows/src/rbac/steps/update-rbac-roles.ts
Normal file
61
packages/core/core-flows/src/rbac/steps/update-rbac-roles.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { IRbacModuleService, UpdateRbacRoleDTO } from "@medusajs/types"
|
||||
|
||||
export type UpdateRbacRolesStepInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacRoleDTO, "id">
|
||||
}
|
||||
|
||||
export const updateRbacRolesStepId = "update-rbac-roles"
|
||||
|
||||
export const updateRbacRolesStep = createStep(
|
||||
updateRbacRolesStepId,
|
||||
async (data: UpdateRbacRolesStepInput, { container }) => {
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.listRbacRoles(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const updates = (prevData ?? []).map((r) => ({
|
||||
id: r.id,
|
||||
...data.update,
|
||||
})) as UpdateRbacRoleDTO[]
|
||||
|
||||
const updated = await service.updateRbacRoles(updates)
|
||||
|
||||
return new StepResponse(updated, {
|
||||
prevData,
|
||||
updateKeys: Object.keys(data.update ?? {}),
|
||||
})
|
||||
},
|
||||
async (
|
||||
compensationData: { prevData: any[]; updateKeys: string[] } | undefined,
|
||||
{ container }
|
||||
) => {
|
||||
if (!compensationData?.prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IRbacModuleService>(Modules.RBAC)
|
||||
|
||||
const updates = compensationData.prevData.map((r) => {
|
||||
const payload: Record<string, any> = { id: r.id }
|
||||
for (const key of compensationData.updateKeys) {
|
||||
payload[key] = r[key]
|
||||
}
|
||||
return payload
|
||||
}) as UpdateRbacRoleDTO[]
|
||||
|
||||
await service.updateRbacRoles(updates)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { createRbacPoliciesStep } from "../steps"
|
||||
|
||||
export type CreateRbacPoliciesWorkflowInput = {
|
||||
policies: any[]
|
||||
}
|
||||
|
||||
export const createRbacPoliciesWorkflowId = "create-rbac-policies"
|
||||
|
||||
export const createRbacPoliciesWorkflow = createWorkflow(
|
||||
createRbacPoliciesWorkflowId,
|
||||
(input: WorkflowData<CreateRbacPoliciesWorkflowInput>) => {
|
||||
return new WorkflowResponse(createRbacPoliciesStep(input))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { createRbacRolePoliciesStep } from "../steps"
|
||||
|
||||
export type CreateRbacRolePoliciesWorkflowInput = {
|
||||
role_policies: any[]
|
||||
}
|
||||
|
||||
export const createRbacRolePoliciesWorkflowId = "create-rbac-role-policies"
|
||||
|
||||
export const createRbacRolePoliciesWorkflow = createWorkflow(
|
||||
createRbacRolePoliciesWorkflowId,
|
||||
(input: WorkflowData<CreateRbacRolePoliciesWorkflowInput>) => {
|
||||
return new WorkflowResponse(createRbacRolePoliciesStep(input))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
createRbacRoleInheritancesStep,
|
||||
createRbacRolePoliciesStep,
|
||||
createRbacRolesStep,
|
||||
} from "../steps"
|
||||
|
||||
export type CreateRbacRolesWorkflowInput = {
|
||||
roles: {
|
||||
name: string
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
inherited_role_ids?: string[]
|
||||
policy_ids?: string[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export const createRbacRolesWorkflowId = "create-rbac-roles"
|
||||
|
||||
export const createRbacRolesWorkflow = createWorkflow(
|
||||
createRbacRolesWorkflowId,
|
||||
(input: WorkflowData<CreateRbacRolesWorkflowInput>) => {
|
||||
const roleData = transform({ input }, ({ input }) => ({
|
||||
roles: input.roles.map((r) => ({
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
metadata: r.metadata,
|
||||
})),
|
||||
}))
|
||||
|
||||
const createdRoles = createRbacRolesStep(roleData)
|
||||
|
||||
const inheritanceData = transform(
|
||||
{ input, createdRoles },
|
||||
({ input, createdRoles }) => {
|
||||
const inheritances: any[] = []
|
||||
|
||||
createdRoles.forEach((role, index) => {
|
||||
const inheritedRoleIds = input.roles[index].inherited_role_ids || []
|
||||
inheritedRoleIds.forEach((inheritedRoleId) => {
|
||||
inheritances.push({
|
||||
role_id: role.id,
|
||||
inherited_role_id: inheritedRoleId,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return { role_inheritances: inheritances }
|
||||
}
|
||||
)
|
||||
|
||||
const policiesData = transform(
|
||||
{ input, createdRoles },
|
||||
({ input, createdRoles }) => {
|
||||
const allPolicies: any[] = []
|
||||
createdRoles.forEach((role, index) => {
|
||||
const policyIds = input.roles[index].policy_ids || []
|
||||
policyIds.forEach((policy_id) => {
|
||||
allPolicies.push({
|
||||
role_id: role.id,
|
||||
scope_id: policy_id,
|
||||
})
|
||||
})
|
||||
})
|
||||
return { role_policies: allPolicies }
|
||||
}
|
||||
)
|
||||
|
||||
createRbacRoleInheritancesStep(inheritanceData)
|
||||
|
||||
createRbacRolePoliciesStep(policiesData)
|
||||
|
||||
return new WorkflowResponse(createdRoles)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
import { deleteRbacPoliciesStep } from "../steps"
|
||||
|
||||
export type DeleteRbacPoliciesWorkflowInput = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteRbacPoliciesWorkflowId = "delete-rbac-policies"
|
||||
|
||||
export const deleteRbacPoliciesWorkflow = createWorkflow(
|
||||
deleteRbacPoliciesWorkflowId,
|
||||
(
|
||||
input: WorkflowData<DeleteRbacPoliciesWorkflowInput>
|
||||
): WorkflowData<void> => {
|
||||
deleteRbacPoliciesStep(input.ids)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
import { deleteRbacRolePoliciesStep } from "../steps"
|
||||
|
||||
export type DeleteRbacRolePoliciesWorkflowInput = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteRbacRolePoliciesWorkflowId = "delete-rbac-role-policies"
|
||||
|
||||
export const deleteRbacRolePoliciesWorkflow = createWorkflow(
|
||||
deleteRbacRolePoliciesWorkflowId,
|
||||
(
|
||||
input: WorkflowData<DeleteRbacRolePoliciesWorkflowInput>
|
||||
): WorkflowData<void> => {
|
||||
deleteRbacRolePoliciesStep(input.ids)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/framework/workflows-sdk"
|
||||
import { deleteRbacRolesStep } from "../steps"
|
||||
|
||||
export type DeleteRbacRolesWorkflowInput = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export const deleteRbacRolesWorkflowId = "delete-rbac-roles"
|
||||
|
||||
export const deleteRbacRolesWorkflow = createWorkflow(
|
||||
deleteRbacRolesWorkflowId,
|
||||
(input: WorkflowData<DeleteRbacRolesWorkflowInput>): WorkflowData<void> => {
|
||||
deleteRbacRolesStep(input.ids)
|
||||
}
|
||||
)
|
||||
11
packages/core/core-flows/src/rbac/workflows/index.ts
Normal file
11
packages/core/core-flows/src/rbac/workflows/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from "./create-rbac-roles"
|
||||
export * from "./delete-rbac-roles"
|
||||
export * from "./update-rbac-roles"
|
||||
|
||||
export * from "./create-rbac-policies"
|
||||
export * from "./delete-rbac-policies"
|
||||
export * from "./update-rbac-policies"
|
||||
|
||||
export * from "./create-rbac-role-policies"
|
||||
export * from "./delete-rbac-role-policies"
|
||||
export * from "./update-rbac-role-policies"
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { UpdateRbacPolicyDTO } from "@medusajs/types"
|
||||
import { updateRbacPoliciesStep } from "../steps/update-rbac-policies"
|
||||
|
||||
export type UpdateRbacPoliciesWorkflowInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacPolicyDTO, "id">
|
||||
}
|
||||
|
||||
export const updateRbacPoliciesWorkflowId = "update-rbac-policies"
|
||||
|
||||
export const updateRbacPoliciesWorkflow = createWorkflow(
|
||||
updateRbacPoliciesWorkflowId,
|
||||
(input: WorkflowData<UpdateRbacPoliciesWorkflowInput>) => {
|
||||
return new WorkflowResponse(updateRbacPoliciesStep(input))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { UpdateRbacRolePolicyDTO } from "@medusajs/types"
|
||||
import { updateRbacRolePoliciesStep } from "../steps/update-rbac-role-policies"
|
||||
|
||||
export type UpdateRbacRolePoliciesWorkflowInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacRolePolicyDTO, "id">
|
||||
}
|
||||
|
||||
export const updateRbacRolePoliciesWorkflowId = "update-rbac-role-policies"
|
||||
|
||||
export const updateRbacRolePoliciesWorkflow = createWorkflow(
|
||||
updateRbacRolePoliciesWorkflowId,
|
||||
(input: WorkflowData<UpdateRbacRolePoliciesWorkflowInput>) => {
|
||||
return new WorkflowResponse(updateRbacRolePoliciesStep(input))
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
import { isDefined } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { UpdateRbacRoleDTO } from "@medusajs/types"
|
||||
import { createRbacRolePoliciesStep, setRoleInheritanceStep } from "../steps"
|
||||
import { updateRbacRolesStep } from "../steps/update-rbac-roles"
|
||||
|
||||
export type UpdateRbacRolesWorkflowInput = {
|
||||
selector: Record<string, any>
|
||||
update: Omit<UpdateRbacRoleDTO, "id"> & {
|
||||
inherited_role_ids?: string[]
|
||||
policy_ids?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export const updateRbacRolesWorkflowId = "update-rbac-roles"
|
||||
|
||||
export const updateRbacRolesWorkflow = createWorkflow(
|
||||
updateRbacRolesWorkflowId,
|
||||
(input: WorkflowData<UpdateRbacRolesWorkflowInput>) => {
|
||||
const roleUpdateData = transform({ input }, ({ input }) => ({
|
||||
selector: input.selector,
|
||||
update: {
|
||||
name: input.update.name,
|
||||
description: input.update.description,
|
||||
metadata: input.update.metadata,
|
||||
},
|
||||
}))
|
||||
|
||||
const updatedRoles = updateRbacRolesStep(roleUpdateData)
|
||||
|
||||
const inheritanceUpdateData = transform(
|
||||
{ input, updatedRoles },
|
||||
({ input, updatedRoles }) => {
|
||||
if (!isDefined(input.update.inherited_role_ids)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return updatedRoles.map((role) => ({
|
||||
role_id: role.id,
|
||||
inherited_role_ids: input.update.inherited_role_ids || [],
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
setRoleInheritanceStep(inheritanceUpdateData)
|
||||
|
||||
const policiesUpdateData = transform(
|
||||
{ input, updatedRoles },
|
||||
({ input, updatedRoles }) => {
|
||||
if (!isDefined(input.update.policy_ids)) {
|
||||
return { role_policies: [] }
|
||||
}
|
||||
|
||||
const allPolicies: any[] = []
|
||||
updatedRoles.forEach((role) => {
|
||||
const policyIds = input.update.policy_ids || []
|
||||
policyIds.forEach((policy_id) => {
|
||||
allPolicies.push({
|
||||
role_id: role.id,
|
||||
scope_id: policy_id,
|
||||
})
|
||||
})
|
||||
})
|
||||
return { role_policies: allPolicies }
|
||||
}
|
||||
)
|
||||
|
||||
createRbacRolePoliciesStep(policiesUpdateData)
|
||||
|
||||
return new WorkflowResponse(updatedRoles)
|
||||
}
|
||||
)
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
IPricingModuleService,
|
||||
IProductModuleService,
|
||||
IPromotionModuleService,
|
||||
IRbacModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
ISettingsModuleService,
|
||||
@@ -82,6 +83,7 @@ declare module "@medusajs/types" {
|
||||
[Modules.CACHING]: ICachingModuleService
|
||||
[Modules.INDEX]: IIndexService
|
||||
[Modules.TRANSLATION]: ITranslationModuleService
|
||||
[Modules.RBAC]: IRbacModuleService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export * from "./pricing"
|
||||
export * from "./product"
|
||||
export * from "./product-category"
|
||||
export * from "./promotion"
|
||||
export * from "./rbac"
|
||||
export * from "./region"
|
||||
export * from "./sales-channel"
|
||||
export * from "./search"
|
||||
|
||||
56
packages/core/types/src/rbac/common.ts
Normal file
56
packages/core/types/src/rbac/common.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export type RbacRoleDTO = {
|
||||
id: string
|
||||
name: string
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type FilterableRbacRoleProps = {
|
||||
id?: string | string[]
|
||||
name?: string
|
||||
q?: string
|
||||
}
|
||||
|
||||
export type RbacPolicyDTO = {
|
||||
id: string
|
||||
key: string
|
||||
resource: string
|
||||
operation: string
|
||||
name?: string | null
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type FilterableRbacPolicyProps = {
|
||||
id?: string | string[]
|
||||
key?: string
|
||||
resource?: string
|
||||
operation?: string
|
||||
q?: string
|
||||
}
|
||||
|
||||
export type RbacRolePolicyDTO = {
|
||||
id: string
|
||||
role_id: string
|
||||
scope_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type FilterableRbacRolePolicyProps = {
|
||||
id?: string | string[]
|
||||
role_id?: string | string[]
|
||||
scope_id?: string | string[]
|
||||
}
|
||||
|
||||
export type RbacRoleInheritanceDTO = {
|
||||
id: string
|
||||
role_id: string
|
||||
inherited_role_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type FilterableRbacRoleInheritanceProps = {
|
||||
id?: string | string[]
|
||||
role_id?: string | string[]
|
||||
inherited_role_id?: string | string[]
|
||||
}
|
||||
3
packages/core/types/src/rbac/index.ts
Normal file
3
packages/core/types/src/rbac/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./common"
|
||||
export * from "./mutations"
|
||||
export * from "./service"
|
||||
43
packages/core/types/src/rbac/mutations.ts
Normal file
43
packages/core/types/src/rbac/mutations.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export type CreateRbacRoleDTO = {
|
||||
name: string
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type UpdateRbacRoleDTO = Partial<CreateRbacRoleDTO> & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type CreateRbacPolicyDTO = {
|
||||
key: string
|
||||
resource: string
|
||||
operation: string
|
||||
name?: string | null
|
||||
description?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type UpdateRbacPolicyDTO = Partial<CreateRbacPolicyDTO> & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type CreateRbacRolePolicyDTO = {
|
||||
role_id: string
|
||||
scope_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type UpdateRbacRolePolicyDTO = Partial<CreateRbacRolePolicyDTO> & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type CreateRbacRoleInheritanceDTO = {
|
||||
role_id: string
|
||||
inherited_role_id: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export type UpdateRbacRoleInheritanceDTO =
|
||||
Partial<CreateRbacRoleInheritanceDTO> & {
|
||||
id: string
|
||||
}
|
||||
194
packages/core/types/src/rbac/service.ts
Normal file
194
packages/core/types/src/rbac/service.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { FindConfig } from "../common"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
import {
|
||||
FilterableRbacPolicyProps,
|
||||
FilterableRbacRoleInheritanceProps,
|
||||
FilterableRbacRolePolicyProps,
|
||||
FilterableRbacRoleProps,
|
||||
RbacPolicyDTO,
|
||||
RbacRoleDTO,
|
||||
RbacRoleInheritanceDTO,
|
||||
RbacRolePolicyDTO,
|
||||
} from "./common"
|
||||
import {
|
||||
CreateRbacPolicyDTO,
|
||||
CreateRbacRoleDTO,
|
||||
CreateRbacRoleInheritanceDTO,
|
||||
CreateRbacRolePolicyDTO,
|
||||
UpdateRbacPolicyDTO,
|
||||
UpdateRbacRoleDTO,
|
||||
UpdateRbacRoleInheritanceDTO,
|
||||
UpdateRbacRolePolicyDTO,
|
||||
} from "./mutations"
|
||||
|
||||
export interface IRbacModuleService extends IModuleService {
|
||||
createRbacRoles(
|
||||
data: CreateRbacRoleDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO>
|
||||
createRbacRoles(
|
||||
data: CreateRbacRoleDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO[]>
|
||||
|
||||
updateRbacRoles(
|
||||
data: UpdateRbacRoleDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO>
|
||||
updateRbacRoles(
|
||||
data: UpdateRbacRoleDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO[]>
|
||||
|
||||
deleteRbacRoles(
|
||||
ids: string | string[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
retrieveRbacRole(
|
||||
id: string,
|
||||
config?: FindConfig<RbacRoleDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO>
|
||||
|
||||
listRbacRoles(
|
||||
filters?: FilterableRbacRoleProps,
|
||||
config?: FindConfig<RbacRoleDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleDTO[]>
|
||||
|
||||
listAndCountRbacRoles(
|
||||
filters?: FilterableRbacRoleProps,
|
||||
config?: FindConfig<RbacRoleDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[RbacRoleDTO[], number]>
|
||||
|
||||
createRbacPolicies(
|
||||
data: CreateRbacPolicyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO>
|
||||
createRbacPolicies(
|
||||
data: CreateRbacPolicyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO[]>
|
||||
|
||||
updateRbacPolicies(
|
||||
data: UpdateRbacPolicyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO>
|
||||
updateRbacPolicies(
|
||||
data: UpdateRbacPolicyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO[]>
|
||||
|
||||
deleteRbacPolicies(
|
||||
ids: string | string[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
retrieveRbacPolicy(
|
||||
id: string,
|
||||
config?: FindConfig<RbacPolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO>
|
||||
|
||||
listRbacPolicies(
|
||||
filters?: FilterableRbacPolicyProps,
|
||||
config?: FindConfig<RbacPolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO[]>
|
||||
|
||||
listAndCountRbacPolicies(
|
||||
filters?: FilterableRbacPolicyProps,
|
||||
config?: FindConfig<RbacPolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[RbacPolicyDTO[], number]>
|
||||
|
||||
createRbacRolePolicies(
|
||||
data: CreateRbacRolePolicyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO>
|
||||
createRbacRolePolicies(
|
||||
data: CreateRbacRolePolicyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO[]>
|
||||
|
||||
updateRbacRolePolicies(
|
||||
data: UpdateRbacRolePolicyDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO>
|
||||
updateRbacRolePolicies(
|
||||
data: UpdateRbacRolePolicyDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO[]>
|
||||
|
||||
deleteRbacRolePolicies(
|
||||
ids: string | string[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
retrieveRbacRolePolicy(
|
||||
id: string,
|
||||
config?: FindConfig<RbacRolePolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO>
|
||||
|
||||
listRbacRolePolicies(
|
||||
filters?: FilterableRbacRolePolicyProps,
|
||||
config?: FindConfig<RbacRolePolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRolePolicyDTO[]>
|
||||
|
||||
listAndCountRbacRolePolicies(
|
||||
filters?: FilterableRbacRolePolicyProps,
|
||||
config?: FindConfig<RbacRolePolicyDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[RbacRolePolicyDTO[], number]>
|
||||
|
||||
createRbacRoleInheritances(
|
||||
data: CreateRbacRoleInheritanceDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO>
|
||||
createRbacRoleInheritances(
|
||||
data: CreateRbacRoleInheritanceDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO[]>
|
||||
|
||||
updateRbacRoleInheritances(
|
||||
data: UpdateRbacRoleInheritanceDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO>
|
||||
updateRbacRoleInheritances(
|
||||
data: UpdateRbacRoleInheritanceDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO[]>
|
||||
|
||||
deleteRbacRoleInheritances(
|
||||
ids: string | string[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
retrieveRbacRoleInheritance(
|
||||
id: string,
|
||||
config?: FindConfig<RbacRoleInheritanceDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO>
|
||||
|
||||
listRbacRoleInheritances(
|
||||
filters?: FilterableRbacRoleInheritanceProps,
|
||||
config?: FindConfig<RbacRoleInheritanceDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacRoleInheritanceDTO[]>
|
||||
|
||||
listAndCountRbacRoleInheritances(
|
||||
filters?: FilterableRbacRoleInheritanceProps,
|
||||
config?: FindConfig<RbacRoleInheritanceDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[RbacRoleInheritanceDTO[], number]>
|
||||
|
||||
listPoliciesForRole(
|
||||
roleId: string,
|
||||
sharedContext?: Context
|
||||
): Promise<RbacPolicyDTO[]>
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export const Modules = {
|
||||
SETTINGS: "settings",
|
||||
CACHING: "caching",
|
||||
TRANSLATION: "translation",
|
||||
RBAC: "rbac",
|
||||
} as const
|
||||
|
||||
export const MODULE_PACKAGE_NAMES = {
|
||||
@@ -62,6 +63,7 @@ export const MODULE_PACKAGE_NAMES = {
|
||||
[Modules.SETTINGS]: "@medusajs/medusa/settings",
|
||||
[Modules.CACHING]: "@medusajs/caching",
|
||||
[Modules.TRANSLATION]: "@medusajs/translation",
|
||||
[Modules.RBAC]: "@medusajs/medusa/rbac",
|
||||
}
|
||||
|
||||
export const REVERSED_MODULE_PACKAGE_NAMES = Object.entries(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { join } from "path"
|
||||
import { Modules } from "./definition"
|
||||
import type { LoadedModule } from "@medusajs/types"
|
||||
import { join } from "path"
|
||||
import { FileSystem } from "../common/file-system"
|
||||
import { toUnixSlash } from "../common/to-unix-slash"
|
||||
import { toCamelCase } from "../common/to-camel-case"
|
||||
import { toUnixSlash } from "../common/to-unix-slash"
|
||||
import { upperCaseFirst } from "../common/upper-case-first"
|
||||
import { Modules } from "./definition"
|
||||
|
||||
/**
|
||||
* For known services that has interfaces, we will set the container
|
||||
@@ -37,6 +37,10 @@ const SERVICES_INTERFACES = {
|
||||
[Modules.FILE]: "IFileModuleService",
|
||||
[Modules.NOTIFICATION]: "INotificationModuleService",
|
||||
[Modules.LOCKING]: "ILockingModule",
|
||||
[Modules.SETTINGS]: "ISettingsModuleService",
|
||||
[Modules.CACHING]: "ICachingModuleService",
|
||||
[Modules.TRANSLATION]: "ITranslationModuleService",
|
||||
[Modules.RBAC]: "IRbacModuleService",
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
11
packages/medusa/src/api/admin/rbac/middlewares.ts
Normal file
11
packages/medusa/src/api/admin/rbac/middlewares.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { MiddlewareRoute } from "@medusajs/framework/http"
|
||||
|
||||
import { adminRbacPolicyRoutesMiddlewares } from "./policies/middlewares"
|
||||
import { adminRbacRolePolicyRoutesMiddlewares } from "./role-policies/middlewares"
|
||||
import { adminRbacRoleRoutesMiddlewares } from "./roles/middlewares"
|
||||
|
||||
export const adminRbacRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
...adminRbacRoleRoutesMiddlewares,
|
||||
...adminRbacPolicyRoutesMiddlewares,
|
||||
...adminRbacRolePolicyRoutesMiddlewares,
|
||||
]
|
||||
91
packages/medusa/src/api/admin/rbac/policies/[id]/route.ts
Normal file
91
packages/medusa/src/api/admin/rbac/policies/[id]/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
deleteRbacPoliciesWorkflow,
|
||||
updateRbacPoliciesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
import { AdminUpdateRbacPolicyType } from "../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: policies } = await query.graph({
|
||||
entity: "rbac_policy",
|
||||
filters: { id: req.params.id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const policy = policies[0]
|
||||
|
||||
if (!policy) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Policy with id: ${req.params.id} not found`
|
||||
)
|
||||
}
|
||||
|
||||
res.status(200).json({ policy })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminUpdateRbacPolicyType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: existing } = await query.graph({
|
||||
entity: "rbac_policy",
|
||||
filters: { id: req.params.id },
|
||||
fields: ["id"],
|
||||
})
|
||||
|
||||
const existingPolicy = existing[0]
|
||||
if (!existingPolicy) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Policy with id "${req.params.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
const { result } = await updateRbacPoliciesWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
})
|
||||
|
||||
const { data: policies } = await query.graph({
|
||||
entity: "rbac_policy",
|
||||
filters: { id: result[0].id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const policy = policies[0]
|
||||
|
||||
res.status(200).json({ policy })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
await deleteRbacPoliciesWorkflow(req.scope).run({
|
||||
input: { ids: [id] },
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
id,
|
||||
object: "rbac_policy",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
64
packages/medusa/src/api/admin/rbac/policies/middlewares.ts
Normal file
64
packages/medusa/src/api/admin/rbac/policies/middlewares.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
validateAndTransformBody,
|
||||
validateAndTransformQuery,
|
||||
} from "@medusajs/framework"
|
||||
import { MiddlewareRoute } from "@medusajs/framework/http"
|
||||
|
||||
import {
|
||||
AdminCreateRbacPolicy,
|
||||
AdminGetRbacPoliciesParams,
|
||||
AdminGetRbacPolicyParams,
|
||||
AdminUpdateRbacPolicy,
|
||||
} from "./validators"
|
||||
|
||||
export const adminRbacPolicyRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/policies",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacPoliciesParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/policies/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacPolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/policies",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateRbacPolicy),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacPolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/policies/:id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateRbacPolicy),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacPolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/rbac/policies/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
23
packages/medusa/src/api/admin/rbac/policies/query-config.ts
Normal file
23
packages/medusa/src/api/admin/rbac/policies/query-config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const defaultAdminRbacPolicyFields = [
|
||||
"id",
|
||||
"key",
|
||||
"resource",
|
||||
"operation",
|
||||
"name",
|
||||
"description",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaults: defaultAdminRbacPolicyFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
defaultLimit: 20,
|
||||
isList: true,
|
||||
}
|
||||
50
packages/medusa/src/api/admin/rbac/policies/route.ts
Normal file
50
packages/medusa/src/api/admin/rbac/policies/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createRbacPoliciesWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
import { AdminCreateRbacPolicyType } from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
const { data: policies, metadata } = await query.graph({
|
||||
entity: "rbac_policy",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: req.filterableFields,
|
||||
pagination: req.queryConfig.pagination,
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
policies,
|
||||
count: metadata?.count ?? 0,
|
||||
offset: metadata?.skip ?? 0,
|
||||
limit: metadata?.take ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminCreateRbacPolicyType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [req.validatedBody]
|
||||
|
||||
const { result } = await createRbacPoliciesWorkflow(req.scope).run({
|
||||
input: { policies: input },
|
||||
})
|
||||
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: policies } = await query.graph({
|
||||
entity: "rbac_policy",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: { id: result[0].id },
|
||||
})
|
||||
|
||||
const policy = policies[0]
|
||||
|
||||
res.status(200).json({ policy })
|
||||
}
|
||||
57
packages/medusa/src/api/admin/rbac/policies/validators.ts
Normal file
57
packages/medusa/src/api/admin/rbac/policies/validators.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { z } from "zod"
|
||||
import { applyAndAndOrOperators } from "../../../utils/common-validators"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../../utils/validators"
|
||||
|
||||
export type AdminGetRbacPolicyParamsType = z.infer<
|
||||
typeof AdminGetRbacPolicyParams
|
||||
>
|
||||
export const AdminGetRbacPolicyParams = createSelectParams()
|
||||
|
||||
export const AdminGetRbacPoliciesParamsFields = z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
key: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
resource: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
operation: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
})
|
||||
|
||||
export type AdminGetRbacPoliciesParamsType = z.infer<
|
||||
typeof AdminGetRbacPoliciesParams
|
||||
>
|
||||
export const AdminGetRbacPoliciesParams = createFindParams({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
})
|
||||
.merge(AdminGetRbacPoliciesParamsFields)
|
||||
.merge(applyAndAndOrOperators(AdminGetRbacPoliciesParamsFields))
|
||||
|
||||
export type AdminCreateRbacPolicyType = z.infer<typeof AdminCreateRbacPolicy>
|
||||
export const AdminCreateRbacPolicy = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
resource: z.string(),
|
||||
operation: z.string(),
|
||||
name: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminUpdateRbacPolicyType = z.infer<typeof AdminUpdateRbacPolicy>
|
||||
export const AdminUpdateRbacPolicy = z
|
||||
.object({
|
||||
key: z.string().optional(),
|
||||
resource: z.string().optional(),
|
||||
operation: z.string().optional(),
|
||||
name: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
deleteRbacRolePoliciesWorkflow,
|
||||
updateRbacRolePoliciesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
import { AdminUpdateRbacRolePolicyType } from "../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: rolePolicies } = await query.graph({
|
||||
entity: "rbac_role_policy",
|
||||
filters: { id: req.params.id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const role_policy = rolePolicies[0]
|
||||
|
||||
if (!role_policy) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Role policy with id: ${req.params.id} not found`
|
||||
)
|
||||
}
|
||||
|
||||
res.status(200).json({ role_policy })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminUpdateRbacRolePolicyType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: existing } = await query.graph({
|
||||
entity: "rbac_role_policy",
|
||||
filters: { id: req.params.id },
|
||||
fields: ["id"],
|
||||
})
|
||||
|
||||
const existingRolePolicy = existing[0]
|
||||
if (!existingRolePolicy) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Role policy with id "${req.params.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
const { result } = await updateRbacRolePoliciesWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
})
|
||||
|
||||
const { data: rolePolicies } = await query.graph({
|
||||
entity: "rbac_role_policy",
|
||||
filters: { id: result[0].id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const role_policy = rolePolicies[0]
|
||||
|
||||
res.status(200).json({ role_policy })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
await deleteRbacRolePoliciesWorkflow(req.scope).run({
|
||||
input: { ids: [id] },
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
id,
|
||||
object: "rbac_role_policy",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
validateAndTransformBody,
|
||||
validateAndTransformQuery,
|
||||
} from "@medusajs/framework"
|
||||
import { MiddlewareRoute } from "@medusajs/framework/http"
|
||||
|
||||
import {
|
||||
AdminCreateRbacRolePolicy,
|
||||
AdminGetRbacRolePoliciesParams,
|
||||
AdminGetRbacRolePolicyParams,
|
||||
AdminUpdateRbacRolePolicy,
|
||||
} from "./validators"
|
||||
|
||||
export const adminRbacRolePolicyRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/role-policies",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRolePoliciesParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/role-policies/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRolePolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/role-policies",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateRbacRolePolicy),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRolePolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/role-policies/:id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateRbacRolePolicy),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRolePolicyParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/rbac/role-policies/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
export const defaultAdminRbacRolePolicyFields = [
|
||||
"id",
|
||||
"role_id",
|
||||
"scope_id",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaults: defaultAdminRbacRolePolicyFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
defaultLimit: 20,
|
||||
isList: true,
|
||||
}
|
||||
50
packages/medusa/src/api/admin/rbac/role-policies/route.ts
Normal file
50
packages/medusa/src/api/admin/rbac/role-policies/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createRbacRolePoliciesWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
import { AdminCreateRbacRolePolicyType } from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
const { data: role_policies, metadata } = await query.graph({
|
||||
entity: "rbac_role_policy",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: req.filterableFields,
|
||||
pagination: req.queryConfig.pagination,
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
role_policies,
|
||||
count: metadata?.count ?? 0,
|
||||
offset: metadata?.skip ?? 0,
|
||||
limit: metadata?.take ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminCreateRbacRolePolicyType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [req.validatedBody]
|
||||
|
||||
const { result } = await createRbacRolePoliciesWorkflow(req.scope).run({
|
||||
input: { role_policies: input },
|
||||
})
|
||||
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: rolePolicies } = await query.graph({
|
||||
entity: "rbac_role_policy",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: { id: result[0].id },
|
||||
})
|
||||
|
||||
const role_policy = rolePolicies[0]
|
||||
|
||||
res.status(200).json({ role_policy })
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod"
|
||||
import { applyAndAndOrOperators } from "../../../utils/common-validators"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../../utils/validators"
|
||||
|
||||
export type AdminGetRbacRolePolicyParamsType = z.infer<
|
||||
typeof AdminGetRbacRolePolicyParams
|
||||
>
|
||||
export const AdminGetRbacRolePolicyParams = createSelectParams()
|
||||
|
||||
export const AdminGetRbacRolePoliciesParamsFields = z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
role_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
scope_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
})
|
||||
|
||||
export type AdminGetRbacRolePoliciesParamsType = z.infer<
|
||||
typeof AdminGetRbacRolePoliciesParams
|
||||
>
|
||||
export const AdminGetRbacRolePoliciesParams = createFindParams({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
})
|
||||
.merge(AdminGetRbacRolePoliciesParamsFields)
|
||||
.merge(applyAndAndOrOperators(AdminGetRbacRolePoliciesParamsFields))
|
||||
|
||||
export type AdminCreateRbacRolePolicyType = z.infer<
|
||||
typeof AdminCreateRbacRolePolicy
|
||||
>
|
||||
export const AdminCreateRbacRolePolicy = z
|
||||
.object({
|
||||
role_id: z.string(),
|
||||
scope_id: z.string(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminUpdateRbacRolePolicyType = z.infer<
|
||||
typeof AdminUpdateRbacRolePolicy
|
||||
>
|
||||
export const AdminUpdateRbacRolePolicy = z
|
||||
.object({
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
91
packages/medusa/src/api/admin/rbac/roles/[id]/route.ts
Normal file
91
packages/medusa/src/api/admin/rbac/roles/[id]/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
deleteRbacRolesWorkflow,
|
||||
updateRbacRolesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
import { AdminUpdateRbacRoleType } from "../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: roles } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
filters: { id: req.params.id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const role = roles[0]
|
||||
|
||||
if (!role) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Role with id: ${req.params.id} not found`
|
||||
)
|
||||
}
|
||||
|
||||
res.status(200).json({ role })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminUpdateRbacRoleType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: existing } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
filters: { id: req.params.id },
|
||||
fields: ["id"],
|
||||
})
|
||||
|
||||
const existingRole = existing[0]
|
||||
if (!existingRole) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Role with id "${req.params.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
const { result } = await updateRbacRolesWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
})
|
||||
|
||||
const { data: roles } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
filters: { id: result[0].id },
|
||||
fields: req.queryConfig.fields,
|
||||
})
|
||||
|
||||
const role = roles[0]
|
||||
|
||||
res.status(200).json({ role })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
await deleteRbacRolesWorkflow(req.scope).run({
|
||||
input: { ids: [id] },
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
id,
|
||||
object: "rbac_role",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
1
packages/medusa/src/api/admin/rbac/roles/helpers.ts
Normal file
1
packages/medusa/src/api/admin/rbac/roles/helpers.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {}
|
||||
64
packages/medusa/src/api/admin/rbac/roles/middlewares.ts
Normal file
64
packages/medusa/src/api/admin/rbac/roles/middlewares.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
validateAndTransformBody,
|
||||
validateAndTransformQuery,
|
||||
} from "@medusajs/framework"
|
||||
import { MiddlewareRoute } from "@medusajs/framework/http"
|
||||
|
||||
import {
|
||||
AdminCreateRbacRole,
|
||||
AdminGetRbacRoleParams,
|
||||
AdminGetRbacRolesParams,
|
||||
AdminUpdateRbacRole,
|
||||
} from "./validators"
|
||||
|
||||
export const adminRbacRoleRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/roles",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRolesParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/rbac/roles/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRoleParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/roles",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminCreateRbacRole),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRoleParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/rbac/roles/:id",
|
||||
middlewares: [
|
||||
validateAndTransformBody(AdminUpdateRbacRole),
|
||||
validateAndTransformQuery(
|
||||
AdminGetRbacRoleParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/rbac/roles/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
21
packages/medusa/src/api/admin/rbac/roles/query-config.ts
Normal file
21
packages/medusa/src/api/admin/rbac/roles/query-config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const defaultAdminRbacRoleFields = [
|
||||
"id",
|
||||
"name",
|
||||
"parent_id",
|
||||
"description",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaults: defaultAdminRbacRoleFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
defaultLimit: 20,
|
||||
isList: true,
|
||||
}
|
||||
50
packages/medusa/src/api/admin/rbac/roles/route.ts
Normal file
50
packages/medusa/src/api/admin/rbac/roles/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createRbacRolesWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
import { AdminCreateRbacRoleType } from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
const { data: roles, metadata } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: req.filterableFields,
|
||||
pagination: req.queryConfig.pagination,
|
||||
})
|
||||
|
||||
res.status(200).json({
|
||||
roles,
|
||||
count: metadata?.count ?? 0,
|
||||
offset: metadata?.skip ?? 0,
|
||||
limit: metadata?.take ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<AdminCreateRbacRoleType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const input = [req.validatedBody]
|
||||
|
||||
const { result } = await createRbacRolesWorkflow(req.scope).run({
|
||||
input: { roles: input },
|
||||
})
|
||||
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const { data: roles } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
fields: req.queryConfig.fields,
|
||||
filters: { id: result[0].id },
|
||||
})
|
||||
|
||||
const role = roles[0]
|
||||
|
||||
res.status(200).json({ role })
|
||||
}
|
||||
50
packages/medusa/src/api/admin/rbac/roles/validators.ts
Normal file
50
packages/medusa/src/api/admin/rbac/roles/validators.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { z } from "zod"
|
||||
import { applyAndAndOrOperators } from "../../../utils/common-validators"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../../utils/validators"
|
||||
|
||||
export type AdminGetRbacRoleParamsType = z.infer<typeof AdminGetRbacRoleParams>
|
||||
export const AdminGetRbacRoleParams = createSelectParams()
|
||||
|
||||
export const AdminGetRbacRolesParamsFields = z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
name: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
parent_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
})
|
||||
|
||||
export type AdminGetRbacRolesParamsType = z.infer<
|
||||
typeof AdminGetRbacRolesParams
|
||||
>
|
||||
export const AdminGetRbacRolesParams = createFindParams({
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
})
|
||||
.merge(AdminGetRbacRolesParamsFields)
|
||||
.merge(applyAndAndOrOperators(AdminGetRbacRolesParamsFields))
|
||||
|
||||
export type AdminCreateRbacRoleType = z.infer<typeof AdminCreateRbacRole>
|
||||
export const AdminCreateRbacRole = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
parent_id: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminUpdateRbacRoleType = z.infer<typeof AdminUpdateRbacRole>
|
||||
export const AdminUpdateRbacRole = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
parent_id: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
.strict()
|
||||
@@ -27,6 +27,7 @@ import { adminProductTypeRoutesMiddlewares } from "./admin/product-types/middlew
|
||||
import { adminProductVariantRoutesMiddlewares } from "./admin/product-variants/middlewares"
|
||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRbacRoutesMiddlewares } from "./admin/rbac/middlewares"
|
||||
import { adminRefundReasonsRoutesMiddlewares } from "./admin/refund-reasons/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
import { adminReservationRoutesMiddlewares } from "./admin/reservations/middlewares"
|
||||
@@ -92,6 +93,7 @@ export default defineMiddlewares([
|
||||
...adminReturnRoutesMiddlewares,
|
||||
...storeRegionRoutesMiddlewares,
|
||||
...adminRegionRoutesMiddlewares,
|
||||
...adminRbacRoutesMiddlewares,
|
||||
...adminReturnRoutesMiddlewares,
|
||||
...adminUserRoutesMiddlewares,
|
||||
...adminInviteRoutesMiddlewares,
|
||||
|
||||
6
packages/modules/rbac/.gitignore
vendored
Normal file
6
packages/modules/rbac/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
10
packages/modules/rbac/jest.config.js
Normal file
10
packages/modules/rbac/jest.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const defineJestConfig = require("../../../define_jest_config")
|
||||
module.exports = defineJestConfig({
|
||||
moduleNameMapper: {
|
||||
"^@models": "<rootDir>/src/models",
|
||||
"^@services": "<rootDir>/src/services",
|
||||
"^@repositories": "<rootDir>/src/repositories",
|
||||
"^@types": "<rootDir>/src/types",
|
||||
"^@utils": "<rootDir>/src/utils",
|
||||
},
|
||||
})
|
||||
6
packages/modules/rbac/mikro-orm.config.dev.ts
Normal file
6
packages/modules/rbac/mikro-orm.config.dev.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineMikroOrmCliConfig } from "@medusajs/framework/utils"
|
||||
import * as entities from "./src/models"
|
||||
|
||||
export default defineMikroOrmCliConfig("rbac", {
|
||||
entities: Object.values(entities),
|
||||
})
|
||||
45
packages/modules/rbac/package.json
Normal file
45
packages/modules/rbac/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@medusajs/rbac",
|
||||
"version": "2.12.4",
|
||||
"description": "Medusa RBAC module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/__tests__",
|
||||
"!dist/**/__mocks__",
|
||||
"!dist/**/__fixtures__"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/modules/rbac"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "yarn run -T tsc --build --watch",
|
||||
"watch:test": "yarn run -T tsc --build tsconfig.spec.json --watch",
|
||||
"resolve:aliases": "yarn run -T tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && yarn run -T tsc-alias -p tsconfig.resolved.json && yarn run -T rimraf tsconfig.resolved.json",
|
||||
"build": "yarn run -T rimraf dist && yarn run -T tsc --build && npm run resolve:aliases",
|
||||
"test": "../../../node_modules/.bin/jest --passWithNoTests --bail --forceExit --testPathPattern=src",
|
||||
"test:integration": "../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/.*\\.ts\"",
|
||||
"migration:initial": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:create --initial",
|
||||
"migration:create": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:create",
|
||||
"migration:up": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:up",
|
||||
"orm:cache:clear": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm cache:clear"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@medusajs/framework": "2.12.4",
|
||||
"@medusajs/test-utils": "2.12.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/framework": "2.12.4"
|
||||
}
|
||||
}
|
||||
6
packages/modules/rbac/src/index.ts
Normal file
6
packages/modules/rbac/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Module } from "@medusajs/framework/utils"
|
||||
import { RbacModuleService } from "@services"
|
||||
|
||||
export default Module("rbac", {
|
||||
service: RbacModuleService,
|
||||
})
|
||||
568
packages/modules/rbac/src/migrations/.snapshot-medusa-rbac.json
Normal file
568
packages/modules/rbac/src/migrations/.snapshot-medusa-rbac.json
Normal file
@@ -0,0 +1,568 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"resource": {
|
||||
"name": "resource",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"operation": {
|
||||
"name": "operation",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "rbac_policy",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_rbac_policy_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_policy_deleted_at\" ON \"rbac_policy\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_policy_key_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_policy_key_unique\" ON \"rbac_policy\" (\"key\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_policy_resource",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_policy_resource\" ON \"rbac_policy\" (\"resource\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_policy_operation",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_policy_operation\" ON \"rbac_policy\" (\"operation\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_policy_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "rbac_role",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_rbac_role_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_rbac_role_deleted_at\" ON \"rbac_role\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_name_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_rbac_role_name_unique\" ON \"rbac_role\" (\"name\") WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"inherited_role_id": {
|
||||
"name": "inherited_role_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "rbac_role_inheritance",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_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"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_inherited_role_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"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_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"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_inheritance_role_id_inherited_role_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"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_inheritance_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"referencedTableName": "public.rbac_role",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"scope_id": {
|
||||
"name": "scope_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "rbac_role_policy",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_rbac_role_policy_role_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_rbac_role_policy_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"keyName": "rbac_role_policy_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"rbac_role_policy_role_id_foreign": {
|
||||
"constraintName": "rbac_role_policy_role_id_foreign",
|
||||
"columnNames": [
|
||||
"role_id"
|
||||
],
|
||||
"localTableName": "public.rbac_role_policy",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.rbac_role",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"rbac_role_policy_scope_id_foreign": {
|
||||
"constraintName": "rbac_role_policy_scope_id_foreign",
|
||||
"columnNames": [
|
||||
"scope_id"
|
||||
],
|
||||
"localTableName": "public.rbac_role_policy",
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.rbac_policy",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
}
|
||||
],
|
||||
"nativeEnums": {}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20251215113723 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" 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"));`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_policy_deleted_at" ON "rbac_policy" ("deleted_at") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_rbac_policy_key_unique" ON "rbac_policy" ("key") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_policy_resource" ON "rbac_policy" ("resource") WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_rbac_policy_operation" ON "rbac_policy" ("operation") WHERE deleted_at IS NULL;`);
|
||||
|
||||
this.addSql(`create table if not exists "rbac_role" ("id" text not null, "name" text not 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_role_pkey" primary key ("id"));`);
|
||||
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_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 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_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(`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_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;`);
|
||||
}
|
||||
|
||||
}
|
||||
4
packages/modules/rbac/src/models/index.ts
Normal file
4
packages/modules/rbac/src/models/index.ts
Normal file
@@ -0,0 +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 RbacRolePolicy } from "./rbac-role-policy"
|
||||
29
packages/modules/rbac/src/models/rbac-policy.ts
Normal file
29
packages/modules/rbac/src/models/rbac-policy.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const RbacPolicy = model
|
||||
.define("rbac_policy", {
|
||||
id: model.id({ prefix: "rpol" }).primaryKey(),
|
||||
key: model.text().searchable(),
|
||||
resource: model.text().searchable(),
|
||||
operation: model.text().searchable(),
|
||||
name: model.text().searchable().nullable(),
|
||||
description: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["key"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["resource"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["operation"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default RbacPolicy
|
||||
29
packages/modules/rbac/src/models/rbac-role-inheritance.ts
Normal file
29
packages/modules/rbac/src/models/rbac-role-inheritance.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import RbacRole from "./rbac-role"
|
||||
|
||||
const RbacRoleInheritance = model
|
||||
.define("rbac_role_inheritance", {
|
||||
id: model.id({ prefix: "rlin" }).primaryKey(),
|
||||
role: model.belongsTo(() => RbacRole, { mappedBy: "inherited_roles" }),
|
||||
inherited_role: model.belongsTo(() => RbacRole, {
|
||||
mappedBy: "inheritedBy",
|
||||
}),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["role_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["inherited_role_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["role_id", "inherited_role_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default RbacRoleInheritance
|
||||
28
packages/modules/rbac/src/models/rbac-role-policy.ts
Normal file
28
packages/modules/rbac/src/models/rbac-role-policy.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import RbacPolicy from "./rbac-policy"
|
||||
import RbacRole from "./rbac-role"
|
||||
|
||||
const RbacRolePolicy = model
|
||||
.define("rbac_role_policy", {
|
||||
id: model.id({ prefix: "rlpl" }).primaryKey(),
|
||||
role: model.belongsTo(() => RbacRole),
|
||||
scope: model.belongsTo(() => RbacPolicy),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["role_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["scope_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
on: ["role_id", "scope_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default RbacRolePolicy
|
||||
18
packages/modules/rbac/src/models/rbac-role.ts
Normal file
18
packages/modules/rbac/src/models/rbac-role.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const RbacRole = model
|
||||
.define("rbac_role", {
|
||||
id: model.id({ prefix: "role" }).primaryKey(),
|
||||
name: model.text().searchable(),
|
||||
description: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["name"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
export default RbacRole
|
||||
1
packages/modules/rbac/src/repositories/index.ts
Normal file
1
packages/modules/rbac/src/repositories/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./rbac"
|
||||
88
packages/modules/rbac/src/repositories/rbac.ts
Normal file
88
packages/modules/rbac/src/repositories/rbac.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { SqlEntityManager } from "@medusajs/framework/mikro-orm/postgresql"
|
||||
import { Context } from "@medusajs/framework/types"
|
||||
import { MikroOrmBase } from "@medusajs/framework/utils"
|
||||
|
||||
export class RbacRepository extends MikroOrmBase {
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(...arguments)
|
||||
}
|
||||
|
||||
async listPoliciesForRole(
|
||||
roleId: string,
|
||||
sharedContext: Context = {}
|
||||
): Promise<any[]> {
|
||||
const policiesByRole = await this.listPoliciesForRoles(
|
||||
[roleId],
|
||||
sharedContext
|
||||
)
|
||||
return policiesByRole.get(roleId) || []
|
||||
}
|
||||
|
||||
async listPoliciesForRoles(
|
||||
roleIds: string[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<Map<string, any[]>> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(sharedContext)
|
||||
const knex = manager.getKnex()
|
||||
|
||||
if (!roleIds?.length) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
const placeholders = roleIds.map(() => "?").join(",")
|
||||
|
||||
const query = `
|
||||
WITH RECURSIVE role_hierarchy AS (
|
||||
SELECT id, name, id as original_role_id
|
||||
FROM rbac_role
|
||||
WHERE id IN (${placeholders}) AND deleted_at IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT r.id, r.name, rh.original_role_id
|
||||
FROM rbac_role r
|
||||
INNER JOIN rbac_role_inheritance ri ON ri.inherited_role_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
|
||||
)
|
||||
SELECT DISTINCT
|
||||
rh.original_role_id,
|
||||
p.id,
|
||||
p.key,
|
||||
p.resource,
|
||||
p.operation,
|
||||
p.name,
|
||||
p.description,
|
||||
p.metadata,
|
||||
p.created_at,
|
||||
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 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
|
||||
`
|
||||
|
||||
const result = await knex.raw(query, roleIds)
|
||||
const rows = result.rows || []
|
||||
|
||||
// Group policies by role_id
|
||||
const policiesByRole = new Map<string, any[]>()
|
||||
|
||||
for (const row of rows) {
|
||||
const roleId = row.original_role_id
|
||||
delete row.original_role_id
|
||||
|
||||
if (!policiesByRole.has(roleId)) {
|
||||
policiesByRole.set(roleId, [])
|
||||
}
|
||||
|
||||
policiesByRole.get(roleId)!.push(row)
|
||||
}
|
||||
|
||||
return policiesByRole
|
||||
}
|
||||
}
|
||||
1
packages/modules/rbac/src/services/index.ts
Normal file
1
packages/modules/rbac/src/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as RbacModuleService } from "./rbac-module-service"
|
||||
105
packages/modules/rbac/src/services/rbac-module-service.ts
Normal file
105
packages/modules/rbac/src/services/rbac-module-service.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Context, FindConfig } from "@medusajs/framework/types"
|
||||
import {
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaService,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
RbacPolicy,
|
||||
RbacRole,
|
||||
RbacRoleInheritance,
|
||||
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,
|
||||
}) {
|
||||
protected readonly rbacRepository_: RbacRepository
|
||||
|
||||
constructor({ rbacRepository }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.rbacRepository_ = rbacRepository
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async listPoliciesForRole(
|
||||
roleId: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<any[]> {
|
||||
return await this.rbacRepository_.listPoliciesForRole(roleId, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
// @ts-expect-error
|
||||
async listRbacRoles(
|
||||
filters: any = {},
|
||||
config: FindConfig<any> = {},
|
||||
@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(
|
||||
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, count]
|
||||
}
|
||||
}
|
||||
1
packages/modules/rbac/src/types/index.ts
Normal file
1
packages/modules/rbac/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type RbacModuleOptions = Record<string, unknown>
|
||||
12
packages/modules/rbac/tsconfig.json
Normal file
12
packages/modules/rbac/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../../_tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@models": ["./src/models"],
|
||||
"@services": ["./src/services"],
|
||||
"@repositories": ["./src/repositories"],
|
||||
"@types": ["./src/types"],
|
||||
"@utils": ["./src/utils"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
yarn.lock
11
yarn.lock
@@ -4013,6 +4013,17 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/rbac@workspace:packages/modules/rbac":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/rbac@workspace:packages/modules/rbac"
|
||||
dependencies:
|
||||
"@medusajs/framework": 2.12.4
|
||||
"@medusajs/test-utils": 2.12.4
|
||||
peerDependencies:
|
||||
"@medusajs/framework": 2.12.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/region@2.12.4, @medusajs/region@workspace:^, @medusajs/region@workspace:packages/modules/region":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/region@workspace:packages/modules/region"
|
||||
|
||||
Reference in New Issue
Block a user