@reventlessdev/reventless-aws 3.0.0-alpha.394 → 3.0.0-alpha.396

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +8 -8
  3. package/scripts/DeployApp.res +7 -1
  4. package/scripts/DeployApp.res.mjs +1 -1
  5. package/scripts/ProvisionAccounts.res +89 -4
  6. package/scripts/ProvisionAccounts.res.mjs +101 -5
  7. package/scripts/ProvisionIdentity.res +42 -18
  8. package/scripts/ProvisionIdentity.res.mjs +40 -26
  9. package/src/Platform.res +47 -10
  10. package/src/Platform.res.mjs +44 -10
  11. package/src/Platform_Stack.res +17 -0
  12. package/src/Platform_Stack.res.mjs +7 -1
  13. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +16 -7
  14. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +16 -3
  15. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +59 -9
  16. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +53 -6
  17. package/src/adapter/Auth/Auth_Cognito.res +13 -2
  18. package/src/adapter/Auth/Auth_Cognito.res.mjs +9 -2
  19. package/src/adapter/Auth/Auth_IdentityStore.res +52 -0
  20. package/src/adapter/Auth/Auth_IdentityStore.res.mjs +46 -0
  21. package/src/adapter/Auth/Auth_IdentityStore_Schema.res +39 -0
  22. package/src/adapter/Auth/Auth_IdentityStore_Schema.res.mjs +35 -0
  23. package/src/adapter/Identity/IdentityProvider_Cognito_Backend.res +251 -0
  24. package/src/adapter/Identity/IdentityProvider_Cognito_Backend.res.mjs +300 -0
  25. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +7 -4
  26. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +2 -2
  27. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +80 -0
  28. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +53 -2
  29. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  30. package/src/components/OutboundTranslationSlice_Builder.res +2 -0
  31. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  32. package/src/plugin/runtime/PluginRuntime_Builder.res +23 -0
  33. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +29 -0
  34. package/tests/Auth_ActiveRoleTrigger_OpsTest.res +40 -0
  35. package/tests/Auth_ActiveRoleTrigger_OpsTest.res.mjs +59 -8
  36. package/tests/Auth_CognitoTest.res +62 -0
  37. package/tests/Auth_CognitoTest.res.mjs +98 -0
  38. package/tests/Auth_IdentityStore_SchemaTest.res +35 -0
  39. package/tests/Auth_IdentityStore_SchemaTest.res.mjs +43 -0
  40. package/tests/IdentityProvider_Cognito_BackendTest.res +314 -0
  41. package/tests/IdentityProvider_Cognito_BackendTest.res.mjs +442 -0
  42. package/tests/ProvisionAccountsTest.res.mjs +1 -1
  43. package/tests/Util_ShellConfigTest.res.mjs +1 -22
