endpoint-permissions-kit 0.2.0 → 0.2.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 +2 -1
- package/dist/cjs/index.cjs +228 -201
- package/dist/cjs/index.cjs.map +7 -7
- package/dist/esm/cli/child.js +226 -4
- package/dist/esm/cli/child.js.map +6 -5
- package/dist/esm/index.js +228 -201
- package/dist/esm/index.js.map +7 -7
- package/dist/types/properties.d.ts +1 -12
- package/dist/types/state.d.ts +1 -2
- package/dist/types/types.d.ts +1 -0
- package/dist/types/validate.d.ts +0 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ Until you run `pkit generate`, TypeScript only knows the role `general`; the rol
|
|
|
66
66
|
- **One-hop grants.** Access received through a grant never counts as an assignment that activates another grant. Cycles between different names are allowed; only self-reference is rejected.
|
|
67
67
|
- **Data properties.** The keys of `data` are the fields the request touches, walked structurally and compared path by path with the permission. A declared property matches the exact leaf path: `'unicorn'` allows `unicorn` only when it is an empty object or array, and `'unicorn.name'` is what allows `{ unicorn: { name: 'x' } }`. Each `*` stands for exactly one segment and never the first one, so `'unicorn.*'` allows `unicorn.name` but not `unicorn.treasures.id`, and `'*.name'` is rejected at registration. Array elements share their container's path, so `treasures[0].id` is compared as `treasures.id` and `tags: ['a', 'b']` as `tags`; an object key is always a path segment, so `{ treasures: { '0': { id: 1 } } }` is compared as `treasures.0.id`, not `treasures.id`. No key is exempt: `constructor`, `prototype` and `__proto__` are compared and filtered like any other field.
|
|
68
68
|
- **Deny or crop.** With the default `cropper: false` any path outside the permission denies the request, and `fields` lists the index-free paths. With `pkit.context.set('cropper', true)` nothing is denied: the allowed part of `data` is copied out, arrays are compacted and the containers the crop emptied are pruned. Containers that arrived empty and are allowed stay. The `data` object you pass is never mutated.
|
|
69
|
+
- **Reserved fields.** `pkit.context.set('reservedFields', ['id', 'meta.version'])` adds paths that every permission with a property list allows, in both modes: they are never denied and always kept by the crop. The list starts empty, replaces the previous one on each call, is validated once with the rules of declared properties, and is frozen by `seal()`. Matching is exact, like any declared property.
|
|
69
70
|
- **Direct assignment precedence.** If the user holds a name of the requested module directly, that definition decides completely, even when a grant to the same target would be wider. Only when no name of the module is assigned are the applicable grants combined, and their fields are unioned.
|
|
70
71
|
- **One name per module.** A user holds at most one name of a given `role::module`, and at most one name of a module is reachable by grant. Two of either is `AMBIGUOUS_PERMISSION`: the request names no name, so the library denies instead of choosing between a wider and a narrower variant.
|
|
71
72
|
|
|
@@ -131,7 +132,7 @@ The registry lives in `globalThis[Symbol.for('endpoint-permissions-kit')]`, so t
|
|
|
131
132
|
| `src/constants.ts` | Available methods, the `general` role, the global hook owner marker and the field wildcard |
|
|
132
133
|
| `src/errors.ts` | Creates `PkitError` exceptions with a `code` and renders untrusted values for their messages |
|
|
133
134
|
| `src/state.ts` | Creates and returns the shared registry stored on `globalThis`, and guards its open/sealed lifecycle |
|
|
134
|
-
| `src/context.ts` | Declares and reads the role catalog
|
|
135
|
+
| `src/context.ts` | Declares and reads the role catalog, the `cropper` flag and the `reservedFields` paths |
|
|
135
136
|
| `src/identifiers.ts` | The `[role]::[module]::[name]` format: builds identifiers, parses them and checks each segment |
|
|
136
137
|
| `src/definitions.ts` | Reads a `registerActions` literal into the frozen definition the registry stores; grant restrictions |
|
|
137
138
|
| `src/registry.ts` | Module, name, role and grant builders; registers actions, grants and hooks |
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -120,13 +120,216 @@ function invalidIdentifier(value, code, subject) {
|
|
|
120
120
|
}
|
|
121
121
|
var identifiers_default = identifiers;
|
|
122
122
|
|
|
123
|
+
// src/properties.ts
|
|
124
|
+
var PRUNED = Symbol("pruned");
|
|
125
|
+
var MAX_DEPTH = 1000;
|
|
126
|
+
var properties = {
|
|
127
|
+
checkDeclaredPath(property, permissionPath) {
|
|
128
|
+
if (property.includes("[") || property.includes("]")) {
|
|
129
|
+
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: array indexes are not allowed in properties: "${property}"`);
|
|
130
|
+
}
|
|
131
|
+
const segments = segmentsOf(property);
|
|
132
|
+
for (const segment of segments) {
|
|
133
|
+
if (segment.length === 0)
|
|
134
|
+
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: empty segment in property path: "${property}"`);
|
|
135
|
+
}
|
|
136
|
+
if (segments[0] === constants_default.ALL_FIELDS) {
|
|
137
|
+
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: a property path cannot start with "${constants_default.ALL_FIELDS}": "${property}"`);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
resolve(data, allowed, reserved, cropper) {
|
|
141
|
+
if (allowed === constants_default.ALL_FIELDS)
|
|
142
|
+
return data;
|
|
143
|
+
const patterns = [];
|
|
144
|
+
for (const pattern of allowed.concat(reserved))
|
|
145
|
+
patterns.push(segmentsOf(pattern));
|
|
146
|
+
const walk = { patterns, segments: [], ancestors: new Set([data]) };
|
|
147
|
+
if (cropper)
|
|
148
|
+
return cropData(data, walk);
|
|
149
|
+
rejectForbiddenPaths(data, walk);
|
|
150
|
+
return data;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function segmentsOf(path) {
|
|
154
|
+
const segments = [];
|
|
155
|
+
let segment = "";
|
|
156
|
+
let index = 0;
|
|
157
|
+
while (index < path.length) {
|
|
158
|
+
const character = path[index];
|
|
159
|
+
if (character === "\\") {
|
|
160
|
+
segment += path[index + 1] ?? "";
|
|
161
|
+
index += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (character === ".") {
|
|
165
|
+
segments.push(segment);
|
|
166
|
+
segment = "";
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
segment += character;
|
|
171
|
+
index += 1;
|
|
172
|
+
}
|
|
173
|
+
segments.push(segment);
|
|
174
|
+
return segments;
|
|
175
|
+
}
|
|
176
|
+
function isAllowedPath(segments, patterns) {
|
|
177
|
+
for (const pattern of patterns) {
|
|
178
|
+
if (pattern.length !== segments.length)
|
|
179
|
+
continue;
|
|
180
|
+
let isMatch = true;
|
|
181
|
+
for (let index = 0;index < segments.length; index += 1) {
|
|
182
|
+
const patternSegment = pattern[index];
|
|
183
|
+
if (patternSegment === constants_default.ALL_FIELDS)
|
|
184
|
+
continue;
|
|
185
|
+
if (patternSegment === segments[index])
|
|
186
|
+
continue;
|
|
187
|
+
isMatch = false;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
if (isMatch)
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
function isPlainObject(value) {
|
|
196
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
197
|
+
return false;
|
|
198
|
+
const prototype = Object.getPrototypeOf(value);
|
|
199
|
+
return prototype === Object.prototype || prototype === null;
|
|
200
|
+
}
|
|
201
|
+
function requireDepth(walk) {
|
|
202
|
+
if (walk.ancestors.size < MAX_DEPTH)
|
|
203
|
+
return;
|
|
204
|
+
throw new RangeError(`data is nested deeper than ${MAX_DEPTH} levels`);
|
|
205
|
+
}
|
|
206
|
+
function setField(target, key, value) {
|
|
207
|
+
if (key === "__proto__") {
|
|
208
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
target[key] = value;
|
|
212
|
+
}
|
|
213
|
+
function cropValue(value, walk) {
|
|
214
|
+
if (Array.isArray(value)) {
|
|
215
|
+
if (value.length === 0)
|
|
216
|
+
return isAllowedPath(walk.segments, walk.patterns) ? [] : PRUNED;
|
|
217
|
+
if (walk.ancestors.has(value))
|
|
218
|
+
throw new RangeError("data contains a circular reference");
|
|
219
|
+
requireDepth(walk);
|
|
220
|
+
walk.ancestors.add(value);
|
|
221
|
+
const items = [];
|
|
222
|
+
for (const item of value) {
|
|
223
|
+
const croppedItem = cropValue(item, walk);
|
|
224
|
+
if (croppedItem === PRUNED)
|
|
225
|
+
continue;
|
|
226
|
+
items.push(croppedItem);
|
|
227
|
+
}
|
|
228
|
+
walk.ancestors.delete(value);
|
|
229
|
+
return items.length === 0 ? PRUNED : items;
|
|
230
|
+
}
|
|
231
|
+
if (isPlainObject(value)) {
|
|
232
|
+
const container = value;
|
|
233
|
+
const keys = Object.keys(container);
|
|
234
|
+
if (keys.length === 0)
|
|
235
|
+
return isAllowedPath(walk.segments, walk.patterns) ? {} : PRUNED;
|
|
236
|
+
if (walk.ancestors.has(container))
|
|
237
|
+
throw new RangeError("data contains a circular reference");
|
|
238
|
+
requireDepth(walk);
|
|
239
|
+
walk.ancestors.add(container);
|
|
240
|
+
const cropped = {};
|
|
241
|
+
let keptCount = 0;
|
|
242
|
+
for (const key of keys) {
|
|
243
|
+
walk.segments.push(key);
|
|
244
|
+
const croppedValue = cropValue(container[key], walk);
|
|
245
|
+
walk.segments.pop();
|
|
246
|
+
if (croppedValue === PRUNED)
|
|
247
|
+
continue;
|
|
248
|
+
setField(cropped, key, croppedValue);
|
|
249
|
+
keptCount += 1;
|
|
250
|
+
}
|
|
251
|
+
walk.ancestors.delete(container);
|
|
252
|
+
return keptCount === 0 ? PRUNED : cropped;
|
|
253
|
+
}
|
|
254
|
+
return isAllowedPath(walk.segments, walk.patterns) ? value : PRUNED;
|
|
255
|
+
}
|
|
256
|
+
function cropData(data, walk) {
|
|
257
|
+
const cropped = {};
|
|
258
|
+
for (const key of Object.keys(data)) {
|
|
259
|
+
walk.segments.push(key);
|
|
260
|
+
const croppedValue = cropValue(data[key], walk);
|
|
261
|
+
walk.segments.pop();
|
|
262
|
+
if (croppedValue === PRUNED)
|
|
263
|
+
continue;
|
|
264
|
+
setField(cropped, key, croppedValue);
|
|
265
|
+
}
|
|
266
|
+
return cropped;
|
|
267
|
+
}
|
|
268
|
+
function collectForbiddenPaths(value, walk, forbidden) {
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
if (value.length === 0 || walk.ancestors.has(value)) {
|
|
271
|
+
reportPath(walk, forbidden);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
requireDepth(walk);
|
|
275
|
+
walk.ancestors.add(value);
|
|
276
|
+
for (const item of value)
|
|
277
|
+
collectForbiddenPaths(item, walk, forbidden);
|
|
278
|
+
walk.ancestors.delete(value);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (isPlainObject(value)) {
|
|
282
|
+
const container = value;
|
|
283
|
+
const keys = Object.keys(container);
|
|
284
|
+
if (keys.length === 0 || walk.ancestors.has(container)) {
|
|
285
|
+
reportPath(walk, forbidden);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
requireDepth(walk);
|
|
289
|
+
walk.ancestors.add(container);
|
|
290
|
+
for (const key of keys) {
|
|
291
|
+
walk.segments.push(key);
|
|
292
|
+
collectForbiddenPaths(container[key], walk, forbidden);
|
|
293
|
+
walk.segments.pop();
|
|
294
|
+
}
|
|
295
|
+
walk.ancestors.delete(container);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
reportPath(walk, forbidden);
|
|
299
|
+
}
|
|
300
|
+
function reportPath(walk, forbidden) {
|
|
301
|
+
if (isAllowedPath(walk.segments, walk.patterns))
|
|
302
|
+
return;
|
|
303
|
+
forbidden.add(walk.segments.join("."));
|
|
304
|
+
}
|
|
305
|
+
function rejectForbiddenPaths(data, walk) {
|
|
306
|
+
const forbidden = new Set;
|
|
307
|
+
for (const key of Object.keys(data)) {
|
|
308
|
+
walk.segments.push(key);
|
|
309
|
+
collectForbiddenPaths(data[key], walk, forbidden);
|
|
310
|
+
walk.segments.pop();
|
|
311
|
+
}
|
|
312
|
+
if (forbidden.size === 0)
|
|
313
|
+
return;
|
|
314
|
+
const fields = [...forbidden];
|
|
315
|
+
const error = errors_default.create("PROPERTIES_NOT_ALLOWED", `fields not allowed: ${fields.join(", ")}`);
|
|
316
|
+
throw Object.assign(error, { fields });
|
|
317
|
+
}
|
|
318
|
+
var properties_default = properties;
|
|
319
|
+
|
|
123
320
|
// src/state.ts
|
|
124
321
|
var STATE_KEY = Symbol.for("endpoint-permissions-kit");
|
|
125
322
|
var state = {
|
|
126
323
|
getOrCreate() {
|
|
127
324
|
const stateHost = globalThis;
|
|
128
325
|
if (stateHost[STATE_KEY] === undefined) {
|
|
129
|
-
stateHost[STATE_KEY] = {
|
|
326
|
+
stateHost[STATE_KEY] = {
|
|
327
|
+
roles: new Set([constants_default.GENERAL_ROLE]),
|
|
328
|
+
cropper: false,
|
|
329
|
+
reservedFields: [],
|
|
330
|
+
modules: new Map,
|
|
331
|
+
snapshot: null
|
|
332
|
+
};
|
|
130
333
|
}
|
|
131
334
|
return stateHost[STATE_KEY];
|
|
132
335
|
},
|
|
@@ -155,16 +358,23 @@ var context = Object.freeze({
|
|
|
155
358
|
currentState.cropper = value;
|
|
156
359
|
return;
|
|
157
360
|
}
|
|
361
|
+
if (contextKey === "reservedFields") {
|
|
362
|
+
currentState.reservedFields = Object.freeze(readReservedFields(value));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
158
365
|
currentState.roles = new Set(readRoleCatalog(value, currentState.modules.size));
|
|
159
366
|
},
|
|
160
367
|
get(key) {
|
|
161
368
|
const contextKey = checkKey(key);
|
|
162
369
|
const currentState = state_default.getOrCreate();
|
|
163
|
-
|
|
370
|
+
if (contextKey === "cropper")
|
|
371
|
+
return currentState.cropper;
|
|
372
|
+
const paths = contextKey === "roles" ? [...currentState.roles] : [...currentState.reservedFields];
|
|
373
|
+
return paths;
|
|
164
374
|
}
|
|
165
375
|
});
|
|
166
376
|
function checkKey(key) {
|
|
167
|
-
if (key === "roles" || key === "cropper")
|
|
377
|
+
if (key === "roles" || key === "cropper" || key === "reservedFields")
|
|
168
378
|
return key;
|
|
169
379
|
throw errors_default.create("INVALID_DEFINITION", `unknown context key: "${errors_default.describe(key)}"`);
|
|
170
380
|
}
|
|
@@ -186,6 +396,18 @@ function readRoleCatalog(value, registeredModuleCount) {
|
|
|
186
396
|
identifiers_default.checkRole(role);
|
|
187
397
|
return catalog;
|
|
188
398
|
}
|
|
399
|
+
function readReservedFields(value) {
|
|
400
|
+
if (!Array.isArray(value))
|
|
401
|
+
throw errors_default.create("INVALID_DEFINITION", "reservedFields must be an array of property paths");
|
|
402
|
+
const fields = [];
|
|
403
|
+
for (const field of value) {
|
|
404
|
+
if (typeof field !== "string")
|
|
405
|
+
throw errors_default.create("INVALID_DEFINITION", "reservedFields must be an array of property paths");
|
|
406
|
+
properties_default.checkDeclaredPath(field, "reservedFields");
|
|
407
|
+
fields.push(field);
|
|
408
|
+
}
|
|
409
|
+
return fields;
|
|
410
|
+
}
|
|
189
411
|
var context_default = context;
|
|
190
412
|
|
|
191
413
|
// src/resolve.ts
|
|
@@ -317,202 +539,6 @@ var permissions = Object.freeze({
|
|
|
317
539
|
});
|
|
318
540
|
var permissions_default = permissions;
|
|
319
541
|
|
|
320
|
-
// src/properties.ts
|
|
321
|
-
var PRUNED = Symbol("pruned");
|
|
322
|
-
var MAX_DEPTH = 1000;
|
|
323
|
-
var properties = {
|
|
324
|
-
checkDeclaredPath(property, permissionPath) {
|
|
325
|
-
if (property.includes("[") || property.includes("]")) {
|
|
326
|
-
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: array indexes are not allowed in properties: "${property}"`);
|
|
327
|
-
}
|
|
328
|
-
const segments = segmentsOf(property);
|
|
329
|
-
for (const segment of segments) {
|
|
330
|
-
if (segment.length === 0)
|
|
331
|
-
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: empty segment in property path: "${property}"`);
|
|
332
|
-
}
|
|
333
|
-
if (segments[0] === constants_default.ALL_FIELDS) {
|
|
334
|
-
throw errors_default.create("INVALID_DEFINITION", `${permissionPath}: a property path cannot start with "${constants_default.ALL_FIELDS}": "${property}"`);
|
|
335
|
-
}
|
|
336
|
-
},
|
|
337
|
-
resolve(data, allowed, cropper) {
|
|
338
|
-
if (allowed === constants_default.ALL_FIELDS)
|
|
339
|
-
return data;
|
|
340
|
-
const patterns = [];
|
|
341
|
-
for (const pattern of allowed)
|
|
342
|
-
patterns.push(segmentsOf(pattern));
|
|
343
|
-
const walk = { patterns, segments: [], ancestors: new Set([data]) };
|
|
344
|
-
if (cropper)
|
|
345
|
-
return cropData(data, walk);
|
|
346
|
-
rejectForbiddenPaths(data, walk);
|
|
347
|
-
return data;
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
function segmentsOf(path) {
|
|
351
|
-
const segments = [];
|
|
352
|
-
let segment = "";
|
|
353
|
-
let index = 0;
|
|
354
|
-
while (index < path.length) {
|
|
355
|
-
const character = path[index];
|
|
356
|
-
if (character === "\\") {
|
|
357
|
-
segment += path[index + 1] ?? "";
|
|
358
|
-
index += 2;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (character === ".") {
|
|
362
|
-
segments.push(segment);
|
|
363
|
-
segment = "";
|
|
364
|
-
index += 1;
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
segment += character;
|
|
368
|
-
index += 1;
|
|
369
|
-
}
|
|
370
|
-
segments.push(segment);
|
|
371
|
-
return segments;
|
|
372
|
-
}
|
|
373
|
-
function isAllowedPath(segments, patterns) {
|
|
374
|
-
for (const pattern of patterns) {
|
|
375
|
-
if (pattern.length !== segments.length)
|
|
376
|
-
continue;
|
|
377
|
-
let isMatch = true;
|
|
378
|
-
for (let index = 0;index < segments.length; index += 1) {
|
|
379
|
-
const patternSegment = pattern[index];
|
|
380
|
-
if (patternSegment === constants_default.ALL_FIELDS)
|
|
381
|
-
continue;
|
|
382
|
-
if (patternSegment === segments[index])
|
|
383
|
-
continue;
|
|
384
|
-
isMatch = false;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
if (isMatch)
|
|
388
|
-
return true;
|
|
389
|
-
}
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
function isPlainObject(value) {
|
|
393
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
394
|
-
return false;
|
|
395
|
-
const prototype = Object.getPrototypeOf(value);
|
|
396
|
-
return prototype === Object.prototype || prototype === null;
|
|
397
|
-
}
|
|
398
|
-
function requireDepth(walk) {
|
|
399
|
-
if (walk.ancestors.size < MAX_DEPTH)
|
|
400
|
-
return;
|
|
401
|
-
throw new RangeError(`data is nested deeper than ${MAX_DEPTH} levels`);
|
|
402
|
-
}
|
|
403
|
-
function setField(target, key, value) {
|
|
404
|
-
if (key === "__proto__") {
|
|
405
|
-
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
target[key] = value;
|
|
409
|
-
}
|
|
410
|
-
function cropValue(value, walk) {
|
|
411
|
-
if (Array.isArray(value)) {
|
|
412
|
-
if (value.length === 0)
|
|
413
|
-
return isAllowedPath(walk.segments, walk.patterns) ? [] : PRUNED;
|
|
414
|
-
if (walk.ancestors.has(value))
|
|
415
|
-
throw new RangeError("data contains a circular reference");
|
|
416
|
-
requireDepth(walk);
|
|
417
|
-
walk.ancestors.add(value);
|
|
418
|
-
const items = [];
|
|
419
|
-
for (const item of value) {
|
|
420
|
-
const croppedItem = cropValue(item, walk);
|
|
421
|
-
if (croppedItem === PRUNED)
|
|
422
|
-
continue;
|
|
423
|
-
items.push(croppedItem);
|
|
424
|
-
}
|
|
425
|
-
walk.ancestors.delete(value);
|
|
426
|
-
return items.length === 0 ? PRUNED : items;
|
|
427
|
-
}
|
|
428
|
-
if (isPlainObject(value)) {
|
|
429
|
-
const container = value;
|
|
430
|
-
const keys = Object.keys(container);
|
|
431
|
-
if (keys.length === 0)
|
|
432
|
-
return isAllowedPath(walk.segments, walk.patterns) ? {} : PRUNED;
|
|
433
|
-
if (walk.ancestors.has(container))
|
|
434
|
-
throw new RangeError("data contains a circular reference");
|
|
435
|
-
requireDepth(walk);
|
|
436
|
-
walk.ancestors.add(container);
|
|
437
|
-
const cropped = {};
|
|
438
|
-
let keptCount = 0;
|
|
439
|
-
for (const key of keys) {
|
|
440
|
-
walk.segments.push(key);
|
|
441
|
-
const croppedValue = cropValue(container[key], walk);
|
|
442
|
-
walk.segments.pop();
|
|
443
|
-
if (croppedValue === PRUNED)
|
|
444
|
-
continue;
|
|
445
|
-
setField(cropped, key, croppedValue);
|
|
446
|
-
keptCount += 1;
|
|
447
|
-
}
|
|
448
|
-
walk.ancestors.delete(container);
|
|
449
|
-
return keptCount === 0 ? PRUNED : cropped;
|
|
450
|
-
}
|
|
451
|
-
return isAllowedPath(walk.segments, walk.patterns) ? value : PRUNED;
|
|
452
|
-
}
|
|
453
|
-
function cropData(data, walk) {
|
|
454
|
-
const cropped = {};
|
|
455
|
-
for (const key of Object.keys(data)) {
|
|
456
|
-
walk.segments.push(key);
|
|
457
|
-
const croppedValue = cropValue(data[key], walk);
|
|
458
|
-
walk.segments.pop();
|
|
459
|
-
if (croppedValue === PRUNED)
|
|
460
|
-
continue;
|
|
461
|
-
setField(cropped, key, croppedValue);
|
|
462
|
-
}
|
|
463
|
-
return cropped;
|
|
464
|
-
}
|
|
465
|
-
function collectForbiddenPaths(value, walk, forbidden) {
|
|
466
|
-
if (Array.isArray(value)) {
|
|
467
|
-
if (value.length === 0 || walk.ancestors.has(value)) {
|
|
468
|
-
reportPath(walk, forbidden);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
requireDepth(walk);
|
|
472
|
-
walk.ancestors.add(value);
|
|
473
|
-
for (const item of value)
|
|
474
|
-
collectForbiddenPaths(item, walk, forbidden);
|
|
475
|
-
walk.ancestors.delete(value);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
if (isPlainObject(value)) {
|
|
479
|
-
const container = value;
|
|
480
|
-
const keys = Object.keys(container);
|
|
481
|
-
if (keys.length === 0 || walk.ancestors.has(container)) {
|
|
482
|
-
reportPath(walk, forbidden);
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
requireDepth(walk);
|
|
486
|
-
walk.ancestors.add(container);
|
|
487
|
-
for (const key of keys) {
|
|
488
|
-
walk.segments.push(key);
|
|
489
|
-
collectForbiddenPaths(container[key], walk, forbidden);
|
|
490
|
-
walk.segments.pop();
|
|
491
|
-
}
|
|
492
|
-
walk.ancestors.delete(container);
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
reportPath(walk, forbidden);
|
|
496
|
-
}
|
|
497
|
-
function reportPath(walk, forbidden) {
|
|
498
|
-
if (isAllowedPath(walk.segments, walk.patterns))
|
|
499
|
-
return;
|
|
500
|
-
forbidden.add(walk.segments.join("."));
|
|
501
|
-
}
|
|
502
|
-
function rejectForbiddenPaths(data, walk) {
|
|
503
|
-
const forbidden = new Set;
|
|
504
|
-
for (const key of Object.keys(data)) {
|
|
505
|
-
walk.segments.push(key);
|
|
506
|
-
collectForbiddenPaths(data[key], walk, forbidden);
|
|
507
|
-
walk.segments.pop();
|
|
508
|
-
}
|
|
509
|
-
if (forbidden.size === 0)
|
|
510
|
-
return;
|
|
511
|
-
const fields = [...forbidden];
|
|
512
|
-
throw Object.assign(errors_default.create("PROPERTIES_NOT_ALLOWED", `fields not allowed: ${fields.join(", ")}`), { fields });
|
|
513
|
-
}
|
|
514
|
-
var properties_default = properties;
|
|
515
|
-
|
|
516
542
|
// src/definitions.ts
|
|
517
543
|
var METHODS = new Set(constants_default.METHODS);
|
|
518
544
|
var definitions = {
|
|
@@ -837,7 +863,8 @@ function prepareRequest(input, currentState) {
|
|
|
837
863
|
if (group)
|
|
838
864
|
hooks.push(...group);
|
|
839
865
|
}
|
|
840
|
-
|
|
866
|
+
const allowedData = properties_default.resolve(data, access.properties, currentState.reservedFields, currentState.cropper);
|
|
867
|
+
return { hooks, context, permissions, data: allowedData };
|
|
841
868
|
}
|
|
842
869
|
function readOptionalObject(value, message) {
|
|
843
870
|
if (value === undefined)
|
|
@@ -876,4 +903,4 @@ var defineModule = registry_default.defineModule;
|
|
|
876
903
|
var pkit = Object.freeze({ context: context_default, module: defineModule, seal, permissions: permissions_default, validate });
|
|
877
904
|
var src_default = pkit;
|
|
878
905
|
|
|
879
|
-
//# debugId=
|
|
906
|
+
//# debugId=276C6E13E676044E64756E2164756E21
|