@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.
- package/CHANGELOG.md +22 -0
- package/package.json +8 -8
- package/scripts/DeployApp.res +7 -1
- package/scripts/DeployApp.res.mjs +1 -1
- package/scripts/ProvisionAccounts.res +89 -4
- package/scripts/ProvisionAccounts.res.mjs +101 -5
- package/scripts/ProvisionIdentity.res +42 -18
- package/scripts/ProvisionIdentity.res.mjs +40 -26
- package/src/Platform.res +47 -10
- package/src/Platform.res.mjs +44 -10
- package/src/Platform_Stack.res +17 -0
- package/src/Platform_Stack.res.mjs +7 -1
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +16 -7
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +16 -3
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +59 -9
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +53 -6
- package/src/adapter/Auth/Auth_Cognito.res +13 -2
- package/src/adapter/Auth/Auth_Cognito.res.mjs +9 -2
- package/src/adapter/Auth/Auth_IdentityStore.res +52 -0
- package/src/adapter/Auth/Auth_IdentityStore.res.mjs +46 -0
- package/src/adapter/Auth/Auth_IdentityStore_Schema.res +39 -0
- package/src/adapter/Auth/Auth_IdentityStore_Schema.res.mjs +35 -0
- package/src/adapter/Identity/IdentityProvider_Cognito_Backend.res +251 -0
- package/src/adapter/Identity/IdentityProvider_Cognito_Backend.res.mjs +300 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +7 -4
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +2 -2
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +80 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +53 -2
- package/src/components/AutomationSlice_Builder.res.mjs +1 -1
- package/src/components/OutboundTranslationSlice_Builder.res +2 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
- package/src/plugin/runtime/PluginRuntime_Builder.res +23 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +29 -0
- package/tests/Auth_ActiveRoleTrigger_OpsTest.res +40 -0
- package/tests/Auth_ActiveRoleTrigger_OpsTest.res.mjs +59 -8
- package/tests/Auth_CognitoTest.res +62 -0
- package/tests/Auth_CognitoTest.res.mjs +98 -0
- package/tests/Auth_IdentityStore_SchemaTest.res +35 -0
- package/tests/Auth_IdentityStore_SchemaTest.res.mjs +43 -0
- package/tests/IdentityProvider_Cognito_BackendTest.res +314 -0
- package/tests/IdentityProvider_Cognito_BackendTest.res.mjs +442 -0
- package/tests/ProvisionAccountsTest.res.mjs +1 -1
- 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
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
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:
|
|
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,
|