@platformatic/db-authorization 3.4.1 → 3.5.1
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/README.md +1 -1
- package/eslint.config.js +2 -6
- package/index.d.ts +5 -5
- package/index.js +41 -43
- package/lib/errors.js +8 -9
- package/lib/find-rule.js +1 -5
- package/{schema.js → lib/schema.js} +6 -10
- package/lib/utils.js +2 -8
- package/package.json +17 -13
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Fastify plugin that adds role-based authorization hooks to [`@platformatic/sql-mapper`](https://www.npmjs.com/package/@platformatic/sql-mapper).
|
|
4
4
|
|
|
5
|
-
Check out the full documentation on [our website](https://docs.platformatic.dev/docs/db/authorization/overview).
|
|
5
|
+
Check out the full documentation on [our website](https://docs.platformatic.dev/docs/reference/db/authorization/overview).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
package/eslint.config.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type PlatformaticContext,
|
|
3
|
-
type
|
|
3
|
+
type WhereClause,
|
|
4
4
|
} from '@platformatic/sql-mapper'
|
|
5
5
|
import { type FastifyPluginAsync } from 'fastify'
|
|
6
6
|
import { type FastifyUserPluginOptions } from 'fastify-user'
|
|
@@ -9,11 +9,11 @@ import { FastifyError } from '@fastify/error'
|
|
|
9
9
|
export type OperationFunction<T> = (args: {
|
|
10
10
|
user: T,
|
|
11
11
|
ctx: PlatformaticContext,
|
|
12
|
-
where:
|
|
13
|
-
}) =>
|
|
12
|
+
where: WhereClause
|
|
13
|
+
}) => WhereClause
|
|
14
14
|
|
|
15
15
|
export interface OperationChecks {
|
|
16
|
-
checks: Record<string, any> |
|
|
16
|
+
checks: Record<string, any> | WhereClause
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface OperationFields {
|
|
@@ -52,7 +52,7 @@ export interface DBAuthorizationPluginOptions<T = any> extends FastifyUserPlugin
|
|
|
52
52
|
roleKey?: string
|
|
53
53
|
isRolePath?: boolean
|
|
54
54
|
anonymousRole?: string
|
|
55
|
-
rules
|
|
55
|
+
rules?: Array<AuthorizationRule<T>>
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export interface DBAuthorizationPluginInterface {
|
package/index.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const findRule = require('./lib/find-rule')
|
|
9
|
-
const { getRequestFromContext, getRoles } = require('./lib/utils')
|
|
10
|
-
const {
|
|
11
|
-
Unauthorized,
|
|
12
|
-
UnauthorizedField,
|
|
13
|
-
MissingNotNullableError,
|
|
14
|
-
} = require('./lib/errors')
|
|
1
|
+
import fp from 'fastify-plugin'
|
|
2
|
+
import fastifyUser from 'fastify-user'
|
|
3
|
+
import leven from 'leven'
|
|
4
|
+
import { MissingNotNullableError, Unauthorized, UnauthorizedField } from './lib/errors.js'
|
|
5
|
+
import { findRule } from './lib/find-rule.js'
|
|
6
|
+
import { getRequestFromContext, getRoles } from './lib/utils.js'
|
|
15
7
|
|
|
16
8
|
const PLT_ADMIN_ROLE = 'platformatic-admin'
|
|
17
9
|
|
|
@@ -49,7 +41,7 @@ async function auth (app, opts) {
|
|
|
49
41
|
value = PLT_ADMIN_ROLE
|
|
50
42
|
}
|
|
51
43
|
return value
|
|
52
|
-
}
|
|
44
|
+
}
|
|
53
45
|
})
|
|
54
46
|
}
|
|
55
47
|
}
|
|
@@ -58,14 +50,14 @@ async function auth (app, opts) {
|
|
|
58
50
|
// We replace just the role in `request.user`, all the rest is untouched
|
|
59
51
|
request.user = {
|
|
60
52
|
...request.user,
|
|
61
|
-
[roleKey]: PLT_ADMIN_ROLE
|
|
53
|
+
[roleKey]: PLT_ADMIN_ROLE
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
const rules = opts.rules || []
|
|
67
59
|
|
|
68
|
-
app.platformatic.addRulesForRoles =
|
|
60
|
+
app.platformatic.addRulesForRoles = _rules => {
|
|
69
61
|
for (const rule of _rules) {
|
|
70
62
|
rules.push(rule)
|
|
71
63
|
}
|
|
@@ -76,14 +68,17 @@ async function auth (app, opts) {
|
|
|
76
68
|
// There is an unknown entity. Let's find out the nearest one for a nice error message
|
|
77
69
|
const entities = Object.keys(app.platformatic.entities)
|
|
78
70
|
|
|
79
|
-
const nearest = entities.reduce(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
acc.distance
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
const nearest = entities.reduce(
|
|
72
|
+
(acc, entity) => {
|
|
73
|
+
const distance = leven(ruleEntity, entity)
|
|
74
|
+
if (distance < acc.distance) {
|
|
75
|
+
acc.distance = distance
|
|
76
|
+
acc.entity = entity
|
|
77
|
+
}
|
|
78
|
+
return acc
|
|
79
|
+
},
|
|
80
|
+
{ distance: Infinity, entity: null }
|
|
81
|
+
)
|
|
87
82
|
return nearest
|
|
88
83
|
}
|
|
89
84
|
|
|
@@ -105,7 +100,9 @@ async function auth (app, opts) {
|
|
|
105
100
|
const newRule = { ...rule, entity: ruleEntity, entities: undefined }
|
|
106
101
|
if (!app.platformatic.entities[newRule.entity]) {
|
|
107
102
|
const nearest = findNearestEntity(ruleEntity)
|
|
108
|
-
throw new Error(
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Unknown entity '${ruleEntity}' in authorization rule ${i}. Did you mean '${nearest.entity}'?`
|
|
105
|
+
)
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
if (!entityRules[ruleEntity]) {
|
|
@@ -131,7 +128,9 @@ async function auth (app, opts) {
|
|
|
131
128
|
}
|
|
132
129
|
const keys = Object.keys(checks)
|
|
133
130
|
if (keys.length !== 1) {
|
|
134
|
-
throw new Error(
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Subscription requires that the role "${rule.role}" has only one check in the find rule for entity "${rule.entity}"`
|
|
133
|
+
)
|
|
135
134
|
}
|
|
136
135
|
const key = keys[0]
|
|
137
136
|
|
|
@@ -153,7 +152,7 @@ async function auth (app, opts) {
|
|
|
153
152
|
role: PLT_ADMIN_ROLE,
|
|
154
153
|
find: true,
|
|
155
154
|
save: true,
|
|
156
|
-
delete: true
|
|
155
|
+
delete: true
|
|
157
156
|
})
|
|
158
157
|
}
|
|
159
158
|
|
|
@@ -219,7 +218,7 @@ async function auth (app, opts) {
|
|
|
219
218
|
const found = await type.find({
|
|
220
219
|
where,
|
|
221
220
|
ctx,
|
|
222
|
-
fields
|
|
221
|
+
fields
|
|
223
222
|
})
|
|
224
223
|
|
|
225
224
|
if (found.length === 0) {
|
|
@@ -311,7 +310,7 @@ async function auth (app, opts) {
|
|
|
311
310
|
}
|
|
312
311
|
|
|
313
312
|
return originalTopic
|
|
314
|
-
}
|
|
313
|
+
}
|
|
315
314
|
})
|
|
316
315
|
}
|
|
317
316
|
})
|
|
@@ -333,19 +332,19 @@ async function fromRuleToWhere (ctx, rule, where, user) {
|
|
|
333
332
|
for (const key of Object.keys(checks)) {
|
|
334
333
|
const clauses = checks[key]
|
|
335
334
|
if (typeof clauses === 'string') {
|
|
336
|
-
|
|
335
|
+
// case: "userId": "X-PLATFORMATIC-USER-ID"
|
|
337
336
|
where[key] = {
|
|
338
|
-
eq: request.user[clauses]
|
|
337
|
+
eq: request.user[clauses]
|
|
339
338
|
}
|
|
340
339
|
} else {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
340
|
+
// case:
|
|
341
|
+
// userId: {
|
|
342
|
+
// eq: 'X-PLATFORMATIC-USER-ID'
|
|
343
|
+
// }
|
|
345
344
|
for (const clauseKey of Object.keys(clauses)) {
|
|
346
345
|
const clause = clauses[clauseKey]
|
|
347
346
|
where[key] = {
|
|
348
|
-
[clauseKey]: request.user[clause]
|
|
347
|
+
[clauseKey]: request.user[clause]
|
|
349
348
|
}
|
|
350
349
|
}
|
|
351
350
|
}
|
|
@@ -412,10 +411,9 @@ function checkInputFromRuleFields (rule, inputs) {
|
|
|
412
411
|
|
|
413
412
|
function checkSaveMandatoryFieldsInRules (type, rules) {
|
|
414
413
|
// List of not nullable, not PKs field to validate save/insert when allowed fields are specified on the rule
|
|
415
|
-
const mandatoryFields =
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
.map(({ camelcase }) => (camelcase))
|
|
414
|
+
const mandatoryFields = Object.values(type.fields)
|
|
415
|
+
.filter(k => !k.isNullable && !k.primaryKey)
|
|
416
|
+
.map(({ camelcase }) => camelcase)
|
|
419
417
|
|
|
420
418
|
for (const rule of rules) {
|
|
421
419
|
const { entity, save } = rule
|
|
@@ -430,5 +428,5 @@ function checkSaveMandatoryFieldsInRules (type, rules) {
|
|
|
430
428
|
}
|
|
431
429
|
}
|
|
432
430
|
|
|
433
|
-
|
|
434
|
-
|
|
431
|
+
export default fp(auth)
|
|
432
|
+
export * as errors from './lib/errors.js'
|
package/lib/errors.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import createError from '@fastify/error'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const ERROR_PREFIX = 'PLT_DB_AUTH'
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
5
|
+
export const Unauthorized = createError(`${ERROR_PREFIX}_UNAUTHORIZED`, 'operation not allowed', 401)
|
|
6
|
+
export const UnauthorizedField = createError(`${ERROR_PREFIX}_FIELD_UNAUTHORIZED`, 'field not allowed: %s', 401)
|
|
7
|
+
export const MissingNotNullableError = createError(
|
|
8
|
+
`${ERROR_PREFIX}_NOT_NULLABLE_MISSING`,
|
|
9
|
+
'missing not nullable field: "%s" in save rule for entity "%s"'
|
|
10
|
+
)
|
package/lib/find-rule.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function findRule (rules, roles) {
|
|
1
|
+
export function findRule (rules, roles) {
|
|
4
2
|
let found = null
|
|
5
3
|
for (const rule of rules) {
|
|
6
4
|
for (const role of roles) {
|
|
@@ -15,5 +13,3 @@ function findRule (rules, roles) {
|
|
|
15
13
|
}
|
|
16
14
|
return found
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
module.exports = findRule
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const AuthSchema = {
|
|
1
|
+
export const AuthSchema = {
|
|
4
2
|
$id: '/BasegraphAuth',
|
|
5
3
|
type: 'object',
|
|
6
4
|
properties: {
|
|
7
5
|
adminSecret: {
|
|
8
6
|
type: 'string',
|
|
9
|
-
description: 'The password should be used to access routes under /_admin prefix.'
|
|
7
|
+
description: 'The password should be used to access routes under /_admin prefix.'
|
|
10
8
|
},
|
|
11
9
|
roleKey: {
|
|
12
10
|
type: 'string',
|
|
13
|
-
description: 'The key in the user object that contains the roles.'
|
|
11
|
+
description: 'The key in the user object that contains the roles.'
|
|
14
12
|
},
|
|
15
13
|
rolePath: {
|
|
16
14
|
type: 'string',
|
|
17
|
-
description: 'The path in the user object that contains the roles.'
|
|
18
|
-
}
|
|
15
|
+
description: 'The path in the user object that contains the roles.'
|
|
16
|
+
}
|
|
19
17
|
},
|
|
20
|
-
additionalProperties: true
|
|
18
|
+
additionalProperties: true // TODO remove and add proper validation for the rules
|
|
21
19
|
}
|
|
22
|
-
|
|
23
|
-
module.exports = AuthSchema
|
package/lib/utils.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function getRequestFromContext (ctx) {
|
|
1
|
+
export function getRequestFromContext (ctx) {
|
|
4
2
|
if (ctx && !ctx.reply) {
|
|
5
3
|
throw new Error('Missing reply in context. You should call this function with { ctx: { reply }}')
|
|
6
4
|
}
|
|
7
5
|
return ctx.reply.request
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
function getRoles (request, roleKey, anonymousRole, isRolePath = false) {
|
|
8
|
+
export function getRoles (request, roleKey, anonymousRole, isRolePath = false) {
|
|
11
9
|
let output = []
|
|
12
10
|
const user = request.user
|
|
13
11
|
if (!user) {
|
|
@@ -37,7 +35,3 @@ function getRoles (request, roleKey, anonymousRole, isRolePath = false) {
|
|
|
37
35
|
|
|
38
36
|
return output
|
|
39
37
|
}
|
|
40
|
-
module.exports = {
|
|
41
|
-
getRequestFromContext,
|
|
42
|
-
getRoles,
|
|
43
|
-
}
|
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/db-authorization",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"types": "index.d.ts",
|
|
7
|
-
"author": "
|
|
8
|
+
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
11
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
11
12
|
},
|
|
12
13
|
"license": "Apache-2.0",
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"@fastify/cookie": "^
|
|
15
|
+
"@fastify/cookie": "^11.0.0",
|
|
15
16
|
"@fastify/session": "^11.0.0",
|
|
16
|
-
"
|
|
17
|
-
"borp": "^0.17.0",
|
|
17
|
+
"cleaner-spec-reporter": "^0.5.0",
|
|
18
18
|
"eslint": "9",
|
|
19
|
-
"fast-jwt": "^
|
|
19
|
+
"fast-jwt": "^5.0.0",
|
|
20
20
|
"fastify": "^5.0.0",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"get-jwks": "^11.0.0",
|
|
22
|
+
"neostandard": "^0.12.0",
|
|
23
|
+
"tsd": "^0.33.0",
|
|
23
24
|
"typescript": "^5.5.4",
|
|
24
25
|
"why-is-node-running": "^2.2.2",
|
|
25
26
|
"ws": "^8.16.0",
|
|
26
|
-
"
|
|
27
|
-
"@platformatic/db-core": "3.
|
|
28
|
-
"@platformatic/sql-mapper": "3.4.1"
|
|
27
|
+
"@platformatic/sql-mapper": "3.5.1",
|
|
28
|
+
"@platformatic/db-core": "3.5.1"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@fastify/error": "^4.0.0",
|
|
@@ -33,13 +33,17 @@
|
|
|
33
33
|
"fastify-plugin": "^5.0.0",
|
|
34
34
|
"fastify-user": "^1.0.2",
|
|
35
35
|
"leven": "~3.1.0",
|
|
36
|
-
"undici": "^
|
|
36
|
+
"undici": "^7.0.0"
|
|
37
37
|
},
|
|
38
38
|
"tsd": {
|
|
39
39
|
"directory": "test/types"
|
|
40
40
|
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.19.0"
|
|
43
|
+
},
|
|
41
44
|
"scripts": {
|
|
42
|
-
"test": "
|
|
45
|
+
"test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
46
|
+
"posttest": "tsd",
|
|
43
47
|
"lint": "eslint"
|
|
44
48
|
}
|
|
45
49
|
}
|