@reventlessdev/reventless-aws 3.0.0-alpha.345 → 3.0.0-alpha.347
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 +54 -0
- package/package.json +19 -12
- package/rescript.json +2 -1
- package/run-bake-manifest.mjs +3 -0
- package/run-deploy-app.mjs +3 -0
- package/run-provision-accounts.mjs +3 -0
- package/run-provision-admin.mjs +3 -0
- package/scripts/BakeManifest.res +395 -0
- package/scripts/BakeManifest.res.mjs +487 -0
- package/scripts/DeployApp.res +708 -0
- package/scripts/DeployApp.res.mjs +1015 -0
- package/scripts/DeployManifest.res +84 -0
- package/scripts/DeployManifest.res.mjs +112 -0
- package/scripts/ProvisionAccounts.res +339 -0
- package/scripts/ProvisionAccounts.res.mjs +351 -0
- package/scripts/ProvisionAdmin.res +275 -0
- package/scripts/ProvisionAdmin.res.mjs +289 -0
- package/scripts/ProvisionCognito.res +149 -0
- package/scripts/ProvisionCognito.res.mjs +98 -0
- package/scripts/ProvisionIdentity.res +76 -11
- package/scripts/ProvisionIdentity.res.mjs +72 -7
- package/scripts/ProvisionProvider.res +104 -0
- package/scripts/ProvisionProvider.res.mjs +123 -0
- package/scripts/PulumiCli.res +43 -0
- package/scripts/PulumiCli.res.mjs +106 -0
- package/src/Platform.res +51 -4
- package/src/Platform.res.mjs +32 -14
- package/src/Platform_Stack.res +119 -9
- package/src/Platform_Stack.res.mjs +42 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
- package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
- package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
- package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
- package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
- package/src/adapter/Task/TaskBucket_S3.res +1 -3
- package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
- package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
- package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
- package/src/components/Api/AppSync_Adapter.res +56 -72
- package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
- package/src/components/Api/AppSync_SdlDecorate.res +82 -1
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
- package/src/plugin/stack/Plugin_Stack.res +4 -0
- package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
- package/src/util/Util_DeadLetterQueue.res +1 -5
- package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
- package/src/util/Util_StoreLayout.res +15 -2
- package/src/util/Util_StoreLayout.res.mjs +11 -3
- package/tests/AppSync_AdapterTest.res +83 -0
- package/tests/AppSync_AdapterTest.res.mjs +69 -0
- package/tests/Auth_LoginIdentifierTest.res +51 -0
- package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
- package/tests/Auth_SignUpModeTest.res +56 -0
- package/tests/Auth_SignUpModeTest.res.mjs +53 -0
- package/tests/BakeManifestTest.res +204 -0
- package/tests/BakeManifestTest.res.mjs +259 -0
- package/tests/DcbColdStartPartitionTest.res +32 -0
- package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
- package/tests/DcbUnresolvedPartitionSlice.res +28 -0
- package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
- package/tests/DeployAppTest.res +90 -0
- package/tests/DeployAppTest.res.mjs +133 -0
- package/tests/LambdaLayerLookupTest.res +104 -0
- package/tests/LambdaLayerLookupTest.res.mjs +117 -0
- package/tests/ProvisionAccountsTest.res +168 -0
- package/tests/ProvisionAccountsTest.res.mjs +184 -0
- package/tests/ProvisionAdminTest.res +55 -0
- package/tests/ProvisionAdminTest.res.mjs +66 -0
- package/tests/ProvisionIdentityTest.res +89 -3
- package/tests/ProvisionIdentityTest.res.mjs +66 -1
- package/tests/Util_OwnerScopeEnvTest.res +25 -0
- package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
- package/tests/Util_StoreLayoutTest.res +6 -0
- package/tests/Util_StoreLayoutTest.res.mjs +7 -4
- package/tests/dcbColdStartPartition.mjs +66 -0
- package/tests/setup/layerArn.cjs +5 -0
|
@@ -183,14 +183,32 @@ let rec waitForMergeSuccess = async (
|
|
|
183
183
|
// When both are present on the same field, the spec-level permission wins
|
|
184
184
|
// (it is more specific). `AllowGroups([g1, g2, ...])` emits
|
|
185
185
|
// `@aws_cognito_user_pools(cognito_groups: ["g1", "g2", ...])`.
|
|
186
|
-
// `AllowAuthenticated`
|
|
187
|
-
//
|
|
188
|
-
// any reaching request is already authenticated), but stated rather than left
|
|
186
|
+
// `AllowAuthenticated` emits the group-less `@aws_cognito_user_pools` — the same
|
|
187
|
+
// reachability an undirectived field already has, but stated rather than left
|
|
189
188
|
// implicit, so `assertGateable` can tell "deliberately open" from "nobody
|
|
190
189
|
// stamped this". `AllowGroups([])` and `DenyAll` emit a sentinel `__deny_all__`
|
|
191
190
|
// group that no Cognito user can belong to — effectively blocking the field at
|
|
192
191
|
// the API layer.
|
|
193
192
|
//
|
|
193
|
+
// `AllowAnonymous` is REFUSED at deploy time, and this used to be the bug:
|
|
194
|
+
// it shared `AllowAuthenticated`'s arm and emitted the same group-less
|
|
195
|
+
// directive, which on a Cognito-primary API means *any authenticated caller* —
|
|
196
|
+
// the opposite of what the spec says. AppSync has no anonymous mode to emit
|
|
197
|
+
// instead (`authenticationType` is API_KEY | AWS_IAM | AMAZON_COGNITO_USER_POOLS
|
|
198
|
+
// | OPENID_CONNECT, and this adapter provisions Cognito primary with AWS_IAM
|
|
199
|
+
// additional), so there is no directive that means "no auth". The old behaviour
|
|
200
|
+
// deployed green, passed `assertGateable`, worked on the local platform — whose
|
|
201
|
+
// resolvers call `Authorization.isAllowed` and honour the rule — and refused
|
|
202
|
+
// anonymous callers only on AWS. It failed CLOSED, which is why nothing
|
|
203
|
+
// surfaced it.
|
|
204
|
+
//
|
|
205
|
+
// Refusing follows `Auth_LoginIdentifier`'s rule for an unrecognised spelling:
|
|
206
|
+
// a silent contradiction of the source is worse than a stopped deploy, because
|
|
207
|
+
// only one of the two is discoverable. AppSync mutations have no runtime
|
|
208
|
+
// authorization check to fall back on — the only `Authorization.isAllowed` call
|
|
209
|
+
// in this package is on the Postgres query path — so the directive is the whole
|
|
210
|
+
// enforcement, and a directive that cannot say this must not pretend to.
|
|
211
|
+
//
|
|
194
212
|
// NOT `@aws_auth(...)`: that is the single-mode form, which AppSync ignores on a
|
|
195
213
|
// multi-auth API — which every API this adapter provisions is. See
|
|
196
214
|
// `AppSync_SdlDecorate.formatCognitoGroupsDirective` for the full rationale and
|
|
@@ -209,62 +227,13 @@ let rec waitForMergeSuccess = async (
|
|
|
209
227
|
// and a least-privilege deploy-role policy — see
|
|
210
228
|
// `docs/guides/appsync-iam-system-caller.md`.
|
|
211
229
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
| AllowAuthenticated | AllowAnonymous => None
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Both directive forms are defined once in the runtime-pure AppSync_SdlDecorate
|
|
223
|
-
// so this deploy path and the bundled AdminEventCollector Lambda's reactive push
|
|
224
|
-
// cannot drift. The Cognito-only arm carries the rationale for why it is
|
|
225
|
-
// `@aws_cognito_user_pools(...)` and never `@aws_auth(...)`.
|
|
226
|
-
let _formatGroupsDirective = AppSync_SdlDecorate.formatCognitoGroupsDirective
|
|
227
|
-
|
|
228
|
-
// Multi-auth directive for a field that must accept BOTH Cognito and IAM.
|
|
229
|
-
// `groups=Some([...])` preserves Cognito group gating; `groups=None` keeps the
|
|
230
|
-
// field open to any authenticated Cognito user. `@aws_iam` admits the
|
|
231
|
-
// deploy-time SigV4 system caller. See the dual-auth note above.
|
|
232
|
-
let _formatDualAuthDirective = AppSync_SdlDecorate.formatDualAuthDirective
|
|
233
|
-
|
|
234
|
-
// ── Type-level dual-auth ─────────────────────────────────────────────────────
|
|
235
|
-
// On a multi-auth API, object TYPES without auth directives are accessible only
|
|
236
|
-
// via the default auth mode — a field-level `@aws_iam` admits the system caller
|
|
237
|
-
// to the top-level field, but response shaping then walks the return types
|
|
238
|
-
// (`…Connection` → `…Edge` → node type → nested state types) and dies with
|
|
239
|
-
// "Not Authorized to access <field> on type <T>" one level in. Types reachable
|
|
240
|
-
// from a systemCallable field therefore carry the bare multi-auth pair: the
|
|
241
|
-
// group-less Cognito arm matches the pre-existing accessibility of an
|
|
242
|
-
// undirectived type (any authenticated user; entry gating stays on the fields),
|
|
243
|
-
// the IAM arm admits the traversal. Only `type` declarations take auth
|
|
244
|
-
// directives (inputs/enums/interfaces do not).
|
|
245
|
-
|
|
246
|
-
// The declared name of a `type …` SDL declaration; None for input/enum/union/etc.
|
|
247
|
-
let _typeDeclName = (decl: string): option<string> =>
|
|
248
|
-
if decl->String.startsWith("type ") {
|
|
249
|
-
let rest = decl->String.slice(~start=5, ~end=decl->String.length)
|
|
250
|
-
let end = switch rest->String.search(/[\s{]/) {
|
|
251
|
-
| -1 => rest->String.length
|
|
252
|
-
| i => i
|
|
253
|
-
}
|
|
254
|
-
Some(rest->String.slice(~start=0, ~end))
|
|
255
|
-
} else {
|
|
256
|
-
None
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Insert the multi-auth pair into a type declaration header (before its `{`).
|
|
260
|
-
let _stampTypeDualAuth = (decl: string): string =>
|
|
261
|
-
switch decl->String.indexOfOpt("{") {
|
|
262
|
-
| Some(i) =>
|
|
263
|
-
decl->String.slice(~start=0, ~end=i) ++
|
|
264
|
-
"@aws_cognito_user_pools @aws_iam " ++
|
|
265
|
-
decl->String.slice(~start=i, ~end=decl->String.length)
|
|
266
|
-
| None => decl
|
|
267
|
-
}
|
|
230
|
+
// Everything this file needs to decide what a directive says — the two directive
|
|
231
|
+
// formats, the permission → gate mapping, the `AllowAnonymous` refusal, the type
|
|
232
|
+
// header stampers and `typeDeclNameOf` — lives in the runtime-pure
|
|
233
|
+
// AppSync_SdlDecorate, so this deploy path and the bundled AdminEventCollector
|
|
234
|
+
// Lambda's reactive push cannot drift. The Cognito-only arm carries the rationale
|
|
235
|
+
// for why it is `@aws_cognito_user_pools(...)` and never `@aws_auth(...)`.
|
|
236
|
+
// This file is left with resource creation, which is what it is for.
|
|
268
237
|
|
|
269
238
|
// Shared traversal types every callable surface reaches — `PageInfo` (relay
|
|
270
239
|
// connections, injected by the stitcher) and the `CommandResult` members
|
|
@@ -299,6 +268,10 @@ let injectAwsAuth = (
|
|
|
299
268
|
let iamQueryFieldPrefixes: array<string> = []
|
|
300
269
|
let iamTypePrefixes: array<string> = []
|
|
301
270
|
|
|
271
|
+
// Collected across both entry kinds and refused once, so a deploy reports
|
|
272
|
+
// every offending field rather than the first one found.
|
|
273
|
+
let anonymousFields: array<string> = []
|
|
274
|
+
|
|
302
275
|
let mutationAuthMap: Dict.t<array<string>> = Dict.make()
|
|
303
276
|
mutationEntries->Array.forEach(entry => {
|
|
304
277
|
if entry.systemCallable->Option.getOr(false) {
|
|
@@ -314,9 +287,10 @@ let injectAwsAuth = (
|
|
|
314
287
|
fp
|
|
315
288
|
->Dict.toArray
|
|
316
289
|
->Array.forEach(((fieldName, permission)) => {
|
|
317
|
-
switch
|
|
318
|
-
|
|
|
319
|
-
|
|
|
290
|
+
switch AppSync_SdlDecorate.permissionToGate(permission) {
|
|
291
|
+
| Groups(groups) => mutationAuthMap->Dict.set(fieldName, groups)
|
|
292
|
+
| AnyAuthenticated => mutationAuthMap->Dict.delete(fieldName)
|
|
293
|
+
| Anonymous => anonymousFields->Array.push(fieldName)
|
|
320
294
|
}
|
|
321
295
|
})
|
|
322
296
|
| None => ()
|
|
@@ -342,18 +316,28 @@ let injectAwsAuth = (
|
|
|
342
316
|
}
|
|
343
317
|
switch entry.permission {
|
|
344
318
|
| Some(permission) =>
|
|
345
|
-
switch
|
|
346
|
-
|
|
|
319
|
+
switch AppSync_SdlDecorate.permissionToGate(permission) {
|
|
320
|
+
| Groups(groups) =>
|
|
347
321
|
queryAuthMap->Dict.set(entry.singleFieldName, groups)
|
|
348
322
|
queryAuthMap->Dict.set(entry.listFieldName, groups)
|
|
349
|
-
|
|
|
323
|
+
| AnyAuthenticated =>
|
|
350
324
|
queryAuthMap->Dict.delete(entry.singleFieldName)
|
|
351
325
|
queryAuthMap->Dict.delete(entry.listFieldName)
|
|
326
|
+
| Anonymous =>
|
|
327
|
+
// Both derived field names, because the spec declares one permission and
|
|
328
|
+
// the generator emits two fields from it; naming only one would send the
|
|
329
|
+
// author looking for a second declaration that does not exist.
|
|
330
|
+
anonymousFields->Array.push(entry.singleFieldName)
|
|
331
|
+
anonymousFields->Array.push(entry.listFieldName)
|
|
352
332
|
}
|
|
353
333
|
| None => ()
|
|
354
334
|
}
|
|
355
335
|
})
|
|
356
336
|
|
|
337
|
+
if anonymousFields->Array.length > 0 {
|
|
338
|
+
AppSync_SdlDecorate.refuseAnonymousFields(anonymousFields)
|
|
339
|
+
}
|
|
340
|
+
|
|
357
341
|
// A field with no group restriction gets the group-less Cognito directive
|
|
358
342
|
// rather than no directive at all. Same reachability under `defaultAction:
|
|
359
343
|
// ALLOW`; the difference only shows once the default is DENY, where an
|
|
@@ -364,10 +348,10 @@ let injectAwsAuth = (
|
|
|
364
348
|
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
365
349
|
let groups = mutationAuthMap->Dict.get(fieldName)
|
|
366
350
|
if iamFields->Dict.get(fieldName)->Option.getOr(false) {
|
|
367
|
-
`${field}\n ${
|
|
351
|
+
`${field}\n ${AppSync_SdlDecorate.formatDualAuthDirective(groups)}`
|
|
368
352
|
} else {
|
|
369
353
|
switch groups {
|
|
370
|
-
| Some(groups) => `${field}\n ${
|
|
354
|
+
| Some(groups) => `${field}\n ${AppSync_SdlDecorate.formatCognitoGroupsDirective(groups)}`
|
|
371
355
|
| None => `${field}\n ${openDirective}`
|
|
372
356
|
}
|
|
373
357
|
}
|
|
@@ -380,10 +364,10 @@ let injectAwsAuth = (
|
|
|
380
364
|
iamFields->Dict.get(fieldName)->Option.getOr(false) ||
|
|
381
365
|
iamQueryFieldPrefixes->Array.some(p => fieldName->String.startsWith(p))
|
|
382
366
|
if isIam {
|
|
383
|
-
`${field} ${
|
|
367
|
+
`${field} ${AppSync_SdlDecorate.formatDualAuthDirective(groups)}`
|
|
384
368
|
} else {
|
|
385
369
|
switch groups {
|
|
386
|
-
| Some(groups) => `${field} ${
|
|
370
|
+
| Some(groups) => `${field} ${AppSync_SdlDecorate.formatCognitoGroupsDirective(groups)}`
|
|
387
371
|
| None => `${field} ${openDirective}`
|
|
388
372
|
}
|
|
389
373
|
}
|
|
@@ -396,7 +380,7 @@ let injectAwsAuth = (
|
|
|
396
380
|
let augmentedSubscriptions = parts.subscriptions->Array.map(field => {
|
|
397
381
|
let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
|
|
398
382
|
switch mutationAuthMap->Dict.get(fieldName) {
|
|
399
|
-
| Some(groups) => `${field}\n ${
|
|
383
|
+
| Some(groups) => `${field}\n ${AppSync_SdlDecorate.formatCognitoGroupsDirective(groups)}`
|
|
400
384
|
| None => `${field}\n ${openDirective}`
|
|
401
385
|
}
|
|
402
386
|
})
|
|
@@ -405,9 +389,9 @@ let injectAwsAuth = (
|
|
|
405
389
|
// the node type, its Connection/Edge wrappers, and nested state types all
|
|
406
390
|
// share the entry's returnTypeName prefix.
|
|
407
391
|
let augmentedTypes = parts.types->Array.map(decl =>
|
|
408
|
-
switch
|
|
392
|
+
switch AppSync_SdlDecorate.typeDeclNameOf(decl) {
|
|
409
393
|
| Some(name) if iamTypePrefixes->Array.some(p => name->String.startsWith(p)) =>
|
|
410
|
-
|
|
394
|
+
AppSync_SdlDecorate.stampTypeDualAuth(decl)
|
|
411
395
|
| _ => decl
|
|
412
396
|
}
|
|
413
397
|
)
|
|
@@ -6,7 +6,6 @@ import * as Aws from "@pulumi/aws";
|
|
|
6
6
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
7
7
|
import * as Nodecrypto from "node:crypto";
|
|
8
8
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
|
-
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
10
9
|
import * as Effect$1 from "effect/Effect";
|
|
11
10
|
import * as Pulumi from "@pulumi/pulumi";
|
|
12
11
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
@@ -107,49 +106,12 @@ async function waitForMergeSuccess(client, associationId, mergedApiIdentifier, m
|
|
|
107
106
|
return Stdlib_JsError.throwWithMessage(`Source API association ` + associationId + ` on ` + mergedApiIdentifier + ` failed to merge (` + status + `): ` + detail);
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
function _permissionToCognitoGroups(permission) {
|
|
111
|
-
if (typeof permission !== "object") {
|
|
112
|
-
switch (permission) {
|
|
113
|
-
case "AllowAuthenticated" :
|
|
114
|
-
case "AllowAnonymous" :
|
|
115
|
-
return;
|
|
116
|
-
case "DenyAll" :
|
|
117
|
-
return ["__deny_all__"];
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
let groups = permission._0;
|
|
121
|
-
if (groups.length !== 0) {
|
|
122
|
-
return groups;
|
|
123
|
-
} else {
|
|
124
|
-
return ["__deny_all__"];
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function _typeDeclName(decl) {
|
|
130
|
-
if (!decl.startsWith("type ")) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
let rest = decl.slice(5, decl.length);
|
|
134
|
-
let i = rest.search(/[\s{]/);
|
|
135
|
-
let end = i !== -1 ? i : rest.length;
|
|
136
|
-
return rest.slice(0, end);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function _stampTypeDualAuth(decl) {
|
|
140
|
-
let i = Stdlib_String.indexOfOpt(decl, "{");
|
|
141
|
-
if (i !== undefined) {
|
|
142
|
-
return decl.slice(0, i) + "@aws_cognito_user_pools @aws_iam " + decl.slice(i, decl.length);
|
|
143
|
-
} else {
|
|
144
|
-
return decl;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
109
|
function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
149
110
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
150
111
|
let iamFields = {};
|
|
151
112
|
let iamQueryFieldPrefixes = [];
|
|
152
113
|
let iamTypePrefixes = [];
|
|
114
|
+
let anonymousFields = [];
|
|
153
115
|
let mutationAuthMap = {};
|
|
154
116
|
mutationEntries.forEach(entry => {
|
|
155
117
|
if (Stdlib_Option.getOr(entry.systemCallable, false)) {
|
|
@@ -168,12 +130,16 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
168
130
|
if (fp !== undefined) {
|
|
169
131
|
Object.entries(fp).forEach(param => {
|
|
170
132
|
let fieldName = param[0];
|
|
171
|
-
let groups =
|
|
172
|
-
if (groups !==
|
|
173
|
-
|
|
133
|
+
let groups = AppSync_SdlDecorate$ReventlessAws.permissionToGate(param[1]);
|
|
134
|
+
if (typeof groups !== "object") {
|
|
135
|
+
if (groups === "AnyAuthenticated") {
|
|
136
|
+
return Stdlib_Dict.$$delete(mutationAuthMap, fieldName);
|
|
137
|
+
}
|
|
138
|
+
anonymousFields.push(fieldName);
|
|
174
139
|
return;
|
|
175
140
|
} else {
|
|
176
|
-
|
|
141
|
+
mutationAuthMap[fieldName] = groups._0;
|
|
142
|
+
return;
|
|
177
143
|
}
|
|
178
144
|
});
|
|
179
145
|
return;
|
|
@@ -198,16 +164,25 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
198
164
|
if (permission === undefined) {
|
|
199
165
|
return;
|
|
200
166
|
}
|
|
201
|
-
let groups =
|
|
202
|
-
if (groups !==
|
|
203
|
-
|
|
204
|
-
|
|
167
|
+
let groups = AppSync_SdlDecorate$ReventlessAws.permissionToGate(permission);
|
|
168
|
+
if (typeof groups !== "object") {
|
|
169
|
+
if (groups === "AnyAuthenticated") {
|
|
170
|
+
Stdlib_Dict.$$delete(queryAuthMap, entry.singleFieldName);
|
|
171
|
+
return Stdlib_Dict.$$delete(queryAuthMap, entry.listFieldName);
|
|
172
|
+
}
|
|
173
|
+
anonymousFields.push(entry.singleFieldName);
|
|
174
|
+
anonymousFields.push(entry.listFieldName);
|
|
205
175
|
return;
|
|
206
176
|
} else {
|
|
207
|
-
|
|
208
|
-
|
|
177
|
+
let groups$1 = groups._0;
|
|
178
|
+
queryAuthMap[entry.singleFieldName] = groups$1;
|
|
179
|
+
queryAuthMap[entry.listFieldName] = groups$1;
|
|
180
|
+
return;
|
|
209
181
|
}
|
|
210
182
|
});
|
|
183
|
+
if (anonymousFields.length !== 0) {
|
|
184
|
+
AppSync_SdlDecorate$ReventlessAws.refuseAnonymousFields(anonymousFields);
|
|
185
|
+
}
|
|
211
186
|
let augmentedMutations = parts.mutations.map(field => {
|
|
212
187
|
let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
|
|
213
188
|
let groups = mutationAuthMap[fieldName];
|
|
@@ -241,9 +216,9 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
|
|
|
241
216
|
}
|
|
242
217
|
});
|
|
243
218
|
let augmentedTypes = parts.types.map(decl => {
|
|
244
|
-
let name =
|
|
219
|
+
let name = AppSync_SdlDecorate$ReventlessAws.typeDeclNameOf(decl);
|
|
245
220
|
if (name !== undefined && iamTypePrefixes.some(p => name.startsWith(p))) {
|
|
246
|
-
return
|
|
221
|
+
return AppSync_SdlDecorate$ReventlessAws.stampTypeDualAuth(decl);
|
|
247
222
|
} else {
|
|
248
223
|
return decl;
|
|
249
224
|
}
|
|
@@ -340,10 +315,6 @@ function generateFragment(mutationEntries, queryEntries) {
|
|
|
340
315
|
return injectAwsAuth(fragment, mutationEntries, queryEntries);
|
|
341
316
|
}
|
|
342
317
|
|
|
343
|
-
let _formatGroupsDirective = AppSync_SdlDecorate$ReventlessAws.formatCognitoGroupsDirective;
|
|
344
|
-
|
|
345
|
-
let _formatDualAuthDirective = AppSync_SdlDecorate$ReventlessAws.formatDualAuthDirective;
|
|
346
|
-
|
|
347
318
|
let stampSharedIamTypes = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes;
|
|
348
319
|
|
|
349
320
|
let primaryAuthenticationType = "AMAZON_COGNITO_USER_POOLS";
|
|
@@ -357,11 +328,6 @@ export {
|
|
|
357
328
|
_client,
|
|
358
329
|
getClient,
|
|
359
330
|
waitForMergeSuccess,
|
|
360
|
-
_permissionToCognitoGroups,
|
|
361
|
-
_formatGroupsDirective,
|
|
362
|
-
_formatDualAuthDirective,
|
|
363
|
-
_typeDeclName,
|
|
364
|
-
_stampTypeDualAuth,
|
|
365
331
|
stampSharedIamTypes,
|
|
366
332
|
injectAwsAuth,
|
|
367
333
|
injectAwsAuthAll,
|
|
@@ -90,7 +90,11 @@ let formatCognitoGroupsDirective = (groups: array<string>): string => {
|
|
|
90
90
|
|
|
91
91
|
// The group-less Cognito directive: reachable by any authenticated Cognito
|
|
92
92
|
// caller. Emitted where a field or type is deliberately open — `AllowAuthenticated`,
|
|
93
|
-
//
|
|
93
|
+
// a field carrying no permission at all, and every object type.
|
|
94
|
+
//
|
|
95
|
+
// NOT for `AllowAnonymous`: "any authenticated caller" is the inverse of what
|
|
96
|
+
// that declares, and emitting this for it is the defect `permissionToGate` and
|
|
97
|
+
// `refuseAnonymousFields` above exist to prevent.
|
|
94
98
|
//
|
|
95
99
|
// Under `userPoolConfig.defaultAction: ALLOW` this is a no-op: an undirectived
|
|
96
100
|
// field is already reachable by any authenticated caller, so stamping it changes
|
|
@@ -112,6 +116,58 @@ let formatDualAuthDirective = (groups: option<array<string>>): string => {
|
|
|
112
116
|
`${cognito} @aws_iam`
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
// ── What a declared permission asks this API to enforce ─────────────────────
|
|
120
|
+
//
|
|
121
|
+
// Lives here rather than beside the Pulumi resources because it is the same
|
|
122
|
+
// concern as the directive vocabulary above: reading a spec-level rule and
|
|
123
|
+
// deciding what the schema says. Keeping the mapping next to the strings it
|
|
124
|
+
// maps onto is what stops a fourth spelling of a directive appearing.
|
|
125
|
+
|
|
126
|
+
/** Three arms rather than an `option<array<string>>`, which is the shape this
|
|
127
|
+
had while it was wrong: the option form could not hold `AllowAuthenticated`
|
|
128
|
+
and `AllowAnonymous` apart, so both became `None` and emitted one directive.
|
|
129
|
+
AppSync can enforce the first and cannot express the second at all. */
|
|
130
|
+
type fieldGate =
|
|
131
|
+
| Groups(array<string>)
|
|
132
|
+
| AnyAuthenticated
|
|
133
|
+
/** No directive can express this; the deploy is refused rather than gated
|
|
134
|
+
into its opposite. See `refuseAnonymousFields`. */
|
|
135
|
+
| Anonymous
|
|
136
|
+
|
|
137
|
+
let permissionToGate = (permission: Reventless.Authorization.permission): fieldGate =>
|
|
138
|
+
switch permission {
|
|
139
|
+
| AllowGroups([]) => Groups(["__deny_all__"])
|
|
140
|
+
| AllowGroups(groups) => Groups(groups)
|
|
141
|
+
| DenyAll => Groups(["__deny_all__"])
|
|
142
|
+
| AllowAuthenticated => AnyAuthenticated
|
|
143
|
+
| AllowAnonymous => Anonymous
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The deploy-time refusal for fields declared `AllowAnonymous`, and the
|
|
147
|
+
sibling of `assertGateable` below: both refuse a schema that would claim a
|
|
148
|
+
gate it does not have. This one fires earlier, while the permission is still
|
|
149
|
+
a value — by SDL time the two permissions are indistinguishable. */
|
|
150
|
+
let refuseAnonymousFields = (fieldNames: array<string>): 'a =>
|
|
151
|
+
JsError.throwWithMessage(
|
|
152
|
+
`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` ++
|
|
153
|
+
` ${fieldNames->Array.length->Int.toString} field(s) declare AllowAnonymous:\n` ++
|
|
154
|
+
` ${fieldNames->Array.join(", ")}\n\n` ++
|
|
155
|
+
`AppSync has no anonymous authorization mode. This API is provisioned with\n` ++
|
|
156
|
+
`AMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\n` ++
|
|
157
|
+
`directive available is @aws_cognito_user_pools — which means ANY\n` ++
|
|
158
|
+
`AUTHENTICATED caller, the opposite of what the spec declares. Mutations have\n` ++
|
|
159
|
+
`no runtime authorization check to correct it: on this platform the directive\n` ++
|
|
160
|
+
`is the whole enforcement.\n\n` ++
|
|
161
|
+
`The local platform DOES honour AllowAnonymous (its resolvers call\n` ++
|
|
162
|
+
`Authorization.isAllowed), so a spec that passes locally can still not be\n` ++
|
|
163
|
+
`deployable here. That divergence is the reason this refuses instead of\n` ++
|
|
164
|
+
`emitting a directive that reads as gated and is not.\n\n` ++
|
|
165
|
+
`Anonymous fields on AWS are deferred. When they land it will be as a separate\n` ++
|
|
166
|
+
`API carrying only the unauthenticated fields. Do not add API_KEY to this one:\n` ++
|
|
167
|
+
`every field here would then have to say whether the key reaches it, and one\n` ++
|
|
168
|
+
`that forgot would be open. Until then, declare the field AllowAuthenticated\n` ++ `(or AllowGroups) and mean it.`,
|
|
169
|
+
)
|
|
170
|
+
|
|
115
171
|
// Injects the Cognito group gate on ALL mutation, query, and subscription fields
|
|
116
172
|
// in a fragment. `~iamFieldNames` opts the named mutation/query fields into
|
|
117
173
|
// deploy-time IAM as well, appending the `@aws_iam` arm. Subscriptions are never
|
|
@@ -226,6 +282,31 @@ let stampAllTypesCognito = (sdl: string): string =>
|
|
|
226
282
|
)
|
|
227
283
|
->Array.join("\n")
|
|
228
284
|
|
|
285
|
+
// Insert the multi-auth pair into ONE type declaration's header, before its `{`.
|
|
286
|
+
//
|
|
287
|
+
// The sibling of `stampAllTypesCognito` and deliberately not folded into it:
|
|
288
|
+
// that one sweeps a whole assembled SDL and fills in the Cognito-only arm for
|
|
289
|
+
// anything undirectived, this one is applied to a declaration already chosen by
|
|
290
|
+
// its caller and adds the IAM arm the traversal needs. Different input, different
|
|
291
|
+
// directive, same header surgery.
|
|
292
|
+
//
|
|
293
|
+
// On a multi-auth API, object TYPES without auth directives are reachable only
|
|
294
|
+
// via the default auth mode: a field-level `@aws_iam` admits the system caller to
|
|
295
|
+
// the top-level field, but response shaping then walks the return types
|
|
296
|
+
// (`…Connection` → `…Edge` → node → nested state types) and dies with "Not
|
|
297
|
+
// Authorized to access <field> on type <T>" one level in. The group-less Cognito
|
|
298
|
+
// arm matches the accessibility an undirectived type already has (entry gating
|
|
299
|
+
// stays on the fields); the IAM arm admits the traversal.
|
|
300
|
+
let stampTypeDualAuth = (decl: string): string =>
|
|
301
|
+
switch decl->String.indexOfOpt("{") {
|
|
302
|
+
| Some(i) =>
|
|
303
|
+
decl->String.slice(~start=0, ~end=i) ++
|
|
304
|
+
formatDualAuthDirective(None) ++
|
|
305
|
+
" " ++
|
|
306
|
+
decl->String.slice(~start=i, ~end=decl->String.length)
|
|
307
|
+
| None => decl
|
|
308
|
+
}
|
|
309
|
+
|
|
229
310
|
// ── Deploy-time gate invariant ──────────────────────────────────────────────
|
|
230
311
|
//
|
|
231
312
|
// Refuse to push a schema that cannot be gated. This is the fail-closed
|
|
@@ -51,6 +51,39 @@ function formatDualAuthDirective(groups) {
|
|
|
51
51
|
return cognito + ` @aws_iam`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function permissionToGate(permission) {
|
|
55
|
+
if (typeof permission !== "object") {
|
|
56
|
+
switch (permission) {
|
|
57
|
+
case "AllowAuthenticated" :
|
|
58
|
+
return "AnyAuthenticated";
|
|
59
|
+
case "AllowAnonymous" :
|
|
60
|
+
return "Anonymous";
|
|
61
|
+
case "DenyAll" :
|
|
62
|
+
return {
|
|
63
|
+
TAG: "Groups",
|
|
64
|
+
_0: ["__deny_all__"]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
let groups = permission._0;
|
|
69
|
+
if (groups.length !== 0) {
|
|
70
|
+
return {
|
|
71
|
+
TAG: "Groups",
|
|
72
|
+
_0: groups
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
|
+
return {
|
|
76
|
+
TAG: "Groups",
|
|
77
|
+
_0: ["__deny_all__"]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function refuseAnonymousFields(fieldNames) {
|
|
84
|
+
return Stdlib_JsError.throwWithMessage(`Refusing to push an AppSync schema that cannot honour AllowAnonymous.\n\n` + (` ` + fieldNames.length.toString() + ` field(s) declare AllowAnonymous:\n`) + (` ` + fieldNames.join(", ") + `\n\n`) + `AppSync has no anonymous authorization mode. This API is provisioned with\nAMAZON_COGNITO_USER_POOLS primary and AWS_IAM additional, so the only\ndirective available is @aws_cognito_user_pools — which means ANY\nAUTHENTICATED caller, the opposite of what the spec declares. Mutations have\nno runtime authorization check to correct it: on this platform the directive\nis the whole enforcement.\n\nThe local platform DOES honour AllowAnonymous (its resolvers call\nAuthorization.isAllowed), so a spec that passes locally can still not be\ndeployable here. That divergence is the reason this refuses instead of\nemitting a directive that reads as gated and is not.\n\nAnonymous fields on AWS are deferred. When they land it will be as a separate\nAPI carrying only the unauthenticated fields. Do not add API_KEY to this one:\nevery field here would then have to say whether the key reaches it, and one\nthat forgot would be open. Until then, declare the field AllowAuthenticated\n(or AllowGroups) and mean it.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
54
87
|
function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
55
88
|
let iamFieldNames = iamFieldNamesOpt !== undefined ? iamFieldNamesOpt : [];
|
|
56
89
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
|
|
@@ -122,6 +155,15 @@ function stampAllTypesCognito(sdl) {
|
|
|
122
155
|
}).join("\n");
|
|
123
156
|
}
|
|
124
157
|
|
|
158
|
+
function stampTypeDualAuth(decl) {
|
|
159
|
+
let i = Stdlib_String.indexOfOpt(decl, "{");
|
|
160
|
+
if (i !== undefined) {
|
|
161
|
+
return decl.slice(0, i) + formatDualAuthDirective(undefined) + " " + decl.slice(i, decl.length);
|
|
162
|
+
} else {
|
|
163
|
+
return decl;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
125
167
|
let enforcedDirectives = [
|
|
126
168
|
"@aws_cognito_user_pools",
|
|
127
169
|
"@aws_iam"
|
|
@@ -328,11 +370,14 @@ export {
|
|
|
328
370
|
formatCognitoGroupsDirective,
|
|
329
371
|
cognitoOpenDirective,
|
|
330
372
|
formatDualAuthDirective,
|
|
373
|
+
permissionToGate,
|
|
374
|
+
refuseAnonymousFields,
|
|
331
375
|
injectAwsAuthAll,
|
|
332
376
|
sharedIamTypeNames,
|
|
333
377
|
stampSharedIamTypes,
|
|
334
378
|
stampUndirectivedFields,
|
|
335
379
|
stampAllTypesCognito,
|
|
380
|
+
stampTypeDualAuth,
|
|
336
381
|
enforcedDirectives,
|
|
337
382
|
hasEnforcedDirective,
|
|
338
383
|
rootOperationTypes,
|
|
@@ -141,11 +141,7 @@ export const handler = async (event) => {
|
|
|
141
141
|
let code = Pulumi.Archive.assetArchive(clonerArchiveContents)
|
|
142
142
|
let sourceCodeHash = Util_Bundle.hashString(entryPointCode ++ "\n---\n" ++ loaderHash)
|
|
143
143
|
|
|
144
|
-
let layers =
|
|
145
|
-
Lambda.reventlessLayerArn
|
|
146
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
147
|
-
->Option.getOr([])
|
|
148
|
-
->Pulumi.Input.make
|
|
144
|
+
let layers = Lambda.reventlessLayers()
|
|
149
145
|
|
|
150
146
|
let vpcStackName = Pulumi.Config.make(Some("vpc"))->Pulumi.Config.get("stack")->Option.getOrThrow
|
|
151
147
|
let vpcConfig = Util_Vpc.getVpcConfig(~stackName=vpcStackName, ~outputName="vpc")
|
|
@@ -87,7 +87,7 @@ export const handler = async (event) => {
|
|
|
87
87
|
let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(clonerArchiveContents);
|
|
88
88
|
let code = new (Pulumi.asset.AssetArchive)(clonerArchiveContents);
|
|
89
89
|
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode + "\n---\n" + loaderHash);
|
|
90
|
-
let layers =
|
|
90
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
91
91
|
let vpcStackName = Stdlib_Option.getOrThrow(new Pulumi.Config("vpc").get("stack"), undefined);
|
|
92
92
|
let vpcConfig = Util_Vpc$ReventlessAws.getVpcConfig(vpcStackName, "vpc");
|
|
93
93
|
let secrets = Pulumi.all(secretUrns.map(urn => GetSecretVersion$PulumiAws.getSecretNames(urn).apply(names => names.map(name => ({
|
|
@@ -157,9 +157,13 @@ let makeUiBundleDistribution = (
|
|
|
157
157
|
// shell, where blue/green between versions is wanted.
|
|
158
158
|
let name = stableName ? pluginId : pluginId ++ "-" ++ bundleVersion
|
|
159
159
|
|
|
160
|
+
// Emptied on removal from a disposable stack: the manifest bake writes files
|
|
161
|
+
// here outside Pulumi, which would otherwise fail the destroy.
|
|
160
162
|
let bucket = PulumiAws.S3.Bucket.make(
|
|
161
163
|
~name=name ++ "-bundle",
|
|
162
164
|
~args={
|
|
165
|
+
forceDestroy: (Util_StoreLayout.protectionOfDeployedStack() == Unprotected)
|
|
166
|
+
->Pulumi.Input.make,
|
|
163
167
|
tags: AWS.Tags.make(
|
|
164
168
|
~name=name ++ "-bundle",
|
|
165
169
|
~kind=ReventlessCore.ComponentType.Plugin,
|
|
@@ -11,6 +11,7 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
|
|
|
11
11
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
12
12
|
import * as AWS_Tags$ReventlessAws from "../../adapter/AWS_Tags.res.mjs";
|
|
13
13
|
import * as ClientCloudfront from "@aws-sdk/client-cloudfront";
|
|
14
|
+
import * as Util_StoreLayout$ReventlessAws from "../../util/Util_StoreLayout.res.mjs";
|
|
14
15
|
import * as Util_StaticBundle$ReventlessAws from "../../util/Util_StaticBundle.res.mjs";
|
|
15
16
|
|
|
16
17
|
let log = Logger$ReventlessCore.fromEnv();
|
|
@@ -76,6 +77,7 @@ function makeUiBundleDistribution(pluginId, bundleVersion, assetsDir, spaFallbac
|
|
|
76
77
|
let servedBuckets = servedBucketsOpt !== undefined ? servedBucketsOpt : [];
|
|
77
78
|
let name = stableName ? pluginId : pluginId + "-" + bundleVersion;
|
|
78
79
|
let bucket = new (Aws.s3.Bucket)(name + "-bundle", {
|
|
80
|
+
forceDestroy: Util_StoreLayout$ReventlessAws.protectionOfDeployedStack() === "Unprotected",
|
|
79
81
|
tags: AWS_Tags$ReventlessAws.make(name + "-bundle", "Plugin", "Hosting", "Plugin", undefined, undefined, pluginId, undefined)
|
|
80
82
|
});
|
|
81
83
|
new (Aws.s3.BucketPublicAccessBlock)(name + "-bundle-pab", {
|
|
@@ -123,11 +123,7 @@ archiveContents->Dict.set(
|
|
|
123
123
|
let code = Pulumi.Archive.assetArchive(archiveContents)
|
|
124
124
|
let sourceCodeHash = Util_Bundle.hashString(entryPointCode)
|
|
125
125
|
|
|
126
|
-
let layers =
|
|
127
|
-
Lambda.reventlessLayerArn
|
|
128
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
129
|
-
->Option.getOr([])
|
|
130
|
-
->Pulumi.Input.make
|
|
126
|
+
let layers = Lambda.reventlessLayers()
|
|
131
127
|
|
|
132
128
|
// This is the one Lambda in the framework built by hand rather than through
|
|
133
129
|
// `RuntimeEnvironment_Lambda`, and it was the one Lambda whose logs Lambda
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
5
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
6
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
7
|
import * as AWS$ReventlessAws from "../adapter/AWS.res.mjs";
|
|
@@ -76,7 +75,7 @@ let code = new (Pulumi.asset.AssetArchive)(archiveContents);
|
|
|
76
75
|
|
|
77
76
|
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode);
|
|
78
77
|
|
|
79
|
-
let layers =
|
|
78
|
+
let layers = Lambda$PulumiAws.reventlessLayers();
|
|
80
79
|
|
|
81
80
|
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name, undefined, AWS_Tags$ReventlessAws.make(name + `LogGroup`, "Plugin", "Logs", "Plugin", undefined, undefined, undefined, undefined), opts, undefined);
|
|
82
81
|
|
|
@@ -86,13 +86,26 @@ Destroy semantics follow disposability, **not** layout.
|
|
|
86
86
|
`reventless:wipeable`, but that authorises the reset tool to empty stores
|
|
87
87
|
*deliberately*, after a scope prompt and a typed confirmation. It does not say a
|
|
88
88
|
field rename may destroy a bucket by accident. Only stacks that are routinely
|
|
89
|
-
torn down are unprotected
|
|
89
|
+
torn down are unprotected: a `pr-*` stack, or one that declares
|
|
90
|
+
`reventless:disposable: "true"` — a try-out meant to be removed whole.
|
|
90
91
|
*/
|
|
91
92
|
let protectionFor = (
|
|
92
93
|
~stack: string,
|
|
94
|
+
~disposable: bool=false,
|
|
93
95
|
~ephemeralPrefixes: array<string>=defaultEphemeralPrefixes,
|
|
94
96
|
): protection =>
|
|
95
|
-
ephemeralPrefixes->Array.some(p => stack->String.startsWith(p))
|
|
97
|
+
disposable || ephemeralPrefixes->Array.some(p => stack->String.startsWith(p))
|
|
98
|
+
? Unprotected
|
|
99
|
+
: Protected
|
|
100
|
+
|
|
101
|
+
/** `protectionFor` the stack being deployed, reading its `reventless:disposable`
|
|
102
|
+
setting. The one function here that touches Pulumi. */
|
|
103
|
+
let protectionOfDeployedStack = (): protection =>
|
|
104
|
+
protectionFor(
|
|
105
|
+
~stack=Pulumi.Pulumi.getStackName(),
|
|
106
|
+
~disposable=Pulumi.Config.make(Some("reventless"))->Pulumi.Config.get("disposable") ==
|
|
107
|
+
Some("true"),
|
|
108
|
+
)
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
111
|
The bucket a store's objects live in.
|