@@ -0,0 +1,300 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
6
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
7
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
8
+ import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
9
+ import * as Auth_UserId$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_UserId.res.mjs";
10
+ import * as IdentityProvider$Reventless from "@reventlessdev/reventless-spec/src/semantic/IdentityProvider.res.mjs";
11
+ import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
12
+ import * as CognitoIdentityServiceProvider$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/CognitoIdentityServiceProvider.res.mjs";
13
+ import * as Auth_IdentityStore_Schema$ReventlessAws from "../Auth/Auth_IdentityStore_Schema.res.mjs";
14
+ import * as ClientCognitoIdentityProvider from "@aws-sdk/client-cognito-identity-provider";
15
+
16
+ function codeOf(exn) {
17
+ return Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), prim => Primitive_option.fromNullable(prim.name));
18
+ }
19
+
20
+ function messageOf(exn) {
21
+ return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "the identity provider failed");
22
+ }
23
+
24
+ function failureOf(exn) {
25
+ let match = codeOf(exn);
26
+ if (match === undefined) {
27
+ return {
28
+ TAG: "Unavailable",
29
+ _0: messageOf(exn)
30
+ };
31
+ }
32
+ switch (match) {
33
+ case "InvalidParameterException" :
34
+ case "InvalidPasswordException" :
35
+ case "ResourceNotFoundException" :
36
+ case "UserNotFoundException" :
37
+ return {
38
+ TAG: "Refused",
39
+ _0: messageOf(exn)
40
+ };
41
+ case "AliasExistsException" :
42
+ case "UsernameExistsException" :
43
+ return "Conflict";
44
+ default:
45
+ return {
46
+ TAG: "Unavailable",
47
+ _0: messageOf(exn)
48
+ };
49
+ }
50
+ }
51
+
52
+ function usernameOf(contact) {
53
+ switch (contact.TAG) {
54
+ case "ToEmail" :
55
+ case "ToSms" :
56
+ return {
57
+ TAG: "Ok",
58
+ _0: contact._0
59
+ };
60
+ case "ToPush" :
61
+ return {
62
+ TAG: "Error",
63
+ _0: {
64
+ TAG: "Refused",
65
+ _0: "a push address cannot be signed in with"
66
+ }
67
+ };
68
+ }
69
+ }
70
+
71
+ function attributesOf(contact) {
72
+ switch (contact.TAG) {
73
+ case "ToEmail" :
74
+ return [
75
+ [
76
+ "email",
77
+ contact._0
78
+ ],
79
+ [
80
+ "email_verified",
81
+ "true"
82
+ ]
83
+ ];
84
+ case "ToSms" :
85
+ return [
86
+ [
87
+ "phone_number",
88
+ contact._0
89
+ ],
90
+ [
91
+ "phone_number_verified",
92
+ "true"
93
+ ]
94
+ ];
95
+ case "ToPush" :
96
+ return [];
97
+ }
98
+ }
99
+
100
+ async function attempt(run) {
101
+ try {
102
+ return {
103
+ TAG: "Ok",
104
+ _0: await run()
105
+ };
106
+ } catch (raw_exn) {
107
+ let exn = Primitive_exceptions.internalToException(raw_exn);
108
+ return {
109
+ TAG: "Error",
110
+ _0: failureOf(exn)
111
+ };
112
+ }
113
+ }
114
+
115
+ function withOps(ops, hasStore) {
116
+ let complete = async (username, sub, credential, groups) => {
117
+ let steps = async () => {
118
+ if (typeof credential === "object") {
119
+ await ops.setPassword(username, credential._0);
120
+ }
121
+ for (let i = 0, i_finish = groups.length; i < i_finish; ++i) {
122
+ await ops.addToGroup(username, groups[i]);
123
+ }
124
+ if (hasStore) {
125
+ return await ops.putUserId(sub, Auth_UserId$ReventlessCore.mint());
126
+ }
127
+ };
128
+ let failed = await attempt(steps);
129
+ if (failed.TAG === "Ok") {
130
+ return {
131
+ TAG: "Ok",
132
+ _0: {
133
+ providerId: username
134
+ }
135
+ };
136
+ }
137
+ await attempt(() => ops.deleteUser(username));
138
+ return failed;
139
+ };
140
+ let createPrincipal = async (contact, credential, groups) => {
141
+ let e = usernameOf(contact);
142
+ if (e.TAG !== "Ok") {
143
+ return e;
144
+ }
145
+ let username = e._0;
146
+ let e$1 = await attempt(() => ops.createUser(username, attributesOf(contact)));
147
+ if (e$1.TAG !== "Ok") {
148
+ return e$1;
149
+ }
150
+ let created = e$1._0;
151
+ let sub = created !== undefined ? created : Stdlib_Result.getOr(await attempt(() => ops.subOf(username)), undefined);
152
+ if (sub !== undefined) {
153
+ return await complete(username, sub, credential, groups);
154
+ } else {
155
+ await attempt(() => ops.deleteUser(username));
156
+ return {
157
+ TAG: "Error",
158
+ _0: {
159
+ TAG: "Unavailable",
160
+ _0: `the pool created ` + username + ` but named no sub`
161
+ }
162
+ };
163
+ }
164
+ };
165
+ let addToGroup = (principal, group) => attempt(() => ops.addToGroup(principal.providerId, group));
166
+ let removeFromGroup = (principal, group) => attempt(() => ops.removeFromGroup(principal.providerId, group));
167
+ let deletePrincipal = async principal => {
168
+ let username = principal.providerId;
169
+ let e = await attempt(() => ops.subOf(username));
170
+ if (e.TAG === "Ok") {
171
+ let sub = e._0;
172
+ let e$1 = await attempt(() => ops.deleteUser(username));
173
+ if (e$1.TAG !== "Ok") {
174
+ let tmp = e$1._0;
175
+ if (typeof tmp === "object" && tmp.TAG === "Unavailable") {
176
+ return e$1;
177
+ }
178
+ }
179
+ if (sub !== undefined && hasStore) {
180
+ await attempt(() => ops.deleteUserId(sub));
181
+ }
182
+ return {
183
+ TAG: "Ok",
184
+ _0: undefined
185
+ };
186
+ }
187
+ let tmp$1 = e._0;
188
+ if (typeof tmp$1 !== "object" || tmp$1.TAG === "Unavailable") {
189
+ return e;
190
+ } else {
191
+ return {
192
+ TAG: "Ok",
193
+ _0: undefined
194
+ };
195
+ }
196
+ };
197
+ return IdentityProvider$Reventless.make(createPrincipal, addToGroup, removeFromGroup, deletePrincipal, [
198
+ "CreatePrincipal",
199
+ "AddToGroup",
200
+ "RemoveFromGroup",
201
+ "DeletePrincipal"
202
+ ]);
203
+ }
204
+
205
+ function subFrom(attributes) {
206
+ return Stdlib_Option.map(Stdlib_Option.getOr(attributes, []).find(a => a.Name === "sub"), a => a.Value);
207
+ }
208
+
209
+ function sdkOps(poolId, identityStore) {
210
+ return {
211
+ createUser: async (username, attributes) => {
212
+ let out = await CognitoIdentityServiceProvider$AwsSdk.AdminCreateUserCommand.send(new ClientCognitoIdentityProvider.AdminCreateUserCommand({
213
+ UserPoolId: poolId,
214
+ Username: username,
215
+ UserAttributes: attributes.map(param => ({
216
+ Name: param[0],
217
+ Value: param[1]
218
+ })),
219
+ MessageAction: "SUPPRESS"
220
+ }));
221
+ return Stdlib_Option.flatMap(out.User, u => subFrom(u.Attributes));
222
+ },
223
+ subOf: async username => {
224
+ let out = await CognitoIdentityServiceProvider$AwsSdk.AdminGetUserCommand.send(new ClientCognitoIdentityProvider.AdminGetUserCommand({
225
+ UserPoolId: poolId,
226
+ Username: username
227
+ }));
228
+ return Stdlib_Option.map(Stdlib_Option.getOr(out.UserAttributes, []).find(a => a.Name === "sub"), a => a.Value);
229
+ },
230
+ setPassword: (username, password) => CognitoIdentityServiceProvider$AwsSdk.AdminSetUserPasswordCommand.send(new ClientCognitoIdentityProvider.AdminSetUserPasswordCommand({
231
+ UserPoolId: poolId,
232
+ Username: username,
233
+ Password: password,
234
+ Permanent: true
235
+ })),
236
+ addToGroup: (username, group) => CognitoIdentityServiceProvider$AwsSdk.AdminAddUserToGroupCommand.send(new ClientCognitoIdentityProvider.AdminAddUserToGroupCommand({
237
+ Username: username,
238
+ GroupName: group,
239
+ UserPoolId: poolId
240
+ })),
241
+ removeFromGroup: (username, group) => CognitoIdentityServiceProvider$AwsSdk.AdminRemoveUserFromGroupCommand.send(new ClientCognitoIdentityProvider.AdminRemoveUserFromGroupCommand({
242
+ Username: username,
243
+ GroupName: group,
244
+ UserPoolId: poolId
245
+ })),
246
+ deleteUser: username => CognitoIdentityServiceProvider$AwsSdk.AdminDeleteUserCommand.send(new ClientCognitoIdentityProvider.AdminDeleteUserCommand({
247
+ UserPoolId: poolId,
248
+ Username: username
249
+ })),
250
+ putUserId: async (sub, userId) => {
251
+ await DynamoDb_DocumentClient$AwsSdk.PutCommand.send(new LibDynamodb.PutCommand({
252
+ Item: Object.fromEntries([
253
+ [
254
+ Auth_IdentityStore_Schema$ReventlessAws.partitionKey,
255
+ sub
256
+ ],
257
+ [
258
+ Auth_IdentityStore_Schema$ReventlessAws.userIdAttribute,
259
+ userId
260
+ ]
261
+ ]),
262
+ TableName: identityStore
263
+ }));
264
+ },
265
+ deleteUserId: async sub => {
266
+ await DynamoDb_DocumentClient$AwsSdk.DeleteCommand.send(new LibDynamodb.DeleteCommand({
267
+ TableName: identityStore,
268
+ Key: Object.fromEntries([[
269
+ Auth_IdentityStore_Schema$ReventlessAws.partitionKey,
270
+ sub
271
+ ]])
272
+ }));
273
+ }
274
+ };
275
+ }
276
+
277
+ function provider(poolId, identityStore) {
278
+ if (poolId === "") {
279
+ return IdentityProvider$Reventless.unavailable("no identity provider is configured for this platform");
280
+ } else {
281
+ return withOps(sdkOps(poolId, identityStore), identityStore !== "");
282
+ }
283
+ }
284
+
285
+ let Cognito;
286
+
287
+ export {
288
+ codeOf,
289
+ messageOf,
290
+ failureOf,
291
+ usernameOf,
292
+ attributesOf,
293
+ attempt,
294
+ withOps,
295
+ Cognito,
296
+ subFrom,
297
+ sdkOps,
298
+ provider,
299
+ }
300
+ /* Stdlib_JsExn Not a pure module */
@@ -420,10 +420,13 @@ let capabilities = (): Reventless.Capabilities.t => {
420
420
  ~text,
421
421
  ),
