@reventlessdev/reventless-aws 3.0.0-alpha.296 → 3.0.0-alpha.298
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 +15 -0
- package/package.json +4 -4
- package/src/Platform.res +1 -1
- package/src/Platform.res.mjs +2 -2
- package/src/components/Api/AppSync_Adapter.res +16 -1
- package/src/components/Api/AppSync_Adapter.res.mjs +3 -2
- package/src/components/Api/AppSync_MergedApi.res +1 -1
- package/src/components/Api/AppSync_MergedApi.res.mjs +1 -1
- package/src/components/Api/AppSync_SdlDecorate.res +31 -0
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +19 -0
- package/tests/AppSync_SdlDecorateTest.res +61 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +40 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.298 (2026-08-14)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.297 (2026-08-14)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **aws:** sweep undirectived fields at the schema assembly choke point ([f0ed736](https://github.com/ReventlessDev/reventless-core/commit/f0ed7369ec4dddfb697a3c93115b12f310a491b0))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.296 (2026-08-14)
|
|
7
22
|
|
|
8
23
|
### Features
|
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.298",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"sury": "11.0.0-alpha.4",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.10",
|
|
16
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
17
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
18
18
|
"@reventlessdev/rescript-node": "2.0.0-alpha.6",
|
|
19
19
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.75",
|
|
20
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
20
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.140",
|
|
23
21
|
"@reventlessdev/reventless-core": "3.0.0-alpha.233",
|
|
22
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
23
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.140",
|
|
24
24
|
"@reventlessdev/reventless-postgres": "3.0.0-alpha.97",
|
|
25
25
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.113",
|
|
26
26
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.31"
|
package/src/Platform.res
CHANGED
package/src/Platform.res.mjs
CHANGED
|
@@ -220,7 +220,7 @@ function MakeWithConfig(Config) {
|
|
|
220
220
|
let awsRegion = Stdlib_Option.orElse(param[1], getFromDefault("cognitoRegion"));
|
|
221
221
|
return {
|
|
222
222
|
userPoolId: userPoolId,
|
|
223
|
-
defaultAction: "
|
|
223
|
+
defaultAction: "DENY",
|
|
224
224
|
awsRegion: awsRegion
|
|
225
225
|
};
|
|
226
226
|
});
|
|
@@ -1523,7 +1523,7 @@ function Make($star) {
|
|
|
1523
1523
|
let awsRegion = Stdlib_Option.orElse(param[1], getFromDefault("cognitoRegion"));
|
|
1524
1524
|
return {
|
|
1525
1525
|
userPoolId: userPoolId,
|
|
1526
|
-
defaultAction: "
|
|
1526
|
+
defaultAction: "DENY",
|
|
1527
1527
|
awsRegion: awsRegion
|
|
1528
1528
|
};
|
|
1529
1529
|
});
|
|
@@ -460,6 +460,10 @@ let stitchStandaloneWithAwsDirectives = (
|
|
|
460
460
|
~baseFragment=fragment,
|
|
461
461
|
~pluginFragments=[],
|
|
462
462
|
)
|
|
463
|
+
// Every AWS source-API document assembles through here, so this is the one
|
|
464
|
+
// place that can guarantee no field or type reaches a deployed schema without
|
|
465
|
+
// an auth directive — whichever path injected it.
|
|
466
|
+
let fragment = AppSync_SdlDecorate.stampUndirectivedFields(fragment)
|
|
463
467
|
ReventlessCore.GraphQL_Stitcher.stitchStandalone(~fragment)
|
|
464
468
|
->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
|
|
465
469
|
->stampSharedIamTypes
|
|
@@ -529,6 +533,17 @@ let _makeApiResourceWith = (
|
|
|
529
533
|
// Auth_Cognito (cached inside Platform_Stack, so calling from each API call
|
|
530
534
|
// site — DomainApi, PlatformApi — is safe). A single Output yielding the
|
|
531
535
|
// {userPoolId, awsRegion, defaultAction} record AppSync expects.
|
|
536
|
+
//
|
|
537
|
+
// `DENY`, not `ALLOW`: an undirectived field or type is refused rather than
|
|
538
|
+
// served to any authenticated Cognito caller. This is what makes a missing
|
|
539
|
+
// directive fail closed — under `ALLOW` the group gate that was inert for the
|
|
540
|
+
// whole of `@aws_auth`'s life produced no symptom at all, because "no
|
|
541
|
+
// effective directive" and "open to everyone" were the same thing.
|
|
542
|
+
//
|
|
543
|
+
// Safe only because every field and type now carries an explicit directive
|
|
544
|
+
// (`stampUndirectivedFields` + `stampAllTypesCognito` at the assembly choke
|
|
545
|
+
// point). Verify with `scripts/check-appsync-directive-coverage.mjs` against a
|
|
546
|
+
// DEPLOYED api — it must report zero before this value is trusted.
|
|
532
547
|
let userPoolConfigOut = switch userPoolConfig {
|
|
533
548
|
| Some(config) => config
|
|
534
549
|
| None =>
|
|
@@ -537,7 +552,7 @@ let _makeApiResourceWith = (
|
|
|
537
552
|
{
|
|
538
553
|
userPoolId: c.userPoolId,
|
|
539
554
|
awsRegion: c.region,
|
|
540
|
-
defaultAction: AppSync.GraphQLApi.
|
|
555
|
+
defaultAction: AppSync.GraphQLApi.DENY,
|
|
541
556
|
}: AppSync.GraphQLApi.userPoolConfig
|
|
542
557
|
)
|
|
543
558
|
)
|
|
@@ -264,7 +264,8 @@ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
|
264
264
|
|
|
265
265
|
function stitchStandaloneWithAwsDirectives(fragment) {
|
|
266
266
|
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
|
|
267
|
-
|
|
267
|
+
let fragment$1 = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(fragment);
|
|
268
|
+
return AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment$1), sources)));
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
|
|
@@ -287,7 +288,7 @@ function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
|
|
|
287
288
|
};
|
|
288
289
|
let userPoolConfigOut = userPoolConfig !== undefined ? userPoolConfig : Auth_Cognito$ReventlessAws.make(name + `-auth`, undefined).apply(c => ({
|
|
289
290
|
userPoolId: c.userPoolId,
|
|
290
|
-
defaultAction: "
|
|
291
|
+
defaultAction: "DENY",
|
|
291
292
|
awsRegion: c.region
|
|
292
293
|
}));
|
|
293
294
|
let apiArgs_schema = Stdlib_Option.map(schema, prim => prim);
|
|
@@ -100,7 +100,7 @@ let make = (~name: string, ~opts: Pulumi.ComponentResource.options): t => {
|
|
|
100
100
|
): AppSync.GraphQLApi.userPoolConfig => {
|
|
101
101
|
userPoolId: c.userPoolId,
|
|
102
102
|
awsRegion: c.region,
|
|
103
|
-
defaultAction: AppSync.GraphQLApi.
|
|
103
|
+
defaultAction: AppSync.GraphQLApi.DENY,
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
let mergedApi = AppSync.GraphQLApi.make(
|
|
@@ -55,7 +55,7 @@ function make(name, opts) {
|
|
|
55
55
|
let authConfigOut = Auth_Cognito$ReventlessAws.make(name + `-auth`, undefined);
|
|
56
56
|
let userPoolConfigOut = authConfigOut.apply(c => ({
|
|
57
57
|
userPoolId: c.userPoolId,
|
|
58
|
-
defaultAction: "
|
|
58
|
+
defaultAction: "DENY",
|
|
59
59
|
awsRegion: c.region
|
|
60
60
|
}));
|
|
61
61
|
let mergedApi = new (Aws.appsync.GraphQLApi)(name, {
|
|
@@ -162,6 +162,37 @@ let stampSharedIamTypes = (sdl: string): string =>
|
|
|
162
162
|
acc->String.replace(`type ${name} {`, `type ${name} @aws_cognito_user_pools @aws_iam {`)
|
|
163
163
|
)
|
|
164
164
|
|
|
165
|
+
// Final field sweep, run at the assembly choke point on the FRAGMENT (where each
|
|
166
|
+
// element is one whole field, so "does it already carry a directive?" is a
|
|
167
|
+
// reliable question — on raw SDL text a directive placed on the following line
|
|
168
|
+
// would read as absent).
|
|
169
|
+
//
|
|
170
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry, and
|
|
171
|
+
// several surfaces never reach it at all: the domain base document
|
|
172
|
+
// (`Platform_ping`), event-history queries, event-log `…_eventAppended`
|
|
173
|
+
// subscriptions, the upload presign/release mutations, `Platform_SetActiveRole`,
|
|
174
|
+
// and `geocode`. Those deployed with no directive — invisible under
|
|
175
|
+
// `defaultAction: ALLOW`, refused under DENY. Sweeping here rather than at each
|
|
176
|
+
// emission site means a future injected field cannot silently miss the net.
|
|
177
|
+
//
|
|
178
|
+
// Idempotent, and deliberately does not second-guess gating: it only fills in
|
|
179
|
+
// "open to any authenticated Cognito caller", which is what an undirectived
|
|
180
|
+
// field already means today.
|
|
181
|
+
let stampUndirectivedFields = (
|
|
182
|
+
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
183
|
+
): Reventless.Plugin.apiSchemaFragment => {
|
|
184
|
+
let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
|
|
185
|
+
let stamp = (field: string): string =>
|
|
186
|
+
field->String.includes("@aws_") ? field : `${field}\n ${cognitoOpenDirective}`
|
|
187
|
+
|
|
188
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
189
|
+
...parts,
|
|
190
|
+
mutations: parts.mutations->Array.map(stamp),
|
|
191
|
+
queries: parts.queries->Array.map(stamp),
|
|
192
|
+
subscriptions: parts.subscriptions->Array.map(stamp),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
165
196
|
// Stamp every object `type` declaration that does not already carry an auth
|
|
166
197
|
// directive with the group-less Cognito arm. Run on the ASSEMBLED SDL, AFTER
|
|
167
198
|
// stampSharedIamTypes so the shared traversal types keep their `@aws_iam` arm
|
|
@@ -88,6 +88,24 @@ function stampSharedIamTypes(sdl) {
|
|
|
88
88
|
return Stdlib_Array.reduce(sharedIamTypeNames, sdl, (acc, name) => acc.replace(`type ` + name + ` {`, `type ` + name + ` @aws_cognito_user_pools @aws_iam {`));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function stampUndirectivedFields(fragment) {
|
|
92
|
+
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
93
|
+
let stamp = field => {
|
|
94
|
+
if (field.includes("@aws_")) {
|
|
95
|
+
return field;
|
|
96
|
+
} else {
|
|
97
|
+
return field + `\n ` + cognitoOpenDirective;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
101
|
+
types: parts.types,
|
|
102
|
+
mutations: parts.mutations.map(stamp),
|
|
103
|
+
queries: parts.queries.map(stamp),
|
|
104
|
+
subscriptions: parts.subscriptions.map(stamp),
|
|
105
|
+
subscriptionSources: parts.subscriptionSources
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
function stampAllTypesCognito(sdl) {
|
|
92
110
|
return sdl.split("\n").map(line => {
|
|
93
111
|
if (!(line.startsWith("type ") && !line.includes("@aws_"))) {
|
|
@@ -147,6 +165,7 @@ export {
|
|
|
147
165
|
injectAwsAuthAll,
|
|
148
166
|
sharedIamTypeNames,
|
|
149
167
|
stampSharedIamTypes,
|
|
168
|
+
stampUndirectivedFields,
|
|
150
169
|
stampAllTypesCognito,
|
|
151
170
|
canonicalTypeNames,
|
|
152
171
|
stampCanonicalTypes,
|
|
@@ -188,3 +188,64 @@ describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
|
188
188
|
expect(AppSync_SdlDecorate.stampAllTypesCognito(once))->toBe(once)
|
|
189
189
|
})
|
|
190
190
|
})
|
|
191
|
+
|
|
192
|
+
// ── Final field sweep ───────────────────────────────────────────────────────
|
|
193
|
+
//
|
|
194
|
+
// `injectAwsAuth` only decorates fields it can pair with a schema entry. Several
|
|
195
|
+
// surfaces never reach it: the domain base (`Platform_ping`), event-history
|
|
196
|
+
// queries, `…_eventAppended` subscriptions, upload mutations,
|
|
197
|
+
// `Platform_SetActiveRole`, `geocode`. A deployed-SDL count after the first
|
|
198
|
+
// sweep found 11 such fields still bare on the domain API. This pass is the net.
|
|
199
|
+
|
|
200
|
+
describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
201
|
+
let frag = (~mutations=[], ~queries=[], ~subscriptions=[], ()) =>
|
|
202
|
+
ReventlessCore.GraphQL_Stitcher.encode({
|
|
203
|
+
types: [],
|
|
204
|
+
mutations,
|
|
205
|
+
queries,
|
|
206
|
+
subscriptions,
|
|
207
|
+
subscriptionSources: [],
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
testSync("stamps a query no schema entry ever paired with", () => {
|
|
211
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
212
|
+
frag(~queries=[" Platform_ping: String"], ()),
|
|
213
|
+
)
|
|
214
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
215
|
+
expect(q)->toContain("@aws_cognito_user_pools")
|
|
216
|
+
expect(q)->not_->toContain("cognito_groups")
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
testSync("stamps an event-log subscription", () => {
|
|
220
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(
|
|
221
|
+
frag(~subscriptions=[" onFoo_eventAppended: FooEvent"], ()),
|
|
222
|
+
)
|
|
223
|
+
expect(
|
|
224
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).subscriptions->Array.getUnsafe(0),
|
|
225
|
+
)->toContain("@aws_cognito_user_pools")
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
testSync("leaves an already-gated field untouched, group and all", () => {
|
|
229
|
+
let gated = ` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`
|
|
230
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[gated], ()))
|
|
231
|
+
let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
|
|
232
|
+
expect(q)->toContain(`cognito_groups: ["Admin"]`)
|
|
233
|
+
expect(q->String.includes("@aws_cognito_user_pools @aws_cognito_user_pools"))->toBe(false)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
testSync("does not strip an @aws_iam arm", () => {
|
|
237
|
+
let dual = " Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"
|
|
238
|
+
let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~mutations=[dual], ()))
|
|
239
|
+
expect(
|
|
240
|
+
ReventlessCore.GraphQL_Stitcher.decode(out).mutations->Array.getUnsafe(0),
|
|
241
|
+
)->toContain("@aws_iam")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
testSync("is idempotent", () => {
|
|
245
|
+
let once = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[" a: String"], ()))
|
|
246
|
+
let twice = AppSync_SdlDecorate.stampUndirectivedFields(once)
|
|
247
|
+
expect(ReventlessCore.GraphQL_Stitcher.decode(twice).queries->Array.getUnsafe(0))->toBe(
|
|
248
|
+
ReventlessCore.GraphQL_Stitcher.decode(once).queries->Array.getUnsafe(0),
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -152,6 +152,46 @@ globalThis.describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
|
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
globalThis.describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
|
|
156
|
+
let frag = (mutationsOpt, queriesOpt, subscriptionsOpt, param) => {
|
|
157
|
+
let mutations = mutationsOpt !== undefined ? mutationsOpt : [];
|
|
158
|
+
let queries = queriesOpt !== undefined ? queriesOpt : [];
|
|
159
|
+
let subscriptions = subscriptionsOpt !== undefined ? subscriptionsOpt : [];
|
|
160
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
161
|
+
types: [],
|
|
162
|
+
mutations: mutations,
|
|
163
|
+
queries: queries,
|
|
164
|
+
subscriptions: subscriptions,
|
|
165
|
+
subscriptionSources: []
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
globalThis.test("stamps a query no schema entry ever paired with", () => {
|
|
169
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" Platform_ping: String"], undefined, undefined));
|
|
170
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
171
|
+
globalThis.expect(q).toContain("@aws_cognito_user_pools");
|
|
172
|
+
globalThis.expect(q).not.toContain("cognito_groups");
|
|
173
|
+
});
|
|
174
|
+
globalThis.test("stamps an event-log subscription", () => {
|
|
175
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, undefined, [" onFoo_eventAppended: FooEvent"], undefined));
|
|
176
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).subscriptions[0]).toContain("@aws_cognito_user_pools");
|
|
177
|
+
});
|
|
178
|
+
globalThis.test("leaves an already-gated field untouched, group and all", () => {
|
|
179
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`], undefined, undefined));
|
|
180
|
+
let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
|
|
181
|
+
globalThis.expect(q).toContain(`cognito_groups: ["Admin"]`);
|
|
182
|
+
globalThis.expect(q.includes("@aws_cognito_user_pools @aws_cognito_user_pools")).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
globalThis.test("does not strip an @aws_iam arm", () => {
|
|
185
|
+
let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag([" Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"], undefined, undefined, undefined));
|
|
186
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).mutations[0]).toContain("@aws_iam");
|
|
187
|
+
});
|
|
188
|
+
globalThis.test("is idempotent", () => {
|
|
189
|
+
let once = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" a: String"], undefined, undefined));
|
|
190
|
+
let twice = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(once);
|
|
191
|
+
globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(twice).queries[0]).toBe(GraphQL_Stitcher$ReventlessCore.decode(once).queries[0]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
155
195
|
export {
|
|
156
196
|
sources,
|
|
157
197
|
stitchedSdl,
|