@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.383",
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-infra": "3.0.0-alpha.193",
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-core": "3.0.0-alpha.296",
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
- queryAuthMap->Dict.set(entry.singleFieldName, groups)
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
- queryAuthMap[entry.singleFieldName] = [group];
161
- queryAuthMap[entry.listFieldName] = [group];
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, entry.singleFieldName);
171
- return Stdlib_Dict.$$delete(queryAuthMap, entry.listFieldName);
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
- queryAuthMap[entry.singleFieldName] = groups$1;
179
- queryAuthMap[entry.listFieldName] = groups$1;
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", () => {