422
422
  messaging: messagingEmailProvider(),
423
- // Refusing until the Cognito backend lands. The pool is provisioned, but the
424
- // store mapping a domain user id to a provider handle is not, and creating a
425
- // principal before that exists would write a handle with nowhere to live.
426
- identityProvider: Reventless.Capabilities.none.identityProvider,
423
+ // The platform's pool and identity store, both read per call like the sender
424
+ // above. An empty pool id is a plugin deployed with no platform to read one
425
+ // from, and answers `Unavailable` with no operations.
426
+ identityProvider: IdentityProvider_Cognito_Backend.provider(
427
+ ~poolId=NodeProcess.env->Dict.get("IDENTITY_PROVIDER_ID")->Option.getOr(""),
428
+ ~identityStore=NodeProcess.env->Dict.get("IDENTITY_STORE_TABLE")->Option.getOr(""),
429
+ ),
427
430
  secrets: ReventlessCore.Secrets_Node_Backend.provider,
428
431
  }
429
432
 
@@ -13,7 +13,6 @@ import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/compon
13
13
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
14
  import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
15
15
  import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
16
- import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
17
16
  import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
18
17
  import * as DynamoDb_Error$ReventlessAws from "../../errors/DynamoDb_Error.res.mjs";
19
18
  import * as Messaging_Ses_Backend$ReventlessAws from "../Messaging/Messaging_Ses_Backend.res.mjs";
