@web-ts-toolkit/access-router 0.57.0 → 0.58.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/index.js +421 -110
- package/index.mjs +421 -110
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -3543,6 +3543,47 @@ function serviceAuthorizationInput(policy, targets) {
|
|
|
3543
3543
|
}
|
|
3544
3544
|
return { rows: policy.rows, outputFields: policy.outputFields, related };
|
|
3545
3545
|
}
|
|
3546
|
+
function hasStableConfigurationCopies(value) {
|
|
3547
|
+
if (value === null || typeof value !== "object") return true;
|
|
3548
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
3549
|
+
return Object.values(value).every(hasStableConfigurationCopies);
|
|
3550
|
+
}
|
|
3551
|
+
function captureAuthorizationAccess(value) {
|
|
3552
|
+
const input = authorizationRecord(value, "access");
|
|
3553
|
+
const access = {};
|
|
3554
|
+
for (const key of ["operation", "rowAccess", "virtualAccess", "outputAccess", "docPermissionsAccess"]) {
|
|
3555
|
+
if (key === "rowAccess" && input[key] === void 0) continue;
|
|
3556
|
+
if (typeof input[key] !== "string") authorizationError(`invalid access.${key}`);
|
|
3557
|
+
access[key] = input[key];
|
|
3558
|
+
}
|
|
3559
|
+
return Object.freeze(access);
|
|
3560
|
+
}
|
|
3561
|
+
function captureAuthorizationCatalog(value) {
|
|
3562
|
+
const input = authorizationRecord(value, "catalog");
|
|
3563
|
+
const catalog = {};
|
|
3564
|
+
for (const key of [
|
|
3565
|
+
"paths",
|
|
3566
|
+
"persistedPaths",
|
|
3567
|
+
"arrayPaths",
|
|
3568
|
+
"schemaVirtualPaths",
|
|
3569
|
+
"metadataPaths",
|
|
3570
|
+
"dynamicPaths",
|
|
3571
|
+
"containerPaths",
|
|
3572
|
+
"virtualPaths",
|
|
3573
|
+
"virtualScopePaths",
|
|
3574
|
+
"opaquePaths",
|
|
3575
|
+
"referencePaths",
|
|
3576
|
+
"protectedPaths"
|
|
3577
|
+
]) {
|
|
3578
|
+
const paths = input[key];
|
|
3579
|
+
if (key !== "paths" && paths === void 0) continue;
|
|
3580
|
+
if (!Array.isArray(paths) || Array.from(paths).some((path) => typeof path !== "string")) {
|
|
3581
|
+
authorizationError(`invalid catalog.${key}`);
|
|
3582
|
+
}
|
|
3583
|
+
catalog[key] = paths;
|
|
3584
|
+
}
|
|
3585
|
+
return Object.freeze(catalog);
|
|
3586
|
+
}
|
|
3546
3587
|
function assertAuthorizationPath(path) {
|
|
3547
3588
|
if (typeof path !== "string" || !isSafeOutputPath(path)) {
|
|
3548
3589
|
authorizationError(`invalid response path: ${String(path)}`);
|
|
@@ -3672,6 +3713,9 @@ function resolveServiceAuthorization(input) {
|
|
|
3672
3713
|
const raw = copyAuthorizationValue(input.authorization);
|
|
3673
3714
|
const configuration = copyAuthorizationValue(input.configuration, true);
|
|
3674
3715
|
const targets = copyAuthorizationValue(input.relatedTargets ?? [], true);
|
|
3716
|
+
const access = copyAuthorizationValue(input.access);
|
|
3717
|
+
const outputAccess = captureAuthorizationAccess(access);
|
|
3718
|
+
const catalog = captureAuthorizationCatalog(configuration.catalog);
|
|
3675
3719
|
const policy = copyAuthorizationValue(normalizePolicy(raw, input.applicability ?? "service", configuration, targets));
|
|
3676
3720
|
const scopePath = Array.from(scope(input.binding));
|
|
3677
3721
|
for (const part of scopePath) {
|
|
@@ -3684,17 +3728,21 @@ function resolveServiceAuthorization(input) {
|
|
|
3684
3728
|
authorizationError("derived call request/runtime mismatch");
|
|
3685
3729
|
}
|
|
3686
3730
|
}
|
|
3687
|
-
const permissions =
|
|
3688
|
-
input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key)])
|
|
3731
|
+
const permissions = new permission_default(
|
|
3732
|
+
Object.fromEntries(input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key) === true]))
|
|
3689
3733
|
);
|
|
3734
|
+
for (const value of Object.values(permissions)) if (value && typeof value === "object") Object.freeze(value);
|
|
3735
|
+
Object.freeze(permissions);
|
|
3690
3736
|
const call = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3691
3737
|
calls.set(call, {
|
|
3692
3738
|
binding: Object.freeze({ ...input.binding, scopePath: Object.freeze(scopePath) }),
|
|
3693
|
-
access
|
|
3739
|
+
access,
|
|
3694
3740
|
configuration,
|
|
3695
3741
|
policy,
|
|
3696
3742
|
relatedTargets: targets,
|
|
3697
|
-
|
|
3743
|
+
// normalizeFields constructs only mode strings and validated string paths.
|
|
3744
|
+
output: Object.freeze({ access: outputAccess, catalog, outputFields: policy.outputFields }),
|
|
3745
|
+
permissions,
|
|
3698
3746
|
provenance: input.parent ? callRecord(input.parent).provenance : call
|
|
3699
3747
|
});
|
|
3700
3748
|
return call;
|
|
@@ -3712,12 +3760,75 @@ function readServiceAuthorization(call, binding) {
|
|
|
3712
3760
|
true
|
|
3713
3761
|
);
|
|
3714
3762
|
}
|
|
3763
|
+
function readServiceAuthorizationAccess(call, binding) {
|
|
3764
|
+
assertAuthorizationBinding(call, binding);
|
|
3765
|
+
return callRecord(call).output.access;
|
|
3766
|
+
}
|
|
3767
|
+
function readServiceAuthorizationHydration(call, binding) {
|
|
3768
|
+
assertAuthorizationBinding(call, binding);
|
|
3769
|
+
return copyAuthorizationValue({ hydration: callRecord(call).configuration.hydration }, true);
|
|
3770
|
+
}
|
|
3771
|
+
function outputMetadata(configuration) {
|
|
3772
|
+
return {
|
|
3773
|
+
documentPermissionField: configuration.documentPermissionField,
|
|
3774
|
+
exposedDocPermissionKeys: configuration.exposedDocPermissionKeys,
|
|
3775
|
+
stripPermissionsField: configuration.stripPermissionsField,
|
|
3776
|
+
disableFieldPermissions: configuration.disableFieldPermissions
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
function readServiceOutputMetadata(call, binding) {
|
|
3780
|
+
assertAuthorizationBinding(call, binding);
|
|
3781
|
+
return copyAuthorizationValue(outputMetadata(callRecord(call).configuration), true);
|
|
3782
|
+
}
|
|
3783
|
+
function readServiceDocumentPermissions(call, binding) {
|
|
3784
|
+
assertAuthorizationBinding(call, binding);
|
|
3785
|
+
const record3 = callRecord(call);
|
|
3786
|
+
return Object.freeze({
|
|
3787
|
+
catalog: record3.output.catalog,
|
|
3788
|
+
...copyAuthorizationValue({ docPermissions: record3.configuration.docPermissions }, true)
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
function readServiceFinalizerConfiguration(call, binding) {
|
|
3792
|
+
assertAuthorizationBinding(call, binding);
|
|
3793
|
+
const record3 = callRecord(call);
|
|
3794
|
+
return Object.freeze({
|
|
3795
|
+
catalog: record3.output.catalog,
|
|
3796
|
+
...copyAuthorizationValue(
|
|
3797
|
+
{
|
|
3798
|
+
...outputMetadata(record3.configuration),
|
|
3799
|
+
virtuals: record3.configuration.virtuals,
|
|
3800
|
+
docPermissions: record3.configuration.docPermissions
|
|
3801
|
+
},
|
|
3802
|
+
true
|
|
3803
|
+
)
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
function readServiceOutputPolicy(call, binding) {
|
|
3807
|
+
assertAuthorizationBinding(call, binding);
|
|
3808
|
+
return callRecord(call).output;
|
|
3809
|
+
}
|
|
3810
|
+
function readServiceOutputFieldAuthorization(call, binding) {
|
|
3811
|
+
assertAuthorizationBinding(call, binding);
|
|
3812
|
+
const record3 = callRecord(call);
|
|
3813
|
+
const reusable = record3.reusableFieldConfiguration ??= hasStableConfigurationCopies(record3.configuration.permissionSchema) && // Coercion methods can observe an object's identity even without native
|
|
3814
|
+
// leaves. Keep those receivers local to the original resolution boundary.
|
|
3815
|
+
(record3.configuration.modelPermissionPrefix === null || typeof record3.configuration.modelPermissionPrefix !== "object");
|
|
3816
|
+
return Object.freeze({
|
|
3817
|
+
access: record3.output.access,
|
|
3818
|
+
outputFields: record3.output.outputFields,
|
|
3819
|
+
reusable,
|
|
3820
|
+
...copyAuthorizationValue(
|
|
3821
|
+
{
|
|
3822
|
+
permissionSchema: record3.configuration.permissionSchema,
|
|
3823
|
+
modelPermissionPrefix: record3.configuration.modelPermissionPrefix
|
|
3824
|
+
},
|
|
3825
|
+
true
|
|
3826
|
+
)
|
|
3827
|
+
});
|
|
3828
|
+
}
|
|
3715
3829
|
function getAuthorizationPermissions(call, binding) {
|
|
3716
3830
|
assertAuthorizationBinding(call, binding);
|
|
3717
|
-
|
|
3718
|
-
for (const value of Object.values(permission)) if (value && typeof value === "object") Object.freeze(value);
|
|
3719
|
-
Object.freeze(permission);
|
|
3720
|
-
return permission;
|
|
3831
|
+
return callRecord(call).permissions;
|
|
3721
3832
|
}
|
|
3722
3833
|
function getAuthorizationProvenance(call) {
|
|
3723
3834
|
return callRecord(call).provenance;
|
|
@@ -3756,7 +3867,7 @@ function normalizeOutputAuthorizationOptions(input, options, purpose) {
|
|
|
3756
3867
|
const stage = stageRecord(documentRecord(handle).stage);
|
|
3757
3868
|
const binding = Object.freeze({ ...input.binding, scopePath: stage.binding.scopePath });
|
|
3758
3869
|
assertAuthorizationBinding(stage.call, binding);
|
|
3759
|
-
const access =
|
|
3870
|
+
const access = readServiceAuthorizationAccess(stage.call, binding);
|
|
3760
3871
|
if (input.access.outputAccess !== access.outputAccess) authorizationError("document access mismatch");
|
|
3761
3872
|
return Object.freeze({ kind: "document", documentAuthorization: handle, binding });
|
|
3762
3873
|
}
|
|
@@ -3834,7 +3945,16 @@ function readDocumentAuthorization(handle, binding) {
|
|
|
3834
3945
|
authorization: stage.call,
|
|
3835
3946
|
stage: record3.stage,
|
|
3836
3947
|
grants: copyAuthorizationValue(record3.grants),
|
|
3837
|
-
...
|
|
3948
|
+
...readServiceAuthorizationAccess(stage.call, binding)
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3951
|
+
function readDocumentAuthorizationAccess(handle, binding) {
|
|
3952
|
+
const record3 = documentRecord(handle);
|
|
3953
|
+
const stage = stageRecord(record3.stage);
|
|
3954
|
+
return Object.freeze({
|
|
3955
|
+
authorization: stage.call,
|
|
3956
|
+
stage: record3.stage,
|
|
3957
|
+
...readServiceAuthorizationAccess(stage.call, binding)
|
|
3838
3958
|
});
|
|
3839
3959
|
}
|
|
3840
3960
|
function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
|
|
@@ -3842,15 +3962,23 @@ function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
|
|
|
3842
3962
|
const stage = stageRecord(record3.stage);
|
|
3843
3963
|
assertAuthorizationBinding(stage.call, binding);
|
|
3844
3964
|
let decision = record3.decisions.get(field);
|
|
3845
|
-
if (
|
|
3965
|
+
if (decision) {
|
|
3966
|
+
return decision.then((result2) => {
|
|
3967
|
+
stageRecord(record3.stage);
|
|
3968
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3969
|
+
return result2;
|
|
3970
|
+
});
|
|
3971
|
+
} else {
|
|
3846
3972
|
decision = Promise.resolve().then(async () => {
|
|
3847
3973
|
stageRecord(record3.stage);
|
|
3974
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3848
3975
|
const result2 = await evaluate.call(
|
|
3849
3976
|
stage.binding.request,
|
|
3850
3977
|
getAuthorizationPermissions(stage.call, binding),
|
|
3851
3978
|
copyAuthorizationValue(record3.grants)
|
|
3852
3979
|
);
|
|
3853
3980
|
stageRecord(record3.stage);
|
|
3981
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3854
3982
|
if (typeof result2 !== "boolean") authorizationError("field decision must be boolean");
|
|
3855
3983
|
return result2;
|
|
3856
3984
|
});
|
|
@@ -3867,7 +3995,7 @@ function assertDocumentAuthorization(handle, use) {
|
|
|
3867
3995
|
const record3 = documentRecord(handle);
|
|
3868
3996
|
const stage = stageRecord(record3.stage);
|
|
3869
3997
|
assertAuthorizationBinding(stage.call, use.binding);
|
|
3870
|
-
if (use.authorization !== void 0 && stage.call !== use.authorization || use.stage !== void 0 && record3.stage !== use.stage ||
|
|
3998
|
+
if (use.authorization !== void 0 && stage.call !== use.authorization || use.stage !== void 0 && record3.stage !== use.stage || readServiceAuthorizationAccess(stage.call, use.binding).outputAccess !== use.outputAccess) {
|
|
3871
3999
|
authorizationError("document call/access/stage mismatch");
|
|
3872
4000
|
}
|
|
3873
4001
|
const association = outputs.get(use.output);
|
|
@@ -3922,7 +4050,7 @@ function transferDocumentAuthorizationGraph(source, copy, copyOrigins) {
|
|
|
3922
4050
|
{
|
|
3923
4051
|
binding: stage.binding,
|
|
3924
4052
|
output: original,
|
|
3925
|
-
outputAccess:
|
|
4053
|
+
outputAccess: readServiceAuthorizationAccess(stage.call, stage.binding).outputAccess,
|
|
3926
4054
|
authorization: stage.call,
|
|
3927
4055
|
stage: documentRecord(association.handle).stage
|
|
3928
4056
|
},
|
|
@@ -4087,12 +4215,20 @@ function classifyRequestFieldRule(rule, permissions, modelPermissionPrefix = "")
|
|
|
4087
4215
|
);
|
|
4088
4216
|
return (typeof rule === "string" ? possible.stringHandler(rule) : possible.arrayHandler(rule)) ? "deferred" : "denied";
|
|
4089
4217
|
}
|
|
4218
|
+
function prepareRequestFieldRule(rule, permissions, modelPermissionPrefix = "") {
|
|
4219
|
+
const state = classifyRequestFieldRule(rule, permissions, modelPermissionPrefix);
|
|
4220
|
+
const invariant = state === "denied" && modelPermissionPrefix ? classifyRequestFieldRule(rule, permissions) : state;
|
|
4221
|
+
return Object.freeze({ state, decision: invariant === "deferred" ? void 0 : invariant === "granted" });
|
|
4222
|
+
}
|
|
4223
|
+
function combineFieldAuthorizationStates(states2) {
|
|
4224
|
+
return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
|
|
4225
|
+
}
|
|
4090
4226
|
function classifyEffectiveFieldAuthorization(args) {
|
|
4091
4227
|
if (hasExplicitFieldGrant(args.outputFields, args.path)) return "granted";
|
|
4092
4228
|
const states2 = args.rules.map(
|
|
4093
4229
|
(rule) => classifyRequestFieldRule(rule, args.globalPermissions, args.modelPermissionPrefix)
|
|
4094
4230
|
);
|
|
4095
|
-
return
|
|
4231
|
+
return combineFieldAuthorizationStates(states2);
|
|
4096
4232
|
}
|
|
4097
4233
|
async function evaluateScopedAccessRule(args) {
|
|
4098
4234
|
const { req, rule, globalPermissions, docPermissions, modelPermissionPrefix } = args;
|
|
@@ -5741,16 +5877,111 @@ var init_virtual_populate_execution = __esm({
|
|
|
5741
5877
|
});
|
|
5742
5878
|
|
|
5743
5879
|
// src/acl/output-field-filter.ts
|
|
5880
|
+
function callFieldPolicy(authorization, binding) {
|
|
5881
|
+
assertAuthorizationBinding(authorization, binding);
|
|
5882
|
+
let policy = fieldPolicies.get(authorization);
|
|
5883
|
+
if (!policy) {
|
|
5884
|
+
const view = readServiceOutputFieldAuthorization(authorization, binding);
|
|
5885
|
+
policy = {
|
|
5886
|
+
schema: view.permissionSchema,
|
|
5887
|
+
requestPaths: view.permissionSchema == null || (0, import_utils28.isPlainObject)(view.permissionSchema) && Object.isFrozen(view.permissionSchema) ? Object.freeze(Object.keys(view.permissionSchema ?? {})) : void 0,
|
|
5888
|
+
prefix: view.modelPermissionPrefix ?? "",
|
|
5889
|
+
outputFields: view.outputFields,
|
|
5890
|
+
accesses: /* @__PURE__ */ new Map()
|
|
5891
|
+
};
|
|
5892
|
+
if (view.reusable) fieldPolicies.set(authorization, policy);
|
|
5893
|
+
}
|
|
5894
|
+
return policy;
|
|
5895
|
+
}
|
|
5896
|
+
function requestRuleClassification(entry, authorization, permissions, prefix) {
|
|
5897
|
+
if (typeof prefix !== "string") return void 0;
|
|
5898
|
+
let classification = entry.classifications.get(authorization);
|
|
5899
|
+
if (!classification) {
|
|
5900
|
+
classification = prepareRequestFieldRule(entry.rule, permissions, prefix);
|
|
5901
|
+
entry.classifications.set(authorization, classification);
|
|
5902
|
+
}
|
|
5903
|
+
return classification;
|
|
5904
|
+
}
|
|
5905
|
+
function invariantRuleLookup(schema, path) {
|
|
5906
|
+
if (schema == null) return true;
|
|
5907
|
+
if (!(0, import_utils28.isPlainObject)(schema) || !Object.isFrozen(schema)) return false;
|
|
5908
|
+
const exact = Object.getOwnPropertyDescriptor(schema, path);
|
|
5909
|
+
if (exact) {
|
|
5910
|
+
const raw2 = exact.value;
|
|
5911
|
+
return !raw2 || typeof raw2 !== "object" || Array.isArray(raw2) || (0, import_utils28.isPlainObject)(raw2) && Object.isFrozen(raw2);
|
|
5912
|
+
}
|
|
5913
|
+
const [head, ...rest] = path.split(".");
|
|
5914
|
+
const raw = Object.getOwnPropertyDescriptor(schema, head)?.value;
|
|
5915
|
+
if (raw == null) return true;
|
|
5916
|
+
if (!(0, import_utils28.isPlainObject)(raw) || !Object.isFrozen(raw)) return false;
|
|
5917
|
+
const sub = Object.getOwnPropertyDescriptor(raw, "sub");
|
|
5918
|
+
return !rest.length || !sub || invariantRuleLookup(sub.value, rest.join("."));
|
|
5919
|
+
}
|
|
5920
|
+
function prepareOutputPath(policy, authorization, binding, path, access) {
|
|
5921
|
+
let cache = policy.accesses.get(access);
|
|
5922
|
+
if (!cache) {
|
|
5923
|
+
cache = /* @__PURE__ */ new Map();
|
|
5924
|
+
policy.accesses.set(access, cache);
|
|
5925
|
+
}
|
|
5926
|
+
let prepared = cache.get(path);
|
|
5927
|
+
if (!prepared) {
|
|
5928
|
+
const invariant = invariantRuleLookup(policy.schema, path);
|
|
5929
|
+
const permissions = getAuthorizationPermissions(authorization, binding);
|
|
5930
|
+
const entries = extractOutputFieldRuleEntries(policy.schema, path, access).map((entry) => ({
|
|
5931
|
+
...entry,
|
|
5932
|
+
classifications: /* @__PURE__ */ new WeakMap()
|
|
5933
|
+
}));
|
|
5934
|
+
const classifications = entries.map(
|
|
5935
|
+
(entry) => requestRuleClassification(entry, authorization, permissions, policy.prefix)
|
|
5936
|
+
);
|
|
5937
|
+
const backendGrant = hasExplicitFieldGrant(policy.outputFields, path);
|
|
5938
|
+
prepared = {
|
|
5939
|
+
entries,
|
|
5940
|
+
backendGrant,
|
|
5941
|
+
state: backendGrant ? "granted" : classifications.every((value) => value !== void 0) ? combineFieldAuthorizationStates(classifications.map((value) => value.state)) : void 0
|
|
5942
|
+
};
|
|
5943
|
+
if (invariant) cache.set(path, prepared);
|
|
5944
|
+
}
|
|
5945
|
+
return prepared;
|
|
5946
|
+
}
|
|
5947
|
+
function assertOwningOutputDocuments(handle) {
|
|
5948
|
+
const owner = owningDocuments.get(handle);
|
|
5949
|
+
if (owner) {
|
|
5950
|
+
readDocumentAuthorizationAccess(owner.handle, owner.binding);
|
|
5951
|
+
assertOwningOutputDocuments(owner.handle);
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5954
|
+
function prepareOutputDocumentAuthorization(authorization, binding, configuration) {
|
|
5955
|
+
const access = readServiceAuthorizationAccess(authorization, binding);
|
|
5956
|
+
const key = `docPermissions.${access.docPermissionsAccess}`;
|
|
5957
|
+
const docPermissions = configuration.docPermissions;
|
|
5958
|
+
const invariant = /^[A-Za-z0-9_-]+$/.test(access.docPermissionsAccess) && (docPermissions == null || (0, import_utils28.isPlainObject)(docPermissions));
|
|
5959
|
+
const option = invariant ? getCapturedAuthorizationOption(configuration, key) : void 0;
|
|
5960
|
+
const hook = typeof option === "function" ? option : void 0;
|
|
5961
|
+
const resolveHook = invariant ? () => hook : () => {
|
|
5962
|
+
const slice = { catalog: configuration.catalog, ...copyAuthorizationValue({ docPermissions }, true) };
|
|
5963
|
+
const option2 = getCapturedAuthorizationOption(slice, key);
|
|
5964
|
+
return typeof option2 === "function" ? option2 : void 0;
|
|
5965
|
+
};
|
|
5966
|
+
return async (args) => {
|
|
5967
|
+
if (args.authorization !== authorization) authorizationError("document resolver call mismatch");
|
|
5968
|
+
assertAuthorizationBinding(authorization, args.binding);
|
|
5969
|
+
return resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook);
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5744
5972
|
async function resolveOutputDocumentAuthorization(args) {
|
|
5973
|
+
const configuration = readServiceDocumentPermissions(args.authorization, args.binding);
|
|
5974
|
+
return prepareOutputDocumentAuthorization(args.authorization, args.binding, configuration)(args);
|
|
5975
|
+
}
|
|
5976
|
+
async function resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook) {
|
|
5745
5977
|
const { authorization, binding } = args;
|
|
5746
|
-
const { configuration, access } = readServiceAuthorization(authorization, binding);
|
|
5747
5978
|
const stage = args.stage ?? createDocumentAuthorizationStage(authorization, binding, args.snapshot);
|
|
5748
5979
|
if (args.stage) assertDocumentAuthorizationStage(stage, authorization, binding, args.snapshot);
|
|
5749
5980
|
const handle = await resolveDocumentAuthorization(stage, async function(doc, permissions) {
|
|
5750
5981
|
if (args.owningDocument) return readDocumentAuthorization(args.owningDocument, args.owningBinding).grants;
|
|
5751
5982
|
if (args.knownOutput) return {};
|
|
5752
|
-
const hook =
|
|
5753
|
-
if (
|
|
5983
|
+
const hook = resolveHook();
|
|
5984
|
+
if (!hook) return {};
|
|
5754
5985
|
try {
|
|
5755
5986
|
const run = () => hook.call(this, doc, permissions, args.context);
|
|
5756
5987
|
const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
|
|
@@ -5771,6 +6002,7 @@ async function resolveOutputDocumentAuthorization(args) {
|
|
|
5771
6002
|
return handle;
|
|
5772
6003
|
}
|
|
5773
6004
|
function evaluateRequestRule(handle, binding, entry, prefix, access) {
|
|
6005
|
+
assertOwningOutputDocuments(handle);
|
|
5774
6006
|
const owner = owningDocuments.get(handle);
|
|
5775
6007
|
if (owner && !entry.path.startsWith(`${(binding.scopePath ?? []).join(".")}.`)) {
|
|
5776
6008
|
return evaluateRequestRule(owner.handle, owner.binding, entry, prefix, access);
|
|
@@ -5779,65 +6011,78 @@ function evaluateRequestRule(handle, binding, entry, prefix, access) {
|
|
|
5779
6011
|
handle,
|
|
5780
6012
|
binding,
|
|
5781
6013
|
`request:${access}:${entry.path}`,
|
|
5782
|
-
function(permissions, grants) {
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
6014
|
+
async function(permissions, grants) {
|
|
6015
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6016
|
+
const classification = requestRuleClassification(entry, authorization, permissions, prefix);
|
|
6017
|
+
let allowed = classification?.decision;
|
|
6018
|
+
if (allowed === void 0) {
|
|
6019
|
+
allowed = await evaluateScopedAccessRule({
|
|
6020
|
+
req: this,
|
|
6021
|
+
rule: entry.rule,
|
|
6022
|
+
globalPermissions: permissions,
|
|
6023
|
+
docPermissions: grants,
|
|
6024
|
+
modelPermissionPrefix: prefix
|
|
6025
|
+
});
|
|
6026
|
+
}
|
|
6027
|
+
assertOwningOutputDocuments(handle);
|
|
6028
|
+
return allowed;
|
|
5790
6029
|
}
|
|
5791
6030
|
);
|
|
5792
6031
|
}
|
|
5793
6032
|
function evaluateAuthorizedOutputField(handle, binding, field) {
|
|
5794
|
-
const { authorization } =
|
|
5795
|
-
|
|
6033
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6034
|
+
assertOwningOutputDocuments(handle);
|
|
5796
6035
|
const path = [...binding.scopePath ?? [], field].join(".");
|
|
5797
|
-
const
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
const args = {
|
|
6036
|
+
const decision = memoizeDocumentFieldDecision(handle, binding, `output:${path}`, async function(permissions) {
|
|
6037
|
+
const policy = callFieldPolicy(authorization, binding);
|
|
6038
|
+
const { outputAccess } = readDocumentAuthorizationAccess(handle, binding);
|
|
6039
|
+
const prepared = prepareOutputPath(policy, authorization, binding, path, outputAccess);
|
|
6040
|
+
const unresolved = unresolvedDocuments.has(handle);
|
|
6041
|
+
const state = unresolved ? prepared.state ?? classifyEffectiveFieldAuthorization({
|
|
5804
6042
|
path,
|
|
5805
|
-
rules: entries.map((entry) => entry.rule),
|
|
6043
|
+
rules: prepared.entries.map((entry) => entry.rule),
|
|
6044
|
+
modelPermissionPrefix: policy.prefix,
|
|
5806
6045
|
outputFields: policy.outputFields,
|
|
5807
|
-
globalPermissions: permissions
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
if (
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
6046
|
+
globalPermissions: permissions
|
|
6047
|
+
}) : void 0;
|
|
6048
|
+
let allowed;
|
|
6049
|
+
if (unresolved && state !== "granted") allowed = false;
|
|
6050
|
+
else if (prepared.backendGrant) allowed = true;
|
|
6051
|
+
else {
|
|
6052
|
+
allowed = true;
|
|
6053
|
+
for (const entry of prepared.entries) {
|
|
6054
|
+
if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, outputAccess)) {
|
|
6055
|
+
allowed = false;
|
|
6056
|
+
break;
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
assertOwningOutputDocuments(handle);
|
|
6061
|
+
return allowed;
|
|
5817
6062
|
});
|
|
6063
|
+
return owningDocuments.has(handle) ? decision.then((allowed) => {
|
|
6064
|
+
assertOwningOutputDocuments(handle);
|
|
6065
|
+
return allowed;
|
|
6066
|
+
}) : decision;
|
|
5818
6067
|
}
|
|
5819
6068
|
async function requestOutputFieldPaths(handle, binding, access) {
|
|
5820
|
-
const { authorization } =
|
|
5821
|
-
|
|
5822
|
-
const
|
|
6069
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6070
|
+
assertOwningOutputDocuments(handle);
|
|
6071
|
+
const policy = callFieldPolicy(authorization, binding);
|
|
5823
6072
|
const fields = [];
|
|
5824
|
-
for (const path of Object.keys(schema ?? {})) {
|
|
5825
|
-
const entries =
|
|
6073
|
+
for (const path of policy.requestPaths ?? Object.keys(policy.schema ?? {})) {
|
|
6074
|
+
const { entries } = prepareOutputPath(policy, authorization, binding, path, access);
|
|
5826
6075
|
let allowed = true;
|
|
5827
6076
|
for (const entry of entries) {
|
|
5828
|
-
if (!await evaluateRequestRule(
|
|
5829
|
-
handle,
|
|
5830
|
-
binding,
|
|
5831
|
-
entry,
|
|
5832
|
-
configuration.modelPermissionPrefix ?? "",
|
|
5833
|
-
access
|
|
5834
|
-
)) {
|
|
6077
|
+
if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, access)) {
|
|
5835
6078
|
allowed = false;
|
|
5836
6079
|
break;
|
|
5837
6080
|
}
|
|
5838
6081
|
}
|
|
5839
6082
|
if (allowed && entries.length) fields.push(path);
|
|
5840
6083
|
}
|
|
6084
|
+
assertOwningOutputDocuments(handle);
|
|
6085
|
+
readDocumentAuthorizationAccess(handle, binding);
|
|
5841
6086
|
return fields;
|
|
5842
6087
|
}
|
|
5843
6088
|
function authorizeEmbeddedOutputPlan(parent, child) {
|
|
@@ -5858,13 +6103,13 @@ function authorizeEmbeddedOutputPlan(parent, child) {
|
|
|
5858
6103
|
function hasWholeOutputValueGrant(plan, field) {
|
|
5859
6104
|
if (plan.fieldStates?.[field] !== "granted") return false;
|
|
5860
6105
|
const binding = plan.authorizationBinding;
|
|
5861
|
-
const {
|
|
5862
|
-
return hasExplicitFieldGrant(
|
|
6106
|
+
const { outputFields } = readServiceOutputPolicy(plan.authorization, binding);
|
|
6107
|
+
return hasExplicitFieldGrant(outputFields, [...binding.scopePath ?? [], field].join("."));
|
|
5863
6108
|
}
|
|
5864
6109
|
function copyOutputValue(value, plan, path) {
|
|
5865
|
-
const {
|
|
5866
|
-
const virtualValue =
|
|
5867
|
-
const opaque =
|
|
6110
|
+
const { catalog } = readServiceOutputPolicy(plan.authorization, plan.authorizationBinding);
|
|
6111
|
+
const virtualValue = catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
|
|
6112
|
+
const opaque = catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
|
|
5868
6113
|
const excluded = plan.effectiveSelectNorm.filter((token) => token.startsWith("-")).map((token) => [...plan.authorizationBinding.scopePath ?? [], token.slice(1)].join("."));
|
|
5869
6114
|
const visit = (input, current) => {
|
|
5870
6115
|
if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
|
|
@@ -5875,7 +6120,7 @@ function copyOutputValue(value, plan, path) {
|
|
|
5875
6120
|
const child = `${current}.${key}`;
|
|
5876
6121
|
if (excluded.some((entry2) => child === entry2 || child.startsWith(`${entry2}.`))) continue;
|
|
5877
6122
|
const safeValueKey = !["__proto__", "constructor", "prototype", "_doc", "$__", "__v"].includes(key);
|
|
5878
|
-
if (!(opaque ? safeValueKey : isSafeOutputPath(key)) ||
|
|
6123
|
+
if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(catalog, child))
|
|
5879
6124
|
continue;
|
|
5880
6125
|
output[key] = visit(entry, child);
|
|
5881
6126
|
}
|
|
@@ -5955,7 +6200,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
|
|
|
5955
6200
|
const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
|
|
5956
6201
|
let child = scoped;
|
|
5957
6202
|
if (childHandle) {
|
|
5958
|
-
const state =
|
|
6203
|
+
const state = readDocumentAuthorizationAccess(childHandle, scoped.authorizationBinding);
|
|
5959
6204
|
if (getAuthorizationProvenance(state.authorization) !== getAuthorizationProvenance(plan.authorization))
|
|
5960
6205
|
authorizationError("child call mismatch");
|
|
5961
6206
|
child = helperPlan(state.authorization, scoped.authorizationBinding);
|
|
@@ -6019,7 +6264,7 @@ async function filterModelOutputForHelper(args) {
|
|
|
6019
6264
|
if (normalized.kind === "document") {
|
|
6020
6265
|
const { documentAuthorization: handle2, binding: binding2 } = normalized;
|
|
6021
6266
|
assertDocumentAuthorization(handle2, { binding: binding2, output: args.doc, outputAccess: access });
|
|
6022
|
-
const state =
|
|
6267
|
+
const state = readDocumentAuthorizationAccess(handle2, binding2);
|
|
6023
6268
|
return filterExistingOutput(
|
|
6024
6269
|
args.doc,
|
|
6025
6270
|
helperPlan(state.authorization, binding2),
|
|
@@ -6042,7 +6287,7 @@ async function filterModelOutputForHelper(args) {
|
|
|
6042
6287
|
});
|
|
6043
6288
|
return filterExistingOutput(source, plan, handle, false);
|
|
6044
6289
|
}
|
|
6045
|
-
var import_utils28, unresolvedDocuments, owningDocuments;
|
|
6290
|
+
var import_utils28, unresolvedDocuments, owningDocuments, fieldPolicies;
|
|
6046
6291
|
var init_output_field_filter = __esm({
|
|
6047
6292
|
"src/acl/output-field-filter.ts"() {
|
|
6048
6293
|
import_utils28 = require("@web-ts-toolkit/utils");
|
|
@@ -6059,6 +6304,7 @@ var init_output_field_filter = __esm({
|
|
|
6059
6304
|
init_output_projection();
|
|
6060
6305
|
unresolvedDocuments = /* @__PURE__ */ new WeakSet();
|
|
6061
6306
|
owningDocuments = /* @__PURE__ */ new WeakMap();
|
|
6307
|
+
fieldPolicies = /* @__PURE__ */ new WeakMap();
|
|
6062
6308
|
}
|
|
6063
6309
|
});
|
|
6064
6310
|
|
|
@@ -6127,15 +6373,41 @@ __export(finalize_model_output_exports, {
|
|
|
6127
6373
|
finalizeModelOutput: () => finalizeModelOutput,
|
|
6128
6374
|
finalizeModelOutputs: () => finalizeModelOutputs
|
|
6129
6375
|
});
|
|
6130
|
-
|
|
6131
|
-
const scopePath = args.scopePath;
|
|
6376
|
+
function assertFinalizerCall(args, prepared) {
|
|
6132
6377
|
const plan = args.plan;
|
|
6133
6378
|
const binding = plan.authorizationBinding;
|
|
6134
|
-
|
|
6135
|
-
if (plan.authorization &&
|
|
6379
|
+
if (plan.authorization !== prepared.authorization) authorizationError("finalizer call changed during pass");
|
|
6380
|
+
if (plan.authorization && binding) assertAuthorizationBinding(plan.authorization, binding);
|
|
6381
|
+
const access = prepared.policy?.access;
|
|
6382
|
+
if (plan.authorization && (!binding || binding.request !== args.request || binding.modelName !== args.receivingModelName || access?.virtualAccess !== args.virtualAccess || access.outputAccess !== args.outputAccess || access.docPermissionsAccess !== args.docPermissionsAccess || (binding.scopePath ?? []).join(".") !== args.scopePath.filter((p) => p !== "sub").join("."))) {
|
|
6136
6383
|
authorizationError("finalizer call/model/scope/access mismatch");
|
|
6137
6384
|
}
|
|
6138
|
-
|
|
6385
|
+
}
|
|
6386
|
+
function prepareFinalizer(args, sharedContext = false) {
|
|
6387
|
+
const { authorization, authorizationBinding: binding } = args.plan;
|
|
6388
|
+
const policy = authorization && binding ? readServiceOutputPolicy(authorization, binding) : void 0;
|
|
6389
|
+
assertFinalizerCall(args, { authorization, policy });
|
|
6390
|
+
const configuration = authorization ? readServiceFinalizerConfiguration(authorization, binding) : void 0;
|
|
6391
|
+
const snapshot = configuration ? configuration : deepIsolateValue(args.snapshot);
|
|
6392
|
+
return {
|
|
6393
|
+
authorization,
|
|
6394
|
+
policy,
|
|
6395
|
+
snapshot,
|
|
6396
|
+
baseContext: deepIsolateValue(args.context ?? {}),
|
|
6397
|
+
sharedContext,
|
|
6398
|
+
scopeVirtualNames: getScopeVirtualNames(snapshot, args.scopePath),
|
|
6399
|
+
scopedSchema: configuration ? null : getScopedPermissionSchema2(snapshot.permissionSchema, args.scopePath),
|
|
6400
|
+
permissions: authorization ? getAuthorizationPermissions(authorization, binding) : void 0,
|
|
6401
|
+
resolveDocument: authorization ? prepareOutputDocumentAuthorization(authorization, binding, configuration) : void 0
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
async function finalizeSingleDoc(args, input, prepared) {
|
|
6405
|
+
assertFinalizerCall(args, prepared);
|
|
6406
|
+
const scopePath = args.scopePath;
|
|
6407
|
+
const plan = args.plan;
|
|
6408
|
+
const binding = plan.authorizationBinding;
|
|
6409
|
+
const captured = prepared.policy;
|
|
6410
|
+
const snapshot = prepared.snapshot;
|
|
6139
6411
|
const limit = args.resolvedLimit;
|
|
6140
6412
|
const gate = args.sharedGate;
|
|
6141
6413
|
const original = getIncludeOriginal(input);
|
|
@@ -6147,7 +6419,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6147
6419
|
for (const key of Object.keys(output)) {
|
|
6148
6420
|
transferDocumentAuthorizationGraph(input[key], output[key]);
|
|
6149
6421
|
}
|
|
6150
|
-
const baseContext =
|
|
6422
|
+
const baseContext = prepared.sharedContext ? deepIsolateValue(prepared.baseContext) : prepared.baseContext;
|
|
6151
6423
|
const operation = captured?.access.operation ?? baseContext.operation ?? args.operation ?? args.virtualAccess;
|
|
6152
6424
|
const scopeContext = {
|
|
6153
6425
|
...baseContext,
|
|
@@ -6165,7 +6437,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6165
6437
|
if (captured) {
|
|
6166
6438
|
const projected = args.documentAuthorizationStage ? input : deepIsolateValue(output);
|
|
6167
6439
|
metadataSource = deepIsolateValue(output);
|
|
6168
|
-
documentAuthorization = await
|
|
6440
|
+
documentAuthorization = await prepared.resolveDocument({
|
|
6169
6441
|
authorization: plan.authorization,
|
|
6170
6442
|
binding,
|
|
6171
6443
|
snapshot: projected,
|
|
@@ -6178,13 +6450,10 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6178
6450
|
}
|
|
6179
6451
|
const docGrants = documentAuthorization ? readDocumentAuthorization(documentAuthorization, binding).grants : await resolveDocPermissions(args, output, scopeContext);
|
|
6180
6452
|
scopeContext.docPermissions = deepIsolateValue(docGrants);
|
|
6181
|
-
const globalPerms =
|
|
6182
|
-
const modelPermissionPrefix = snapshot.modelPermissionPrefix ?? "";
|
|
6183
|
-
const documentPermissionField = snapshot.documentPermissionField ?? "_permissions";
|
|
6184
|
-
const scopedSchema =
|
|
6185
|
-
snapshot.permissionSchema ?? null,
|
|
6186
|
-
scopePath
|
|
6187
|
-
);
|
|
6453
|
+
const globalPerms = prepared.permissions ?? resolveGlobalPermissions(args);
|
|
6454
|
+
const modelPermissionPrefix = captured ? "" : deepIsolateValue(snapshot.modelPermissionPrefix ?? "");
|
|
6455
|
+
const documentPermissionField = captured ? "_permissions" : deepIsolateValue(snapshot.documentPermissionField ?? "_permissions");
|
|
6456
|
+
const scopedSchema = deepIsolateValue(prepared.scopedSchema);
|
|
6188
6457
|
const childPlans = plan.childPlans ?? {};
|
|
6189
6458
|
for (const [containerName, childPlan] of Object.entries(childPlans)) {
|
|
6190
6459
|
if (!(containerName in output)) continue;
|
|
@@ -6203,12 +6472,13 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6203
6472
|
continue;
|
|
6204
6473
|
}
|
|
6205
6474
|
retainRawAssociation(output, containerName, rawContainer);
|
|
6206
|
-
const childSnapshot = snapshot;
|
|
6207
6475
|
const childBase = {
|
|
6208
6476
|
...args,
|
|
6209
6477
|
scopePath: [...childPlan.scopePath ?? [...scopePath, containerName, "sub"]],
|
|
6210
6478
|
plan: captured ? authorizeEmbeddedOutputPlan(plan, childPlan) : childPlan,
|
|
6211
|
-
|
|
6479
|
+
// Authenticated preparation reads its own authority. Do not clone an
|
|
6480
|
+
// otherwise unused public configuration graph on default traversal.
|
|
6481
|
+
snapshot: args.snapshot,
|
|
6212
6482
|
// Embedded reuses the owning parent's grants + accesses (VIRT-00A D2).
|
|
6213
6483
|
docPermissions: docGrants,
|
|
6214
6484
|
context: scopeContext,
|
|
@@ -6219,6 +6489,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6219
6489
|
related: [],
|
|
6220
6490
|
virtualPopulateRow: privateRow
|
|
6221
6491
|
};
|
|
6492
|
+
let childPreparation;
|
|
6222
6493
|
const finalizeOneChild = async (rawChild, address) => {
|
|
6223
6494
|
if (captured && Array.isArray(rawChild))
|
|
6224
6495
|
return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
|
|
@@ -6239,7 +6510,14 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6239
6510
|
} : { ...args, virtualPopulateRow: privateRow, virtualPopulateAddress: address }
|
|
6240
6511
|
});
|
|
6241
6512
|
}
|
|
6242
|
-
return finalizeSingleDoc(
|
|
6513
|
+
return finalizeSingleDoc(
|
|
6514
|
+
{ ...childBase, virtualPopulateAddress: address },
|
|
6515
|
+
rawChild,
|
|
6516
|
+
childPreparation ??= prepareFinalizer(
|
|
6517
|
+
childBase,
|
|
6518
|
+
Array.isArray(rawContainer) && (rawContainer.length > 1 || Array.isArray(rawContainer[0]))
|
|
6519
|
+
)
|
|
6520
|
+
);
|
|
6243
6521
|
};
|
|
6244
6522
|
if (Array.isArray(rawContainer)) {
|
|
6245
6523
|
const finalized = await mapWithConcurrencyLimit(
|
|
@@ -6291,6 +6569,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6291
6569
|
context: scopeContext,
|
|
6292
6570
|
related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
|
|
6293
6571
|
};
|
|
6572
|
+
let relatedPreparation;
|
|
6294
6573
|
const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
|
|
6295
6574
|
if (!(0, import_utils29.isPlainObject)(rawChild) && !Array.isArray(rawChild)) return rawChild;
|
|
6296
6575
|
if (typeof rawChild?._bsontype === "string") return rawChild;
|
|
@@ -6304,7 +6583,11 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6304
6583
|
parentArgs: { ...args, virtualPopulateRow: childRow, virtualPopulateAddress: [] }
|
|
6305
6584
|
});
|
|
6306
6585
|
}
|
|
6307
|
-
return finalizeSingleDoc(
|
|
6586
|
+
return finalizeSingleDoc(
|
|
6587
|
+
{ ...relatedBase, virtualPopulateRow: childRow },
|
|
6588
|
+
rawChild,
|
|
6589
|
+
relatedPreparation ??= prepareFinalizer(relatedBase, Array.isArray(rawRelated) && rawRelated.length > 1)
|
|
6590
|
+
);
|
|
6308
6591
|
};
|
|
6309
6592
|
let finalized;
|
|
6310
6593
|
if (Array.isArray(rawRelated)) {
|
|
@@ -6324,18 +6607,24 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6324
6607
|
} catch {
|
|
6325
6608
|
}
|
|
6326
6609
|
}
|
|
6327
|
-
const
|
|
6328
|
-
for (const virtualName of scopeVirtualNames) {
|
|
6610
|
+
for (const virtualName of prepared.scopeVirtualNames) {
|
|
6329
6611
|
if (virtualName in output) delete output[virtualName];
|
|
6330
6612
|
}
|
|
6331
|
-
const stableBase = deepIsolateValue(output);
|
|
6332
|
-
const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
|
|
6333
6613
|
const candidates = plan.candidates ?? [];
|
|
6614
|
+
const needsGetterView = candidates.some((candidate) => {
|
|
6615
|
+
const descriptor = candidate.descriptor;
|
|
6616
|
+
if (typeof descriptor?.get !== "function") return false;
|
|
6617
|
+
if (!privateRow && descriptor.dependsOnPopulate?.length) return false;
|
|
6618
|
+
const dependsOn = Array.isArray(descriptor.dependsOn) ? descriptor.dependsOn : [];
|
|
6619
|
+
return dependsOn.every((dep) => typeof dep === "string" && !isAbsentDependency(output, dep));
|
|
6620
|
+
});
|
|
6621
|
+
let stableBase = needsGetterView ? deepIsolateValue(output) : void 0;
|
|
6622
|
+
const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
|
|
6334
6623
|
const staged = /* @__PURE__ */ new Map();
|
|
6335
6624
|
const runCandidate = async (candidate) => {
|
|
6336
6625
|
const dependsOn = Array.isArray(candidate.descriptor?.dependsOn) ? candidate.descriptor.dependsOn : [];
|
|
6337
6626
|
for (const dep of dependsOn) {
|
|
6338
|
-
if (typeof dep !== "string" || isAbsentDependency(stableBase, dep)) return;
|
|
6627
|
+
if (typeof dep !== "string" || isAbsentDependency(stableBase ?? output, dep)) return;
|
|
6339
6628
|
}
|
|
6340
6629
|
const allowed = documentAuthorization ? await evaluateAuthorizedOutputField(documentAuthorization, binding, candidate.name) : await evaluateEffectiveFieldAuthorization({
|
|
6341
6630
|
req: args.request,
|
|
@@ -6350,9 +6639,9 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6350
6639
|
if (typeof getter !== "function") return;
|
|
6351
6640
|
const privateInputs = privateRow ? readVirtualPopulateInputs(privateRow, privateAddress, candidate.name) : candidate.descriptor.dependsOnPopulate?.length ? { available: false } : { available: true, populated: {} };
|
|
6352
6641
|
if (!privateInputs.available) return;
|
|
6353
|
-
const isolatedView = deepIsolateValue(stableBase);
|
|
6642
|
+
const isolatedView = deepIsolateValue(stableBase ??= deepIsolateValue(output));
|
|
6354
6643
|
const virtualContext = {
|
|
6355
|
-
...scopeContext,
|
|
6644
|
+
...deepIsolateValue(scopeContext),
|
|
6356
6645
|
docPermissions: deepIsolateValue(docGrants)
|
|
6357
6646
|
};
|
|
6358
6647
|
const outcome = await runVirtualGetter({
|
|
@@ -6443,12 +6732,25 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6443
6732
|
if (!(key in output)) continue;
|
|
6444
6733
|
if (captured && !plan.childPlans[key]) {
|
|
6445
6734
|
const path = [...binding.scopePath ?? [], key].join(".");
|
|
6446
|
-
trimmed[key] = captured.
|
|
6735
|
+
trimmed[key] = captured.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
|
|
6447
6736
|
if (trimmed[key] === void 0) delete trimmed[key];
|
|
6448
6737
|
} else trimmed[key] = output[key];
|
|
6449
6738
|
}
|
|
6450
6739
|
if (documentAuthorization) {
|
|
6451
|
-
retainOutputMetadata(
|
|
6740
|
+
retainOutputMetadata(
|
|
6741
|
+
trimmed,
|
|
6742
|
+
metadataSource,
|
|
6743
|
+
copyAuthorizationValue(
|
|
6744
|
+
{
|
|
6745
|
+
documentPermissionField: snapshot.documentPermissionField,
|
|
6746
|
+
stripPermissionsField: snapshot.stripPermissionsField,
|
|
6747
|
+
exposedDocPermissionKeys: snapshot.exposedDocPermissionKeys,
|
|
6748
|
+
disableFieldPermissions: snapshot.disableFieldPermissions
|
|
6749
|
+
},
|
|
6750
|
+
true
|
|
6751
|
+
),
|
|
6752
|
+
binding.scopePath ?? []
|
|
6753
|
+
);
|
|
6452
6754
|
associateDocumentAuthorization(documentAuthorization, binding, trimmed);
|
|
6453
6755
|
args.onFinalized?.(
|
|
6454
6756
|
trimmed,
|
|
@@ -6457,7 +6759,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6457
6759
|
output: trimmed,
|
|
6458
6760
|
outputAccess: args.outputAccess,
|
|
6459
6761
|
authorization: plan.authorization,
|
|
6460
|
-
stage:
|
|
6762
|
+
stage: readDocumentAuthorizationAccess(documentAuthorization, binding).stage
|
|
6461
6763
|
})
|
|
6462
6764
|
);
|
|
6463
6765
|
}
|
|
@@ -6478,7 +6780,7 @@ async function finalizeModelOutput(args) {
|
|
|
6478
6780
|
sharedGate: gate,
|
|
6479
6781
|
resolvedLimit: limit
|
|
6480
6782
|
};
|
|
6481
|
-
return finalizeSingleDoc(internal, args.input);
|
|
6783
|
+
return finalizeSingleDoc(internal, args.input, prepareFinalizer(internal));
|
|
6482
6784
|
}
|
|
6483
6785
|
async function finalizeModelOutputs(args) {
|
|
6484
6786
|
const gate = finalizerGate(args);
|
|
@@ -6487,16 +6789,24 @@ async function finalizeModelOutputs(args) {
|
|
|
6487
6789
|
if ("documentAuthorizationStage" in args || documentAuthorizationStages && (documentAuthorizationStages.length !== inputs.length || new Set(documentAuthorizationStages).size !== inputs.length)) {
|
|
6488
6790
|
authorizationError("list finalization requires a distinct document stage per row");
|
|
6489
6791
|
}
|
|
6792
|
+
if (!inputs.length) return [];
|
|
6793
|
+
const internal = {
|
|
6794
|
+
...rest,
|
|
6795
|
+
scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
|
|
6796
|
+
sharedGate: gate,
|
|
6797
|
+
resolvedLimit: limit
|
|
6798
|
+
};
|
|
6799
|
+
const prepared = prepareFinalizer(internal, inputs.length > 1);
|
|
6490
6800
|
return mapWithConcurrencyLimit(inputs, limit, (input, index) => {
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6801
|
+
return finalizeSingleDoc(
|
|
6802
|
+
{
|
|
6803
|
+
...internal,
|
|
6804
|
+
onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
|
|
6805
|
+
documentAuthorizationStage: documentAuthorizationStages?.[index]
|
|
6806
|
+
},
|
|
6807
|
+
input,
|
|
6808
|
+
prepared
|
|
6809
|
+
);
|
|
6500
6810
|
});
|
|
6501
6811
|
}
|
|
6502
6812
|
var import_utils29, isVirtualContainerValue3, getScopeVirtualsObject2, getScopeVirtualNames, getScopedPermissionSchema2, resolveGlobalPermissions, resolveDocPermissions, isAbsentDependency, __finalizeInternals;
|
|
@@ -6566,7 +6876,7 @@ var init_finalize_model_output = __esm({
|
|
|
6566
6876
|
};
|
|
6567
6877
|
resolveDocPermissions = async (args, isolatedForHook, scopeContext) => {
|
|
6568
6878
|
if (args.docPermissions != null && typeof args.docPermissions === "object" && !Array.isArray(args.docPermissions)) {
|
|
6569
|
-
const grants = args.docPermissions;
|
|
6879
|
+
const grants = deepIsolateValue(args.docPermissions);
|
|
6570
6880
|
scopeContext.docPermissions = grants;
|
|
6571
6881
|
return grants;
|
|
6572
6882
|
}
|
|
@@ -7537,9 +7847,9 @@ function createServiceExecution(receiver, access, authorization, applicability =
|
|
|
7537
7847
|
receiver,
|
|
7538
7848
|
binding,
|
|
7539
7849
|
authorization: call,
|
|
7540
|
-
access:
|
|
7850
|
+
access: readServiceAuthorizationAccess(call, binding),
|
|
7541
7851
|
family: execution,
|
|
7542
|
-
hydration: resolveHydration(
|
|
7852
|
+
hydration: resolveHydration(readServiceAuthorizationHydration(call, binding), hydration)
|
|
7543
7853
|
})
|
|
7544
7854
|
);
|
|
7545
7855
|
return execution;
|
|
@@ -7550,7 +7860,7 @@ function resolveHydration(configuration, source) {
|
|
|
7550
7860
|
}
|
|
7551
7861
|
function withExecutionHydration(receiver, parent, source) {
|
|
7552
7862
|
const record3 = readServiceExecution(receiver, parent);
|
|
7553
|
-
const
|
|
7863
|
+
const configuration = readServiceAuthorizationHydration(record3.authorization, record3.binding);
|
|
7554
7864
|
const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
7555
7865
|
executions.set(execution, Object.freeze({ ...record3, hydration: resolveHydration(configuration, source) }));
|
|
7556
7866
|
return execution;
|
|
@@ -7587,7 +7897,7 @@ function deriveServiceExecution(receiver, parent, access, applicability = "servi
|
|
|
7587
7897
|
const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
7588
7898
|
executions.set(
|
|
7589
7899
|
execution,
|
|
7590
|
-
Object.freeze({ ...record3, authorization: call, access:
|
|
7900
|
+
Object.freeze({ ...record3, authorization: call, access: readServiceAuthorizationAccess(call, record3.binding) })
|
|
7591
7901
|
);
|
|
7592
7902
|
return execution;
|
|
7593
7903
|
}
|
|
@@ -10663,7 +10973,8 @@ var Service = class extends Base {
|
|
|
10663
10973
|
async finalizeDirectOutput(args) {
|
|
10664
10974
|
const { plan, context } = args;
|
|
10665
10975
|
const { authorization, authorizationBinding: binding } = plan;
|
|
10666
|
-
const
|
|
10976
|
+
const access = readServiceAuthorizationAccess(authorization, binding);
|
|
10977
|
+
const configuration = readServiceOutputMetadata(authorization, binding);
|
|
10667
10978
|
const source = getIncludeSource(args.doc);
|
|
10668
10979
|
const snapshot = isolateDocForOutput(source);
|
|
10669
10980
|
for (const key of Object.keys(snapshot))
|