feat(rbac): role-based access control module (#14310)

This commit is contained in:
Carlos R. L. Rodrigues
2026-01-07 05:36:39 -03:00
committed by GitHub
parent d6d7d14a6a
commit 1bfde8dc57
74 changed files with 4186 additions and 3 deletions
+1
View File
@@ -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"
@@ -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)
}
)
@@ -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 () => {}
)
@@ -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 () => {}
)
@@ -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"
@@ -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)
}
)
@@ -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)
}
)
@@ -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
}
}
+1
View File
@@ -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
View 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
View File
@@ -0,0 +1,3 @@
export * from "./common"
export * from "./mutations"
export * from "./service"
+43
View 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
View 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",
}
/**
@@ -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,
]
@@ -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,
})
}
@@ -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: [],
},
]
@@ -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,
}
@@ -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 })
}
@@ -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,
}
@@ -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()
@@ -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,
})
}
@@ -0,0 +1 @@
export {}
@@ -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: [],
},
]
@@ -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,
}
@@ -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 })
}
@@ -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()
+2
View File
@@ -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
View File
@@ -0,0 +1,6 @@
/dist
node_modules
.DS_store
.env*
.env
*.sql
+10
View 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",
},
})
@@ -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
View 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
View File
@@ -0,0 +1,6 @@
import { Module } from "@medusajs/framework/utils"
import { RbacModuleService } from "@services"
export default Module("rbac", {
service: RbacModuleService,
})
@@ -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;`);
}
}
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
export * from "./rbac"
@@ -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
}
}
@@ -0,0 +1 @@
export { default as RbacModuleService } from "./rbac-module-service"
@@ -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
View File
@@ -0,0 +1 @@
export type RbacModuleOptions = Record<string, unknown>
+12
View 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"]
}
}
}