@@ -26,6 +25,7 @@ import * as StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPo
26
25
  import * as Geocoder_AwsLocation_Backend$ReventlessAws from "../Geocoder/Geocoder_AwsLocation_Backend.res.mjs";
27
26
  import * as CommandTopicChannel_SQS_Runtime$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
28
27
  import * as QueryDbStorage_DynamoDb_Runtime$ReventlessAws from "../QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs";
28
+ import * as IdentityProvider_Cognito_Backend$ReventlessAws from "../Identity/IdentityProvider_Cognito_Backend.res.mjs";
29
29
  import * as OutboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs";
30
30
  import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "../ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
31
31
 
@@ -275,7 +275,7 @@ function capabilities() {
275
275
  return {
276
276
  geocode: text => Geocoder_AwsLocation_Backend$ReventlessAws.search(Stdlib_Option.getOr(process.env["PLACE_INDEX_NAME"], ""), text, undefined),
277
277
  messaging: messagingEmailProvider(),
278
- identityProvider: Capabilities$Reventless.none.identityProvider,
278
+ identityProvider: IdentityProvider_Cognito_Backend$ReventlessAws.provider(Stdlib_Option.getOr(process.env["IDENTITY_PROVIDER_ID"], ""), Stdlib_Option.getOr(process.env["IDENTITY_STORE_TABLE"], "")),
279
279
  secrets: Secrets_Node_Backend$ReventlessCore.provider
280
280
  };
281
281
  }
