@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.mjs
CHANGED
|
@@ -3534,6 +3534,47 @@ function serviceAuthorizationInput(policy, targets) {
|
|
|
3534
3534
|
}
|
|
3535
3535
|
return { rows: policy.rows, outputFields: policy.outputFields, related };
|
|
3536
3536
|
}
|
|
3537
|
+
function hasStableConfigurationCopies(value) {
|
|
3538
|
+
if (value === null || typeof value !== "object") return true;
|
|
3539
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
3540
|
+
return Object.values(value).every(hasStableConfigurationCopies);
|
|
3541
|
+
}
|
|
3542
|
+
function captureAuthorizationAccess(value) {
|
|
3543
|
+
const input = authorizationRecord(value, "access");
|
|
3544
|
+
const access = {};
|
|
3545
|
+
for (const key of ["operation", "rowAccess", "virtualAccess", "outputAccess", "docPermissionsAccess"]) {
|
|
3546
|
+
if (key === "rowAccess" && input[key] === void 0) continue;
|
|
3547
|
+
if (typeof input[key] !== "string") authorizationError(`invalid access.${key}`);
|
|
3548
|
+
access[key] = input[key];
|
|
3549
|
+
}
|
|
3550
|
+
return Object.freeze(access);
|
|
3551
|
+
}
|
|
3552
|
+
function captureAuthorizationCatalog(value) {
|
|
3553
|
+
const input = authorizationRecord(value, "catalog");
|
|
3554
|
+
const catalog = {};
|
|
3555
|
+
for (const key of [
|
|
3556
|
+
"paths",
|
|
3557
|
+
"persistedPaths",
|
|
3558
|
+
"arrayPaths",
|
|
3559
|
+
"schemaVirtualPaths",
|
|
3560
|
+
"metadataPaths",
|
|
3561
|
+
"dynamicPaths",
|
|
3562
|
+
"containerPaths",
|
|
3563
|
+
"virtualPaths",
|
|
3564
|
+
"virtualScopePaths",
|
|
3565
|
+
"opaquePaths",
|
|
3566
|
+
"referencePaths",
|
|
3567
|
+
"protectedPaths"
|
|
3568
|
+
]) {
|
|
3569
|
+
const paths = input[key];
|
|
3570
|
+
if (key !== "paths" && paths === void 0) continue;
|
|
3571
|
+
if (!Array.isArray(paths) || Array.from(paths).some((path) => typeof path !== "string")) {
|
|
3572
|
+
authorizationError(`invalid catalog.${key}`);
|
|
3573
|
+
}
|
|
3574
|
+
catalog[key] = paths;
|
|
3575
|
+
}
|
|
3576
|
+
return Object.freeze(catalog);
|
|
3577
|
+
}
|
|
3537
3578
|
function assertAuthorizationPath(path) {
|
|
3538
3579
|
if (typeof path !== "string" || !isSafeOutputPath(path)) {
|
|
3539
3580
|
authorizationError(`invalid response path: ${String(path)}`);
|
|
@@ -3663,6 +3704,9 @@ function resolveServiceAuthorization(input) {
|
|
|
3663
3704
|
const raw = copyAuthorizationValue(input.authorization);
|
|
3664
3705
|
const configuration = copyAuthorizationValue(input.configuration, true);
|
|
3665
3706
|
const targets = copyAuthorizationValue(input.relatedTargets ?? [], true);
|
|
3707
|
+
const access = copyAuthorizationValue(input.access);
|
|
3708
|
+
const outputAccess = captureAuthorizationAccess(access);
|
|
3709
|
+
const catalog = captureAuthorizationCatalog(configuration.catalog);
|
|
3666
3710
|
const policy = copyAuthorizationValue(normalizePolicy(raw, input.applicability ?? "service", configuration, targets));
|
|
3667
3711
|
const scopePath = Array.from(scope(input.binding));
|
|
3668
3712
|
for (const part of scopePath) {
|
|
@@ -3675,17 +3719,21 @@ function resolveServiceAuthorization(input) {
|
|
|
3675
3719
|
authorizationError("derived call request/runtime mismatch");
|
|
3676
3720
|
}
|
|
3677
3721
|
}
|
|
3678
|
-
const permissions =
|
|
3679
|
-
input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key)])
|
|
3722
|
+
const permissions = new permission_default(
|
|
3723
|
+
Object.fromEntries(input.requestPermissions.keys.map((key) => [key, input.requestPermissions.has(key) === true]))
|
|
3680
3724
|
);
|
|
3725
|
+
for (const value of Object.values(permissions)) if (value && typeof value === "object") Object.freeze(value);
|
|
3726
|
+
Object.freeze(permissions);
|
|
3681
3727
|
const call = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3682
3728
|
calls.set(call, {
|
|
3683
3729
|
binding: Object.freeze({ ...input.binding, scopePath: Object.freeze(scopePath) }),
|
|
3684
|
-
access
|
|
3730
|
+
access,
|
|
3685
3731
|
configuration,
|
|
3686
3732
|
policy,
|
|
3687
3733
|
relatedTargets: targets,
|
|
3688
|
-
|
|
3734
|
+
// normalizeFields constructs only mode strings and validated string paths.
|
|
3735
|
+
output: Object.freeze({ access: outputAccess, catalog, outputFields: policy.outputFields }),
|
|
3736
|
+
permissions,
|
|
3689
3737
|
provenance: input.parent ? callRecord(input.parent).provenance : call
|
|
3690
3738
|
});
|
|
3691
3739
|
return call;
|
|
@@ -3703,12 +3751,75 @@ function readServiceAuthorization(call, binding) {
|
|
|
3703
3751
|
true
|
|
3704
3752
|
);
|
|
3705
3753
|
}
|
|
3754
|
+
function readServiceAuthorizationAccess(call, binding) {
|
|
3755
|
+
assertAuthorizationBinding(call, binding);
|
|
3756
|
+
return callRecord(call).output.access;
|
|
3757
|
+
}
|
|
3758
|
+
function readServiceAuthorizationHydration(call, binding) {
|
|
3759
|
+
assertAuthorizationBinding(call, binding);
|
|
3760
|
+
return copyAuthorizationValue({ hydration: callRecord(call).configuration.hydration }, true);
|
|
3761
|
+
}
|
|
3762
|
+
function outputMetadata(configuration) {
|
|
3763
|
+
return {
|
|
3764
|
+
documentPermissionField: configuration.documentPermissionField,
|
|
3765
|
+
exposedDocPermissionKeys: configuration.exposedDocPermissionKeys,
|
|
3766
|
+
stripPermissionsField: configuration.stripPermissionsField,
|
|
3767
|
+
disableFieldPermissions: configuration.disableFieldPermissions
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function readServiceOutputMetadata(call, binding) {
|
|
3771
|
+
assertAuthorizationBinding(call, binding);
|
|
3772
|
+
return copyAuthorizationValue(outputMetadata(callRecord(call).configuration), true);
|
|
3773
|
+
}
|
|
3774
|
+
function readServiceDocumentPermissions(call, binding) {
|
|
3775
|
+
assertAuthorizationBinding(call, binding);
|
|
3776
|
+
const record3 = callRecord(call);
|
|
3777
|
+
return Object.freeze({
|
|
3778
|
+
catalog: record3.output.catalog,
|
|
3779
|
+
...copyAuthorizationValue({ docPermissions: record3.configuration.docPermissions }, true)
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
function readServiceFinalizerConfiguration(call, binding) {
|
|
3783
|
+
assertAuthorizationBinding(call, binding);
|
|
3784
|
+
const record3 = callRecord(call);
|
|
3785
|
+
return Object.freeze({
|
|
3786
|
+
catalog: record3.output.catalog,
|
|
3787
|
+
...copyAuthorizationValue(
|
|
3788
|
+
{
|
|
3789
|
+
...outputMetadata(record3.configuration),
|
|
3790
|
+
virtuals: record3.configuration.virtuals,
|
|
3791
|
+
docPermissions: record3.configuration.docPermissions
|
|
3792
|
+
},
|
|
3793
|
+
true
|
|
3794
|
+
)
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
function readServiceOutputPolicy(call, binding) {
|
|
3798
|
+
assertAuthorizationBinding(call, binding);
|
|
3799
|
+
return callRecord(call).output;
|
|
3800
|
+
}
|
|
3801
|
+
function readServiceOutputFieldAuthorization(call, binding) {
|
|
3802
|
+
assertAuthorizationBinding(call, binding);
|
|
3803
|
+
const record3 = callRecord(call);
|
|
3804
|
+
const reusable = record3.reusableFieldConfiguration ??= hasStableConfigurationCopies(record3.configuration.permissionSchema) && // Coercion methods can observe an object's identity even without native
|
|
3805
|
+
// leaves. Keep those receivers local to the original resolution boundary.
|
|
3806
|
+
(record3.configuration.modelPermissionPrefix === null || typeof record3.configuration.modelPermissionPrefix !== "object");
|
|
3807
|
+
return Object.freeze({
|
|
3808
|
+
access: record3.output.access,
|
|
3809
|
+
outputFields: record3.output.outputFields,
|
|
3810
|
+
reusable,
|
|
3811
|
+
...copyAuthorizationValue(
|
|
3812
|
+
{
|
|
3813
|
+
permissionSchema: record3.configuration.permissionSchema,
|
|
3814
|
+
modelPermissionPrefix: record3.configuration.modelPermissionPrefix
|
|
3815
|
+
},
|
|
3816
|
+
true
|
|
3817
|
+
)
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3706
3820
|
function getAuthorizationPermissions(call, binding) {
|
|
3707
3821
|
assertAuthorizationBinding(call, binding);
|
|
3708
|
-
|
|
3709
|
-
for (const value of Object.values(permission)) if (value && typeof value === "object") Object.freeze(value);
|
|
3710
|
-
Object.freeze(permission);
|
|
3711
|
-
return permission;
|
|
3822
|
+
return callRecord(call).permissions;
|
|
3712
3823
|
}
|
|
3713
3824
|
function getAuthorizationProvenance(call) {
|
|
3714
3825
|
return callRecord(call).provenance;
|
|
@@ -3748,7 +3859,7 @@ function normalizeOutputAuthorizationOptions(input, options, purpose) {
|
|
|
3748
3859
|
const stage = stageRecord(documentRecord(handle).stage);
|
|
3749
3860
|
const binding = Object.freeze({ ...input.binding, scopePath: stage.binding.scopePath });
|
|
3750
3861
|
assertAuthorizationBinding(stage.call, binding);
|
|
3751
|
-
const access =
|
|
3862
|
+
const access = readServiceAuthorizationAccess(stage.call, binding);
|
|
3752
3863
|
if (input.access.outputAccess !== access.outputAccess) authorizationError("document access mismatch");
|
|
3753
3864
|
return Object.freeze({ kind: "document", documentAuthorization: handle, binding });
|
|
3754
3865
|
}
|
|
@@ -3826,7 +3937,16 @@ function readDocumentAuthorization(handle, binding) {
|
|
|
3826
3937
|
authorization: stage.call,
|
|
3827
3938
|
stage: record3.stage,
|
|
3828
3939
|
grants: copyAuthorizationValue(record3.grants),
|
|
3829
|
-
...
|
|
3940
|
+
...readServiceAuthorizationAccess(stage.call, binding)
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
function readDocumentAuthorizationAccess(handle, binding) {
|
|
3944
|
+
const record3 = documentRecord(handle);
|
|
3945
|
+
const stage = stageRecord(record3.stage);
|
|
3946
|
+
return Object.freeze({
|
|
3947
|
+
authorization: stage.call,
|
|
3948
|
+
stage: record3.stage,
|
|
3949
|
+
...readServiceAuthorizationAccess(stage.call, binding)
|
|
3830
3950
|
});
|
|
3831
3951
|
}
|
|
3832
3952
|
function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
|
|
@@ -3834,15 +3954,23 @@ function memoizeDocumentFieldDecision(handle, binding, field, evaluate) {
|
|
|
3834
3954
|
const stage = stageRecord(record3.stage);
|
|
3835
3955
|
assertAuthorizationBinding(stage.call, binding);
|
|
3836
3956
|
let decision = record3.decisions.get(field);
|
|
3837
|
-
if (
|
|
3957
|
+
if (decision) {
|
|
3958
|
+
return decision.then((result2) => {
|
|
3959
|
+
stageRecord(record3.stage);
|
|
3960
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3961
|
+
return result2;
|
|
3962
|
+
});
|
|
3963
|
+
} else {
|
|
3838
3964
|
decision = Promise.resolve().then(async () => {
|
|
3839
3965
|
stageRecord(record3.stage);
|
|
3966
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3840
3967
|
const result2 = await evaluate.call(
|
|
3841
3968
|
stage.binding.request,
|
|
3842
3969
|
getAuthorizationPermissions(stage.call, binding),
|
|
3843
3970
|
copyAuthorizationValue(record3.grants)
|
|
3844
3971
|
);
|
|
3845
3972
|
stageRecord(record3.stage);
|
|
3973
|
+
assertAuthorizationBinding(stage.call, binding);
|
|
3846
3974
|
if (typeof result2 !== "boolean") authorizationError("field decision must be boolean");
|
|
3847
3975
|
return result2;
|
|
3848
3976
|
});
|
|
@@ -3859,7 +3987,7 @@ function assertDocumentAuthorization(handle, use) {
|
|
|
3859
3987
|
const record3 = documentRecord(handle);
|
|
3860
3988
|
const stage = stageRecord(record3.stage);
|
|
3861
3989
|
assertAuthorizationBinding(stage.call, use.binding);
|
|
3862
|
-
if (use.authorization !== void 0 && stage.call !== use.authorization || use.stage !== void 0 && record3.stage !== use.stage ||
|
|
3990
|
+
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) {
|
|
3863
3991
|
authorizationError("document call/access/stage mismatch");
|
|
3864
3992
|
}
|
|
3865
3993
|
const association = outputs.get(use.output);
|
|
@@ -3914,7 +4042,7 @@ function transferDocumentAuthorizationGraph(source, copy, copyOrigins) {
|
|
|
3914
4042
|
{
|
|
3915
4043
|
binding: stage.binding,
|
|
3916
4044
|
output: original,
|
|
3917
|
-
outputAccess:
|
|
4045
|
+
outputAccess: readServiceAuthorizationAccess(stage.call, stage.binding).outputAccess,
|
|
3918
4046
|
authorization: stage.call,
|
|
3919
4047
|
stage: documentRecord(association.handle).stage
|
|
3920
4048
|
},
|
|
@@ -4078,12 +4206,20 @@ function classifyRequestFieldRule(rule, permissions, modelPermissionPrefix = "")
|
|
|
4078
4206
|
);
|
|
4079
4207
|
return (typeof rule === "string" ? possible.stringHandler(rule) : possible.arrayHandler(rule)) ? "deferred" : "denied";
|
|
4080
4208
|
}
|
|
4209
|
+
function prepareRequestFieldRule(rule, permissions, modelPermissionPrefix = "") {
|
|
4210
|
+
const state = classifyRequestFieldRule(rule, permissions, modelPermissionPrefix);
|
|
4211
|
+
const invariant = state === "denied" && modelPermissionPrefix ? classifyRequestFieldRule(rule, permissions) : state;
|
|
4212
|
+
return Object.freeze({ state, decision: invariant === "deferred" ? void 0 : invariant === "granted" });
|
|
4213
|
+
}
|
|
4214
|
+
function combineFieldAuthorizationStates(states2) {
|
|
4215
|
+
return states2.includes("denied") ? "denied" : states2.includes("deferred") ? "deferred" : "granted";
|
|
4216
|
+
}
|
|
4081
4217
|
function classifyEffectiveFieldAuthorization(args) {
|
|
4082
4218
|
if (hasExplicitFieldGrant(args.outputFields, args.path)) return "granted";
|
|
4083
4219
|
const states2 = args.rules.map(
|
|
4084
4220
|
(rule) => classifyRequestFieldRule(rule, args.globalPermissions, args.modelPermissionPrefix)
|
|
4085
4221
|
);
|
|
4086
|
-
return
|
|
4222
|
+
return combineFieldAuthorizationStates(states2);
|
|
4087
4223
|
}
|
|
4088
4224
|
async function evaluateScopedAccessRule(args) {
|
|
4089
4225
|
const { req, rule, globalPermissions, docPermissions, modelPermissionPrefix } = args;
|
|
@@ -5742,16 +5878,111 @@ var init_virtual_populate_execution = __esm({
|
|
|
5742
5878
|
|
|
5743
5879
|
// src/acl/output-field-filter.ts
|
|
5744
5880
|
import { isPlainObject as isPlainObject21 } from "@web-ts-toolkit/utils";
|
|
5881
|
+
function callFieldPolicy(authorization, binding) {
|
|
5882
|
+
assertAuthorizationBinding(authorization, binding);
|
|
5883
|
+
let policy = fieldPolicies.get(authorization);
|
|
5884
|
+
if (!policy) {
|
|
5885
|
+
const view = readServiceOutputFieldAuthorization(authorization, binding);
|
|
5886
|
+
policy = {
|
|
5887
|
+
schema: view.permissionSchema,
|
|
5888
|
+
requestPaths: view.permissionSchema == null || isPlainObject21(view.permissionSchema) && Object.isFrozen(view.permissionSchema) ? Object.freeze(Object.keys(view.permissionSchema ?? {})) : void 0,
|
|
5889
|
+
prefix: view.modelPermissionPrefix ?? "",
|
|
5890
|
+
outputFields: view.outputFields,
|
|
5891
|
+
accesses: /* @__PURE__ */ new Map()
|
|
5892
|
+
};
|
|
5893
|
+
if (view.reusable) fieldPolicies.set(authorization, policy);
|
|
5894
|
+
}
|
|
5895
|
+
return policy;
|
|
5896
|
+
}
|
|
5897
|
+
function requestRuleClassification(entry, authorization, permissions, prefix) {
|
|
5898
|
+
if (typeof prefix !== "string") return void 0;
|
|
5899
|
+
let classification = entry.classifications.get(authorization);
|
|
5900
|
+
if (!classification) {
|
|
5901
|
+
classification = prepareRequestFieldRule(entry.rule, permissions, prefix);
|
|
5902
|
+
entry.classifications.set(authorization, classification);
|
|
5903
|
+
}
|
|
5904
|
+
return classification;
|
|
5905
|
+
}
|
|
5906
|
+
function invariantRuleLookup(schema, path) {
|
|
5907
|
+
if (schema == null) return true;
|
|
5908
|
+
if (!isPlainObject21(schema) || !Object.isFrozen(schema)) return false;
|
|
5909
|
+
const exact = Object.getOwnPropertyDescriptor(schema, path);
|
|
5910
|
+
if (exact) {
|
|
5911
|
+
const raw2 = exact.value;
|
|
5912
|
+
return !raw2 || typeof raw2 !== "object" || Array.isArray(raw2) || isPlainObject21(raw2) && Object.isFrozen(raw2);
|
|
5913
|
+
}
|
|
5914
|
+
const [head, ...rest] = path.split(".");
|
|
5915
|
+
const raw = Object.getOwnPropertyDescriptor(schema, head)?.value;
|
|
5916
|
+
if (raw == null) return true;
|
|
5917
|
+
if (!isPlainObject21(raw) || !Object.isFrozen(raw)) return false;
|
|
5918
|
+
const sub = Object.getOwnPropertyDescriptor(raw, "sub");
|
|
5919
|
+
return !rest.length || !sub || invariantRuleLookup(sub.value, rest.join("."));
|
|
5920
|
+
}
|
|
5921
|
+
function prepareOutputPath(policy, authorization, binding, path, access) {
|
|
5922
|
+
let cache = policy.accesses.get(access);
|
|
5923
|
+
if (!cache) {
|
|
5924
|
+
cache = /* @__PURE__ */ new Map();
|
|
5925
|
+
policy.accesses.set(access, cache);
|
|
5926
|
+
}
|
|
5927
|
+
let prepared = cache.get(path);
|
|
5928
|
+
if (!prepared) {
|
|
5929
|
+
const invariant = invariantRuleLookup(policy.schema, path);
|
|
5930
|
+
const permissions = getAuthorizationPermissions(authorization, binding);
|
|
5931
|
+
const entries = extractOutputFieldRuleEntries(policy.schema, path, access).map((entry) => ({
|
|
5932
|
+
...entry,
|
|
5933
|
+
classifications: /* @__PURE__ */ new WeakMap()
|
|
5934
|
+
}));
|
|
5935
|
+
const classifications = entries.map(
|
|
5936
|
+
(entry) => requestRuleClassification(entry, authorization, permissions, policy.prefix)
|
|
5937
|
+
);
|
|
5938
|
+
const backendGrant = hasExplicitFieldGrant(policy.outputFields, path);
|
|
5939
|
+
prepared = {
|
|
5940
|
+
entries,
|
|
5941
|
+
backendGrant,
|
|
5942
|
+
state: backendGrant ? "granted" : classifications.every((value) => value !== void 0) ? combineFieldAuthorizationStates(classifications.map((value) => value.state)) : void 0
|
|
5943
|
+
};
|
|
5944
|
+
if (invariant) cache.set(path, prepared);
|
|
5945
|
+
}
|
|
5946
|
+
return prepared;
|
|
5947
|
+
}
|
|
5948
|
+
function assertOwningOutputDocuments(handle) {
|
|
5949
|
+
const owner = owningDocuments.get(handle);
|
|
5950
|
+
if (owner) {
|
|
5951
|
+
readDocumentAuthorizationAccess(owner.handle, owner.binding);
|
|
5952
|
+
assertOwningOutputDocuments(owner.handle);
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
function prepareOutputDocumentAuthorization(authorization, binding, configuration) {
|
|
5956
|
+
const access = readServiceAuthorizationAccess(authorization, binding);
|
|
5957
|
+
const key = `docPermissions.${access.docPermissionsAccess}`;
|
|
5958
|
+
const docPermissions = configuration.docPermissions;
|
|
5959
|
+
const invariant = /^[A-Za-z0-9_-]+$/.test(access.docPermissionsAccess) && (docPermissions == null || isPlainObject21(docPermissions));
|
|
5960
|
+
const option = invariant ? getCapturedAuthorizationOption(configuration, key) : void 0;
|
|
5961
|
+
const hook = typeof option === "function" ? option : void 0;
|
|
5962
|
+
const resolveHook = invariant ? () => hook : () => {
|
|
5963
|
+
const slice = { catalog: configuration.catalog, ...copyAuthorizationValue({ docPermissions }, true) };
|
|
5964
|
+
const option2 = getCapturedAuthorizationOption(slice, key);
|
|
5965
|
+
return typeof option2 === "function" ? option2 : void 0;
|
|
5966
|
+
};
|
|
5967
|
+
return async (args) => {
|
|
5968
|
+
if (args.authorization !== authorization) authorizationError("document resolver call mismatch");
|
|
5969
|
+
assertAuthorizationBinding(authorization, args.binding);
|
|
5970
|
+
return resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook);
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5745
5973
|
async function resolveOutputDocumentAuthorization(args) {
|
|
5974
|
+
const configuration = readServiceDocumentPermissions(args.authorization, args.binding);
|
|
5975
|
+
return prepareOutputDocumentAuthorization(args.authorization, args.binding, configuration)(args);
|
|
5976
|
+
}
|
|
5977
|
+
async function resolveOutputDocumentAuthorizationWithHook(args, access, resolveHook) {
|
|
5746
5978
|
const { authorization, binding } = args;
|
|
5747
|
-
const { configuration, access } = readServiceAuthorization(authorization, binding);
|
|
5748
5979
|
const stage = args.stage ?? createDocumentAuthorizationStage(authorization, binding, args.snapshot);
|
|
5749
5980
|
if (args.stage) assertDocumentAuthorizationStage(stage, authorization, binding, args.snapshot);
|
|
5750
5981
|
const handle = await resolveDocumentAuthorization(stage, async function(doc, permissions) {
|
|
5751
5982
|
if (args.owningDocument) return readDocumentAuthorization(args.owningDocument, args.owningBinding).grants;
|
|
5752
5983
|
if (args.knownOutput) return {};
|
|
5753
|
-
const hook =
|
|
5754
|
-
if (
|
|
5984
|
+
const hook = resolveHook();
|
|
5985
|
+
if (!hook) return {};
|
|
5755
5986
|
try {
|
|
5756
5987
|
const run = () => hook.call(this, doc, permissions, args.context);
|
|
5757
5988
|
const grants = await getRequestWorkState(binding.request, binding.runtime).hookGate.run(run);
|
|
@@ -5772,6 +6003,7 @@ async function resolveOutputDocumentAuthorization(args) {
|
|
|
5772
6003
|
return handle;
|
|
5773
6004
|
}
|
|
5774
6005
|
function evaluateRequestRule(handle, binding, entry, prefix, access) {
|
|
6006
|
+
assertOwningOutputDocuments(handle);
|
|
5775
6007
|
const owner = owningDocuments.get(handle);
|
|
5776
6008
|
if (owner && !entry.path.startsWith(`${(binding.scopePath ?? []).join(".")}.`)) {
|
|
5777
6009
|
return evaluateRequestRule(owner.handle, owner.binding, entry, prefix, access);
|
|
@@ -5780,65 +6012,78 @@ function evaluateRequestRule(handle, binding, entry, prefix, access) {
|
|
|
5780
6012
|
handle,
|
|
5781
6013
|
binding,
|
|
5782
6014
|
`request:${access}:${entry.path}`,
|
|
5783
|
-
function(permissions, grants) {
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
6015
|
+
async function(permissions, grants) {
|
|
6016
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6017
|
+
const classification = requestRuleClassification(entry, authorization, permissions, prefix);
|
|
6018
|
+
let allowed = classification?.decision;
|
|
6019
|
+
if (allowed === void 0) {
|
|
6020
|
+
allowed = await evaluateScopedAccessRule({
|
|
6021
|
+
req: this,
|
|
6022
|
+
rule: entry.rule,
|
|
6023
|
+
globalPermissions: permissions,
|
|
6024
|
+
docPermissions: grants,
|
|
6025
|
+
modelPermissionPrefix: prefix
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
assertOwningOutputDocuments(handle);
|
|
6029
|
+
return allowed;
|
|
5791
6030
|
}
|
|
5792
6031
|
);
|
|
5793
6032
|
}
|
|
5794
6033
|
function evaluateAuthorizedOutputField(handle, binding, field) {
|
|
5795
|
-
const { authorization } =
|
|
5796
|
-
|
|
6034
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6035
|
+
assertOwningOutputDocuments(handle);
|
|
5797
6036
|
const path = [...binding.scopePath ?? [], field].join(".");
|
|
5798
|
-
const
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
const args = {
|
|
6037
|
+
const decision = memoizeDocumentFieldDecision(handle, binding, `output:${path}`, async function(permissions) {
|
|
6038
|
+
const policy = callFieldPolicy(authorization, binding);
|
|
6039
|
+
const { outputAccess } = readDocumentAuthorizationAccess(handle, binding);
|
|
6040
|
+
const prepared = prepareOutputPath(policy, authorization, binding, path, outputAccess);
|
|
6041
|
+
const unresolved = unresolvedDocuments.has(handle);
|
|
6042
|
+
const state = unresolved ? prepared.state ?? classifyEffectiveFieldAuthorization({
|
|
5805
6043
|
path,
|
|
5806
|
-
rules: entries.map((entry) => entry.rule),
|
|
6044
|
+
rules: prepared.entries.map((entry) => entry.rule),
|
|
6045
|
+
modelPermissionPrefix: policy.prefix,
|
|
5807
6046
|
outputFields: policy.outputFields,
|
|
5808
|
-
globalPermissions: permissions
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
if (
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6047
|
+
globalPermissions: permissions
|
|
6048
|
+
}) : void 0;
|
|
6049
|
+
let allowed;
|
|
6050
|
+
if (unresolved && state !== "granted") allowed = false;
|
|
6051
|
+
else if (prepared.backendGrant) allowed = true;
|
|
6052
|
+
else {
|
|
6053
|
+
allowed = true;
|
|
6054
|
+
for (const entry of prepared.entries) {
|
|
6055
|
+
if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, outputAccess)) {
|
|
6056
|
+
allowed = false;
|
|
6057
|
+
break;
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
assertOwningOutputDocuments(handle);
|
|
6062
|
+
return allowed;
|
|
5818
6063
|
});
|
|
6064
|
+
return owningDocuments.has(handle) ? decision.then((allowed) => {
|
|
6065
|
+
assertOwningOutputDocuments(handle);
|
|
6066
|
+
return allowed;
|
|
6067
|
+
}) : decision;
|
|
5819
6068
|
}
|
|
5820
6069
|
async function requestOutputFieldPaths(handle, binding, access) {
|
|
5821
|
-
const { authorization } =
|
|
5822
|
-
|
|
5823
|
-
const
|
|
6070
|
+
const { authorization } = readDocumentAuthorizationAccess(handle, binding);
|
|
6071
|
+
assertOwningOutputDocuments(handle);
|
|
6072
|
+
const policy = callFieldPolicy(authorization, binding);
|
|
5824
6073
|
const fields = [];
|
|
5825
|
-
for (const path of Object.keys(schema ?? {})) {
|
|
5826
|
-
const entries =
|
|
6074
|
+
for (const path of policy.requestPaths ?? Object.keys(policy.schema ?? {})) {
|
|
6075
|
+
const { entries } = prepareOutputPath(policy, authorization, binding, path, access);
|
|
5827
6076
|
let allowed = true;
|
|
5828
6077
|
for (const entry of entries) {
|
|
5829
|
-
if (!await evaluateRequestRule(
|
|
5830
|
-
handle,
|
|
5831
|
-
binding,
|
|
5832
|
-
entry,
|
|
5833
|
-
configuration.modelPermissionPrefix ?? "",
|
|
5834
|
-
access
|
|
5835
|
-
)) {
|
|
6078
|
+
if (!await evaluateRequestRule(handle, binding, entry, policy.prefix, access)) {
|
|
5836
6079
|
allowed = false;
|
|
5837
6080
|
break;
|
|
5838
6081
|
}
|
|
5839
6082
|
}
|
|
5840
6083
|
if (allowed && entries.length) fields.push(path);
|
|
5841
6084
|
}
|
|
6085
|
+
assertOwningOutputDocuments(handle);
|
|
6086
|
+
readDocumentAuthorizationAccess(handle, binding);
|
|
5842
6087
|
return fields;
|
|
5843
6088
|
}
|
|
5844
6089
|
function authorizeEmbeddedOutputPlan(parent, child) {
|
|
@@ -5859,13 +6104,13 @@ function authorizeEmbeddedOutputPlan(parent, child) {
|
|
|
5859
6104
|
function hasWholeOutputValueGrant(plan, field) {
|
|
5860
6105
|
if (plan.fieldStates?.[field] !== "granted") return false;
|
|
5861
6106
|
const binding = plan.authorizationBinding;
|
|
5862
|
-
const {
|
|
5863
|
-
return hasExplicitFieldGrant(
|
|
6107
|
+
const { outputFields } = readServiceOutputPolicy(plan.authorization, binding);
|
|
6108
|
+
return hasExplicitFieldGrant(outputFields, [...binding.scopePath ?? [], field].join("."));
|
|
5864
6109
|
}
|
|
5865
6110
|
function copyOutputValue(value, plan, path) {
|
|
5866
|
-
const {
|
|
5867
|
-
const virtualValue =
|
|
5868
|
-
const opaque =
|
|
6111
|
+
const { catalog } = readServiceOutputPolicy(plan.authorization, plan.authorizationBinding);
|
|
6112
|
+
const virtualValue = catalog.virtualPaths?.some((p) => matchesCatalogPattern(p, path));
|
|
6113
|
+
const opaque = catalog.opaquePaths?.some((p) => matchesCatalogPattern(p, path));
|
|
5869
6114
|
const excluded = plan.effectiveSelectNorm.filter((token) => token.startsWith("-")).map((token) => [...plan.authorizationBinding.scopePath ?? [], token.slice(1)].join("."));
|
|
5870
6115
|
const visit = (input, current) => {
|
|
5871
6116
|
if (Array.isArray(input)) return input.map((entry) => visit(entry, current));
|
|
@@ -5876,7 +6121,7 @@ function copyOutputValue(value, plan, path) {
|
|
|
5876
6121
|
const child = `${current}.${key}`;
|
|
5877
6122
|
if (excluded.some((entry2) => child === entry2 || child.startsWith(`${entry2}.`))) continue;
|
|
5878
6123
|
const safeValueKey = !["__proto__", "constructor", "prototype", "_doc", "$__", "__v"].includes(key);
|
|
5879
|
-
if (!(opaque ? safeValueKey : isSafeOutputPath(key)) ||
|
|
6124
|
+
if (!(opaque ? safeValueKey : isSafeOutputPath(key)) || catalog.protectedPaths?.some((p) => matchesCatalogPattern(p, child, true)) || !opaque && !virtualValue && !isCatalogOutputPath(catalog, child))
|
|
5880
6125
|
continue;
|
|
5881
6126
|
output[key] = visit(entry, child);
|
|
5882
6127
|
}
|
|
@@ -5956,7 +6201,7 @@ async function filterExistingOutput(source, plan, handle, associated) {
|
|
|
5956
6201
|
const childHandle = associated ? getAssociatedDocumentAuthorization(value2) : void 0;
|
|
5957
6202
|
let child = scoped;
|
|
5958
6203
|
if (childHandle) {
|
|
5959
|
-
const state =
|
|
6204
|
+
const state = readDocumentAuthorizationAccess(childHandle, scoped.authorizationBinding);
|
|
5960
6205
|
if (getAuthorizationProvenance(state.authorization) !== getAuthorizationProvenance(plan.authorization))
|
|
5961
6206
|
authorizationError("child call mismatch");
|
|
5962
6207
|
child = helperPlan(state.authorization, scoped.authorizationBinding);
|
|
@@ -6020,7 +6265,7 @@ async function filterModelOutputForHelper(args) {
|
|
|
6020
6265
|
if (normalized.kind === "document") {
|
|
6021
6266
|
const { documentAuthorization: handle2, binding: binding2 } = normalized;
|
|
6022
6267
|
assertDocumentAuthorization(handle2, { binding: binding2, output: args.doc, outputAccess: access });
|
|
6023
|
-
const state =
|
|
6268
|
+
const state = readDocumentAuthorizationAccess(handle2, binding2);
|
|
6024
6269
|
return filterExistingOutput(
|
|
6025
6270
|
args.doc,
|
|
6026
6271
|
helperPlan(state.authorization, binding2),
|
|
@@ -6043,7 +6288,7 @@ async function filterModelOutputForHelper(args) {
|
|
|
6043
6288
|
});
|
|
6044
6289
|
return filterExistingOutput(source, plan, handle, false);
|
|
6045
6290
|
}
|
|
6046
|
-
var unresolvedDocuments, owningDocuments;
|
|
6291
|
+
var unresolvedDocuments, owningDocuments, fieldPolicies;
|
|
6047
6292
|
var init_output_field_filter = __esm({
|
|
6048
6293
|
"src/acl/output-field-filter.ts"() {
|
|
6049
6294
|
init_permissions_alias();
|
|
@@ -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
|
|
|
@@ -6128,15 +6374,41 @@ __export(finalize_model_output_exports, {
|
|
|
6128
6374
|
finalizeModelOutputs: () => finalizeModelOutputs
|
|
6129
6375
|
});
|
|
6130
6376
|
import { get as get9, isPlainObject as isPlainObject22, set as set7 } from "@web-ts-toolkit/utils";
|
|
6131
|
-
|
|
6132
|
-
const scopePath = args.scopePath;
|
|
6377
|
+
function assertFinalizerCall(args, prepared) {
|
|
6133
6378
|
const plan = args.plan;
|
|
6134
6379
|
const binding = plan.authorizationBinding;
|
|
6135
|
-
|
|
6136
|
-
if (plan.authorization &&
|
|
6380
|
+
if (plan.authorization !== prepared.authorization) authorizationError("finalizer call changed during pass");
|
|
6381
|
+
if (plan.authorization && binding) assertAuthorizationBinding(plan.authorization, binding);
|
|
6382
|
+
const access = prepared.policy?.access;
|
|
6383
|
+
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("."))) {
|
|
6137
6384
|
authorizationError("finalizer call/model/scope/access mismatch");
|
|
6138
6385
|
}
|
|
6139
|
-
|
|
6386
|
+
}
|
|
6387
|
+
function prepareFinalizer(args, sharedContext = false) {
|
|
6388
|
+
const { authorization, authorizationBinding: binding } = args.plan;
|
|
6389
|
+
const policy = authorization && binding ? readServiceOutputPolicy(authorization, binding) : void 0;
|
|
6390
|
+
assertFinalizerCall(args, { authorization, policy });
|
|
6391
|
+
const configuration = authorization ? readServiceFinalizerConfiguration(authorization, binding) : void 0;
|
|
6392
|
+
const snapshot = configuration ? configuration : deepIsolateValue(args.snapshot);
|
|
6393
|
+
return {
|
|
6394
|
+
authorization,
|
|
6395
|
+
policy,
|
|
6396
|
+
snapshot,
|
|
6397
|
+
baseContext: deepIsolateValue(args.context ?? {}),
|
|
6398
|
+
sharedContext,
|
|
6399
|
+
scopeVirtualNames: getScopeVirtualNames(snapshot, args.scopePath),
|
|
6400
|
+
scopedSchema: configuration ? null : getScopedPermissionSchema2(snapshot.permissionSchema, args.scopePath),
|
|
6401
|
+
permissions: authorization ? getAuthorizationPermissions(authorization, binding) : void 0,
|
|
6402
|
+
resolveDocument: authorization ? prepareOutputDocumentAuthorization(authorization, binding, configuration) : void 0
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6405
|
+
async function finalizeSingleDoc(args, input, prepared) {
|
|
6406
|
+
assertFinalizerCall(args, prepared);
|
|
6407
|
+
const scopePath = args.scopePath;
|
|
6408
|
+
const plan = args.plan;
|
|
6409
|
+
const binding = plan.authorizationBinding;
|
|
6410
|
+
const captured = prepared.policy;
|
|
6411
|
+
const snapshot = prepared.snapshot;
|
|
6140
6412
|
const limit = args.resolvedLimit;
|
|
6141
6413
|
const gate = args.sharedGate;
|
|
6142
6414
|
const original = getIncludeOriginal(input);
|
|
@@ -6148,7 +6420,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6148
6420
|
for (const key of Object.keys(output)) {
|
|
6149
6421
|
transferDocumentAuthorizationGraph(input[key], output[key]);
|
|
6150
6422
|
}
|
|
6151
|
-
const baseContext =
|
|
6423
|
+
const baseContext = prepared.sharedContext ? deepIsolateValue(prepared.baseContext) : prepared.baseContext;
|
|
6152
6424
|
const operation = captured?.access.operation ?? baseContext.operation ?? args.operation ?? args.virtualAccess;
|
|
6153
6425
|
const scopeContext = {
|
|
6154
6426
|
...baseContext,
|
|
@@ -6166,7 +6438,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6166
6438
|
if (captured) {
|
|
6167
6439
|
const projected = args.documentAuthorizationStage ? input : deepIsolateValue(output);
|
|
6168
6440
|
metadataSource = deepIsolateValue(output);
|
|
6169
|
-
documentAuthorization = await
|
|
6441
|
+
documentAuthorization = await prepared.resolveDocument({
|
|
6170
6442
|
authorization: plan.authorization,
|
|
6171
6443
|
binding,
|
|
6172
6444
|
snapshot: projected,
|
|
@@ -6179,13 +6451,10 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6179
6451
|
}
|
|
6180
6452
|
const docGrants = documentAuthorization ? readDocumentAuthorization(documentAuthorization, binding).grants : await resolveDocPermissions(args, output, scopeContext);
|
|
6181
6453
|
scopeContext.docPermissions = deepIsolateValue(docGrants);
|
|
6182
|
-
const globalPerms =
|
|
6183
|
-
const modelPermissionPrefix = snapshot.modelPermissionPrefix ?? "";
|
|
6184
|
-
const documentPermissionField = snapshot.documentPermissionField ?? "_permissions";
|
|
6185
|
-
const scopedSchema =
|
|
6186
|
-
snapshot.permissionSchema ?? null,
|
|
6187
|
-
scopePath
|
|
6188
|
-
);
|
|
6454
|
+
const globalPerms = prepared.permissions ?? resolveGlobalPermissions(args);
|
|
6455
|
+
const modelPermissionPrefix = captured ? "" : deepIsolateValue(snapshot.modelPermissionPrefix ?? "");
|
|
6456
|
+
const documentPermissionField = captured ? "_permissions" : deepIsolateValue(snapshot.documentPermissionField ?? "_permissions");
|
|
6457
|
+
const scopedSchema = deepIsolateValue(prepared.scopedSchema);
|
|
6189
6458
|
const childPlans = plan.childPlans ?? {};
|
|
6190
6459
|
for (const [containerName, childPlan] of Object.entries(childPlans)) {
|
|
6191
6460
|
if (!(containerName in output)) continue;
|
|
@@ -6204,12 +6473,13 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6204
6473
|
continue;
|
|
6205
6474
|
}
|
|
6206
6475
|
retainRawAssociation(output, containerName, rawContainer);
|
|
6207
|
-
const childSnapshot = snapshot;
|
|
6208
6476
|
const childBase = {
|
|
6209
6477
|
...args,
|
|
6210
6478
|
scopePath: [...childPlan.scopePath ?? [...scopePath, containerName, "sub"]],
|
|
6211
6479
|
plan: captured ? authorizeEmbeddedOutputPlan(plan, childPlan) : childPlan,
|
|
6212
|
-
|
|
6480
|
+
// Authenticated preparation reads its own authority. Do not clone an
|
|
6481
|
+
// otherwise unused public configuration graph on default traversal.
|
|
6482
|
+
snapshot: args.snapshot,
|
|
6213
6483
|
// Embedded reuses the owning parent's grants + accesses (VIRT-00A D2).
|
|
6214
6484
|
docPermissions: docGrants,
|
|
6215
6485
|
context: scopeContext,
|
|
@@ -6220,6 +6490,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6220
6490
|
related: [],
|
|
6221
6491
|
virtualPopulateRow: privateRow
|
|
6222
6492
|
};
|
|
6493
|
+
let childPreparation;
|
|
6223
6494
|
const finalizeOneChild = async (rawChild, address) => {
|
|
6224
6495
|
if (captured && Array.isArray(rawChild))
|
|
6225
6496
|
return mapWithConcurrencyLimit(rawChild, limit, (entry, index) => finalizeOneChild(entry, [...address, index]));
|
|
@@ -6240,7 +6511,14 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6240
6511
|
} : { ...args, virtualPopulateRow: privateRow, virtualPopulateAddress: address }
|
|
6241
6512
|
});
|
|
6242
6513
|
}
|
|
6243
|
-
return finalizeSingleDoc(
|
|
6514
|
+
return finalizeSingleDoc(
|
|
6515
|
+
{ ...childBase, virtualPopulateAddress: address },
|
|
6516
|
+
rawChild,
|
|
6517
|
+
childPreparation ??= prepareFinalizer(
|
|
6518
|
+
childBase,
|
|
6519
|
+
Array.isArray(rawContainer) && (rawContainer.length > 1 || Array.isArray(rawContainer[0]))
|
|
6520
|
+
)
|
|
6521
|
+
);
|
|
6244
6522
|
};
|
|
6245
6523
|
if (Array.isArray(rawContainer)) {
|
|
6246
6524
|
const finalized = await mapWithConcurrencyLimit(
|
|
@@ -6292,6 +6570,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6292
6570
|
context: scopeContext,
|
|
6293
6571
|
related: target.plan.relatedPlans?.length ? (args.related ?? []).filter((entry) => entry.path.startsWith(`${target.path}.`)) : []
|
|
6294
6572
|
};
|
|
6573
|
+
let relatedPreparation;
|
|
6295
6574
|
const finalizeOneRelated = async (rawChild, originalChild = rawChild) => {
|
|
6296
6575
|
if (!isPlainObject22(rawChild) && !Array.isArray(rawChild)) return rawChild;
|
|
6297
6576
|
if (typeof rawChild?._bsontype === "string") return rawChild;
|
|
@@ -6305,7 +6584,11 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6305
6584
|
parentArgs: { ...args, virtualPopulateRow: childRow, virtualPopulateAddress: [] }
|
|
6306
6585
|
});
|
|
6307
6586
|
}
|
|
6308
|
-
return finalizeSingleDoc(
|
|
6587
|
+
return finalizeSingleDoc(
|
|
6588
|
+
{ ...relatedBase, virtualPopulateRow: childRow },
|
|
6589
|
+
rawChild,
|
|
6590
|
+
relatedPreparation ??= prepareFinalizer(relatedBase, Array.isArray(rawRelated) && rawRelated.length > 1)
|
|
6591
|
+
);
|
|
6309
6592
|
};
|
|
6310
6593
|
let finalized;
|
|
6311
6594
|
if (Array.isArray(rawRelated)) {
|
|
@@ -6325,18 +6608,24 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6325
6608
|
} catch {
|
|
6326
6609
|
}
|
|
6327
6610
|
}
|
|
6328
|
-
const
|
|
6329
|
-
for (const virtualName of scopeVirtualNames) {
|
|
6611
|
+
for (const virtualName of prepared.scopeVirtualNames) {
|
|
6330
6612
|
if (virtualName in output) delete output[virtualName];
|
|
6331
6613
|
}
|
|
6332
|
-
const stableBase = deepIsolateValue(output);
|
|
6333
|
-
const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
|
|
6334
6614
|
const candidates = plan.candidates ?? [];
|
|
6615
|
+
const needsGetterView = candidates.some((candidate) => {
|
|
6616
|
+
const descriptor = candidate.descriptor;
|
|
6617
|
+
if (typeof descriptor?.get !== "function") return false;
|
|
6618
|
+
if (!privateRow && descriptor.dependsOnPopulate?.length) return false;
|
|
6619
|
+
const dependsOn = Array.isArray(descriptor.dependsOn) ? descriptor.dependsOn : [];
|
|
6620
|
+
return dependsOn.every((dep) => typeof dep === "string" && !isAbsentDependency(output, dep));
|
|
6621
|
+
});
|
|
6622
|
+
let stableBase = needsGetterView ? deepIsolateValue(output) : void 0;
|
|
6623
|
+
const scopeLabel = scopePath.length > 0 ? scopePath.join(".") : "";
|
|
6335
6624
|
const staged = /* @__PURE__ */ new Map();
|
|
6336
6625
|
const runCandidate = async (candidate) => {
|
|
6337
6626
|
const dependsOn = Array.isArray(candidate.descriptor?.dependsOn) ? candidate.descriptor.dependsOn : [];
|
|
6338
6627
|
for (const dep of dependsOn) {
|
|
6339
|
-
if (typeof dep !== "string" || isAbsentDependency(stableBase, dep)) return;
|
|
6628
|
+
if (typeof dep !== "string" || isAbsentDependency(stableBase ?? output, dep)) return;
|
|
6340
6629
|
}
|
|
6341
6630
|
const allowed = documentAuthorization ? await evaluateAuthorizedOutputField(documentAuthorization, binding, candidate.name) : await evaluateEffectiveFieldAuthorization({
|
|
6342
6631
|
req: args.request,
|
|
@@ -6351,9 +6640,9 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6351
6640
|
if (typeof getter !== "function") return;
|
|
6352
6641
|
const privateInputs = privateRow ? readVirtualPopulateInputs(privateRow, privateAddress, candidate.name) : candidate.descriptor.dependsOnPopulate?.length ? { available: false } : { available: true, populated: {} };
|
|
6353
6642
|
if (!privateInputs.available) return;
|
|
6354
|
-
const isolatedView = deepIsolateValue(stableBase);
|
|
6643
|
+
const isolatedView = deepIsolateValue(stableBase ??= deepIsolateValue(output));
|
|
6355
6644
|
const virtualContext = {
|
|
6356
|
-
...scopeContext,
|
|
6645
|
+
...deepIsolateValue(scopeContext),
|
|
6357
6646
|
docPermissions: deepIsolateValue(docGrants)
|
|
6358
6647
|
};
|
|
6359
6648
|
const outcome = await runVirtualGetter({
|
|
@@ -6444,12 +6733,25 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6444
6733
|
if (!(key in output)) continue;
|
|
6445
6734
|
if (captured && !plan.childPlans[key]) {
|
|
6446
6735
|
const path = [...binding.scopePath ?? [], key].join(".");
|
|
6447
|
-
trimmed[key] = captured.
|
|
6736
|
+
trimmed[key] = captured.catalog.referencePaths?.some((p) => matchesCatalogPattern(p, path)) ? copyAuthorizedReference(output[key], plan, path) : copyOutputValue(output[key], plan, path);
|
|
6448
6737
|
if (trimmed[key] === void 0) delete trimmed[key];
|
|
6449
6738
|
} else trimmed[key] = output[key];
|
|
6450
6739
|
}
|
|
6451
6740
|
if (documentAuthorization) {
|
|
6452
|
-
retainOutputMetadata(
|
|
6741
|
+
retainOutputMetadata(
|
|
6742
|
+
trimmed,
|
|
6743
|
+
metadataSource,
|
|
6744
|
+
copyAuthorizationValue(
|
|
6745
|
+
{
|
|
6746
|
+
documentPermissionField: snapshot.documentPermissionField,
|
|
6747
|
+
stripPermissionsField: snapshot.stripPermissionsField,
|
|
6748
|
+
exposedDocPermissionKeys: snapshot.exposedDocPermissionKeys,
|
|
6749
|
+
disableFieldPermissions: snapshot.disableFieldPermissions
|
|
6750
|
+
},
|
|
6751
|
+
true
|
|
6752
|
+
),
|
|
6753
|
+
binding.scopePath ?? []
|
|
6754
|
+
);
|
|
6453
6755
|
associateDocumentAuthorization(documentAuthorization, binding, trimmed);
|
|
6454
6756
|
args.onFinalized?.(
|
|
6455
6757
|
trimmed,
|
|
@@ -6458,7 +6760,7 @@ async function finalizeSingleDoc(args, input) {
|
|
|
6458
6760
|
output: trimmed,
|
|
6459
6761
|
outputAccess: args.outputAccess,
|
|
6460
6762
|
authorization: plan.authorization,
|
|
6461
|
-
stage:
|
|
6763
|
+
stage: readDocumentAuthorizationAccess(documentAuthorization, binding).stage
|
|
6462
6764
|
})
|
|
6463
6765
|
);
|
|
6464
6766
|
}
|
|
@@ -6479,7 +6781,7 @@ async function finalizeModelOutput(args) {
|
|
|
6479
6781
|
sharedGate: gate,
|
|
6480
6782
|
resolvedLimit: limit
|
|
6481
6783
|
};
|
|
6482
|
-
return finalizeSingleDoc(internal, args.input);
|
|
6784
|
+
return finalizeSingleDoc(internal, args.input, prepareFinalizer(internal));
|
|
6483
6785
|
}
|
|
6484
6786
|
async function finalizeModelOutputs(args) {
|
|
6485
6787
|
const gate = finalizerGate(args);
|
|
@@ -6488,16 +6790,24 @@ async function finalizeModelOutputs(args) {
|
|
|
6488
6790
|
if ("documentAuthorizationStage" in args || documentAuthorizationStages && (documentAuthorizationStages.length !== inputs.length || new Set(documentAuthorizationStages).size !== inputs.length)) {
|
|
6489
6791
|
authorizationError("list finalization requires a distinct document stage per row");
|
|
6490
6792
|
}
|
|
6793
|
+
if (!inputs.length) return [];
|
|
6794
|
+
const internal = {
|
|
6795
|
+
...rest,
|
|
6796
|
+
scopePath: [...rest.scopePath ?? rest.plan.scopePath ?? []],
|
|
6797
|
+
sharedGate: gate,
|
|
6798
|
+
resolvedLimit: limit
|
|
6799
|
+
};
|
|
6800
|
+
const prepared = prepareFinalizer(internal, inputs.length > 1);
|
|
6491
6801
|
return mapWithConcurrencyLimit(inputs, limit, (input, index) => {
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6802
|
+
return finalizeSingleDoc(
|
|
6803
|
+
{
|
|
6804
|
+
...internal,
|
|
6805
|
+
onFinalized: rest.onFinalized ? (output, context) => rest.onFinalized(output, context, index) : void 0,
|
|
6806
|
+
documentAuthorizationStage: documentAuthorizationStages?.[index]
|
|
6807
|
+
},
|
|
6808
|
+
input,
|
|
6809
|
+
prepared
|
|
6810
|
+
);
|
|
6501
6811
|
});
|
|
6502
6812
|
}
|
|
6503
6813
|
var 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
|
}
|
|
@@ -7510,9 +7820,9 @@ function createServiceExecution(receiver, access, authorization, applicability =
|
|
|
7510
7820
|
receiver,
|
|
7511
7821
|
binding,
|
|
7512
7822
|
authorization: call,
|
|
7513
|
-
access:
|
|
7823
|
+
access: readServiceAuthorizationAccess(call, binding),
|
|
7514
7824
|
family: execution,
|
|
7515
|
-
hydration: resolveHydration(
|
|
7825
|
+
hydration: resolveHydration(readServiceAuthorizationHydration(call, binding), hydration)
|
|
7516
7826
|
})
|
|
7517
7827
|
);
|
|
7518
7828
|
return execution;
|
|
@@ -7523,7 +7833,7 @@ function resolveHydration(configuration, source) {
|
|
|
7523
7833
|
}
|
|
7524
7834
|
function withExecutionHydration(receiver, parent, source) {
|
|
7525
7835
|
const record3 = readServiceExecution(receiver, parent);
|
|
7526
|
-
const
|
|
7836
|
+
const configuration = readServiceAuthorizationHydration(record3.authorization, record3.binding);
|
|
7527
7837
|
const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
7528
7838
|
executions.set(execution, Object.freeze({ ...record3, hydration: resolveHydration(configuration, source) }));
|
|
7529
7839
|
return execution;
|
|
@@ -7560,7 +7870,7 @@ function deriveServiceExecution(receiver, parent, access, applicability = "servi
|
|
|
7560
7870
|
const execution = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
7561
7871
|
executions.set(
|
|
7562
7872
|
execution,
|
|
7563
|
-
Object.freeze({ ...record3, authorization: call, access:
|
|
7873
|
+
Object.freeze({ ...record3, authorization: call, access: readServiceAuthorizationAccess(call, record3.binding) })
|
|
7564
7874
|
);
|
|
7565
7875
|
return execution;
|
|
7566
7876
|
}
|
|
@@ -10648,7 +10958,8 @@ var Service = class extends Base {
|
|
|
10648
10958
|
async finalizeDirectOutput(args) {
|
|
10649
10959
|
const { plan, context } = args;
|
|
10650
10960
|
const { authorization, authorizationBinding: binding } = plan;
|
|
10651
|
-
const
|
|
10961
|
+
const access = readServiceAuthorizationAccess(authorization, binding);
|
|
10962
|
+
const configuration = readServiceOutputMetadata(authorization, binding);
|
|
10652
10963
|
const source = getIncludeSource(args.doc);
|
|
10653
10964
|
const snapshot = isolateDocForOutput(source);
|
|
10654
10965
|
for (const key of Object.keys(snapshot))
|