@web-ts-toolkit/access-router 0.55.0 → 0.56.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/README.md +128 -23
- package/advanced.d.mts +1 -1
- package/advanced.d.ts +1 -1
- package/advanced.js +12 -2
- package/advanced.mjs +12 -2
- package/index.d.mts +2 -2
- package/index.d.ts +2 -2
- package/index.js +103 -43
- package/index.mjs +103 -43
- package/llms.txt +3 -2
- package/package.json +3 -3
- package/{parsers-BrVRL2fg.d.mts → parsers-BXKoUKMa.d.mts} +59 -8
- package/{parsers-BrVRL2fg.d.ts → parsers-BXKoUKMa.d.ts} +59 -8
package/index.js
CHANGED
|
@@ -2251,7 +2251,8 @@ function normalizeSelection(select, catalog, label) {
|
|
|
2251
2251
|
includeId: selection.includes("_id")
|
|
2252
2252
|
};
|
|
2253
2253
|
}
|
|
2254
|
-
function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
2254
|
+
function captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
|
|
2255
|
+
const requireReadOperationAccessForPopulateDependencies = globalOptions.requireReadOperationAccessForPopulateDependencies !== false;
|
|
2255
2256
|
const model = runtime.getModelInstance(modelName);
|
|
2256
2257
|
const targets = /* @__PURE__ */ new Map();
|
|
2257
2258
|
const scopes = [];
|
|
@@ -2300,6 +2301,7 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
|
2300
2301
|
{
|
|
2301
2302
|
catalog,
|
|
2302
2303
|
documentPermissionField,
|
|
2304
|
+
requireReadOperationAccessForPopulateDependencies,
|
|
2303
2305
|
operationAccess: { read: readOption("operationAccess.read") },
|
|
2304
2306
|
baseFilter: { read: readOption("baseFilter.read") },
|
|
2305
2307
|
overrideFilter: { read: readOption("overrideFilter.read") },
|
|
@@ -2379,8 +2381,8 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
|
2379
2381
|
visit(options.virtuals ?? {}, model?.schema, "", [], !!model && varyingModel(model));
|
|
2380
2382
|
return Object.freeze({ scopes: Object.freeze(scopes) });
|
|
2381
2383
|
}
|
|
2382
|
-
function captureVirtualPopulateSnapshot(runtime, modelName, options) {
|
|
2383
|
-
const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options);
|
|
2384
|
+
function captureVirtualPopulateSnapshot(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
|
|
2385
|
+
const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions);
|
|
2384
2386
|
const token = () => {
|
|
2385
2387
|
};
|
|
2386
2388
|
snapshots.set(token, snapshot);
|
|
@@ -2791,6 +2793,7 @@ var init_runtime = __esm({
|
|
|
2791
2793
|
requestPermissionField: "_permissions",
|
|
2792
2794
|
globalPermissions: () => ({}),
|
|
2793
2795
|
requireRegisteredPopulateModels: true,
|
|
2796
|
+
requireReadOperationAccessForPopulateDependencies: true,
|
|
2794
2797
|
logger: defaultLogger,
|
|
2795
2798
|
requestComplexity: defaultRequestComplexity
|
|
2796
2799
|
},
|
|
@@ -2963,9 +2966,16 @@ var init_runtime = __esm({
|
|
|
2963
2966
|
}
|
|
2964
2967
|
/** Shallowly replace supplied global values; nested objects such as requestComplexity are not deep-merged. */
|
|
2965
2968
|
setGlobalOptions(options) {
|
|
2966
|
-
|
|
2969
|
+
const next = { ...options };
|
|
2970
|
+
if ("requireReadOperationAccessForPopulateDependencies" in next)
|
|
2971
|
+
next.requireReadOperationAccessForPopulateDependencies = next.requireReadOperationAccessForPopulateDependencies !== false;
|
|
2972
|
+
this.globalOptions.assign(next);
|
|
2967
2973
|
}
|
|
2968
2974
|
setGlobalOption(key, value) {
|
|
2975
|
+
if (key === "requireReadOperationAccessForPopulateDependencies") {
|
|
2976
|
+
this.globalOptions.set("requireReadOperationAccessForPopulateDependencies", value !== false);
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2969
2979
|
this.globalOptions.set(key, value);
|
|
2970
2980
|
}
|
|
2971
2981
|
getGlobalOptions() {
|
|
@@ -4606,14 +4616,21 @@ function getCapturedAuthorizationOption(configuration, key) {
|
|
|
4606
4616
|
const defaultValue = key.startsWith("operationAccess.") ? (0, import_utils21.get)(configuration.operationAccessDefaults, key.slice("operationAccess.".length)) : void 0;
|
|
4607
4617
|
return resolveNestedOption((path, fallback) => (0, import_utils21.get)(configuration, path, fallback), key, defaultValue);
|
|
4608
4618
|
}
|
|
4609
|
-
function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName)) {
|
|
4619
|
+
function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName), globalOptions = runtime.getGlobalOptions()) {
|
|
4610
4620
|
const model = runtime.getModelInstance(modelName);
|
|
4611
4621
|
if (!model) return authorizationError(`unknown receiving model: ${modelName}`);
|
|
4612
4622
|
const virtuals = options.virtuals;
|
|
4613
4623
|
return copyAuthorizationValue(
|
|
4614
4624
|
{
|
|
4615
4625
|
catalog: captureAuthorizationFieldCatalog(model.schema, virtuals, options.documentPermissionField),
|
|
4616
|
-
virtualPopulateSnapshot: captureVirtualPopulateSnapshot(
|
|
4626
|
+
virtualPopulateSnapshot: captureVirtualPopulateSnapshot(
|
|
4627
|
+
runtime,
|
|
4628
|
+
modelName,
|
|
4629
|
+
options,
|
|
4630
|
+
globalOptions
|
|
4631
|
+
),
|
|
4632
|
+
requireReadOperationAccessForPopulateDependencies: globalOptions.requireReadOperationAccessForPopulateDependencies !== false,
|
|
4633
|
+
requireRegisteredPopulateModels: globalOptions.requireRegisteredPopulateModels,
|
|
4617
4634
|
permissionSchema: options.permissionSchema,
|
|
4618
4635
|
virtuals,
|
|
4619
4636
|
alwaysSelectFields: options.alwaysSelectFields,
|
|
@@ -5273,7 +5290,8 @@ function createVirtualPopulateBatch(owner) {
|
|
|
5273
5290
|
owner: { ...owner },
|
|
5274
5291
|
rows: [],
|
|
5275
5292
|
requests: [],
|
|
5276
|
-
rowCache: owner.core && getRequestRowCache(owner.core) || new Cache()
|
|
5293
|
+
rowCache: owner.core && getRequestRowCache(owner.core) || new Cache(),
|
|
5294
|
+
readGuards: /* @__PURE__ */ new Map()
|
|
5277
5295
|
});
|
|
5278
5296
|
return batch;
|
|
5279
5297
|
}
|
|
@@ -5322,12 +5340,19 @@ function captureVirtualPopulateRows(batch, inputs, plan, publicPopulate = [], so
|
|
|
5322
5340
|
}
|
|
5323
5341
|
const record3 = storage(value);
|
|
5324
5342
|
if (!record3) return;
|
|
5325
|
-
const occurrence = {
|
|
5343
|
+
const occurrence = {
|
|
5344
|
+
address,
|
|
5345
|
+
plan: currentPlan,
|
|
5346
|
+
refs: /* @__PURE__ */ new Map(),
|
|
5347
|
+
referenceTargets: /* @__PURE__ */ new Map(),
|
|
5348
|
+
dependencies: /* @__PURE__ */ new Map()
|
|
5349
|
+
};
|
|
5326
5350
|
row.occurrences.set(addressKey(address), occurrence);
|
|
5327
5351
|
for (const candidate of currentPlan.candidates) {
|
|
5328
5352
|
const dependencies = (candidate.populateDependencies ?? []).map((dependency) => {
|
|
5329
5353
|
if (!occurrence.refs.has(dependency.path))
|
|
5330
5354
|
occurrence.refs.set(dependency.path, originalRef(value, dependency.path));
|
|
5355
|
+
occurrence.referenceTargets.set(dependency.path, dependency.target);
|
|
5331
5356
|
const request = {
|
|
5332
5357
|
binding: dependency.target,
|
|
5333
5358
|
target: dependency.target,
|
|
@@ -5388,6 +5413,27 @@ function referenceIds(original) {
|
|
|
5388
5413
|
if (!original.present || original.value == null) return [];
|
|
5389
5414
|
return (Array.isArray(original.value) ? original.value : [original.value]).filter((id) => id != null);
|
|
5390
5415
|
}
|
|
5416
|
+
function targetReadAllowed(state, target) {
|
|
5417
|
+
let pending = state.readGuards.get(target.identity);
|
|
5418
|
+
if (!pending) {
|
|
5419
|
+
const { owner } = state;
|
|
5420
|
+
pending = getRequestWorkState(owner.request, owner.runtime).hookGate.run(
|
|
5421
|
+
() => evaluateRouteGuard(
|
|
5422
|
+
owner.request,
|
|
5423
|
+
owner.permissions,
|
|
5424
|
+
getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
|
|
5425
|
+
)
|
|
5426
|
+
).then(
|
|
5427
|
+
(allowed) => !!allowed,
|
|
5428
|
+
() => {
|
|
5429
|
+
warn("Virtual populated dependency unavailable", { modelName: target.modelName, category: "policy" });
|
|
5430
|
+
return false;
|
|
5431
|
+
}
|
|
5432
|
+
);
|
|
5433
|
+
state.readGuards.set(target.identity, pending);
|
|
5434
|
+
}
|
|
5435
|
+
return pending;
|
|
5436
|
+
}
|
|
5391
5437
|
function executeVirtualPopulateBatch(batch) {
|
|
5392
5438
|
const state = batches.get(batch);
|
|
5393
5439
|
if (!state) throw new Error("Unknown private populate batch");
|
|
@@ -5404,14 +5450,8 @@ async function execute(state) {
|
|
|
5404
5450
|
if (!pending) {
|
|
5405
5451
|
pending = (async () => {
|
|
5406
5452
|
try {
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
owner.request,
|
|
5410
|
-
owner.permissions,
|
|
5411
|
-
getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
|
|
5412
|
-
)
|
|
5413
|
-
);
|
|
5414
|
-
if (!allowed) return void 0;
|
|
5453
|
+
if (target.configuration.requireReadOperationAccessForPopulateDependencies !== false && !await targetReadAllowed(state, target))
|
|
5454
|
+
return void 0;
|
|
5415
5455
|
const parts = await work.hookGate.run(
|
|
5416
5456
|
() => resolveAccessFilterParts({
|
|
5417
5457
|
req: owner.request,
|
|
@@ -5496,6 +5536,12 @@ function readVirtualPopulateOriginalReference(row, address, path) {
|
|
|
5496
5536
|
function hasVirtualPopulatePublicReference(row, address, path) {
|
|
5497
5537
|
return !!rows.get(row)?.publicRequests.some((request) => addressKey(request.address) === addressKey([...address, path]));
|
|
5498
5538
|
}
|
|
5539
|
+
function isVirtualPopulateReferenceVisible(row, address, path) {
|
|
5540
|
+
const record3 = rows.get(row);
|
|
5541
|
+
const occurrence = record3?.occurrences.get(addressKey(address));
|
|
5542
|
+
const target = occurrence?.refs.get(path)?.present ? occurrence.referenceTargets.get(path) : void 0;
|
|
5543
|
+
return record3 && target ? targetReadAllowed(record3.batch, target) : Promise.resolve(true);
|
|
5544
|
+
}
|
|
5499
5545
|
function readVirtualPopulateInputs(row, address, virtualName) {
|
|
5500
5546
|
const record3 = rows.get(row);
|
|
5501
5547
|
if (!record3?.batch.completed) return { available: false };
|
|
@@ -5654,7 +5700,7 @@ var init_virtual_populate_execution = __esm({
|
|
|
5654
5700
|
const option = getCapturedAuthorizationOption(configuration, `${key}.${access}`);
|
|
5655
5701
|
return key === "operationAccess" || typeof option === "function" ? option : void 0;
|
|
5656
5702
|
});
|
|
5657
|
-
compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && // Different captures may share only an equivalent terminal persisted binding/policy.
|
|
5703
|
+
compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && a.binding.configuration.requireReadOperationAccessForPopulateDependencies === b.binding.configuration.requireReadOperationAccessForPopulateDependencies && // Different captures may share only an equivalent terminal persisted binding/policy.
|
|
5658
5704
|
equivalentVirtualPopulatePredicate(
|
|
5659
5705
|
a.binding.configuration.catalog.persistedPaths,
|
|
5660
5706
|
b.binding.configuration.catalog.persistedPaths
|
|
@@ -6364,6 +6410,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6364
6410
|
if (!captured && documentPermissionField in output) keep.add(documentPermissionField);
|
|
6365
6411
|
const trimmed = {};
|
|
6366
6412
|
for (const key of keep) {
|
|
6413
|
+
if (privateRow && !await isVirtualPopulateReferenceVisible(privateRow, privateAddress, key)) continue;
|
|
6367
6414
|
if (privateRow && !hasVirtualPopulatePublicReference(privateRow, privateAddress, key)) {
|
|
6368
6415
|
const ref = readVirtualPopulateOriginalReference(privateRow, privateAddress, key);
|
|
6369
6416
|
if (ref) {
|
|
@@ -7269,6 +7316,11 @@ function modelServiceOptions(receiver, read) {
|
|
|
7269
7316
|
if (call?.receiver !== receiver) return read();
|
|
7270
7317
|
return call.options ??= read();
|
|
7271
7318
|
}
|
|
7319
|
+
function modelServiceGlobalOptions(receiver, runtime) {
|
|
7320
|
+
const call = dispatch.getStore();
|
|
7321
|
+
if (call?.receiver !== receiver) return runtime.getGlobalOptions();
|
|
7322
|
+
return call.globalOptions ??= runtime.getGlobalOptions();
|
|
7323
|
+
}
|
|
7272
7324
|
|
|
7273
7325
|
// src/services/base.ts
|
|
7274
7326
|
init_request_work();
|
|
@@ -7709,7 +7761,7 @@ function staticObjectIdReference(runtime, source, path, target) {
|
|
|
7709
7761
|
const scalar = type?.instance === "Array" ? type.$embeddedSchemaType ?? type.embeddedSchemaType ?? type.caster : type;
|
|
7710
7762
|
return scalar?.instance === "ObjectId" && (type?.options?.ref ?? scalar.options?.ref) === target && type?.options?.refPath === void 0 && scalar.options?.refPath === void 0;
|
|
7711
7763
|
}
|
|
7712
|
-
function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
7764
|
+
function captureRelatedTargets(runtime, modelName, args = {}, depth = 0, globalOptions = runtime.getGlobalOptions()) {
|
|
7713
7765
|
if (depth > 32) authorizationError("related descriptor depth exceeded");
|
|
7714
7766
|
assertPublicAdmission(args.populate, "populate");
|
|
7715
7767
|
assertPublicAdmission(args.include, "include");
|
|
@@ -7723,16 +7775,16 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
|
7723
7775
|
targets.push({
|
|
7724
7776
|
path,
|
|
7725
7777
|
modelName: model,
|
|
7726
|
-
configuration: captureModelAuthorizationConfiguration(runtime, model),
|
|
7778
|
+
configuration: captureModelAuthorizationConfiguration(runtime, model, void 0, globalOptions),
|
|
7727
7779
|
staticObjectIdReference: staticObjectIdReference(runtime, modelName, path, model)
|
|
7728
7780
|
});
|
|
7729
|
-
else if (model && !
|
|
7781
|
+
else if (model && !(globalOptions.requireRegisteredPopulateModels === void 0 ? true : globalOptions.requireRegisteredPopulateModels)) {
|
|
7730
7782
|
targets.push({ path, modelName: model, kind: "passthrough", configuration: { catalog: { paths: [] } } });
|
|
7731
7783
|
}
|
|
7732
7784
|
}
|
|
7733
7785
|
for (const entry of entries(args.include)) {
|
|
7734
7786
|
if (!runtime.hasModel(entry.model)) authorizationError(`Model ${entry.model} not found`);
|
|
7735
|
-
const configuration = captureModelAuthorizationConfiguration(runtime, entry.model);
|
|
7787
|
+
const configuration = captureModelAuthorizationConfiguration(runtime, entry.model, void 0, globalOptions);
|
|
7736
7788
|
const queryIntent = entry.op === "count" ? void 0 : captureModelServiceIntent(
|
|
7737
7789
|
{
|
|
7738
7790
|
defaults: configuration.defaults ?? {},
|
|
@@ -7758,7 +7810,13 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
|
7758
7810
|
configuration,
|
|
7759
7811
|
queryIntent,
|
|
7760
7812
|
operationGuard: runtime.getModelOption(entry.model, `operationAccess.${entry.op}`),
|
|
7761
|
-
relatedTargets: captureRelatedTargets(
|
|
7813
|
+
relatedTargets: captureRelatedTargets(
|
|
7814
|
+
runtime,
|
|
7815
|
+
entry.model,
|
|
7816
|
+
queryIntent?.args ?? entry.args ?? {},
|
|
7817
|
+
depth + 1,
|
|
7818
|
+
globalOptions
|
|
7819
|
+
)
|
|
7762
7820
|
});
|
|
7763
7821
|
}
|
|
7764
7822
|
if (new Set(targets.map((target) => target.path)).size !== targets.length)
|
|
@@ -9330,7 +9388,9 @@ function prepareSubdocumentOutput(service, sub, operation, virtualAccess, output
|
|
|
9330
9388
|
captureRelatedTargets(
|
|
9331
9389
|
getServiceExecutors(service).binding().runtime,
|
|
9332
9390
|
service.getServiceModelName(),
|
|
9333
|
-
{ populate }
|
|
9391
|
+
{ populate },
|
|
9392
|
+
0,
|
|
9393
|
+
configuration
|
|
9334
9394
|
),
|
|
9335
9395
|
void 0,
|
|
9336
9396
|
configuration,
|
|
@@ -10369,10 +10429,12 @@ var Service = class extends Base {
|
|
|
10369
10429
|
model: this.model.mongooseModel,
|
|
10370
10430
|
modelName: this.modelName
|
|
10371
10431
|
}),
|
|
10372
|
-
configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10432
|
+
configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(
|
|
10433
|
+
this.requestRuntime,
|
|
10434
|
+
this.modelName,
|
|
10435
|
+
{ ...this.options, defaults: this.defaults },
|
|
10436
|
+
modelServiceGlobalOptions(this, this.requestRuntime)
|
|
10437
|
+
) : { catalog: { paths: [] }, ...this.options },
|
|
10376
10438
|
permissions: () => this.requestCore.getPermissions?.() ?? new permission_default({}),
|
|
10377
10439
|
parse: (filter2) => this.parseClientData(filter2, this.getCorrelatedExecState().scheduler),
|
|
10378
10440
|
validate: (filter2, expanded) => {
|
|
@@ -10901,12 +10963,13 @@ var Service = class extends Base {
|
|
|
10901
10963
|
});
|
|
10902
10964
|
}
|
|
10903
10965
|
captureRelated(args, defaults) {
|
|
10966
|
+
const globalOptions = modelServiceGlobalOptions(this, this.requestRuntime);
|
|
10904
10967
|
const effective = { populate: args?.populate ?? defaults?.populate, include: args?.include ?? defaults?.include };
|
|
10905
10968
|
if (effective.include) {
|
|
10906
10969
|
const processed = this.processInclude(effective.include);
|
|
10907
10970
|
effective.include = [...processed.includes, ...processed.correlatedIncludes];
|
|
10908
10971
|
}
|
|
10909
|
-
return captureRelatedTargets(this.requestRuntime, this.modelName, effective);
|
|
10972
|
+
return captureRelatedTargets(this.requestRuntime, this.modelName, effective, 0, globalOptions);
|
|
10910
10973
|
}
|
|
10911
10974
|
/** Canonical population uses the target's retained policy for both fetch and output. */
|
|
10912
10975
|
async genExecutionPopulate(execution, access, populate) {
|
|
@@ -10928,12 +10991,11 @@ var Service = class extends Base {
|
|
|
10928
10991
|
modelPermissionPrefix: captured.configuration.modelPermissionPrefix
|
|
10929
10992
|
});
|
|
10930
10993
|
if (sourceState === "denied") continue;
|
|
10931
|
-
const
|
|
10994
|
+
const target = captured.relatedTargets.find((target2) => target2.path === descriptor.path);
|
|
10995
|
+
const modelName = target?.modelName;
|
|
10932
10996
|
if (!modelName) continue;
|
|
10933
|
-
if (
|
|
10934
|
-
|
|
10935
|
-
result2.push({ path: descriptor.path, select: descriptor.select });
|
|
10936
|
-
}
|
|
10997
|
+
if (target.kind === "passthrough") {
|
|
10998
|
+
result2.push({ path: descriptor.path, select: descriptor.select });
|
|
10937
10999
|
continue;
|
|
10938
11000
|
}
|
|
10939
11001
|
const policy = relatedPolicy(state.authorization, state.binding, descriptor.path);
|
|
@@ -10986,7 +11048,7 @@ var Service = class extends Base {
|
|
|
10986
11048
|
schema: childState.binding.model.schema,
|
|
10987
11049
|
configuration: targetConfiguration,
|
|
10988
11050
|
requestRead: targetAccess === "read" && policy?.operation.mode !== "grant" && readServiceAuthorization(childState.authorization, childState.binding).authorization.rows.mode === "request",
|
|
10989
|
-
staticObjectIdReference: captured.relatedTargets.find((
|
|
11051
|
+
staticObjectIdReference: captured.relatedTargets.find((target2) => target2.path === descriptor.path)?.staticObjectIdReference
|
|
10990
11052
|
}
|
|
10991
11053
|
});
|
|
10992
11054
|
result2.push(ret);
|
|
@@ -11030,15 +11092,13 @@ var Service = class extends Base {
|
|
|
11030
11092
|
transferDocumentAuthorizationGraph(source, copy2);
|
|
11031
11093
|
return copy2;
|
|
11032
11094
|
}
|
|
11033
|
-
let targetMongooseModel;
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
meta.targetModelName
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
} catch {
|
|
11041
|
-
targetMongooseModel = parentContext.mongooseModel;
|
|
11095
|
+
let targetMongooseModel = meta.fetchBinding?.model;
|
|
11096
|
+
if (!targetMongooseModel) {
|
|
11097
|
+
try {
|
|
11098
|
+
targetMongooseModel = this.requestRuntime.getModelInstance(meta.targetModelName);
|
|
11099
|
+
} catch {
|
|
11100
|
+
targetMongooseModel = parentContext.mongooseModel;
|
|
11101
|
+
}
|
|
11042
11102
|
}
|
|
11043
11103
|
const targetContext = {
|
|
11044
11104
|
mongooseModel: targetMongooseModel ?? parentContext.mongooseModel,
|
package/index.mjs
CHANGED
|
@@ -2231,7 +2231,8 @@ function normalizeSelection(select, catalog, label) {
|
|
|
2231
2231
|
includeId: selection.includes("_id")
|
|
2232
2232
|
};
|
|
2233
2233
|
}
|
|
2234
|
-
function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
2234
|
+
function captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
|
|
2235
|
+
const requireReadOperationAccessForPopulateDependencies = globalOptions.requireReadOperationAccessForPopulateDependencies !== false;
|
|
2235
2236
|
const model = runtime.getModelInstance(modelName);
|
|
2236
2237
|
const targets = /* @__PURE__ */ new Map();
|
|
2237
2238
|
const scopes = [];
|
|
@@ -2280,6 +2281,7 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
|
2280
2281
|
{
|
|
2281
2282
|
catalog,
|
|
2282
2283
|
documentPermissionField,
|
|
2284
|
+
requireReadOperationAccessForPopulateDependencies,
|
|
2283
2285
|
operationAccess: { read: readOption("operationAccess.read") },
|
|
2284
2286
|
baseFilter: { read: readOption("baseFilter.read") },
|
|
2285
2287
|
overrideFilter: { read: readOption("overrideFilter.read") },
|
|
@@ -2359,8 +2361,8 @@ function captureVirtualPopulateDependencies(runtime, modelName, options) {
|
|
|
2359
2361
|
visit(options.virtuals ?? {}, model?.schema, "", [], !!model && varyingModel(model));
|
|
2360
2362
|
return Object.freeze({ scopes: Object.freeze(scopes) });
|
|
2361
2363
|
}
|
|
2362
|
-
function captureVirtualPopulateSnapshot(runtime, modelName, options) {
|
|
2363
|
-
const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options);
|
|
2364
|
+
function captureVirtualPopulateSnapshot(runtime, modelName, options, globalOptions = runtime.getGlobalOptions()) {
|
|
2365
|
+
const snapshot = captureVirtualPopulateDependencies(runtime, modelName, options, globalOptions);
|
|
2364
2366
|
const token = () => {
|
|
2365
2367
|
};
|
|
2366
2368
|
snapshots.set(token, snapshot);
|
|
@@ -2783,6 +2785,7 @@ var init_runtime = __esm({
|
|
|
2783
2785
|
requestPermissionField: "_permissions",
|
|
2784
2786
|
globalPermissions: () => ({}),
|
|
2785
2787
|
requireRegisteredPopulateModels: true,
|
|
2788
|
+
requireReadOperationAccessForPopulateDependencies: true,
|
|
2786
2789
|
logger: defaultLogger,
|
|
2787
2790
|
requestComplexity: defaultRequestComplexity
|
|
2788
2791
|
},
|
|
@@ -2955,9 +2958,16 @@ var init_runtime = __esm({
|
|
|
2955
2958
|
}
|
|
2956
2959
|
/** Shallowly replace supplied global values; nested objects such as requestComplexity are not deep-merged. */
|
|
2957
2960
|
setGlobalOptions(options) {
|
|
2958
|
-
|
|
2961
|
+
const next = { ...options };
|
|
2962
|
+
if ("requireReadOperationAccessForPopulateDependencies" in next)
|
|
2963
|
+
next.requireReadOperationAccessForPopulateDependencies = next.requireReadOperationAccessForPopulateDependencies !== false;
|
|
2964
|
+
this.globalOptions.assign(next);
|
|
2959
2965
|
}
|
|
2960
2966
|
setGlobalOption(key, value) {
|
|
2967
|
+
if (key === "requireReadOperationAccessForPopulateDependencies") {
|
|
2968
|
+
this.globalOptions.set("requireReadOperationAccessForPopulateDependencies", value !== false);
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2961
2971
|
this.globalOptions.set(key, value);
|
|
2962
2972
|
}
|
|
2963
2973
|
getGlobalOptions() {
|
|
@@ -4599,14 +4609,21 @@ function getCapturedAuthorizationOption(configuration, key) {
|
|
|
4599
4609
|
const defaultValue = key.startsWith("operationAccess.") ? get7(configuration.operationAccessDefaults, key.slice("operationAccess.".length)) : void 0;
|
|
4600
4610
|
return resolveNestedOption((path, fallback) => get7(configuration, path, fallback), key, defaultValue);
|
|
4601
4611
|
}
|
|
4602
|
-
function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName)) {
|
|
4612
|
+
function captureModelAuthorizationConfiguration(runtime, modelName, options = runtime.getModelOptions(modelName), globalOptions = runtime.getGlobalOptions()) {
|
|
4603
4613
|
const model = runtime.getModelInstance(modelName);
|
|
4604
4614
|
if (!model) return authorizationError(`unknown receiving model: ${modelName}`);
|
|
4605
4615
|
const virtuals = options.virtuals;
|
|
4606
4616
|
return copyAuthorizationValue(
|
|
4607
4617
|
{
|
|
4608
4618
|
catalog: captureAuthorizationFieldCatalog(model.schema, virtuals, options.documentPermissionField),
|
|
4609
|
-
virtualPopulateSnapshot: captureVirtualPopulateSnapshot(
|
|
4619
|
+
virtualPopulateSnapshot: captureVirtualPopulateSnapshot(
|
|
4620
|
+
runtime,
|
|
4621
|
+
modelName,
|
|
4622
|
+
options,
|
|
4623
|
+
globalOptions
|
|
4624
|
+
),
|
|
4625
|
+
requireReadOperationAccessForPopulateDependencies: globalOptions.requireReadOperationAccessForPopulateDependencies !== false,
|
|
4626
|
+
requireRegisteredPopulateModels: globalOptions.requireRegisteredPopulateModels,
|
|
4610
4627
|
permissionSchema: options.permissionSchema,
|
|
4611
4628
|
virtuals,
|
|
4612
4629
|
alwaysSelectFields: options.alwaysSelectFields,
|
|
@@ -5277,7 +5294,8 @@ function createVirtualPopulateBatch(owner) {
|
|
|
5277
5294
|
owner: { ...owner },
|
|
5278
5295
|
rows: [],
|
|
5279
5296
|
requests: [],
|
|
5280
|
-
rowCache: owner.core && getRequestRowCache(owner.core) || new Cache()
|
|
5297
|
+
rowCache: owner.core && getRequestRowCache(owner.core) || new Cache(),
|
|
5298
|
+
readGuards: /* @__PURE__ */ new Map()
|
|
5281
5299
|
});
|
|
5282
5300
|
return batch;
|
|
5283
5301
|
}
|
|
@@ -5326,12 +5344,19 @@ function captureVirtualPopulateRows(batch, inputs, plan, publicPopulate = [], so
|
|
|
5326
5344
|
}
|
|
5327
5345
|
const record3 = storage(value);
|
|
5328
5346
|
if (!record3) return;
|
|
5329
|
-
const occurrence = {
|
|
5347
|
+
const occurrence = {
|
|
5348
|
+
address,
|
|
5349
|
+
plan: currentPlan,
|
|
5350
|
+
refs: /* @__PURE__ */ new Map(),
|
|
5351
|
+
referenceTargets: /* @__PURE__ */ new Map(),
|
|
5352
|
+
dependencies: /* @__PURE__ */ new Map()
|
|
5353
|
+
};
|
|
5330
5354
|
row.occurrences.set(addressKey(address), occurrence);
|
|
5331
5355
|
for (const candidate of currentPlan.candidates) {
|
|
5332
5356
|
const dependencies = (candidate.populateDependencies ?? []).map((dependency) => {
|
|
5333
5357
|
if (!occurrence.refs.has(dependency.path))
|
|
5334
5358
|
occurrence.refs.set(dependency.path, originalRef(value, dependency.path));
|
|
5359
|
+
occurrence.referenceTargets.set(dependency.path, dependency.target);
|
|
5335
5360
|
const request = {
|
|
5336
5361
|
binding: dependency.target,
|
|
5337
5362
|
target: dependency.target,
|
|
@@ -5392,6 +5417,27 @@ function referenceIds(original) {
|
|
|
5392
5417
|
if (!original.present || original.value == null) return [];
|
|
5393
5418
|
return (Array.isArray(original.value) ? original.value : [original.value]).filter((id) => id != null);
|
|
5394
5419
|
}
|
|
5420
|
+
function targetReadAllowed(state, target) {
|
|
5421
|
+
let pending = state.readGuards.get(target.identity);
|
|
5422
|
+
if (!pending) {
|
|
5423
|
+
const { owner } = state;
|
|
5424
|
+
pending = getRequestWorkState(owner.request, owner.runtime).hookGate.run(
|
|
5425
|
+
() => evaluateRouteGuard(
|
|
5426
|
+
owner.request,
|
|
5427
|
+
owner.permissions,
|
|
5428
|
+
getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
|
|
5429
|
+
)
|
|
5430
|
+
).then(
|
|
5431
|
+
(allowed) => !!allowed,
|
|
5432
|
+
() => {
|
|
5433
|
+
warn("Virtual populated dependency unavailable", { modelName: target.modelName, category: "policy" });
|
|
5434
|
+
return false;
|
|
5435
|
+
}
|
|
5436
|
+
);
|
|
5437
|
+
state.readGuards.set(target.identity, pending);
|
|
5438
|
+
}
|
|
5439
|
+
return pending;
|
|
5440
|
+
}
|
|
5395
5441
|
function executeVirtualPopulateBatch(batch) {
|
|
5396
5442
|
const state = batches.get(batch);
|
|
5397
5443
|
if (!state) throw new Error("Unknown private populate batch");
|
|
@@ -5408,14 +5454,8 @@ async function execute(state) {
|
|
|
5408
5454
|
if (!pending) {
|
|
5409
5455
|
pending = (async () => {
|
|
5410
5456
|
try {
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
owner.request,
|
|
5414
|
-
owner.permissions,
|
|
5415
|
-
getCapturedAuthorizationOption(target.configuration, "operationAccess.read")
|
|
5416
|
-
)
|
|
5417
|
-
);
|
|
5418
|
-
if (!allowed) return void 0;
|
|
5457
|
+
if (target.configuration.requireReadOperationAccessForPopulateDependencies !== false && !await targetReadAllowed(state, target))
|
|
5458
|
+
return void 0;
|
|
5419
5459
|
const parts = await work.hookGate.run(
|
|
5420
5460
|
() => resolveAccessFilterParts({
|
|
5421
5461
|
req: owner.request,
|
|
@@ -5500,6 +5540,12 @@ function readVirtualPopulateOriginalReference(row, address, path) {
|
|
|
5500
5540
|
function hasVirtualPopulatePublicReference(row, address, path) {
|
|
5501
5541
|
return !!rows.get(row)?.publicRequests.some((request) => addressKey(request.address) === addressKey([...address, path]));
|
|
5502
5542
|
}
|
|
5543
|
+
function isVirtualPopulateReferenceVisible(row, address, path) {
|
|
5544
|
+
const record3 = rows.get(row);
|
|
5545
|
+
const occurrence = record3?.occurrences.get(addressKey(address));
|
|
5546
|
+
const target = occurrence?.refs.get(path)?.present ? occurrence.referenceTargets.get(path) : void 0;
|
|
5547
|
+
return record3 && target ? targetReadAllowed(record3.batch, target) : Promise.resolve(true);
|
|
5548
|
+
}
|
|
5503
5549
|
function readVirtualPopulateInputs(row, address, virtualName) {
|
|
5504
5550
|
const record3 = rows.get(row);
|
|
5505
5551
|
if (!record3?.batch.completed) return { available: false };
|
|
@@ -5655,7 +5701,7 @@ var init_virtual_populate_execution = __esm({
|
|
|
5655
5701
|
const option = getCapturedAuthorizationOption(configuration, `${key}.${access}`);
|
|
5656
5702
|
return key === "operationAccess" || typeof option === "function" ? option : void 0;
|
|
5657
5703
|
});
|
|
5658
|
-
compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && // Different captures may share only an equivalent terminal persisted binding/policy.
|
|
5704
|
+
compatible = (a, b) => a.binding.runtime === b.binding.runtime && a.binding.model === b.binding.model && a.binding.schema === b.binding.schema && a.access === b.access && a.requestRead === b.requestRead && a.binding.configuration.requireReadOperationAccessForPopulateDependencies === b.binding.configuration.requireReadOperationAccessForPopulateDependencies && // Different captures may share only an equivalent terminal persisted binding/policy.
|
|
5659
5705
|
equivalentVirtualPopulatePredicate(
|
|
5660
5706
|
a.binding.configuration.catalog.persistedPaths,
|
|
5661
5707
|
b.binding.configuration.catalog.persistedPaths
|
|
@@ -6366,6 +6412,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6366
6412
|
if (!captured && documentPermissionField in output) keep.add(documentPermissionField);
|
|
6367
6413
|
const trimmed = {};
|
|
6368
6414
|
for (const key of keep) {
|
|
6415
|
+
if (privateRow && !await isVirtualPopulateReferenceVisible(privateRow, privateAddress, key)) continue;
|
|
6369
6416
|
if (privateRow && !hasVirtualPopulatePublicReference(privateRow, privateAddress, key)) {
|
|
6370
6417
|
const ref = readVirtualPopulateOriginalReference(privateRow, privateAddress, key);
|
|
6371
6418
|
if (ref) {
|
|
@@ -7242,6 +7289,11 @@ function modelServiceOptions(receiver, read) {
|
|
|
7242
7289
|
if (call?.receiver !== receiver) return read();
|
|
7243
7290
|
return call.options ??= read();
|
|
7244
7291
|
}
|
|
7292
|
+
function modelServiceGlobalOptions(receiver, runtime) {
|
|
7293
|
+
const call = dispatch.getStore();
|
|
7294
|
+
if (call?.receiver !== receiver) return runtime.getGlobalOptions();
|
|
7295
|
+
return call.globalOptions ??= runtime.getGlobalOptions();
|
|
7296
|
+
}
|
|
7245
7297
|
|
|
7246
7298
|
// src/services/base.ts
|
|
7247
7299
|
init_request_work();
|
|
@@ -7682,7 +7734,7 @@ function staticObjectIdReference(runtime, source, path, target) {
|
|
|
7682
7734
|
const scalar = type?.instance === "Array" ? type.$embeddedSchemaType ?? type.embeddedSchemaType ?? type.caster : type;
|
|
7683
7735
|
return scalar?.instance === "ObjectId" && (type?.options?.ref ?? scalar.options?.ref) === target && type?.options?.refPath === void 0 && scalar.options?.refPath === void 0;
|
|
7684
7736
|
}
|
|
7685
|
-
function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
7737
|
+
function captureRelatedTargets(runtime, modelName, args = {}, depth = 0, globalOptions = runtime.getGlobalOptions()) {
|
|
7686
7738
|
if (depth > 32) authorizationError("related descriptor depth exceeded");
|
|
7687
7739
|
assertPublicAdmission(args.populate, "populate");
|
|
7688
7740
|
assertPublicAdmission(args.include, "include");
|
|
@@ -7696,16 +7748,16 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
|
7696
7748
|
targets.push({
|
|
7697
7749
|
path,
|
|
7698
7750
|
modelName: model,
|
|
7699
|
-
configuration: captureModelAuthorizationConfiguration(runtime, model),
|
|
7751
|
+
configuration: captureModelAuthorizationConfiguration(runtime, model, void 0, globalOptions),
|
|
7700
7752
|
staticObjectIdReference: staticObjectIdReference(runtime, modelName, path, model)
|
|
7701
7753
|
});
|
|
7702
|
-
else if (model && !
|
|
7754
|
+
else if (model && !(globalOptions.requireRegisteredPopulateModels === void 0 ? true : globalOptions.requireRegisteredPopulateModels)) {
|
|
7703
7755
|
targets.push({ path, modelName: model, kind: "passthrough", configuration: { catalog: { paths: [] } } });
|
|
7704
7756
|
}
|
|
7705
7757
|
}
|
|
7706
7758
|
for (const entry of entries(args.include)) {
|
|
7707
7759
|
if (!runtime.hasModel(entry.model)) authorizationError(`Model ${entry.model} not found`);
|
|
7708
|
-
const configuration = captureModelAuthorizationConfiguration(runtime, entry.model);
|
|
7760
|
+
const configuration = captureModelAuthorizationConfiguration(runtime, entry.model, void 0, globalOptions);
|
|
7709
7761
|
const queryIntent = entry.op === "count" ? void 0 : captureModelServiceIntent(
|
|
7710
7762
|
{
|
|
7711
7763
|
defaults: configuration.defaults ?? {},
|
|
@@ -7731,7 +7783,13 @@ function captureRelatedTargets(runtime, modelName, args = {}, depth = 0) {
|
|
|
7731
7783
|
configuration,
|
|
7732
7784
|
queryIntent,
|
|
7733
7785
|
operationGuard: runtime.getModelOption(entry.model, `operationAccess.${entry.op}`),
|
|
7734
|
-
relatedTargets: captureRelatedTargets(
|
|
7786
|
+
relatedTargets: captureRelatedTargets(
|
|
7787
|
+
runtime,
|
|
7788
|
+
entry.model,
|
|
7789
|
+
queryIntent?.args ?? entry.args ?? {},
|
|
7790
|
+
depth + 1,
|
|
7791
|
+
globalOptions
|
|
7792
|
+
)
|
|
7735
7793
|
});
|
|
7736
7794
|
}
|
|
7737
7795
|
if (new Set(targets.map((target) => target.path)).size !== targets.length)
|
|
@@ -9315,7 +9373,9 @@ function prepareSubdocumentOutput(service, sub, operation, virtualAccess, output
|
|
|
9315
9373
|
captureRelatedTargets(
|
|
9316
9374
|
getServiceExecutors(service).binding().runtime,
|
|
9317
9375
|
service.getServiceModelName(),
|
|
9318
|
-
{ populate }
|
|
9376
|
+
{ populate },
|
|
9377
|
+
0,
|
|
9378
|
+
configuration
|
|
9319
9379
|
),
|
|
9320
9380
|
void 0,
|
|
9321
9381
|
configuration,
|
|
@@ -10354,10 +10414,12 @@ var Service = class extends Base {
|
|
|
10354
10414
|
model: this.model.mongooseModel,
|
|
10355
10415
|
modelName: this.modelName
|
|
10356
10416
|
}),
|
|
10357
|
-
configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10417
|
+
configuration: () => this.requestRuntime.getModelInstance(this.modelName) ? captureModelAuthorizationConfiguration(
|
|
10418
|
+
this.requestRuntime,
|
|
10419
|
+
this.modelName,
|
|
10420
|
+
{ ...this.options, defaults: this.defaults },
|
|
10421
|
+
modelServiceGlobalOptions(this, this.requestRuntime)
|
|
10422
|
+
) : { catalog: { paths: [] }, ...this.options },
|
|
10361
10423
|
permissions: () => this.requestCore.getPermissions?.() ?? new permission_default({}),
|
|
10362
10424
|
parse: (filter2) => this.parseClientData(filter2, this.getCorrelatedExecState().scheduler),
|
|
10363
10425
|
validate: (filter2, expanded) => {
|
|
@@ -10886,12 +10948,13 @@ var Service = class extends Base {
|
|
|
10886
10948
|
});
|
|
10887
10949
|
}
|
|
10888
10950
|
captureRelated(args, defaults) {
|
|
10951
|
+
const globalOptions = modelServiceGlobalOptions(this, this.requestRuntime);
|
|
10889
10952
|
const effective = { populate: args?.populate ?? defaults?.populate, include: args?.include ?? defaults?.include };
|
|
10890
10953
|
if (effective.include) {
|
|
10891
10954
|
const processed = this.processInclude(effective.include);
|
|
10892
10955
|
effective.include = [...processed.includes, ...processed.correlatedIncludes];
|
|
10893
10956
|
}
|
|
10894
|
-
return captureRelatedTargets(this.requestRuntime, this.modelName, effective);
|
|
10957
|
+
return captureRelatedTargets(this.requestRuntime, this.modelName, effective, 0, globalOptions);
|
|
10895
10958
|
}
|
|
10896
10959
|
/** Canonical population uses the target's retained policy for both fetch and output. */
|
|
10897
10960
|
async genExecutionPopulate(execution, access, populate) {
|
|
@@ -10913,12 +10976,11 @@ var Service = class extends Base {
|
|
|
10913
10976
|
modelPermissionPrefix: captured.configuration.modelPermissionPrefix
|
|
10914
10977
|
});
|
|
10915
10978
|
if (sourceState === "denied") continue;
|
|
10916
|
-
const
|
|
10979
|
+
const target = captured.relatedTargets.find((target2) => target2.path === descriptor.path);
|
|
10980
|
+
const modelName = target?.modelName;
|
|
10917
10981
|
if (!modelName) continue;
|
|
10918
|
-
if (
|
|
10919
|
-
|
|
10920
|
-
result2.push({ path: descriptor.path, select: descriptor.select });
|
|
10921
|
-
}
|
|
10982
|
+
if (target.kind === "passthrough") {
|
|
10983
|
+
result2.push({ path: descriptor.path, select: descriptor.select });
|
|
10922
10984
|
continue;
|
|
10923
10985
|
}
|
|
10924
10986
|
const policy = relatedPolicy(state.authorization, state.binding, descriptor.path);
|
|
@@ -10971,7 +11033,7 @@ var Service = class extends Base {
|
|
|
10971
11033
|
schema: childState.binding.model.schema,
|
|
10972
11034
|
configuration: targetConfiguration,
|
|
10973
11035
|
requestRead: targetAccess === "read" && policy?.operation.mode !== "grant" && readServiceAuthorization(childState.authorization, childState.binding).authorization.rows.mode === "request",
|
|
10974
|
-
staticObjectIdReference: captured.relatedTargets.find((
|
|
11036
|
+
staticObjectIdReference: captured.relatedTargets.find((target2) => target2.path === descriptor.path)?.staticObjectIdReference
|
|
10975
11037
|
}
|
|
10976
11038
|
});
|
|
10977
11039
|
result2.push(ret);
|
|
@@ -11015,15 +11077,13 @@ var Service = class extends Base {
|
|
|
11015
11077
|
transferDocumentAuthorizationGraph(source, copy2);
|
|
11016
11078
|
return copy2;
|
|
11017
11079
|
}
|
|
11018
|
-
let targetMongooseModel;
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
meta.targetModelName
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
} catch {
|
|
11026
|
-
targetMongooseModel = parentContext.mongooseModel;
|
|
11080
|
+
let targetMongooseModel = meta.fetchBinding?.model;
|
|
11081
|
+
if (!targetMongooseModel) {
|
|
11082
|
+
try {
|
|
11083
|
+
targetMongooseModel = this.requestRuntime.getModelInstance(meta.targetModelName);
|
|
11084
|
+
} catch {
|
|
11085
|
+
targetMongooseModel = parentContext.mongooseModel;
|
|
11086
|
+
}
|
|
11027
11087
|
}
|
|
11028
11088
|
const targetContext = {
|
|
11029
11089
|
mongooseModel: targetMongooseModel ?? parentContext.mongooseModel,
|