@opensaas/stack-core 0.37.0 → 0.39.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +191 -0
- package/CLAUDE.md +42 -3
- package/dist/access/access-filter.d.ts +70 -113
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +183 -210
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +148 -188
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts +66 -26
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +62 -17
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/declared-dependencies.test.d.ts +2 -0
- package/dist/access/declared-dependencies.test.d.ts.map +1 -0
- package/dist/access/declared-dependencies.test.js +226 -0
- package/dist/access/declared-dependencies.test.js.map +1 -0
- package/dist/access/depth-limits.d.ts +8 -7
- package/dist/access/depth-limits.d.ts.map +1 -1
- package/dist/access/depth-limits.js +8 -7
- package/dist/access/depth-limits.js.map +1 -1
- package/dist/access/engine.d.ts +0 -20
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +0 -25
- package/dist/access/engine.js.map +1 -1
- package/dist/access/errors.d.ts +61 -8
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +91 -12
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +39 -1
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +108 -42
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +229 -2
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-transforms.d.ts +0 -27
- package/dist/access/field-transforms.d.ts.map +1 -1
- package/dist/access/field-visibility.d.ts +2 -5
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +89 -41
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -3
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +12 -2
- package/dist/access/index.js.map +1 -1
- package/dist/access/query-validation.d.ts +121 -0
- package/dist/access/query-validation.d.ts.map +1 -0
- package/dist/access/query-validation.js +292 -0
- package/dist/access/query-validation.js.map +1 -0
- package/dist/access/relationship-count.d.ts +1 -1
- package/dist/access/relationship-count.d.ts.map +1 -1
- package/dist/access/relationship-count.js +2 -10
- package/dist/access/relationship-count.js.map +1 -1
- package/dist/access/relationship-label-filter.d.ts +23 -23
- package/dist/access/relationship-label-filter.d.ts.map +1 -1
- package/dist/access/relationship-label-filter.js +5 -76
- package/dist/access/relationship-label-filter.js.map +1 -1
- package/dist/access/relationship-label-filter.test.js +19 -56
- package/dist/access/relationship-label-filter.test.js.map +1 -1
- package/dist/access/transaction-registry.d.ts +45 -0
- package/dist/access/transaction-registry.d.ts.map +1 -0
- package/dist/access/transaction-registry.js +37 -0
- package/dist/access/transaction-registry.js.map +1 -0
- package/dist/access/types.d.ts +55 -59
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +9 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/label.d.ts +6 -13
- package/dist/config/label.d.ts.map +1 -1
- package/dist/config/label.js +6 -13
- package/dist/config/label.js.map +1 -1
- package/dist/config/nav-count.d.ts +11 -19
- package/dist/config/nav-count.d.ts.map +1 -1
- package/dist/config/nav-count.js +11 -19
- package/dist/config/nav-count.js.map +1 -1
- package/dist/config/plugin-engine.d.ts +0 -10
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +1 -37
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +94 -109
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/apply-defaults.d.ts +8 -31
- package/dist/context/apply-defaults.d.ts.map +1 -1
- package/dist/context/apply-defaults.js +12 -41
- package/dist/context/apply-defaults.js.map +1 -1
- package/dist/context/hook-pipeline.d.ts +4 -29
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +8 -41
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +4 -15
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +223 -215
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts +7 -26
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +73 -171
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/transaction-boundary.d.ts +36 -47
- package/dist/context/transaction-boundary.d.ts.map +1 -1
- package/dist/context/transaction-boundary.js +92 -79
- package/dist/context/transaction-boundary.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +25 -64
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +81 -111
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/format-prisma-default.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.js +4 -7
- package/dist/fields/format-prisma-default.js.map +1 -1
- package/dist/fields/index.d.ts +17 -2
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +136 -147
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +26 -3
- package/dist/filter/collect.d.ts.map +1 -1
- package/dist/filter/collect.js +30 -12
- package/dist/filter/collect.js.map +1 -1
- package/dist/filter/filter.test.js +98 -15
- package/dist/filter/filter.test.js.map +1 -1
- package/dist/filter/index.d.ts +1 -0
- package/dist/filter/index.d.ts.map +1 -1
- package/dist/filter/index.js.map +1 -1
- package/dist/filter/map.d.ts.map +1 -1
- package/dist/filter/map.js +0 -5
- package/dist/filter/map.js.map +1 -1
- package/dist/filter/parse.d.ts.map +1 -1
- package/dist/filter/parse.js +2 -9
- package/dist/filter/parse.js.map +1 -1
- package/dist/filter/serialize.d.ts.map +1 -1
- package/dist/filter/serialize.js +2 -4
- package/dist/filter/serialize.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -42
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/case-utils.d.ts +0 -32
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +0 -32
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/mcp/handler.d.ts +0 -4
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +12 -69
- package/dist/mcp/handler.js.map +1 -1
- package/dist/query/index.d.ts +55 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +41 -117
- package/dist/query/index.js.map +1 -1
- package/dist/query/relationship-options.d.ts +1 -1
- package/dist/query/relationship-options.js +1 -1
- package/dist/utils/password.d.ts +0 -68
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +7 -76
- package/dist/utils/password.js.map +1 -1
- package/dist/validation/field-config.d.ts.map +1 -1
- package/dist/validation/field-config.js +0 -6
- package/dist/validation/field-config.js.map +1 -1
- package/dist/validation/schema.d.ts +0 -7
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +0 -10
- package/dist/validation/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.test.ts +205 -275
- package/src/access/access-filter.ts +236 -272
- package/src/access/declared-dependencies.test.ts +277 -0
- package/src/access/declared-dependencies.ts +117 -37
- package/src/access/depth-limits.ts +8 -7
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +97 -12
- package/src/access/field-access.test.ts +312 -2
- package/src/access/field-access.ts +118 -42
- package/src/access/field-transforms.ts +8 -39
- package/src/access/field-visibility.ts +99 -45
- package/src/access/index.ts +17 -5
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +3 -11
- package/src/access/relationship-label-filter.test.ts +19 -68
- package/src/access/relationship-label-filter.ts +27 -104
- package/src/access/transaction-registry.ts +58 -0
- package/src/access/types.ts +83 -86
- package/src/config/index.ts +10 -22
- package/src/config/label.ts +6 -13
- package/src/config/nav-count.ts +11 -19
- package/src/config/plugin-engine.ts +1 -37
- package/src/config/types.ts +95 -110
- package/src/context/apply-defaults.ts +12 -44
- package/src/context/hook-pipeline.ts +12 -67
- package/src/context/index.ts +311 -276
- package/src/context/nested-operations.ts +94 -226
- package/src/context/transaction-boundary.ts +113 -109
- package/src/context/write-pipeline.ts +99 -155
- package/src/fields/format-prisma-default.ts +4 -7
- package/src/fields/index.ts +155 -147
- package/src/filter/collect.ts +48 -12
- package/src/filter/filter.test.ts +153 -15
- package/src/filter/index.ts +1 -0
- package/src/filter/map.ts +0 -5
- package/src/filter/parse.ts +2 -9
- package/src/filter/serialize.ts +2 -4
- package/src/hooks/index.ts +5 -59
- package/src/index.ts +23 -4
- package/src/internal.ts +5 -0
- package/src/lib/case-utils.ts +0 -32
- package/src/mcp/handler.ts +13 -71
- package/src/query/index.ts +82 -235
- package/src/query/relationship-options.ts +1 -1
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/access-relationships.test.ts +18 -16
- package/tests/computed-field-selective-evaluation.test.ts +418 -0
- package/tests/context.test.ts +727 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/needs-declared-dependencies.test.ts +7 -4
- package/tests/resolve-chain.test.ts +11 -11
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,147 +1,40 @@
|
|
|
1
1
|
import { checkAccess, getRelatedListConfig } from './engine.js';
|
|
2
2
|
import { READ_INCLUDE_MAX_DEPTH } from './depth-limits.js';
|
|
3
|
-
import { AccessScopeDepthExceededError } from './errors.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* silently stopping, which is correct when no caller asked for anything past
|
|
9
|
-
* this point (see `AccessIncludeResult` doc comment).
|
|
10
|
-
*/
|
|
11
|
-
function toPrismaEntry(entry) {
|
|
12
|
-
const result = {};
|
|
13
|
-
if (entry.where)
|
|
14
|
-
result.where = entry.where;
|
|
15
|
-
if (entry.nested.kind === 'scoped') {
|
|
16
|
-
const nestedInclude = toPrismaInclude(entry.nested);
|
|
17
|
-
if (nestedInclude && Object.keys(nestedInclude).length > 0) {
|
|
18
|
-
result.include = nestedInclude;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return Object.keys(result).length > 0 ? result : true;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Collapse an `AccessIncludeResult` to the plain Prisma `include` shape used
|
|
25
|
-
* when there is no caller-supplied include to merge against (the direct
|
|
26
|
-
* auto-include path). `nothing-to-scope` and `depth-exceeded` both become
|
|
27
|
-
* `undefined` here — at this call site nothing was explicitly requested past
|
|
28
|
-
* either boundary, so there is nothing to deny.
|
|
29
|
-
*/
|
|
30
|
-
export function toPrismaInclude(result) {
|
|
31
|
-
if (result.kind !== 'scoped')
|
|
32
|
-
return undefined;
|
|
33
|
-
const out = {};
|
|
34
|
-
for (const [key, entry] of Object.entries(result.include)) {
|
|
35
|
-
out[key] = toPrismaEntry(entry);
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Build the access-controlled include for a list's fields.
|
|
41
|
-
*
|
|
42
|
-
* This allows us to filter relationships at the database level instead of in
|
|
43
|
-
* memory. Returns an {@link AccessIncludeResult} rather than a plain include
|
|
44
|
-
* object so that `mergeIncludeWithAccessControl` can tell a genuine "nothing
|
|
45
|
-
* to scope" apart from "the engine hit its depth cap" (see that type's doc
|
|
46
|
-
* comment and ADR-0022).
|
|
47
|
-
*/
|
|
48
|
-
export async function buildIncludeWithAccessControl(fieldConfigs, args, config, depth = 0,
|
|
49
|
-
// List names already on the path from the root to here. Used to detect
|
|
50
|
-
// relationship cycles (A → B → … → A) and stop the auto-include from
|
|
51
|
-
// re-descending them. Seed it with the root list name at the call site.
|
|
52
|
-
visitedLists = []) {
|
|
53
|
-
if (depth >= READ_INCLUDE_MAX_DEPTH) {
|
|
54
|
-
return { kind: 'depth-exceeded' };
|
|
55
|
-
}
|
|
56
|
-
// Inside a resolveOutput/virtual-field context we still scope each immediate
|
|
57
|
-
// relation (its own access `where`), but do not auto-expand INTO its nested
|
|
58
|
-
// relations — this keeps a single hook-issued read's include from growing
|
|
59
|
-
// depth-first. Correction (ADR-0023): this does NOT by itself terminate a
|
|
60
|
-
// cycle, despite what an earlier version of this comment claimed. A
|
|
61
|
-
// self-referential relation read from inside a hook is a NEW top-level
|
|
62
|
-
// read, invisible to this function's own recursion — the loop runs through
|
|
63
|
-
// the hook↔read boundary, not through repeated calls to
|
|
64
|
-
// `buildIncludeWithAccessControl`. Loop termination is the resolve chain's
|
|
65
|
-
// job: `field-visibility.ts`'s `resolveReadableFieldValue` refuses to
|
|
66
|
-
// re-enter a `(list, field)` pair already on the chain. What this check
|
|
67
|
-
// still does correctly is row-scope the immediate relation rather than
|
|
68
|
-
// skipping scoping altogether (issue #830's second trigger).
|
|
69
|
-
const insideResolveOutput = args.context._resolveOutputChain.length > 0;
|
|
70
|
-
const include = {};
|
|
71
|
-
let hasRelationships = false;
|
|
72
|
-
for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
|
|
73
|
-
if (fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && fieldConfig.ref) {
|
|
74
|
-
hasRelationships = true;
|
|
75
|
-
const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
|
|
76
|
-
if (relatedConfig) {
|
|
77
|
-
// Check query access for the related list
|
|
78
|
-
const queryAccess = relatedConfig.listConfig.access?.operation?.query;
|
|
79
|
-
const accessResult = await checkAccess(queryAccess, {
|
|
80
|
-
session: args.session,
|
|
81
|
-
context: args.context,
|
|
82
|
-
});
|
|
83
|
-
// If access is completely denied, exclude this relationship
|
|
84
|
-
if (accessResult === false) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const where = typeof accessResult === 'object' ? accessResult : undefined;
|
|
88
|
-
// Cycle guard: if the related list already appears on the path from the
|
|
89
|
-
// root, DO NOT auto-include its relationships again. On a cyclic
|
|
90
|
-
// readable-relationship graph (A → B → … → A) the depth-first walk would
|
|
91
|
-
// otherwise re-descend the cycle on every branch, and — combined with the
|
|
92
|
-
// bare-`true` leaf re-expansion in `mergeIncludeWithAccessControl` — build
|
|
93
|
-
// an include tree deep/large enough to overflow the call stack in
|
|
94
|
-
// downstream processing (the RSC serializer's recursive `Map.set`). This
|
|
95
|
-
// extends the SF-20 (#566) fix: a bare-`true` leaf must resolve to a
|
|
96
|
-
// genuine single-level fetch, not a re-expansion of the full auto-include.
|
|
97
|
-
// The relation itself is still included (as a FLAT fetch of its own
|
|
98
|
-
// columns); only its onward relationships are pruned at the back-edge.
|
|
99
|
-
let nested = { kind: 'nothing-to-scope' };
|
|
100
|
-
const relatedListName = relatedConfig.listName;
|
|
101
|
-
if (!insideResolveOutput && !visitedLists.includes(relatedListName)) {
|
|
102
|
-
nested = await buildIncludeWithAccessControl(relatedConfig.listConfig.fields, args, config, depth + 1, [...visitedLists, relatedListName]);
|
|
103
|
-
}
|
|
104
|
-
include[fieldName] = { where, nested };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return hasRelationships ? { kind: 'scoped', include } : { kind: 'nothing-to-scope' };
|
|
3
|
+
import { AccessScopeDepthExceededError, RelationFilterAccessDeniedError } from './errors.js';
|
|
4
|
+
import { LOGICAL_OPERATORS, RELATION_QUANTIFIERS, resolveQueryField, walkWhereReadAccess, } from './query-validation.js';
|
|
5
|
+
/** A plain object — excludes `null` and arrays, which `typeof x === 'object'` alone would admit. */
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
109
8
|
}
|
|
110
9
|
/**
|
|
111
10
|
* Narrow an unknown include value to the structured object form (vs bare `true`
|
|
112
11
|
* or any other primitive). Caller-supplied includes arrive untyped at the
|
|
113
|
-
* runtime boundary
|
|
12
|
+
* runtime boundary: narrow to a plain object first, then validate each field's
|
|
13
|
+
* own type before trusting it, rather than casting the whole value wholesale.
|
|
114
14
|
*
|
|
115
15
|
* A numeric `take` on a to-many relation include (a caller-supplied row bound,
|
|
116
16
|
* issue #752) is carried through: it only ever NARROWS the fetched rows and can
|
|
117
|
-
* never widen past the access `where`, so preserving it is access-neutral.
|
|
118
|
-
* access-controlled include never sets `take` itself — it originates solely
|
|
119
|
-
* from the caller — so `mergeIncludeWithAccessControl` re-attaches it below.
|
|
17
|
+
* never widen past the access `where`, so preserving it is access-neutral.
|
|
120
18
|
*/
|
|
121
19
|
function asEntryObject(value) {
|
|
122
|
-
if (value
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
entry.take = take;
|
|
134
|
-
return entry;
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
20
|
+
if (!isPlainObject(value))
|
|
21
|
+
return null;
|
|
22
|
+
const { where, include, take } = value;
|
|
23
|
+
const entry = {};
|
|
24
|
+
if (isPlainObject(where))
|
|
25
|
+
entry.where = where;
|
|
26
|
+
if (isPlainObject(include))
|
|
27
|
+
entry.include = include;
|
|
28
|
+
if (typeof take === 'number')
|
|
29
|
+
entry.take = take;
|
|
30
|
+
return entry;
|
|
137
31
|
}
|
|
138
32
|
/**
|
|
139
33
|
* AND-combine an access `where` with a caller-supplied nested `where`.
|
|
140
34
|
*
|
|
141
35
|
* The access filter is authoritative: the caller's filter may only NARROW the
|
|
142
36
|
* result further, never widen past what access permits. We therefore wrap both
|
|
143
|
-
* in a Prisma `AND` so neither can override the other.
|
|
144
|
-
* present, it is returned as-is; if neither is present, the result is undefined.
|
|
37
|
+
* in a Prisma `AND` so neither can override the other.
|
|
145
38
|
*/
|
|
146
39
|
function andWhere(accessWhere, callerWhere) {
|
|
147
40
|
if (accessWhere && callerWhere) {
|
|
@@ -150,104 +43,185 @@ function andWhere(accessWhere, callerWhere) {
|
|
|
150
43
|
return accessWhere ?? callerWhere;
|
|
151
44
|
}
|
|
152
45
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* The caller's `include` decides WHICH relations to fetch; access control decides
|
|
157
|
-
* WHETHER each relation may be fetched and WITH WHAT filter. Replacing the
|
|
158
|
-
* access-controlled include with the caller's wholesale (the bug in #566) drops
|
|
159
|
-
* every per-relation access `where` and denied-relation exclusion, silently
|
|
160
|
-
* bypassing row-level access on any non-sudo read that passes `include`.
|
|
46
|
+
* Build the access-scoped `include` for exactly the relations a read
|
|
47
|
+
* requested, recursing only into branches `requestedInclude` itself names.
|
|
161
48
|
*
|
|
162
|
-
* For each
|
|
163
|
-
* -
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* -
|
|
167
|
-
*
|
|
168
|
-
* nested
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* nested
|
|
172
|
-
*
|
|
173
|
-
*
|
|
49
|
+
* For each key in `requestedInclude`:
|
|
50
|
+
* - Not a config-declared relationship → access control does not govern it;
|
|
51
|
+
* passed through unchanged (e.g. a fragment/caller key that isn't a
|
|
52
|
+
* relationship at all).
|
|
53
|
+
* - A declared relationship whose related list's `query` access denies it
|
|
54
|
+
* (`=== false`) → dropped entirely, no matter what the request asked for
|
|
55
|
+
* nested beneath it (#566): the caller chooses *which* relations, access
|
|
56
|
+
* control chooses *whether* and *with what filter*.
|
|
57
|
+
* - Otherwise → the access `where` is AND-combined with any caller-supplied
|
|
58
|
+
* nested `where` (never replaced — the other half of #566), a
|
|
59
|
+
* caller-supplied `take` rides through unchanged (#752), and — the "One
|
|
60
|
+
* hop" rule (ADR-0026) — nested relations are scoped ONLY if
|
|
61
|
+
* `requestedInclude` itself named a nested `include` here. A bare relation
|
|
62
|
+
* (or one with no nested `include`) fetches its own columns and stops: no
|
|
63
|
+
* recursive call, no access evaluation on anything beneath it.
|
|
174
64
|
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* unscoped (issue #830): throw `AccessScopeDepthExceededError` instead. An
|
|
184
|
-
* empty caller include at this level (nothing further requested) is not an
|
|
185
|
-
* error — there's simply nothing to do.
|
|
186
|
-
* - `scoped` → the normal per-relation merge below: a declared relationship
|
|
187
|
-
* ABSENT from the access include was denied (drop it); one PRESENT is used
|
|
188
|
-
* as the base, AND-combining `where`s and recursing into nested includes.
|
|
189
|
-
*
|
|
190
|
-
* `listKey` and `depth` are carried only to build a useful
|
|
191
|
-
* `AccessScopeDepthExceededError` message; they do not affect merge behaviour.
|
|
65
|
+
* **Depth is a cost limit, not a cycle guard (ADR-0026).** A `requestedInclude`
|
|
66
|
+
* is always a finite literal — the caller's own object, or
|
|
67
|
+
* `foldDeclaredDependencies`'s already-cycle-guarded fold — so this recursion
|
|
68
|
+
* cannot loop unboundedly on its own; nothing here walks the relationship
|
|
69
|
+
* graph unprompted. `READ_INCLUDE_MAX_DEPTH` still bounds how deep a request
|
|
70
|
+
* may reach, fail-closed per ADR-0022: a request naming anything at or past
|
|
71
|
+
* the cap throws `AccessScopeDepthExceededError` rather than silently
|
|
72
|
+
* returning less than what was asked for.
|
|
192
73
|
*/
|
|
193
|
-
export function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (accessControlledInclude.kind === 'depth-exceeded') {
|
|
198
|
-
const [firstRelationName] = Object.keys(callerInclude);
|
|
199
|
-
if (firstRelationName !== undefined) {
|
|
200
|
-
throw new AccessScopeDepthExceededError(listKey, firstRelationName, depth);
|
|
201
|
-
}
|
|
202
|
-
return {};
|
|
74
|
+
export async function buildAccessScopedInclude(requestedInclude, fieldConfigs, args, config, listKey, depth = 0) {
|
|
75
|
+
const requestedKeys = Object.keys(requestedInclude);
|
|
76
|
+
if (depth >= READ_INCLUDE_MAX_DEPTH && requestedKeys.length > 0) {
|
|
77
|
+
throw new AccessScopeDepthExceededError(listKey, requestedKeys[0], depth);
|
|
203
78
|
}
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
for (const [relationName, callerValue] of Object.entries(callerInclude)) {
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const [relationName, requestedValue] of Object.entries(requestedInclude)) {
|
|
207
81
|
const fieldConfig = fieldConfigs[relationName];
|
|
208
82
|
const isDeclaredRelationship = fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && !!fieldConfig.ref;
|
|
209
|
-
// Not a config-declared relationship → access control does not govern it; pass through unchanged.
|
|
210
83
|
if (!isDeclaredRelationship) {
|
|
211
|
-
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const accessEntry = accessInclude[relationName];
|
|
215
|
-
// Declared relationship absent from the access include → query access denied → drop it.
|
|
216
|
-
if (accessEntry === undefined) {
|
|
84
|
+
result[relationName] = requestedValue;
|
|
217
85
|
continue;
|
|
218
86
|
}
|
|
219
|
-
const callerEntry = asEntryObject(callerValue);
|
|
220
|
-
// Resolve the related list's field configs so nested includes merge recursively.
|
|
221
87
|
const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
88
|
+
if (!relatedConfig)
|
|
89
|
+
continue;
|
|
90
|
+
const queryAccess = relatedConfig.listConfig.access?.operation?.query;
|
|
91
|
+
const accessResult = await checkAccess(queryAccess, {
|
|
92
|
+
session: args.session,
|
|
93
|
+
context: args.context,
|
|
94
|
+
});
|
|
95
|
+
if (accessResult === false) {
|
|
96
|
+
continue;
|
|
230
97
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
98
|
+
const accessWhere = typeof accessResult === 'object' ? accessResult : undefined;
|
|
99
|
+
const requestedEntry = asEntryObject(requestedValue);
|
|
100
|
+
const mergedWhere = andWhere(accessWhere, requestedEntry?.where);
|
|
101
|
+
let nestedInclude;
|
|
102
|
+
if (requestedEntry?.include) {
|
|
103
|
+
nestedInclude = await buildAccessScopedInclude(requestedEntry.include, relatedConfig.listConfig.fields, args, config, relatedConfig.listName, depth + 1);
|
|
235
104
|
}
|
|
236
|
-
// If the caller's entry has no nested include, a 'depth-exceeded' nested
|
|
237
|
-
// result is silent here too — nothing was asked for past this point.
|
|
238
105
|
const entry = {};
|
|
239
106
|
if (mergedWhere)
|
|
240
107
|
entry.where = mergedWhere;
|
|
241
|
-
if (
|
|
242
|
-
entry.include =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
108
|
+
if (nestedInclude && Object.keys(nestedInclude).length > 0)
|
|
109
|
+
entry.include = nestedInclude;
|
|
110
|
+
if (requestedEntry?.take !== undefined)
|
|
111
|
+
entry.take = requestedEntry.take;
|
|
112
|
+
result[relationName] = Object.keys(entry).length > 0 ? entry : true;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Scope every relation filter (`some`/`every`/`none`/`is`/`isNot`) nested in a
|
|
118
|
+
* caller's `where` by the related list's own `query` access — the `where`
|
|
119
|
+
* counterpart to `buildAccessScopedInclude` above, closing #916 (the
|
|
120
|
+
* "unclosed half of ADR-0022"). `include` and `where` are fundamentally
|
|
121
|
+
* different requests (which relations come back, vs. which parent rows
|
|
122
|
+
* match), so this is a distinct function, but it shares every primitive that
|
|
123
|
+
* matters: `checkAccess`/`getRelatedListConfig` (the same access-evaluation
|
|
124
|
+
* calls `buildAccessScopedInclude` makes), `andWhere` (the same AND-fold), and
|
|
125
|
+
* `resolveQueryField`/`LOGICAL_OPERATORS`/`RELATION_QUANTIFIERS`/
|
|
126
|
+
* `walkWhereReadAccess` (the same shape-recognition and field-read check
|
|
127
|
+
* `query-validation.ts` already uses for #912/#915) — there is no second,
|
|
128
|
+
* parallel implementation of any of those decisions.
|
|
129
|
+
*
|
|
130
|
+
* For each relationship key found (at any depth — the walk recurses through
|
|
131
|
+
* `AND`/`OR`/`NOT` and through every hop of a chain):
|
|
132
|
+
* - Not a declared relationship, or the key #912 already rejected (this walk
|
|
133
|
+
* runs strictly after that check) → passed through unchanged.
|
|
134
|
+
* - The related list's `query` access denies it (`=== false`) → THROWS
|
|
135
|
+
* `RelationFilterAccessDeniedError`. Unlike `buildAccessScopedInclude`'s
|
|
136
|
+
* silent drop, this is a loud failure: a `where` predicate has no neutral
|
|
137
|
+
* "not requested" outcome the way a missing `include` key does, so a
|
|
138
|
+
* silently-empty match would itself be a distinguishable signal (ADR-0022).
|
|
139
|
+
* - Otherwise → the access filter (if any) is AND-combined into the relation
|
|
140
|
+
* quantifier's nested clause (never replacing the caller's own condition,
|
|
141
|
+
* mirroring `andWhere`'s include-side contract), keys inside that nested
|
|
142
|
+
* clause are checked against the RELATED list's field-level `read` access
|
|
143
|
+
* (`walkWhereReadAccess`, closing #915's stated gap for this path), and the
|
|
144
|
+
* walk recurses into the related list's own fields for a further hop.
|
|
145
|
+
*
|
|
146
|
+
* One quantifier is deliberately conservative rather than exactly precise:
|
|
147
|
+
* folding the access filter into `every`'s nested clause with a plain AND
|
|
148
|
+
* makes `every` require every related row to be BOTH access-visible AND
|
|
149
|
+
* matching, not "every access-visible row matches" (the latter needs a
|
|
150
|
+
* `NOT`/`OR` transform this does not attempt). The conservative version can
|
|
151
|
+
* only reject a query that the precise version would allow — it never
|
|
152
|
+
* widens what a caller can learn — so it is the safe direction to ship; a
|
|
153
|
+
* caller can observe that an inaccessible related row exists (an `every`
|
|
154
|
+
* that "should" pass instead fails), but never that row's field values,
|
|
155
|
+
* which is the property this ticket exists to close.
|
|
156
|
+
*/
|
|
157
|
+
export async function buildAccessScopedWhere(where,
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
159
|
+
listConfig, listName, config, args) {
|
|
160
|
+
if (where === null || typeof where !== 'object')
|
|
161
|
+
return where;
|
|
162
|
+
if (Array.isArray(where)) {
|
|
163
|
+
return Promise.all(where.map((entry) => buildAccessScopedWhere(entry, listConfig, listName, config, args)));
|
|
249
164
|
}
|
|
250
|
-
|
|
165
|
+
const result = {};
|
|
166
|
+
for (const [key, value] of Object.entries(where)) {
|
|
167
|
+
if (LOGICAL_OPERATORS.has(key)) {
|
|
168
|
+
result[key] = await buildAccessScopedWhere(value, listConfig, listName, config, args);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const resolved = resolveQueryField(key, listConfig.fields);
|
|
172
|
+
if (!resolved ||
|
|
173
|
+
!resolved.isRelationship ||
|
|
174
|
+
value === null ||
|
|
175
|
+
typeof value !== 'object' ||
|
|
176
|
+
Array.isArray(value)) {
|
|
177
|
+
result[key] = value;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const related = getRelatedListConfig(resolved.fieldConfig.ref, config);
|
|
181
|
+
if (!related) {
|
|
182
|
+
result[key] = value;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const queryAccess = related.listConfig.access?.operation?.query;
|
|
186
|
+
const accessResult = await checkAccess(queryAccess, {
|
|
187
|
+
session: args.session,
|
|
188
|
+
context: args.context,
|
|
189
|
+
});
|
|
190
|
+
if (accessResult === false) {
|
|
191
|
+
throw new RelationFilterAccessDeniedError(listName, key, related.listName);
|
|
192
|
+
}
|
|
193
|
+
const accessWhere = typeof accessResult === 'object' ? accessResult : undefined;
|
|
194
|
+
const relationEntries = Object.entries(value);
|
|
195
|
+
const hasQuantifier = relationEntries.some(([k]) => RELATION_QUANTIFIERS.has(k));
|
|
196
|
+
if (hasQuantifier) {
|
|
197
|
+
const nestedEntry = {};
|
|
198
|
+
for (const [quantifier, quantifierValue] of relationEntries) {
|
|
199
|
+
if (!RELATION_QUANTIFIERS.has(quantifier)) {
|
|
200
|
+
nestedEntry[quantifier] = quantifierValue;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
await walkWhereReadAccess(quantifierValue, related.listConfig, related.listName, args);
|
|
204
|
+
const scopedNested = await buildAccessScopedWhere(quantifierValue, related.listConfig, related.listName, config, args);
|
|
205
|
+
nestedEntry[quantifier] = accessWhere
|
|
206
|
+
? andWhere(accessWhere, scopedNested)
|
|
207
|
+
: scopedNested;
|
|
208
|
+
}
|
|
209
|
+
result[key] = nestedEntry;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Prisma's direct-nesting to-one form: the whole value IS the nested
|
|
213
|
+
// WHERE clause, with no `is` wrapper. Only wrap it in `is` when there is
|
|
214
|
+
// an access filter to fold in — an unwrapped value that needs no fold
|
|
215
|
+
// (fully-allowed related list) is returned exactly as the caller wrote
|
|
216
|
+
// it, so an already-permitted query is not perturbed by this pass.
|
|
217
|
+
await walkWhereReadAccess(value, related.listConfig, related.listName, args);
|
|
218
|
+
const scopedNested = await buildAccessScopedWhere(value, related.listConfig, related.listName, config, args);
|
|
219
|
+
result[key] = accessWhere
|
|
220
|
+
? { is: andWhere(accessWhere, scopedNested) }
|
|
221
|
+
: scopedNested;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
251
225
|
}
|
|
252
226
|
/**
|
|
253
227
|
* Remove keys that correspond to `virtual` fields from a Prisma `include`
|
|
@@ -271,7 +245,6 @@ export function stripVirtualFieldsFromInclude(include, fieldConfigs, config) {
|
|
|
271
245
|
const result = {};
|
|
272
246
|
for (const [key, value] of Object.entries(include)) {
|
|
273
247
|
const fieldConfig = fieldConfigs[key];
|
|
274
|
-
// Virtual fields have no database column — drop them from the include.
|
|
275
248
|
if (fieldConfig?.virtual)
|
|
276
249
|
continue;
|
|
277
250
|
const isDeclaredRelationship = fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && !!fieldConfig.ref;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"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"}
|