@bedrockio/model 0.1.5 → 0.1.7
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/include.js +10 -0
- package/dist/cjs/search.js +6 -2
- package/dist/cjs/validation.js +30 -6
- package/package.json +3 -3
- package/src/access.js +35 -17
- package/src/errors.js +7 -1
- package/src/include.js +13 -0
- package/src/search.js +6 -2
- package/src/validation.js +34 -7
- 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/include.d.ts +40 -0
- package/types/include.d.ts.map +1 -1
- package/types/search.d.ts +4 -1
- 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/include.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.INCLUDE_FIELD_SCHEMA = void 0;
|
|
6
7
|
exports.applyInclude = applyInclude;
|
|
7
8
|
exports.checkSelects = checkSelects;
|
|
8
9
|
exports.getIncludes = getIncludes;
|
|
9
10
|
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
10
11
|
var _lodash = require("lodash");
|
|
12
|
+
var _yada = _interopRequireDefault(require("@bedrockio/yada"));
|
|
11
13
|
var _utils = require("./utils");
|
|
12
14
|
var _const = require("./const");
|
|
13
15
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -19,6 +21,11 @@ _mongoose.default.Query.prototype.include = function include(paths) {
|
|
|
19
21
|
filter.include = paths;
|
|
20
22
|
return this;
|
|
21
23
|
};
|
|
24
|
+
const DESCRIPTION = 'Field to be selected or populated.';
|
|
25
|
+
const INCLUDE_FIELD_SCHEMA = _yada.default.object({
|
|
26
|
+
include: _yada.default.allow(_yada.default.string().description(DESCRIPTION), _yada.default.array(_yada.default.string().description(DESCRIPTION)))
|
|
27
|
+
});
|
|
28
|
+
exports.INCLUDE_FIELD_SCHEMA = INCLUDE_FIELD_SCHEMA;
|
|
22
29
|
function applyInclude(schema) {
|
|
23
30
|
schema.virtual('include').set(function (include) {
|
|
24
31
|
this.$locals.include = include;
|
|
@@ -138,6 +145,9 @@ function nodeToPopulates(node) {
|
|
|
138
145
|
function pathsToNode(paths, modelName) {
|
|
139
146
|
const node = {};
|
|
140
147
|
for (let str of paths) {
|
|
148
|
+
if (typeof str !== 'string') {
|
|
149
|
+
throw new Error('Provided include path was not as string.');
|
|
150
|
+
}
|
|
141
151
|
let exclude = false;
|
|
142
152
|
if (str.startsWith('-')) {
|
|
143
153
|
exclude = true;
|
package/dist/cjs/search.js
CHANGED
|
@@ -87,7 +87,6 @@ function searchValidation(definition, options = {}) {
|
|
|
87
87
|
return _yada.default.object({
|
|
88
88
|
ids: _yada.default.array(_validation.OBJECT_ID_SCHEMA),
|
|
89
89
|
keyword: _yada.default.string().description('A keyword to perform a text search against.'),
|
|
90
|
-
include: _yada.default.string().description('Fields to be selected or populated.'),
|
|
91
90
|
skip: _yada.default.number().default(0).description('Number of records to skip.'),
|
|
92
91
|
sort: _yada.default.allow(SORT_SCHEMA, _yada.default.array(SORT_SCHEMA)).default(sort),
|
|
93
92
|
limit: _yada.default.number().positive().default(limit).description('Limits the number of results.'),
|
|
@@ -227,8 +226,10 @@ function isNestedQuery(key, value) {
|
|
|
227
226
|
return !isMongoOperator(key);
|
|
228
227
|
});
|
|
229
228
|
}
|
|
229
|
+
|
|
230
|
+
// Exclude "include" here as a special case.
|
|
230
231
|
function isArrayQuery(key, value) {
|
|
231
|
-
return !isMongoOperator(key) && Array.isArray(value);
|
|
232
|
+
return !isMongoOperator(key) && !isInclude(key) && Array.isArray(value);
|
|
232
233
|
}
|
|
233
234
|
function isRangeQuery(schema, key, value) {
|
|
234
235
|
// Range queries only allowed on Date and Number fields.
|
|
@@ -247,6 +248,9 @@ function mapOperatorQuery(obj) {
|
|
|
247
248
|
function isMongoOperator(str) {
|
|
248
249
|
return str.startsWith('$');
|
|
249
250
|
}
|
|
251
|
+
function isInclude(str) {
|
|
252
|
+
return str === 'include';
|
|
253
|
+
}
|
|
250
254
|
|
|
251
255
|
// Regex queries
|
|
252
256
|
|
package/dist/cjs/validation.js
CHANGED
|
@@ -18,6 +18,7 @@ var _errors = require("./errors");
|
|
|
18
18
|
var _softDelete = require("./soft-delete");
|
|
19
19
|
var _utils = require("./utils");
|
|
20
20
|
var _schema = require("./schema");
|
|
21
|
+
var _include = require("./include");
|
|
21
22
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
22
23
|
const DATE_SCHEMA = _yada.default.date().iso().tag({
|
|
23
24
|
'x-schema': 'DateTime',
|
|
@@ -74,6 +75,7 @@ function applyValidation(schema, definition) {
|
|
|
74
75
|
return getSchemaFromMongoose(schema, {
|
|
75
76
|
model: this,
|
|
76
77
|
appendSchema,
|
|
78
|
+
allowIncludes: true,
|
|
77
79
|
stripReserved: true,
|
|
78
80
|
requireWriteAccess: true,
|
|
79
81
|
...(hasUnique && {
|
|
@@ -104,6 +106,7 @@ function applyValidation(schema, definition) {
|
|
|
104
106
|
return getSchemaFromMongoose(schema, {
|
|
105
107
|
allowSearch: true,
|
|
106
108
|
skipRequired: true,
|
|
109
|
+
allowIncludes: true,
|
|
107
110
|
expandDotSyntax: true,
|
|
108
111
|
unwindArrayFields: true,
|
|
109
112
|
requireReadAccess: true,
|
|
@@ -132,7 +135,8 @@ function getSchemaFromMongoose(schema, options = {}) {
|
|
|
132
135
|
function getValidationSchema(attributes, options = {}) {
|
|
133
136
|
const {
|
|
134
137
|
appendSchema,
|
|
135
|
-
assertUniqueOptions
|
|
138
|
+
assertUniqueOptions,
|
|
139
|
+
allowIncludes
|
|
136
140
|
} = options;
|
|
137
141
|
let schema = getObjectSchema(attributes, options);
|
|
138
142
|
if (assertUniqueOptions) {
|
|
@@ -148,6 +152,9 @@ function getValidationSchema(attributes, options = {}) {
|
|
|
148
152
|
if (appendSchema) {
|
|
149
153
|
schema = schema.append(appendSchema);
|
|
150
154
|
}
|
|
155
|
+
if (allowIncludes) {
|
|
156
|
+
schema = schema.append(_include.INCLUDE_FIELD_SCHEMA);
|
|
157
|
+
}
|
|
151
158
|
return schema;
|
|
152
159
|
}
|
|
153
160
|
function getObjectSchema(arg, options) {
|
|
@@ -333,14 +340,31 @@ function validateAccess(type, schema, allowed, options) {
|
|
|
333
340
|
} = options.model;
|
|
334
341
|
return schema.custom((val, options) => {
|
|
335
342
|
const document = options[(0, _lodash.lowerFirst)(modelName)] || options['document'];
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
let isAllowed;
|
|
344
|
+
try {
|
|
345
|
+
isAllowed = (0, _access.hasAccess)(type, allowed, {
|
|
346
|
+
...options,
|
|
347
|
+
document
|
|
348
|
+
});
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error instanceof _errors.ImplementationError) {
|
|
351
|
+
if (type === 'read') {
|
|
352
|
+
// Read access validation for search means that "self" access
|
|
353
|
+
// cannot be fulfilled as there is no single document to test
|
|
354
|
+
// against, so continue on to throw a normal permissions error
|
|
355
|
+
// here instead of raising a problem with the implementation.
|
|
356
|
+
isAllowed = false;
|
|
357
|
+
} else if (type === 'write') {
|
|
358
|
+
throw new Error(`Write access "${error.name}" requires passing { document, authUser } to the validator.`);
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
340
364
|
if (!isAllowed) {
|
|
341
365
|
const currentValue = (0, _lodash.get)(document, options.path);
|
|
342
366
|
if (val !== currentValue) {
|
|
343
|
-
throw new _errors.PermissionsError(
|
|
367
|
+
throw new _errors.PermissionsError(`requires ${type} permissions.`);
|
|
344
368
|
}
|
|
345
369
|
}
|
|
346
370
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/model",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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/include.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import mongoose from 'mongoose';
|
|
2
2
|
import { escapeRegExp } from 'lodash';
|
|
3
|
+
import yd from '@bedrockio/yada';
|
|
3
4
|
|
|
4
5
|
import { resolveInnerField } from './utils';
|
|
5
6
|
import { POPULATE_MAX_DEPTH } from './const';
|
|
@@ -13,6 +14,15 @@ mongoose.Query.prototype.include = function include(paths) {
|
|
|
13
14
|
return this;
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
const DESCRIPTION = 'Field to be selected or populated.';
|
|
18
|
+
|
|
19
|
+
export const INCLUDE_FIELD_SCHEMA = yd.object({
|
|
20
|
+
include: yd.allow(
|
|
21
|
+
yd.string().description(DESCRIPTION),
|
|
22
|
+
yd.array(yd.string().description(DESCRIPTION))
|
|
23
|
+
),
|
|
24
|
+
});
|
|
25
|
+
|
|
16
26
|
export function applyInclude(schema) {
|
|
17
27
|
schema.virtual('include').set(function (include) {
|
|
18
28
|
this.$locals.include = include;
|
|
@@ -126,6 +136,9 @@ function nodeToPopulates(node) {
|
|
|
126
136
|
function pathsToNode(paths, modelName) {
|
|
127
137
|
const node = {};
|
|
128
138
|
for (let str of paths) {
|
|
139
|
+
if (typeof str !== 'string') {
|
|
140
|
+
throw new Error('Provided include path was not as string.');
|
|
141
|
+
}
|
|
129
142
|
let exclude = false;
|
|
130
143
|
if (str.startsWith('-')) {
|
|
131
144
|
exclude = true;
|
package/src/search.js
CHANGED
|
@@ -90,7 +90,6 @@ export function searchValidation(definition, options = {}) {
|
|
|
90
90
|
keyword: yd
|
|
91
91
|
.string()
|
|
92
92
|
.description('A keyword to perform a text search against.'),
|
|
93
|
-
include: yd.string().description('Fields to be selected or populated.'),
|
|
94
93
|
skip: yd.number().default(0).description('Number of records to skip.'),
|
|
95
94
|
sort: yd.allow(SORT_SCHEMA, yd.array(SORT_SCHEMA)).default(sort),
|
|
96
95
|
limit: yd
|
|
@@ -230,8 +229,9 @@ function isNestedQuery(key, value) {
|
|
|
230
229
|
});
|
|
231
230
|
}
|
|
232
231
|
|
|
232
|
+
// Exclude "include" here as a special case.
|
|
233
233
|
function isArrayQuery(key, value) {
|
|
234
|
-
return !isMongoOperator(key) && Array.isArray(value);
|
|
234
|
+
return !isMongoOperator(key) && !isInclude(key) && Array.isArray(value);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
function isRangeQuery(schema, key, value) {
|
|
@@ -254,6 +254,10 @@ function isMongoOperator(str) {
|
|
|
254
254
|
return str.startsWith('$');
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
function isInclude(str) {
|
|
258
|
+
return str === 'include';
|
|
259
|
+
}
|
|
260
|
+
|
|
257
261
|
// Regex queries
|
|
258
262
|
|
|
259
263
|
const REGEX_QUERY = /^\/(.+)\/(\w*)$/;
|
package/src/validation.js
CHANGED
|
@@ -5,10 +5,11 @@ 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';
|
|
12
|
+
import { INCLUDE_FIELD_SCHEMA } from './include';
|
|
12
13
|
|
|
13
14
|
const DATE_SCHEMA = yd.date().iso().tag({
|
|
14
15
|
'x-schema': 'DateTime',
|
|
@@ -76,6 +77,7 @@ export function applyValidation(schema, definition) {
|
|
|
76
77
|
return getSchemaFromMongoose(schema, {
|
|
77
78
|
model: this,
|
|
78
79
|
appendSchema,
|
|
80
|
+
allowIncludes: true,
|
|
79
81
|
stripReserved: true,
|
|
80
82
|
requireWriteAccess: true,
|
|
81
83
|
...(hasUnique && {
|
|
@@ -114,6 +116,7 @@ export function applyValidation(schema, definition) {
|
|
|
114
116
|
return getSchemaFromMongoose(schema, {
|
|
115
117
|
allowSearch: true,
|
|
116
118
|
skipRequired: true,
|
|
119
|
+
allowIncludes: true,
|
|
117
120
|
expandDotSyntax: true,
|
|
118
121
|
unwindArrayFields: true,
|
|
119
122
|
requireReadAccess: true,
|
|
@@ -140,7 +143,7 @@ function getSchemaFromMongoose(schema, options = {}) {
|
|
|
140
143
|
|
|
141
144
|
// Exported for testing
|
|
142
145
|
export function getValidationSchema(attributes, options = {}) {
|
|
143
|
-
const { appendSchema, assertUniqueOptions } = options;
|
|
146
|
+
const { appendSchema, assertUniqueOptions, allowIncludes } = options;
|
|
144
147
|
let schema = getObjectSchema(attributes, options);
|
|
145
148
|
if (assertUniqueOptions) {
|
|
146
149
|
schema = schema.custom(async (obj, { root }) => {
|
|
@@ -153,6 +156,9 @@ export function getValidationSchema(attributes, options = {}) {
|
|
|
153
156
|
if (appendSchema) {
|
|
154
157
|
schema = schema.append(appendSchema);
|
|
155
158
|
}
|
|
159
|
+
if (allowIncludes) {
|
|
160
|
+
schema = schema.append(INCLUDE_FIELD_SCHEMA);
|
|
161
|
+
}
|
|
156
162
|
return schema;
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -368,14 +374,35 @@ function validateAccess(type, schema, allowed, options) {
|
|
|
368
374
|
const { modelName } = options.model;
|
|
369
375
|
return schema.custom((val, options) => {
|
|
370
376
|
const document = options[lowerFirst(modelName)] || options['document'];
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
377
|
+
|
|
378
|
+
let isAllowed;
|
|
379
|
+
try {
|
|
380
|
+
isAllowed = hasAccess(type, allowed, {
|
|
381
|
+
...options,
|
|
382
|
+
document,
|
|
383
|
+
});
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (error instanceof ImplementationError) {
|
|
386
|
+
if (type === 'read') {
|
|
387
|
+
// Read access validation for search means that "self" access
|
|
388
|
+
// cannot be fulfilled as there is no single document to test
|
|
389
|
+
// against, so continue on to throw a normal permissions error
|
|
390
|
+
// here instead of raising a problem with the implementation.
|
|
391
|
+
isAllowed = false;
|
|
392
|
+
} else if (type === 'write') {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Write access "${error.name}" requires passing { document, authUser } to the validator.`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
375
402
|
if (!isAllowed) {
|
|
376
403
|
const currentValue = get(document, options.path);
|
|
377
404
|
if (val !== currentValue) {
|
|
378
|
-
throw new PermissionsError(
|
|
405
|
+
throw new PermissionsError(`requires ${type} permissions.`);
|
|
379
406
|
}
|
|
380
407
|
}
|
|
381
408
|
});
|
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/include.d.ts
CHANGED
|
@@ -1,4 +1,44 @@
|
|
|
1
1
|
export function applyInclude(schema: any): void;
|
|
2
2
|
export function checkSelects(doc: any, ret: any): void;
|
|
3
3
|
export function getIncludes(modelName: any, arg: any): any;
|
|
4
|
+
export const INCLUDE_FIELD_SCHEMA: {
|
|
5
|
+
setup: any;
|
|
6
|
+
getFields: any;
|
|
7
|
+
append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): any;
|
|
8
|
+
pick(...names?: string[]): any;
|
|
9
|
+
omit(...names?: string[]): any;
|
|
10
|
+
format(name: any, fn: any): import("@bedrockio/yada/types/TypeSchema").default;
|
|
11
|
+
toString(): any;
|
|
12
|
+
assertions: any[];
|
|
13
|
+
meta: {};
|
|
14
|
+
required(): any;
|
|
15
|
+
default(value: any): any;
|
|
16
|
+
custom(...args: import("@bedrockio/yada/types/Schema").CustomSignature): any;
|
|
17
|
+
strip(strip: any): any;
|
|
18
|
+
allow(...set: any[]): any;
|
|
19
|
+
reject(...set: any[]): any;
|
|
20
|
+
message(message: any): any;
|
|
21
|
+
tag(tags: any): any;
|
|
22
|
+
description(description: any): any;
|
|
23
|
+
options(options: any): any;
|
|
24
|
+
validate(value: any, options?: {}): Promise<any>;
|
|
25
|
+
clone(meta: any): any;
|
|
26
|
+
toOpenApi(extra: any): any;
|
|
27
|
+
expandExtra(extra?: {}): {};
|
|
28
|
+
assertEnum(set: any, allow: any): any;
|
|
29
|
+
assert(type: any, fn: any): any;
|
|
30
|
+
pushAssertion(assertion: any): void;
|
|
31
|
+
transform(fn: any): any;
|
|
32
|
+
getSortIndex(type: any): number;
|
|
33
|
+
runAssertion(assertion: any, value: any, options?: {}): Promise<any>;
|
|
34
|
+
enumToOpenApi(): {
|
|
35
|
+
type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
|
|
36
|
+
enum: any;
|
|
37
|
+
oneOf?: undefined;
|
|
38
|
+
} | {
|
|
39
|
+
oneOf: any[];
|
|
40
|
+
type?: undefined;
|
|
41
|
+
enum?: undefined;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
4
44
|
//# sourceMappingURL=include.d.ts.map
|
package/types/include.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AAyBA,gDAoCC;AAMD,uDA4BC;AAGD,2DAIC;AApFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKG"}
|
package/types/search.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ export function applySearch(schema: any, definition: any): void;
|
|
|
2
2
|
export function searchValidation(definition: any, options?: {}): {
|
|
3
3
|
setup: any;
|
|
4
4
|
getFields: any;
|
|
5
|
-
append(arg: import("@bedrockio/yada/types/
|
|
5
|
+
append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkEA,kDAEC;AAED,oEA6DC;AAaD,wEAkBC;AA0PD;;;EAEC;AAED;;;EAOC;AA/YD,8EAUK"}
|