@bedrockio/model 0.1.5 → 0.1.6
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/dist/cjs/access.js +30 -16
- package/dist/cjs/errors.js +6 -1
- package/dist/cjs/validation.js +22 -5
- package/package.json +3 -3
- package/src/access.js +35 -17
- package/src/errors.js +7 -1
- package/src/validation.js +27 -6
- package/types/access.d.ts.map +1 -1
- package/types/errors.d.ts +2 -0
- package/types/errors.d.ts.map +1 -1
- package/types/search.d.ts +3 -0
- package/types/search.d.ts.map +1 -1
- package/types/validation.d.ts.map +1 -1
package/dist/cjs/access.js
CHANGED
|
@@ -29,19 +29,17 @@ function hasAccess(type, allowed = 'all', options = {}) {
|
|
|
29
29
|
document,
|
|
30
30
|
authUser
|
|
31
31
|
} = options;
|
|
32
|
-
if (!Array.isArray(allowed)) {
|
|
33
|
-
allowed = [allowed];
|
|
34
|
-
}
|
|
35
32
|
const scopes = resolveScopes(options);
|
|
33
|
+
allowed = resolveAllowed(allowed);
|
|
36
34
|
return allowed.some(token => {
|
|
37
35
|
if (token === 'self') {
|
|
38
|
-
assertOptions(
|
|
36
|
+
assertOptions(token, options);
|
|
39
37
|
return document.id == authUser.id;
|
|
40
38
|
} else if (token === 'user') {
|
|
41
|
-
assertOptions(
|
|
39
|
+
assertOptions(token, options);
|
|
42
40
|
return document.user?.id == authUser.id;
|
|
43
41
|
} else if (token === 'owner') {
|
|
44
|
-
assertOptions(
|
|
42
|
+
assertOptions(token, options);
|
|
45
43
|
return document.owner?.id == authUser.id;
|
|
46
44
|
} else {
|
|
47
45
|
return scopes?.includes(token);
|
|
@@ -53,18 +51,34 @@ function resolveScopes(options) {
|
|
|
53
51
|
if (!options.scope && !options.scopes) {
|
|
54
52
|
(0, _warn.default)('Scopes were requested but not provided.');
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
scopes =
|
|
59
|
-
}
|
|
60
|
-
|
|
54
|
+
let scopes;
|
|
55
|
+
if (options.scopes) {
|
|
56
|
+
scopes = options.scopes;
|
|
57
|
+
} else if (options.scope) {
|
|
58
|
+
scopes = [options.scope];
|
|
59
|
+
} else {
|
|
60
|
+
scopes = [];
|
|
61
|
+
}
|
|
62
|
+
return scopes;
|
|
61
63
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
function resolveAllowed(arg) {
|
|
65
|
+
const allowed = Array.isArray(arg) ? arg : [arg];
|
|
66
|
+
|
|
67
|
+
// Sort allowed scopes to put "self" last allowing
|
|
68
|
+
// role based scopes to be fulfilled first.
|
|
69
|
+
allowed.sort((a, b) => {
|
|
70
|
+
if (a === b) {
|
|
71
|
+
return 0;
|
|
72
|
+
} else if (a === 'self') {
|
|
73
|
+
return 1;
|
|
66
74
|
} else {
|
|
67
|
-
|
|
75
|
+
return -1;
|
|
68
76
|
}
|
|
77
|
+
});
|
|
78
|
+
return allowed;
|
|
79
|
+
}
|
|
80
|
+
function assertOptions(token, options) {
|
|
81
|
+
if (!options.authUser || !options.document) {
|
|
82
|
+
throw new _errors.ImplementationError(token);
|
|
69
83
|
}
|
|
70
84
|
}
|
package/dist/cjs/errors.js
CHANGED
|
@@ -6,7 +6,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.ReferenceError = exports.PermissionsError = exports.ImplementationError = void 0;
|
|
7
7
|
class PermissionsError extends Error {}
|
|
8
8
|
exports.PermissionsError = PermissionsError;
|
|
9
|
-
class ImplementationError extends Error {
|
|
9
|
+
class ImplementationError extends Error {
|
|
10
|
+
constructor(name) {
|
|
11
|
+
super();
|
|
12
|
+
this.name = name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
10
15
|
exports.ImplementationError = ImplementationError;
|
|
11
16
|
class ReferenceError extends Error {
|
|
12
17
|
constructor(message, references) {
|
package/dist/cjs/validation.js
CHANGED
|
@@ -333,14 +333,31 @@ function validateAccess(type, schema, allowed, options) {
|
|
|
333
333
|
} = options.model;
|
|
334
334
|
return schema.custom((val, options) => {
|
|
335
335
|
const document = options[(0, _lodash.lowerFirst)(modelName)] || options['document'];
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
let isAllowed;
|
|
337
|
+
try {
|
|
338
|
+
isAllowed = (0, _access.hasAccess)(type, allowed, {
|
|
339
|
+
...options,
|
|
340
|
+
document
|
|
341
|
+
});
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error instanceof _errors.ImplementationError) {
|
|
344
|
+
if (type === 'read') {
|
|
345
|
+
// Read access validation for search means that "self" access
|
|
346
|
+
// cannot be fulfilled as there is no single document to test
|
|
347
|
+
// against, so continue on to throw a normal permissions error
|
|
348
|
+
// here instead of raising a problem with the implementation.
|
|
349
|
+
isAllowed = false;
|
|
350
|
+
} else if (type === 'write') {
|
|
351
|
+
throw new Error(`Write access "${error.name}" requires passing { document, authUser } to the validator.`);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
340
357
|
if (!isAllowed) {
|
|
341
358
|
const currentValue = (0, _lodash.get)(document, options.path);
|
|
342
359
|
if (val !== currentValue) {
|
|
343
|
-
throw new _errors.PermissionsError(
|
|
360
|
+
throw new _errors.PermissionsError(`requires ${type} permissions.`);
|
|
344
361
|
}
|
|
345
362
|
}
|
|
346
363
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/model",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Bedrock utilities for model creation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lodash": "^4.17.21"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@bedrockio/yada": "^1.0.
|
|
32
|
+
"@bedrockio/yada": "^1.0.25",
|
|
33
33
|
"mongoose": "^6.9.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@babel/core": "^7.20.12",
|
|
38
38
|
"@babel/preset-env": "^7.20.2",
|
|
39
39
|
"@bedrockio/prettier-config": "^1.0.2",
|
|
40
|
-
"@bedrockio/yada": "^1.0.
|
|
40
|
+
"@bedrockio/yada": "^1.0.25",
|
|
41
41
|
"@shelf/jest-mongodb": "^4.1.6",
|
|
42
42
|
"babel-plugin-import-replacement": "^1.0.1",
|
|
43
43
|
"eslint": "^8.33.0",
|
package/src/access.js
CHANGED
|
@@ -19,19 +19,18 @@ export function hasAccess(type, allowed = 'all', options = {}) {
|
|
|
19
19
|
return false;
|
|
20
20
|
} else {
|
|
21
21
|
const { document, authUser } = options;
|
|
22
|
-
if (!Array.isArray(allowed)) {
|
|
23
|
-
allowed = [allowed];
|
|
24
|
-
}
|
|
25
22
|
const scopes = resolveScopes(options);
|
|
23
|
+
|
|
24
|
+
allowed = resolveAllowed(allowed);
|
|
26
25
|
return allowed.some((token) => {
|
|
27
26
|
if (token === 'self') {
|
|
28
|
-
assertOptions(
|
|
27
|
+
assertOptions(token, options);
|
|
29
28
|
return document.id == authUser.id;
|
|
30
29
|
} else if (token === 'user') {
|
|
31
|
-
assertOptions(
|
|
30
|
+
assertOptions(token, options);
|
|
32
31
|
return document.user?.id == authUser.id;
|
|
33
32
|
} else if (token === 'owner') {
|
|
34
|
-
assertOptions(
|
|
33
|
+
assertOptions(token, options);
|
|
35
34
|
return document.owner?.id == authUser.id;
|
|
36
35
|
} else {
|
|
37
36
|
return scopes?.includes(token);
|
|
@@ -44,20 +43,39 @@ function resolveScopes(options) {
|
|
|
44
43
|
if (!options.scope && !options.scopes) {
|
|
45
44
|
warn('Scopes were requested but not provided.');
|
|
46
45
|
}
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
|
|
47
|
+
let scopes;
|
|
48
|
+
if (options.scopes) {
|
|
49
|
+
scopes = options.scopes;
|
|
50
|
+
} else if (options.scope) {
|
|
51
|
+
scopes = [options.scope];
|
|
52
|
+
} else {
|
|
53
|
+
scopes = [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return scopes;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
function resolveAllowed(arg) {
|
|
60
|
+
const allowed = Array.isArray(arg) ? arg : [arg];
|
|
61
|
+
|
|
62
|
+
// Sort allowed scopes to put "self" last allowing
|
|
63
|
+
// role based scopes to be fulfilled first.
|
|
64
|
+
allowed.sort((a, b) => {
|
|
65
|
+
if (a === b) {
|
|
66
|
+
return 0;
|
|
67
|
+
} else if (a === 'self') {
|
|
68
|
+
return 1;
|
|
57
69
|
} else {
|
|
58
|
-
|
|
59
|
-
`Write access "${token}" requires passing { document, authUser } to the validator.`
|
|
60
|
-
);
|
|
70
|
+
return -1;
|
|
61
71
|
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return allowed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assertOptions(token, options) {
|
|
78
|
+
if (!options.authUser || !options.document) {
|
|
79
|
+
throw new ImplementationError(token);
|
|
62
80
|
}
|
|
63
81
|
}
|
package/src/errors.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export class PermissionsError extends Error {}
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
export class ImplementationError extends Error {
|
|
4
|
+
constructor(name) {
|
|
5
|
+
super();
|
|
6
|
+
this.name = name;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
3
9
|
|
|
4
10
|
export class ReferenceError extends Error {
|
|
5
11
|
constructor(message, references) {
|
package/src/validation.js
CHANGED
|
@@ -5,7 +5,7 @@ import { get, omit, lowerFirst } from 'lodash';
|
|
|
5
5
|
|
|
6
6
|
import { hasAccess } from './access';
|
|
7
7
|
import { searchValidation } from './search';
|
|
8
|
-
import { PermissionsError } from './errors';
|
|
8
|
+
import { PermissionsError, ImplementationError } from './errors';
|
|
9
9
|
import { hasUniqueConstraints, assertUnique } from './soft-delete';
|
|
10
10
|
import { isMongooseSchema, isSchemaTypedef } from './utils';
|
|
11
11
|
import { RESERVED_FIELDS } from './schema';
|
|
@@ -368,14 +368,35 @@ function validateAccess(type, schema, allowed, options) {
|
|
|
368
368
|
const { modelName } = options.model;
|
|
369
369
|
return schema.custom((val, options) => {
|
|
370
370
|
const document = options[lowerFirst(modelName)] || options['document'];
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
|
|
372
|
+
let isAllowed;
|
|
373
|
+
try {
|
|
374
|
+
isAllowed = hasAccess(type, allowed, {
|
|
375
|
+
...options,
|
|
376
|
+
document,
|
|
377
|
+
});
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (error instanceof ImplementationError) {
|
|
380
|
+
if (type === 'read') {
|
|
381
|
+
// Read access validation for search means that "self" access
|
|
382
|
+
// cannot be fulfilled as there is no single document to test
|
|
383
|
+
// against, so continue on to throw a normal permissions error
|
|
384
|
+
// here instead of raising a problem with the implementation.
|
|
385
|
+
isAllowed = false;
|
|
386
|
+
} else if (type === 'write') {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Write access "${error.name}" requires passing { document, authUser } to the validator.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
375
396
|
if (!isAllowed) {
|
|
376
397
|
const currentValue = get(document, options.path);
|
|
377
398
|
if (val !== currentValue) {
|
|
378
|
-
throw new PermissionsError(
|
|
399
|
+
throw new PermissionsError(`requires ${type} permissions.`);
|
|
379
400
|
}
|
|
380
401
|
}
|
|
381
402
|
});
|
package/types/access.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.js"],"names":[],"mappings":"AAGA,mEAEC;AAED,oEAEC;AAED;;GAEG;AACH,+CAFW,MAAM,GAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.js"],"names":[],"mappings":"AAGA,mEAEC;AAED,oEAEC;AAED;;GAEG;AACH,+CAFW,MAAM,GAAC,MAAM,EAAE,yBA2BzB"}
|
package/types/errors.d.ts
CHANGED
package/types/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"AAAA;CAA8C;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"AAAA;CAA8C;AAE9C;IACE,uBAGC;IADC,UAAgB;CAEnB;AAED;IACE,2CAGC;IADC,gBAA4B;CAE/B"}
|
package/types/search.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export function searchValidation(definition: any, options?: {}): {
|
|
|
3
3
|
setup: any;
|
|
4
4
|
getFields: any;
|
|
5
5
|
append(arg: import("@bedrockio/yada/types/Schema").default | import("@bedrockio/yada/types/object").SchemaMap): any;
|
|
6
|
+
pick(...names?: string[]): any;
|
|
7
|
+
omit(...names?: string[]): any;
|
|
6
8
|
format(name: any, fn: any): import("@bedrockio/yada/types/TypeSchema").default;
|
|
7
9
|
toString(): any;
|
|
8
10
|
assertions: any[];
|
|
@@ -20,6 +22,7 @@ export function searchValidation(definition: any, options?: {}): {
|
|
|
20
22
|
validate(value: any, options?: {}): Promise<any>;
|
|
21
23
|
clone(meta: any): any;
|
|
22
24
|
toOpenApi(extra: any): any;
|
|
25
|
+
expandExtra(extra?: {}): {};
|
|
23
26
|
assertEnum(set: any, allow: any): any;
|
|
24
27
|
assert(type: any, fn: any): any;
|
|
25
28
|
pushAssertion(assertion: any): void;
|
package/types/search.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAmBA,gEAyDC;AAED
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAmBA,gEAyDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAiEA,kDAEC;AAED,oEA2DC;AAaD,wEAeC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAiEA,kDAEC;AAED,oEA2DC;AAaD,wEAeC;AA0PD;;;EAEC;AAED;;;EAOC;AA1YD,8EAUK"}
|