@@ -54,6 +54,9 @@ type sliceInfo = {
54
54
  // The schedules store's table, empty for a slice that maps no `Clock`. Granted to
55
55
  // the Lambda with the to-do tables, and named in the slice's handler config.
56
56
  schedulesResources: array<ReventlessInfra.Adapter.resource>,
57
+ // The capabilities the slice's spec declares. Read by grants that must not be
58
+ // handed to every slice on the shared Lambda for one slice's sake.
59
+ capabilityNeeds: array<Reventless.CapabilityNeed.t>,
57
60
  }
58
61
 
59
62
  let bundledInfos: dict<sliceInfo> = Dict.make()
@@ -103,6 +106,7 @@ let registerAutomationSlice = (
103
106
  ~commandTargetName=?,
104
107
  ~schedule=?,
105
108
  ~schedulesResources=[],
109
+ ~capabilityNeeds=[],
106
110
  ) =>
107
111
  bundledInfos->Dict.set(
108
112
  name,
@@ -118,6 +122,7 @@ let registerAutomationSlice = (
118
122
  commandTargetName,
119
123
  schedule,
120
124
  schedulesResources,
125
+ capabilityNeeds,
121
126
  },
122
127
  )
123
128
 
@@ -334,6 +339,80 @@ let makeMessagingGrant = (~runtime: ReventlessCore.Runtime.environment<runtimePa
334
339
  )
335
340
  }
336
341
 
