feathers-casl
Version:
Add access control with CASL to your feathers application.
88 lines (78 loc) • 2.79 kB
text/typescript
import { rulesToQuery } from '@casl/ability/extra'
import { mergeQuery } from '@fratzinger/feathers-utils'
import _isEmpty from 'lodash/isEmpty.js'
import { getAdapter } from '../hooks/authorize/authorize.hook.utils.js'
import { convertRuleToQuery } from './convertRuleToQuery.js'
import { hasRestrictingConditions } from './hasRestrictingConditions.js'
import { simplifyQuery } from './simplifyQuery.js'
import type { AnyAbility } from '@casl/ability'
import type { Application, Query } from '@feathersjs/feathers'
import type { AdapterBase } from '@feathersjs/adapter-commons'
import type { Adapter, AuthorizeHookOptions } from '../types.js'
const adaptersFor$not: Adapter[] = []
const adaptersFor$notAsArray: Adapter[] = ['feathers-sequelize']
const adaptersFor$nor: Adapter[] = ['@feathersjs/memory', '@feathersjs/mongodb']
export const mergeQueryFromAbility = <T>(
app: Application,
ability: AnyAbility,
method: string,
modelName: string,
originalQuery: Query,
service: AdapterBase<T>,
options: Pick<AuthorizeHookOptions, 'adapter'>,
): Query => {
if (!hasRestrictingConditions(ability, method, modelName)) {
return originalQuery
}
const adapter = getAdapter(app, options)
let query: Query | null
if (adaptersFor$not.includes(adapter)) {
// nedb
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule
return rule.inverted ? { $not: conditions } : conditions
})
query = simplifyQuery(query)
} else if (adaptersFor$notAsArray.includes(adapter)) {
// objection, sequelize
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule
return rule.inverted ? { $not: [conditions] } : conditions
})
query = simplifyQuery(query)
} else if (adaptersFor$nor.includes(adapter)) {
// memory, mongoose, mongodb
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule
return rule.inverted ? { $nor: [conditions] } : conditions
})
query = simplifyQuery(query)
} else {
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule
return rule.inverted ? convertRuleToQuery(rule) : conditions
})
query = simplifyQuery(query)
if (query?.$and) {
const { $and } = query
delete query.$and
$and.forEach((q: any) => {
query = mergeQuery(query as Query, q, {
defaultHandle: 'intersect',
useLogicalConjunction: true,
})
})
}
}
if (_isEmpty(query)) {
return originalQuery
}
if (!originalQuery) {
return query
} else {
return mergeQuery(originalQuery, query, {
defaultHandle: 'intersect',
useLogicalConjunction: true,
})
}
}