chore(medusa): middleware policies (#14521)
This commit is contained in:
committed by
GitHub
parent
cec8b8e428
commit
8426fca710
@@ -1,5 +1,5 @@
|
||||
export * from "./admin-consts"
|
||||
export * from "./clean-response-data"
|
||||
export * from "./define-middlewares"
|
||||
export * from "./exception-formatter"
|
||||
export * from "./middlewares"
|
||||
export * from "./define-middlewares"
|
||||
export * from "./admin-consts"
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
FeatureFlag,
|
||||
useCache,
|
||||
} from "@medusajs/framework/utils"
|
||||
import RbacFeatureFlag from "../../feature-flags/rbac"
|
||||
|
||||
export type PermissionAction = {
|
||||
resource: string
|
||||
operation: string
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
*
|
||||
* @property roles the role(s) to check. Can be a single string or an array of strings.
|
||||
* @property actions the action(s) to check. Can be a single `PermissionAction` or an array of `PermissionAction`s.
|
||||
* @property container the Medusa container
|
||||
*/
|
||||
export type HasPermissionInput = {
|
||||
roles: string | string[]
|
||||
actions: PermissionAction | PermissionAction[]
|
||||
container: MedusaContainer
|
||||
}
|
||||
|
||||
type RolePoliciesCache = Map<string, Map<string, Set<string>>>
|
||||
|
||||
/**
|
||||
* Checks if the given role(s) have permission to perform the specified action(s).
|
||||
*
|
||||
* @param input - The input containing roles, actions, and container
|
||||
* @returns true if all actions are permitted, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const canWrite = await hasPermission({
|
||||
* roles: ['role_123'],
|
||||
* actions: { resource: 'product', operation: 'write' },
|
||||
* container
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export async function hasPermission(
|
||||
input: HasPermissionInput
|
||||
): Promise<boolean> {
|
||||
const { roles, actions, container } = input
|
||||
|
||||
const roleIds = Array.isArray(roles) ? roles : [roles]
|
||||
const actionList = Array.isArray(actions) ? actions : [actions]
|
||||
|
||||
const isDisabled = !FeatureFlag.isFeatureEnabled(RbacFeatureFlag.key)
|
||||
if (isDisabled || !roleIds?.length || !actionList?.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
const rolePoliciesMap = await fetchRolePolicies(roleIds, container)
|
||||
|
||||
for (const action of actionList) {
|
||||
let hasAccess = false
|
||||
|
||||
for (const roleId of roleIds) {
|
||||
const resourceMap = rolePoliciesMap.get(roleId)
|
||||
if (!resourceMap) {
|
||||
continue
|
||||
}
|
||||
|
||||
const operations = resourceMap.get(action.resource)
|
||||
if (
|
||||
operations &&
|
||||
(operations.has(action.operation) || operations.has("*"))
|
||||
) {
|
||||
hasAccess = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAccess) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single role's policies from cache or database.
|
||||
*/
|
||||
async function fetchSingleRolePolicies(
|
||||
roleId: string,
|
||||
container: MedusaContainer
|
||||
): Promise<Map<string, Set<string>>> {
|
||||
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
const tags: string[] = []
|
||||
return await useCache<Map<string, Set<string>>>(
|
||||
async () => {
|
||||
const { data: roles } = await query.graph({
|
||||
entity: "rbac_role",
|
||||
fields: ["id", "policies.*"],
|
||||
filters: { id: roleId },
|
||||
})
|
||||
|
||||
const role = roles[0]
|
||||
const resourceMap = new Map<string, Set<string>>()
|
||||
|
||||
tags.push(`rbac_role:${roleId}`)
|
||||
if (role?.policies && Array.isArray(role.policies)) {
|
||||
const policyIds: string[] = []
|
||||
|
||||
for (const policy of role.policies) {
|
||||
policyIds.push(policy.id)
|
||||
|
||||
if (!resourceMap.has(policy.resource)) {
|
||||
resourceMap.set(policy.resource, new Set())
|
||||
}
|
||||
resourceMap.get(policy.resource)!.add(policy.operation)
|
||||
|
||||
tags.push(`rbac_policy:${policy.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceMap
|
||||
},
|
||||
{
|
||||
container,
|
||||
key: roleId,
|
||||
tags,
|
||||
ttl: 60 * 60 * 24 * 7,
|
||||
providers: ["cache-memory"],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches policies for multiple roles by composing individually cached role queries.
|
||||
*/
|
||||
async function fetchRolePolicies(
|
||||
roleIds: string[],
|
||||
container: MedusaContainer
|
||||
): Promise<RolePoliciesCache> {
|
||||
const rolePoliciesMap: RolePoliciesCache = new Map()
|
||||
|
||||
await Promise.all(
|
||||
roleIds.map(async (roleId) => {
|
||||
const resourceMap = await fetchSingleRolePolicies(roleId, container)
|
||||
rolePoliciesMap.set(roleId, resourceMap)
|
||||
})
|
||||
)
|
||||
|
||||
return rolePoliciesMap
|
||||
}
|
||||
Reference in New Issue
Block a user