342
+ /** Whether any slice on the shared Lambda declares the identity provider. The
343
+ grant below is the one that can create and delete accounts, so it follows a
344
+ declaration rather than the pool merely existing — which on AWS it always
345
+ does. */
346
+ let declaresIdentityProvider = (infos: array<sliceInfo>): bool =>
347
+ infos->Array.some(info => info.capabilityNeeds->Array.includes(IdentityProvider))
348
+
349
+ /** The admin calls the Cognito provider makes, on the one pool. */
350
+ let identityProviderPoolActions = [
351
+ "cognito-idp:AdminCreateUser",
352
+ "cognito-idp:AdminGetUser",
353
+ "cognito-idp:AdminSetUserPassword",
354
+ "cognito-idp:AdminAddUserToGroup",
355
+ "cognito-idp:AdminRemoveUserFromGroup",
356
+ "cognito-idp:AdminDeleteUser",
357
+ ]
358
+
359
+ // Built like the two grants above: a plain bool decides, the ARNs are Outputs,
360
+ // and the RolePolicy is created at top level rather than inside an `apply`.
361
+ let makeIdentityProviderGrant = (
362
+ ~runtime: ReventlessCore.Runtime.environment<runtimeParts>,
363
+ ~opts,
364
+ ) =>
365
+ if (
366
+ PluginRuntime_Builder.identityProviderProvisioned() &&
367
+ declaresIdentityProvider(bundledInfos->Dict.valuesToArray)
368
+ ) {
369
+ let customOpts =
370
+ opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
371
+ let {
372
+ poolArn,
373
+ storeArn,
374
+ }: PluginRuntime_Builder.identityProviderResources = PluginRuntime_Builder.identityProvider()
375
+ let policyJson = Pulumi.Output.all2((poolArn, storeArn))->Pulumi.Output.apply(((
376
+ pool,
377
+ store,
378
+ )) => {
379
+ open PulumiAws.PolicyDocument
380
+ PulumiAws.PolicyDocument.make(
381
+ ~id="AllAutomationSlicesIdentityProvider",
382
+ ~statements=Array.concat(
383
+ [
384
+ {
385
+ sid: "AllowManagingPrincipals",
386
+ effect: Allow,
387
+ actions: Actions(identityProviderPoolActions),
388
+ resources: Resource(pool),
389
+ },
390
+ ],
391
+ // A pool provisioned before the store has none, and its principals
392
+ // keep their `sub`; there is nothing to grant.
393
+ store == ""
394
+ ? []
395
+ : [
396
+ {
397
+ sid: "AllowRecordingDomainIds",
398
+ effect: Allow,
399
+ actions: Actions(["dynamodb:PutItem", "dynamodb:DeleteItem"]),
400
+ resources: Resource(store),
401
+ },
402
+ ],
403
+ ),
404
+ )->PulumiAws.PolicyDocument.toJsonString
405
+ })
406
+ let _ = PulumiAws.IAM.RolePolicy.make(
407
+ ~name="AllAutomationSlicesIdentityProvider",
408
+ ~args={
409
+ policy: policyJson->Pulumi.Output.asInput,
410
+ role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
411
+ },
412
+ ~opts=customOpts,
413
+ )
414
+ }
415
+
337
416
  // The automations' schedules. The Lambda may manage the schedules in the stack's
338
417
  // scheduler group and pass the scheduler role to them; the scheduler role may invoke
339
418
  // the Lambda; and each deploy invokes the Lambda to reconcile the declared schedules
@@ -833,6 +912,7 @@ let finishWithDcbEventLog = (
833
912
  makeAutomationSchedules(~runtime, ~schedulerRoleArn, ~sourceCodeHash, ~opts)
834
913
  makeGeocoderGrant(~runtime, ~opts)
835
914
  makeMessagingGrant(~runtime, ~opts)
915
+ makeIdentityProviderGrant(~runtime, ~opts)
836
916
  | None =>
837
917
  log.warn(
838
918
  ~comp="AutomationSliceRuntime_Builder_Single",
@@ -56,12 +56,13 @@ function getDcbQueueUrl() {
56
56
  return dcbQueueUrlRef.contents;
57
57
  }
58
58
 
59
- function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context, sourceTopicsOpt, consumesDcbLogOpt, commandTargetName, schedule, schedulesResourcesOpt) {
59
+ function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context, sourceTopicsOpt, consumesDcbLogOpt, commandTargetName, schedule, schedulesResourcesOpt, capabilityNeedsOpt) {
60
60
  let callbackType = callbackTypeOpt !== undefined ? callbackTypeOpt : "automation";
61
61
  let queryDbResources = queryDbResourcesOpt !== undefined ? queryDbResourcesOpt : [];
62
62
  let sourceTopics = sourceTopicsOpt !== undefined ? sourceTopicsOpt : ({});
63
63
  let consumesDcbLog = consumesDcbLogOpt !== undefined ? consumesDcbLogOpt : true;
64
64
  let schedulesResources = schedulesResourcesOpt !== undefined ? schedulesResourcesOpt : [];
65
+ let capabilityNeeds = capabilityNeedsOpt !== undefined ? capabilityNeedsOpt : [];
65
66
  bundledInfos[name] = {
66
67
  specModulePath: specModulePath,
67
68
  bodyModulePath: bodyModulePath,
@@ -73,7 +74,8 @@ function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackT
73
74
  consumesDcbLog: consumesDcbLog,
74
75
  commandTargetName: commandTargetName,
75
76
  schedule: schedule,
76
- schedulesResources: schedulesResources
77
+ schedulesResources: schedulesResources,
78
+ capabilityNeeds: capabilityNeeds
77
79
  };
78
80
  }
79
81
 
@@ -175,6 +177,51 @@ function makeMessagingGrant(runtime, opts) {
175
177
  }, customOpts);
