@platformatic/db-authorization 3.29.1 → 3.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +3 -0
- package/index.js +8 -7
- package/lib/find-rule.d.ts +3 -2
- package/lib/find-rule.js +59 -1
- package/lib/schema.js +6 -0
- package/package.json +4 -4
package/index.d.ts
CHANGED
|
@@ -47,11 +47,14 @@ export type AuthorizationRule<T> = AuthorizationRuleEntity<T> | AuthorizationRul
|
|
|
47
47
|
export type SetupDBAuthorizationUserDecorator = () => Promise<void>
|
|
48
48
|
export type AddRulesForRoles = <T>(rules: Iterable<AuthorizationRule<T>>) => void
|
|
49
49
|
|
|
50
|
+
export type RoleMergeStrategy = 'first-match' | 'most-permissive'
|
|
51
|
+
|
|
50
52
|
export interface DBAuthorizationPluginOptions<T = any> extends FastifyUserPluginOptions {
|
|
51
53
|
adminSecret?: string
|
|
52
54
|
roleKey?: string
|
|
53
55
|
isRolePath?: boolean
|
|
54
56
|
anonymousRole?: string
|
|
57
|
+
roleMergeStrategy?: RoleMergeStrategy
|
|
55
58
|
rules?: Array<AuthorizationRule<T>>
|
|
56
59
|
}
|
|
57
60
|
|
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ async function auth (app, opts) {
|
|
|
13
13
|
const roleKey = opts.rolePath || opts.roleKey || 'X-PLATFORMATIC-ROLE'
|
|
14
14
|
const isRolePath = !!opts.rolePath // if `true` the role is intepreted as path like `user.role`
|
|
15
15
|
const anonymousRole = opts.anonymousRole || 'anonymous'
|
|
16
|
+
const roleMergeStrategy = opts.roleMergeStrategy || 'first-match'
|
|
16
17
|
|
|
17
18
|
app.decorateRequest('setupDBAuthorizationUser', setupUser)
|
|
18
19
|
|
|
@@ -174,7 +175,7 @@ async function auth (app, opts) {
|
|
|
174
175
|
return originalFind({ ...restOpts, where, ctx, fields })
|
|
175
176
|
}
|
|
176
177
|
const request = getRequestFromContext(ctx)
|
|
177
|
-
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)
|
|
178
|
+
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath, roleMergeStrategy)
|
|
178
179
|
checkFieldsFromRule(rule.find, fields || Object.keys(app.platformatic.entities[entityKey].fields))
|
|
179
180
|
where = await fromRuleToWhere(ctx, rule.find, where, request.user)
|
|
180
181
|
|
|
@@ -186,7 +187,7 @@ async function auth (app, opts) {
|
|
|
186
187
|
return originalSave({ ctx, input, fields, ...restOpts })
|
|
187
188
|
}
|
|
188
189
|
const request = getRequestFromContext(ctx)
|
|
189
|
-
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)
|
|
190
|
+
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath, roleMergeStrategy)
|
|
190
191
|
|
|
191
192
|
if (!rule.save) {
|
|
192
193
|
throw new Unauthorized()
|
|
@@ -237,7 +238,7 @@ async function auth (app, opts) {
|
|
|
237
238
|
return originalInsert({ inputs, ctx, fields, ...restOpts })
|
|
238
239
|
}
|
|
239
240
|
const request = getRequestFromContext(ctx)
|
|
240
|
-
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)
|
|
241
|
+
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath, roleMergeStrategy)
|
|
241
242
|
|
|
242
243
|
if (!rule.save) {
|
|
243
244
|
throw new Unauthorized()
|
|
@@ -268,7 +269,7 @@ async function auth (app, opts) {
|
|
|
268
269
|
return originalDelete({ where, ctx, fields, ...restOpts })
|
|
269
270
|
}
|
|
270
271
|
const request = getRequestFromContext(ctx)
|
|
271
|
-
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)
|
|
272
|
+
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath, roleMergeStrategy)
|
|
272
273
|
|
|
273
274
|
where = await fromRuleToWhere(ctx, rule.delete, where, request.user)
|
|
274
275
|
|
|
@@ -280,7 +281,7 @@ async function auth (app, opts) {
|
|
|
280
281
|
return originalUpdateMany({ ...restOpts, where, ctx, fields })
|
|
281
282
|
}
|
|
282
283
|
const request = getRequestFromContext(ctx)
|
|
283
|
-
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath)
|
|
284
|
+
const rule = await findRuleForRequestUser(ctx, rules, roleKey, anonymousRole, isRolePath, roleMergeStrategy)
|
|
284
285
|
|
|
285
286
|
where = await fromRuleToWhere(ctx, rule.updateMany, where, request.user)
|
|
286
287
|
|
|
@@ -357,11 +358,11 @@ async function fromRuleToWhere (ctx, rule, where, user) {
|
|
|
357
358
|
return where
|
|
358
359
|
}
|
|
359
360
|
|
|
360
|
-
async function findRuleForRequestUser (ctx, rules, roleKey, anonymousRole, isRolePath = false) {
|
|
361
|
+
async function findRuleForRequestUser (ctx, rules, roleKey, anonymousRole, isRolePath = false, roleMergeStrategy = 'first-match') {
|
|
361
362
|
const request = getRequestFromContext(ctx)
|
|
362
363
|
await request.setupDBAuthorizationUser()
|
|
363
364
|
const roles = getRoles(request, roleKey, anonymousRole, isRolePath)
|
|
364
|
-
const rule = findRule(rules, roles)
|
|
365
|
+
const rule = findRule(rules, roles, roleMergeStrategy)
|
|
365
366
|
if (!rule) {
|
|
366
367
|
ctx.reply.request.log.warn({ roles, rules }, 'no rule for roles')
|
|
367
368
|
throw new Unauthorized()
|
package/lib/find-rule.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MercuriusContext } from 'mercurius'
|
|
2
1
|
interface IRules {
|
|
3
2
|
[key: string]: {
|
|
4
3
|
role: string,
|
|
@@ -11,4 +10,6 @@ interface IRules {
|
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
export
|
|
13
|
+
export type RoleMergeStrategy = 'first-match' | 'most-permissive'
|
|
14
|
+
|
|
15
|
+
export default function findRule (rules: IRules[], roles: string[], strategy?: RoleMergeStrategy): IRules | null
|
package/lib/find-rule.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
const PLT_ADMIN_ROLE = 'platformatic-admin'
|
|
2
|
+
|
|
3
|
+
export function findRule (rules, roles, strategy = 'first-match') {
|
|
4
|
+
if (strategy === 'first-match') {
|
|
5
|
+
return findRuleFirstMatch(rules, roles)
|
|
6
|
+
}
|
|
7
|
+
return findRuleMostPermissive(rules, roles)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function findRuleFirstMatch (rules, roles) {
|
|
2
11
|
let found = null
|
|
3
12
|
for (const rule of rules) {
|
|
4
13
|
for (const role of roles) {
|
|
@@ -13,3 +22,52 @@ export function findRule (rules, roles) {
|
|
|
13
22
|
}
|
|
14
23
|
return found
|
|
15
24
|
}
|
|
25
|
+
|
|
26
|
+
function findRuleMostPermissive (rules, roles) {
|
|
27
|
+
const matchingRules = []
|
|
28
|
+
|
|
29
|
+
for (const rule of rules) {
|
|
30
|
+
for (const role of roles) {
|
|
31
|
+
if (rule.role === role) {
|
|
32
|
+
matchingRules.push(rule)
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (matchingRules.length === 0) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (matchingRules.length === 1) {
|
|
43
|
+
return matchingRules[0]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const nonAdminRules = matchingRules.filter(rule => rule.role !== PLT_ADMIN_ROLE)
|
|
47
|
+
const rulesToMerge = nonAdminRules.length > 0 ? nonAdminRules : matchingRules
|
|
48
|
+
|
|
49
|
+
if (rulesToMerge.length === 1) {
|
|
50
|
+
return rulesToMerge[0]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const mergedRule = { ...rulesToMerge[0] }
|
|
54
|
+
|
|
55
|
+
for (let i = 1; i < rulesToMerge.length; i++) {
|
|
56
|
+
const rule = rulesToMerge[i]
|
|
57
|
+
|
|
58
|
+
for (const key of Object.keys(rule)) {
|
|
59
|
+
if (key === 'role' || key === 'entity') {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const currentValue = mergedRule[key]
|
|
64
|
+
const newValue = rule[key]
|
|
65
|
+
|
|
66
|
+
if (newValue && !currentValue) {
|
|
67
|
+
mergedRule[key] = newValue
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return mergedRule
|
|
73
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -13,6 +13,12 @@ export const AuthSchema = {
|
|
|
13
13
|
rolePath: {
|
|
14
14
|
type: 'string',
|
|
15
15
|
description: 'The path in the user object that contains the roles.'
|
|
16
|
+
},
|
|
17
|
+
roleMergeStrategy: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
enum: ['first-match', 'most-permissive'],
|
|
20
|
+
default: 'first-match',
|
|
21
|
+
description: 'Strategy for merging permissions when a user has multiple roles. "first-match" returns the first matching rule (default). "most-permissive" merges all matching rules, where truthy permissions win over falsy ones.'
|
|
16
22
|
}
|
|
17
23
|
},
|
|
18
24
|
additionalProperties: true // TODO remove and add proper validation for the rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/db-authorization",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"cleaner-spec-reporter": "^0.5.0",
|
|
18
18
|
"eslint": "9",
|
|
19
19
|
"fast-jwt": "^5.0.0",
|
|
20
|
-
"fastify": "^5.
|
|
20
|
+
"fastify": "^5.7.0",
|
|
21
21
|
"get-jwks": "^11.0.0",
|
|
22
22
|
"neostandard": "^0.12.0",
|
|
23
23
|
"tsd": "^0.33.0",
|
|
24
24
|
"typescript": "^5.5.4",
|
|
25
25
|
"why-is-node-running": "^2.2.2",
|
|
26
26
|
"ws": "^8.16.0",
|
|
27
|
-
"@platformatic/db-core": "3.
|
|
28
|
-
"@platformatic/sql-mapper": "3.
|
|
27
|
+
"@platformatic/db-core": "3.31.0",
|
|
28
|
+
"@platformatic/sql-mapper": "3.31.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@fastify/error": "^4.0.0",
|