@reventlessdev/reventless-aws 3.0.0-alpha.383 → 3.0.0-alpha.384
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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.384 (2026-10-16)
|
|
7
|
+
|
|
8
|
+
* fix(auth)!: only operators read the framework's own stores ([a2fd202](https://github.com/ReventlessDev/reventless-core/commit/a2fd2022b6876dae855b185df7f8263353fc971d))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* a caller outside the elevated groups is refused the
|
|
13
|
+
framework's to-do, schedule and audit stores. On AWS, a roles-gated
|
|
14
|
+
view's …ByIds, …Refs, …Items and …By<Index> fields refuse callers
|
|
15
|
+
outside its roles.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
6
19
|
# 3.0.0-alpha.383 (2026-10-16)
|
|
7
20
|
|
|
8
21
|
* feat!(automation): schedules set by events ([53bc436](https://github.com/ReventlessDev/reventless-core/commit/53bc4363dffce81ef36475728bf0dae704716021))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.384",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -23,18 +23,18 @@
|
|
|
23
23
|
"uuid": "^13.0.0",
|
|
24
24
|
"yaml": "^2.8.3",
|
|
25
25
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.32",
|
|
26
|
-
"@reventlessdev/rescript-effect": "1.0.0-alpha.0",
|
|
27
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.12",
|
|
28
|
-
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.29",
|
|
29
26
|
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
27
|
+
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.29",
|
|
28
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.12",
|
|
29
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.0",
|
|
30
30
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
31
31
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
32
32
|
"@reventlessdev/rescript-web": "1.0.0-alpha.7",
|
|
33
|
-
"@reventlessdev/reventless-
|
|
33
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.297",
|
|
34
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.194",
|
|
35
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.161",
|
|
34
36
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.40",
|
|
35
|
-
"@reventlessdev/reventless-
|
|
36
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.160",
|
|
37
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.165"
|
|
37
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.166"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"rescript": "12.3.1",
|
|
@@ -309,21 +309,18 @@ let injectAwsAuth = (
|
|
|
309
309
|
iamQueryFieldPrefixes->Array.push(entry.listFieldName)
|
|
310
310
|
iamTypePrefixes->Array.push(entry.returnTypeName)
|
|
311
311
|
}
|
|
312
|
+
// Every door the entry emits: the resolvers here do not check the rule, so a
|
|
313
|
+
// by-ids or by-index field left to the open directive reads the gated rows.
|
|
314
|
+
let fieldNames = ReventlessCore.GraphQL_FragmentGenerator.queryFieldNamesOf(entry)
|
|
312
315
|
switch entry.authorization {
|
|
313
|
-
| Some({group}) =>
|
|
314
|
-
queryAuthMap->Dict.set(entry.singleFieldName, [group])
|
|
315
|
-
queryAuthMap->Dict.set(entry.listFieldName, [group])
|
|
316
|
+
| Some({group}) => fieldNames->Array.forEach(f => queryAuthMap->Dict.set(f, [group]))
|
|
316
317
|
| None => ()
|
|
317
318
|
}
|
|
318
319
|
switch entry.permission {
|
|
319
320
|
| Some(permission) =>
|
|
320
321
|
switch AppSync_SdlDecorate.permissionToGate(permission) {
|
|
321
|
-
| Groups(groups) =>
|
|
322
|
-
|
|
323
|
-
queryAuthMap->Dict.set(entry.listFieldName, groups)
|
|
324
|
-
| AnyAuthenticated =>
|
|
325
|
-
queryAuthMap->Dict.delete(entry.singleFieldName)
|
|
326
|
-
queryAuthMap->Dict.delete(entry.listFieldName)
|
|
322
|
+
| Groups(groups) => fieldNames->Array.forEach(f => queryAuthMap->Dict.set(f, groups))
|
|
323
|
+
| AnyAuthenticated => fieldNames->Array.forEach(f => queryAuthMap->Dict.delete(f))
|
|
327
324
|
| Anonymous =>
|
|
328
325
|
// Both derived field names, because the spec declares one permission and
|
|
329
326
|
// the generator emits two fields from it; naming only one would send the
|
|
@@ -154,11 +154,13 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
154
154
|
iamQueryFieldPrefixes.push(entry.listFieldName);
|
|
155
155
|
iamTypePrefixes.push(entry.returnTypeName);
|
|
156
156
|
}
|
|
157
|
+
let fieldNames = GraphQL_FragmentGenerator$ReventlessCore.queryFieldNamesOf(entry);
|
|
157
158
|
let match = entry.authorization;
|
|
158
159
|
if (match !== undefined) {
|
|
159
160
|
let group = match.group;
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
fieldNames.forEach(f => {
|
|
162
|
+
queryAuthMap[f] = [group];
|
|
163
|
+
});
|
|
162
164
|
}
|
|
163
165
|
let permission = entry.permission;
|
|
164
166
|
if (permission === undefined) {
|
|
@@ -167,16 +169,17 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
167
169
|
let groups = AppSync_SdlDecorate$ReventlessAws.permissionToGate(permission);
|
|
168
170
|
if (typeof groups !== "object") {
|
|
169
171
|
if (groups === "AnyAuthenticated") {
|
|
170
|
-
Stdlib_Dict.$$delete(queryAuthMap,
|
|
171
|
-
return
|
|
172
|
+
fieldNames.forEach(f => Stdlib_Dict.$$delete(queryAuthMap, f));
|
|
173
|
+
return;
|
|
172
174
|
}
|
|
173
175
|
anonymousFields.push(entry.singleFieldName);
|
|
174
176
|
anonymousFields.push(entry.listFieldName);
|
|
175
177
|
return;
|
|
176
178
|
} else {
|
|
177
179
|
let groups$1 = groups._0;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
fieldNames.forEach(f => {
|
|
181
|
+
queryAuthMap[f] = groups$1;
|
|
182
|
+
});
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
185
|
});
|
|
@@ -606,6 +606,58 @@ describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth", () => {
|
|
|
606
606
|
)
|
|
607
607
|
},
|
|
608
608
|
)
|
|
609
|
+
|
|
610
|
+
// The resolvers check no rule, so a door the gate misses reads the gated rows.
|
|
611
|
+
// Run through the real generator, so a door it adds later is covered too.
|
|
612
|
+
testSync("a roles-gated entry's every generated door carries its groups", () => {
|
|
613
|
+
let byCategory: Reventless.ReadModel.indexConfig = {
|
|
614
|
+
index: "byCategory",
|
|
615
|
+
type_: "S",
|
|
616
|
+
idField: "categoryId",
|
|
617
|
+
projectionType: ALL,
|
|
618
|
+
}
|
|
619
|
+
let gated = (
|
|
620
|
+
~single,
|
|
621
|
+
~list,
|
|
622
|
+
~subIdField=?,
|
|
623
|
+
~indexQueries=?,
|
|
624
|
+
): ReventlessInfra.Api.querySchemaEntry => {
|
|
625
|
+
singleFieldName: single,
|
|
626
|
+
listFieldName: list,
|
|
627
|
+
returnTypeName: single,
|
|
628
|
+
stateSchema: S.unknown,
|
|
629
|
+
authorization: None,
|
|
630
|
+
permission: AllowRoles([Reventless.Role.make("Admin")]),
|
|
631
|
+
?subIdField,
|
|
632
|
+
?indexQueries,
|
|
633
|
+
}
|
|
634
|
+
let entries = [
|
|
635
|
+
gated(~single="p_Item", ~list="p_Items", ~indexQueries=[byCategory]),
|
|
636
|
+
gated(~single="p_Line", ~list="p_Lines", ~subIdField="lineId"),
|
|
637
|
+
]
|
|
638
|
+
let frag = ReventlessCore.GraphQL_FragmentGenerator.generate(
|
|
639
|
+
~mutationEntries=[],
|
|
640
|
+
~queryEntries=entries,
|
|
641
|
+
)
|
|
642
|
+
let queries = (
|
|
643
|
+
AppSync_Adapter.injectAwsAuth(
|
|
644
|
+
frag,
|
|
645
|
+
~mutationEntries=[],
|
|
646
|
+
~queryEntries=entries,
|
|
647
|
+
)->decodeFragment
|
|
648
|
+
).queries
|
|
649
|
+
let names = queries->Array.map(ReventlessCore.GraphQL_Stitcher.extractLeadingName)
|
|
650
|
+
expect(
|
|
651
|
+
["p_ItemsByIds", "p_ItemsRefs", "p_ItemByCategory", "p_LineItems"]->Array.every(
|
|
652
|
+
door => names->Array.includes(door),
|
|
653
|
+
),
|
|
654
|
+
)->toBe(true)
|
|
655
|
+
expect(
|
|
656
|
+
queries->Array.filter(
|
|
657
|
+
field => !(field->String.includes(`@aws_cognito_user_pools(cognito_groups: ["Admin"])`)),
|
|
658
|
+
),
|
|
659
|
+
)->toEqual([])
|
|
660
|
+
})
|
|
609
661
|
})
|
|
610
662
|
|
|
611
663
|
// ── Type-level dual-auth ─────────────────────────────────────────────────────
|
|
@@ -12,6 +12,7 @@ import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core
|
|
|
12
12
|
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
13
13
|
import * as AppSync_SdlDecorate$ReventlessAws from "../src/components/Api/AppSync_SdlDecorate.res.mjs";
|
|
14
14
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
15
|
+
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
15
16
|
|
|
16
17
|
globalThis.describe("AppSync_Adapter.sha256Hex", () => {
|
|
17
18
|
globalThis.test("returns a 64-character hex string", () => {
|
|
@@ -596,6 +597,40 @@ globalThis.describe("AppSync_Adapter.injectAwsAuth — systemCallable dual-auth"
|
|
|
596
597
|
}
|
|
597
598
|
});
|
|
598
599
|
});
|
|
600
|
+
globalThis.test("a roles-gated entry's every generated door carries its groups", () => {
|
|
601
|
+
let gated = (single, list, subIdField, indexQueries) => ({
|
|
602
|
+
singleFieldName: single,
|
|
603
|
+
listFieldName: list,
|
|
604
|
+
returnTypeName: single,
|
|
605
|
+
stateSchema: Sury.unknown,
|
|
606
|
+
authorization: undefined,
|
|
607
|
+
permission: {
|
|
608
|
+
TAG: "AllowRoles",
|
|
609
|
+
_0: ["Admin"]
|
|
610
|
+
},
|
|
611
|
+
subIdField: subIdField,
|
|
612
|
+
indexQueries: indexQueries
|
|
613
|
+
});
|
|
614
|
+
let entries = [
|
|
615
|
+
gated("p_Item", "p_Items", undefined, [{
|
|
616
|
+
index: "byCategory",
|
|
617
|
+
type_: "S",
|
|
618
|
+
idField: "categoryId",
|
|
619
|
+
projectionType: "ALL"
|
|
620
|
+
}]),
|
|
621
|
+
gated("p_Line", "p_Lines", "lineId", undefined)
|
|
622
|
+
];
|
|
623
|
+
let frag = GraphQL_FragmentGenerator$ReventlessCore.generate([], entries);
|
|
624
|
+
let queries = GraphQL_Stitcher$ReventlessCore.decode(AppSync_Adapter$ReventlessAws.injectAwsAuth(frag, [], entries)).queries;
|
|
625
|
+
let names = queries.map(GraphQL_Stitcher$ReventlessCore.extractLeadingName);
|
|
626
|
+
globalThis.expect([
|
|
627
|
+
"p_ItemsByIds",
|
|
628
|
+
"p_ItemsRefs",
|
|
629
|
+
"p_ItemByCategory",
|
|
630
|
+
"p_LineItems"
|
|
631
|
+
].every(door => names.includes(door))).toBe(true);
|
|
632
|
+
globalThis.expect(queries.filter(field => !field.includes(`@aws_cognito_user_pools(cognito_groups: ["Admin"])`))).toEqual([]);
|
|
633
|
+
});
|
|
599
634
|
});
|
|
600
635
|
|
|
601
636
|
globalThis.describe("AppSync_Adapter — type-level dual-auth", () => {
|