176
178
  }
177
179
 
180
+ function declaresIdentityProvider(infos) {
181
+ return infos.some(info => info.capabilityNeeds.includes("IdentityProvider"));
182
+ }
183
+
184
+ let identityProviderPoolActions = [
185
+ "cognito-idp:AdminCreateUser",
186
+ "cognito-idp:AdminGetUser",
187
+ "cognito-idp:AdminSetUserPassword",
188
+ "cognito-idp:AdminAddUserToGroup",
189
+ "cognito-idp:AdminRemoveUserFromGroup",
190
+ "cognito-idp:AdminDeleteUser"
191
+ ];
192
+
193
+ function makeIdentityProviderGrant(runtime, opts) {
194
+ if (!(PluginRuntime_Builder$ReventlessAws.identityProviderProvisioned() && declaresIdentityProvider(Object.values(bundledInfos)))) {
195
+ return;
196
+ }
197
+ let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
198
+ let match = PluginRuntime_Builder$ReventlessAws.identityProvider();
199
+ let policyJson = Pulumi.all([
200
+ match.poolArn,
201
+ match.storeArn
202
+ ]).apply(param => {
203
+ let store = param[1];
204
+ return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "AllAutomationSlicesIdentityProvider", [{
205
+ Sid: "AllowManagingPrincipals",
206
+ Effect: "Allow",
207
+ Action: identityProviderPoolActions,
208
+ Resource: param[0]
209
+ }].concat(store === "" ? [] : [{
210
+ Sid: "AllowRecordingDomainIds",
211
+ Effect: "Allow",
212
+ Action: [
213
+ "dynamodb:PutItem",
214
+ "dynamodb:DeleteItem"
215
+ ],
216
+ Resource: store
217
+ }])));
218
+ });
219
+ new (Aws.iam.RolePolicy)("AllAutomationSlicesIdentityProvider", {
220
+ policy: policyJson,
221
+ role: runtime.parts.lambdaRole.id
222
+ }, customOpts);
223
+ }
224
+
178
225
  function makeAutomationSchedules(runtime, schedulerRoleArn, sourceCodeHash, opts) {
179
226
  let name = "AllAutomationSlices";
180
227
  let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
@@ -452,6 +499,7 @@ function finishWithDcbEventLog(schedulerRoleArn, dcbEventLog) {
452
499
  makeAutomationSchedules(runtime, schedulerRoleArn, sourceCodeHash, opts);
453
500
  makeGeocoderGrant(runtime, opts);
454
501
  makeMessagingGrant(runtime, opts);
502
+ makeIdentityProviderGrant(runtime, opts);
455
503
  } else {
456
504
  log.warn("AutomationSliceRuntime_Builder_Single", undefined, "finishWithDcbEventLog: DCB EventLog has no parent");
457
505
  }
@@ -483,6 +531,9 @@ export {
483
531
  makeSweepSchedule,
484
532
  makeGeocoderGrant,
485
533
  makeMessagingGrant,
534
+ declaresIdentityProvider,
535
+ identityProviderPoolActions,
536
+ makeIdentityProviderGrant,
486
537
  makeAutomationSchedules,
487
538
  finished,
488
539
  finish,
@@ -62,7 +62,7 @@ function Make(Api) {
62
62
  let queryDbTableName = tableResource.name;
63
63
  let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(sourceNames));
64
64
  let consumesDcbLog = sourceNames.length === 0 || sourceNames.some(name => !(name in allEventTopics));
65
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context, sourceTopics, consumesDcbLog, undefined, Spec.schedule, Stdlib_Option.mapOr(Component$ReventlessCore.outputs(as_).schedulesDb, [], db => db.resources));
65
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context, sourceTopics, consumesDcbLog, undefined, Spec.schedule, Stdlib_Option.mapOr(Component$ReventlessCore.outputs(as_).schedulesDb, [], db => db.resources), undefined);
66
66
  return as_;
67
67
  };
68
68
  return {
@@ -94,6 +94,8 @@ module Make = (
94
94
  ~consumesDcbLog,
95
95
  // Only the name: the target Aggregate's CommandTopic does not exist yet.
96
96
  ~commandTargetName=?Spec.targetName,
97
+ // What decides whether the shared Lambda may create and delete accounts.
98
+ ~capabilityNeeds=Spec.capabilityNeeds,
97
99
  )
98
100
 
99
101
  ots
@@ -63,7 +63,7 @@ function Make(Api) {
63
63
  let declaredSources = Spec.sourceNames;
64
64
  let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(declaredSources));
65
65
  let consumesDcbLog = declaredSources.length === 0 || declaredSources.some(name => !(name in allEventTopics));
66
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined, sourceTopics, consumesDcbLog, Spec.targetName, undefined, undefined);
66
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined, sourceTopics, consumesDcbLog, Spec.targetName, undefined, undefined, Spec.capabilityNeeds);
67
67
  return ots;
68
68
  };
69
69
  return {
@@ -91,6 +91,29 @@ let registerMessagingSender = (sender: Pulumi.Output.t<string>) => {
91
91
  let messagingSender = () => messagingSenderRef.contents
92
92
  let messagingProvisioned = () => messagingProvisionedRef.contents
93
93
 
94
+ /** The pool and the identity store, carried for the grant: a slice making
95
+ principals needs Cognito's admin calls on the one pool and writes on the one
96
+ store. Granted only to a plugin with a slice that declares the need — see
97
+ `AutomationSliceRuntime_Builder_Single.makeIdentityProviderGrant`. */
98
+ type identityProviderResources = {
99
+ poolArn: Pulumi.Output.t<string>,
100
+ storeArn: Pulumi.Output.t<string>,
101
+ }
102
+
103
+ let identityProviderRef: ref<identityProviderResources> = ref({
104
+ poolArn: Pulumi.Output.make(""),
105
+ storeArn: Pulumi.Output.make(""),
106
+ })
107
+ let identityProviderProvisionedRef = ref(false)
108
+
109
+ let registerIdentityProvider = (resources: identityProviderResources) => {
110
+ identityProviderRef := resources
111
+ identityProviderProvisionedRef := true
112
+ }
113
+
114
+ let identityProvider = () => identityProviderRef.contents
115
+ let identityProviderProvisioned = () => identityProviderProvisionedRef.contents
116
+
94
117
  type sliceModulePaths = {
95
118
  specPath: string,
96
119
  decisionPath: string,
@@ -91,6 +91,30 @@ function messagingProvisioned() {
91
91
  return messagingProvisionedRef.contents;
92
92
  }
93
93
 
94
+ let identityProviderRef = {
95
+ contents: {
96
+ poolArn: Pulumi.output(""),
97
+ storeArn: Pulumi.output("")
98
+ }
99
+ };
100
+
101
+ let identityProviderProvisionedRef = {
102
+ contents: false
103
+ };
104
+
105
+ function registerIdentityProvider(resources) {
106
+ identityProviderRef.contents = resources;
107
+ identityProviderProvisionedRef.contents = true;
108
+ }
109
+
110
+ function identityProvider() {
111
+ return identityProviderRef.contents;
112
+ }
113
+
114
+ function identityProviderProvisioned() {
115
+ return identityProviderProvisionedRef.contents;
116
+ }
117
+
94
118
  let dcbConfigRef = {
95
119
  contents: {
96
120
  pluginName: "",
@@ -591,6 +615,11 @@ export {
591
615
  registerMessagingSender,
592
616
  messagingSender,
593
617
  messagingProvisioned,
618
+ identityProviderRef,
619
+ identityProviderProvisionedRef,
620
+ registerIdentityProvider,
621
+ identityProvider,
622
+ identityProviderProvisioned,
594
623
  dcbConfigRef,
595
624
  registeredSliceModulePaths,
596
625
  registerStateChangeSliceSpec,