@reventlessdev/reventless-aws 3.0.0-alpha.395 → 3.0.0-alpha.397
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +7 -7
- 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 +25 -9
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +17 -4
- 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 +91 -0
- package/src/adapter/Auth/Auth_IdentityStore.res.mjs +85 -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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.397 (2026-10-23)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** a supplied pool without an identity store deploys, and its accounts keep their pool id ([49695ef](https://github.com/ReventlessDev/reventless-core/commit/49695ef79de89925c1e718ee9afa9a6ad7abd1f9))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.396 (2026-10-23)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **aws:** a new account gets a domain user id, carried as a token claim from a pool-scoped identity store ([ccfc90c](https://github.com/ReventlessDev/reventless-core/commit/ccfc90ced0c211cab23843864a2b4e580001b3ae))
|
|
18
|
+
* **aws:** a slice declaring the identity provider is handed one over the platform's Cognito pool ([b3a933b](https://github.com/ReventlessDev/reventless-core/commit/b3a933bbda475d0ce932a8de30616f738d639417))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.395 (2026-10-22)
|
|
7
22
|
|
|
8
23
|
* feat(aws)!: the host shell's config.json names the identity provider under the neutral keys only ([6ce24ba](https://github.com/ReventlessDev/reventless-core/commit/6ce24ba7dde27da02f76f7f9f6284ad09a48f15a))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.397",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"sury": "11.0.0",
|
|
23
23
|
"uuid": "^13.0.0",
|
|
24
24
|
"yaml": "^2.8.3",
|
|
25
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.
|
|
26
|
-
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
25
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.35",
|
|
27
26
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
27
|
+
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.33",
|
|
28
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
28
29
|
"@reventlessdev/rescript-node": "2.0.0-alpha.23",
|
|
29
|
-
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.31",
|
|
30
30
|
"@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
|
|
31
31
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
32
32
|
"@reventlessdev/rescript-web": "1.0.0-alpha.8",
|
|
33
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.306",
|
|
34
33
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.42",
|
|
34
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.204",
|
|
35
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.171",
|
|
35
36
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.175",
|
|
36
|
-
"@reventlessdev/reventless-
|
|
37
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.170"
|
|
37
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.307"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"rescript": "12.3.1",
|
package/scripts/DeployApp.res
CHANGED
|
@@ -587,7 +587,13 @@ let up = async (~manifest: DeployManifest.resolved, ~stack: string): result<unit
|
|
|
587
587
|
Error("the platform stack exports no identityProviderId to create accounts in")
|
|
588
588
|
| (Some(file), Some(providerId)) =>
|
|
589
589
|
Console.log("\n── Creating the accounts ──\n")
|
|
590
|
-
|
|
590
|
+
// The stack just deployed is the one that exports the store
|
|
591
|
+
// a new account's domain id is written to.
|
|
592
|
+
switch await ProvisionAccounts.provision(
|
|
593
|
+
~file,
|
|
594
|
+
~providerId,
|
|
595
|
+
~identityStore=?platformOutputs->outputString("identityStore"),
|
|
596
|
+
) {
|
|
591
597
|
| Error(_) as e => e
|
|
592
598
|
| Ok() => Ok(Some(file))
|
|
593
599
|
}
|
|
@@ -728,7 +728,7 @@ async function up(manifest, stack) {
|
|
|
728
728
|
if (match$2 !== undefined) {
|
|
729
729
|
if (match$3 !== undefined) {
|
|
730
730
|
console.log("\n── Creating the accounts ──\n");
|
|
731
|
-
let e$6 = await ProvisionAccounts$ReventlessAws.provision(match$2, match$3, undefined);
|
|
731
|
+
let e$6 = await ProvisionAccounts$ReventlessAws.provision(match$2, match$3, outputString(platformOutputs, "identityStore"), undefined);
|
|
732
732
|
accounts = e$6.TAG === "Ok" ? ({
|
|
733
733
|
TAG: "Ok",
|
|
734
734
|
_0: match$2
|
|
@@ -178,7 +178,86 @@ drifted from the file is exactly the case a re-run is expected to fix.
|
|
|
178
178
|
Returns the id the pool minted, which is the value the manifest cannot know until
|
|
179
179
|
the account exists and which nobody should be pasting by hand.
|
|
180
180
|
*/
|
|
181
|
-
|
|
181
|
+
/**
|
|
182
|
+
Where the rows mapping a subject to its domain id live, for this pool.
|
|
183
|
+
|
|
184
|
+
A stack-owned pool's table carries a generated suffix, so it is read off the
|
|
185
|
+
stack's `identityStore` output; a pool no stack owns has the derived name
|
|
186
|
+
`provision-identity` created it under. `None` when neither can be known, which
|
|
187
|
+
is a pool provisioned before the store existed — accounts made then keep their
|
|
188
|
+
`sub`, exactly as every account before them.
|
|
189
|
+
*/
|
|
190
|
+
let // ── The domain id ────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
identityStoreFor = (~providerId: string, ~stack: option<string>): option<string> =>
|
|
193
|
+
switch stack->Option.orElse(ProvisionProvider.selectedStack()) {
|
|
194
|
+
| Some(stack) =>
|
|
195
|
+
switch ProvisionProvider.pulumi(["stack", "output", "identityStore", "--stack", stack]) {
|
|
196
|
+
| "" => Some(Auth_IdentityStore_Schema.derivedStoreName(~identityProviderId=providerId))
|
|
197
|
+
| name => Some(name)
|
|
198
|
+
| exception _ =>
|
|
199
|
+
Some(Auth_IdentityStore_Schema.derivedStoreName(~identityProviderId=providerId))
|
|
200
|
+
}
|
|
201
|
+
| None => Some(Auth_IdentityStore_Schema.derivedStoreName(~identityProviderId=providerId))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The domain id recorded for a subject, if the store has one. */
|
|
205
|
+
let recordedUserId = async (~store: string, ~sub: string): option<string> =>
|
|
206
|
+
try {
|
|
207
|
+
let out = await DynamoDb_DocumentClient.GetCommand.make({
|
|
208
|
+
tableName: store,
|
|
209
|
+
key: Dict.fromArray([(Auth_IdentityStore_Schema.partitionKey, JSON.Encode.string(sub))]),
|
|
210
|
+
})->DynamoDb_DocumentClient.GetCommand.send
|
|
211
|
+
out.item
|
|
212
|
+
->Option.flatMap(JSON.Decode.object)
|
|
213
|
+
->Option.flatMap(o => o->Dict.get(Auth_IdentityStore_Schema.userIdAttribute))
|
|
214
|
+
->Option.flatMap(JSON.Decode.string)
|
|
215
|
+
} catch {
|
|
216
|
+
| exn if Util_AwsError.isNotFound(exn) => None
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
The id the manifest should carry for this account.
|
|
221
|
+
|
|
222
|
+
🚨 **Minted only for an account this run created.** An account that already
|
|
223
|
+
existed has rows keyed by whatever id it has been writing under — its `sub`, or
|
|
224
|
+
a domain id an earlier run minted — and re-minting would orphan every one of
|
|
225
|
+
them. A store that does not exist leaves the account on its `sub`, which is what
|
|
226
|
+
every account before the store did.
|
|
227
|
+
*/
|
|
228
|
+
let domainIdFor = async (~store: option<string>, ~sub: string, ~created: bool): option<string> =>
|
|
229
|
+
switch store {
|
|
230
|
+
| None => Some(sub)
|
|
231
|
+
| Some(store) =>
|
|
232
|
+
switch await recordedUserId(~store, ~sub) {
|
|
233
|
+
| Some(_) as recorded => recorded
|
|
234
|
+
| None if created =>
|
|
235
|
+
let minted = ReventlessCore.Auth_UserId.mint()
|
|
236
|
+
try {
|
|
237
|
+
let _ = await DynamoDb_DocumentClient.PutCommand.make({
|
|
238
|
+
tableName: store,
|
|
239
|
+
item: JSON.Encode.object(
|
|
240
|
+
Dict.fromArray([
|
|
241
|
+
(Auth_IdentityStore_Schema.partitionKey, JSON.Encode.string(sub)),
|
|
242
|
+
(Auth_IdentityStore_Schema.userIdAttribute, JSON.Encode.string(minted)),
|
|
243
|
+
]),
|
|
244
|
+
),
|
|
245
|
+
})->DynamoDb_DocumentClient.PutCommand.send
|
|
246
|
+
Some(minted)
|
|
247
|
+
} catch {
|
|
248
|
+
| exn if Util_AwsError.isNotFound(exn) =>
|
|
249
|
+
Console.log(`store ${store} (missing — the account keeps its pool id)`)
|
|
250
|
+
Some(sub)
|
|
251
|
+
}
|
|
252
|
+
| None => Some(sub)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let applyEntry = async (
|
|
257
|
+
~providerId: string,
|
|
258
|
+
~entry: Manifest.entry,
|
|
259
|
+
~identityStore: option<string>,
|
|
260
|
+
): option<string> => {
|
|
182
261
|
let attributes: array<Cognito.AdminCreateUserCommand.attributeType> = looksLikeEmail(
|
|
183
262
|
entry.username,
|
|
184
263
|
)
|
|
@@ -200,7 +279,10 @@ let applyEntry = async (~providerId: string, ~entry: Manifest.entry): option<str
|
|
|
200
279
|
await ProvisionCognito.addToGroup(~providerId, ~username=entry.username, ~group)
|
|
201
280
|
Console.log(`member ${entry.username} in ${group}`)
|
|
202
281
|
}
|
|
203
|
-
await ProvisionCognito.subOf(~providerId, ~username=entry.username)
|
|
282
|
+
switch await ProvisionCognito.subOf(~providerId, ~username=entry.username) {
|
|
283
|
+
| None => None
|
|
284
|
+
| Some(sub) => await domainIdFor(~store=identityStore, ~sub, ~created=outcome == Created)
|
|
285
|
+
}
|
|
204
286
|
}
|
|
205
287
|
|
|
206
288
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
@@ -233,6 +315,7 @@ is a parameter so a test can refuse without a pool.
|
|
|
233
315
|
let provision = async (
|
|
234
316
|
~file: string,
|
|
235
317
|
~providerId: string,
|
|
318
|
+
~identityStore: option<string>=?,
|
|
236
319
|
~checkPool=checkPoolAcceptsUsernames,
|
|
237
320
|
): result<unit, string> =>
|
|
238
321
|
switch Manifest.parseFile(file) {
|
|
@@ -256,7 +339,7 @@ let provision = async (
|
|
|
256
339
|
let fills = []
|
|
257
340
|
for index in 0 to entries->Array.length - 1 {
|
|
258
341
|
let entry = entries->Array.getUnsafe(index)
|
|
259
|
-
let userId = await applyEntry(~providerId, ~entry)
|
|
342
|
+
let userId = await applyEntry(~providerId, ~entry, ~identityStore)
|
|
260
343
|
fills->Array.push({Manifest.index, password: None, userId})
|
|
261
344
|
}
|
|
262
345
|
switch Manifest.fillFile(~path=file, ~fills) {
|
|
@@ -288,7 +371,9 @@ let run = async (args: args): result<unit, string> =>
|
|
|
288
371
|
Error(`${message}. To fill in the manifest without creating anything, run prepare-accounts`)
|
|
289
372
|
| Ok((providerId, source)) =>
|
|
290
373
|
Console.log(`provider ${providerId} (from ${source->ProvisionProvider.describe})`)
|
|
291
|
-
|
|
374
|
+
let identityStore = identityStoreFor(~providerId, ~stack=args.stack)
|
|
375
|
+
identityStore->Option.forEach(store => Console.log(`store ${store}`))
|
|
376
|
+
await provision(~file, ~providerId, ~identityStore?)
|
|
292
377
|
}
|
|
293
378
|
}
|
|
294
379
|
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
5
|
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
4
6
|
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
5
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";
|
|
6
10
|
import * as AccountsManifest$Reventless from "@reventlessdev/reventless-spec/src/types/AccountsManifest.res.mjs";
|
|
7
11
|
import * as Util_AwsError$ReventlessAws from "../src/util/Util_AwsError.res.mjs";
|
|
12
|
+
import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
|
|
8
13
|
import * as ProvisionCognito$ReventlessAws from "./ProvisionCognito.res.mjs";
|
|
9
14
|
import * as ProvisionProvider$ReventlessAws from "./ProvisionProvider.res.mjs";
|
|
15
|
+
import * as Auth_IdentityStore_Schema$ReventlessAws from "../src/adapter/Auth/Auth_IdentityStore_Schema.res.mjs";
|
|
10
16
|
|
|
11
17
|
function parseArgs(argv) {
|
|
12
18
|
return Stdlib_Result.map(Stdlib_Result.flatMap(CliArgs$Reventless.parse([
|
|
@@ -103,7 +109,87 @@ async function ensureGroups(providerId, entries) {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
|
|
112
|
+
function identityStoreFor(providerId, stack) {
|
|
113
|
+
let stack$1 = Stdlib_Option.orElse(stack, ProvisionProvider$ReventlessAws.selectedStack());
|
|
114
|
+
if (stack$1 === undefined) {
|
|
115
|
+
return Auth_IdentityStore_Schema$ReventlessAws.derivedStoreName(providerId);
|
|
116
|
+
}
|
|
117
|
+
let name;
|
|
118
|
+
try {
|
|
119
|
+
name = ProvisionProvider$ReventlessAws.pulumi([
|
|
120
|
+
"stack",
|
|
121
|
+
"output",
|
|
122
|
+
"identityStore",
|
|
123
|
+
"--stack",
|
|
124
|
+
stack$1
|
|
125
|
+
]);
|
|
126
|
+
} catch (exn) {
|
|
127
|
+
return Auth_IdentityStore_Schema$ReventlessAws.derivedStoreName(providerId);
|
|
128
|
+
}
|
|
129
|
+
if (name === "") {
|
|
130
|
+
return Auth_IdentityStore_Schema$ReventlessAws.derivedStoreName(providerId);
|
|
131
|
+
} else {
|
|
132
|
+
return name;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function recordedUserId(store, sub) {
|
|
137
|
+
try {
|
|
138
|
+
let out = await DynamoDb_DocumentClient$AwsSdk.GetCommand.send(new LibDynamodb.GetCommand({
|
|
139
|
+
TableName: store,
|
|
140
|
+
Key: Object.fromEntries([[
|
|
141
|
+
Auth_IdentityStore_Schema$ReventlessAws.partitionKey,
|
|
142
|
+
sub
|
|
143
|
+
]])
|
|
144
|
+
}));
|
|
145
|
+
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(out.Item, Stdlib_JSON.Decode.object), o => o[Auth_IdentityStore_Schema$ReventlessAws.userIdAttribute]), Stdlib_JSON.Decode.string);
|
|
146
|
+
} catch (raw_exn) {
|
|
147
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
148
|
+
if (Util_AwsError$ReventlessAws.isNotFound(exn)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
throw exn;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function domainIdFor(store, sub, created) {
|
|
156
|
+
if (store === undefined) {
|
|
157
|
+
return sub;
|
|
158
|
+
}
|
|
159
|
+
let recorded = await recordedUserId(store, sub);
|
|
160
|
+
if (recorded !== undefined) {
|
|
161
|
+
return recorded;
|
|
162
|
+
}
|
|
163
|
+
if (!created) {
|
|
164
|
+
return sub;
|
|
165
|
+
}
|
|
166
|
+
let minted = Auth_UserId$ReventlessCore.mint();
|
|
167
|
+
try {
|
|
168
|
+
await DynamoDb_DocumentClient$AwsSdk.PutCommand.send(new LibDynamodb.PutCommand({
|
|
169
|
+
Item: Object.fromEntries([
|
|
170
|
+
[
|
|
171
|
+
Auth_IdentityStore_Schema$ReventlessAws.partitionKey,
|
|
172
|
+
sub
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
Auth_IdentityStore_Schema$ReventlessAws.userIdAttribute,
|
|
176
|
+
minted
|
|
177
|
+
]
|
|
178
|
+
]),
|
|
179
|
+
TableName: store
|
|
180
|
+
}));
|
|
181
|
+
return minted;
|
|
182
|
+
} catch (raw_exn) {
|
|
183
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
184
|
+
if (Util_AwsError$ReventlessAws.isNotFound(exn)) {
|
|
185
|
+
console.log(`store ` + store + ` (missing — the account keeps its pool id)`);
|
|
186
|
+
return sub;
|
|
187
|
+
}
|
|
188
|
+
throw exn;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function applyEntry(providerId, entry, identityStore) {
|
|
107
193
|
let attributes = looksLikeEmail(entry.username) ? [
|
|
108
194
|
{
|
|
109
195
|
Name: "email",
|
|
@@ -122,7 +208,10 @@ async function applyEntry(providerId, entry) {
|
|
|
122
208
|
await ProvisionCognito$ReventlessAws.addToGroup(providerId, entry.username, group);
|
|
123
209
|
console.log(`member ` + entry.username + ` in ` + group);
|
|
124
210
|
}
|
|
125
|
-
|
|
211
|
+
let sub = await ProvisionCognito$ReventlessAws.subOf(providerId, entry.username);
|
|
212
|
+
if (sub !== undefined) {
|
|
213
|
+
return await domainIdFor(identityStore, sub, outcome === "Created");
|
|
214
|
+
}
|
|
126
215
|
}
|
|
127
216
|
|
|
128
217
|
function manifestNote(file) {
|
|
@@ -133,7 +222,7 @@ function manifestNote(file) {
|
|
|
133
222
|
credentials for a deployment, not secrets to reuse anywhere real.`;
|
|
134
223
|
}
|
|
135
224
|
|
|
136
|
-
async function provision(file, providerId, checkPoolOpt) {
|
|
225
|
+
async function provision(file, providerId, identityStore, checkPoolOpt) {
|
|
137
226
|
let checkPool = checkPoolOpt !== undefined ? checkPoolOpt : checkPoolAcceptsUsernames;
|
|
138
227
|
let message = AccountsManifest$Reventless.parseFile(file);
|
|
139
228
|
if (message.TAG !== "Ok") {
|
|
@@ -168,7 +257,7 @@ async function provision(file, providerId, checkPoolOpt) {
|
|
|
168
257
|
let fills = [];
|
|
169
258
|
for (let index = 0, index_finish = entries.length; index < index_finish; ++index) {
|
|
170
259
|
let entry = entries[index];
|
|
171
|
-
let userId = await applyEntry(providerId, entry);
|
|
260
|
+
let userId = await applyEntry(providerId, entry, identityStore);
|
|
172
261
|
fills.push({
|
|
173
262
|
index: index,
|
|
174
263
|
password: undefined,
|
|
@@ -210,7 +299,11 @@ async function run(args) {
|
|
|
210
299
|
let match = message._0;
|
|
211
300
|
let providerId = match[0];
|
|
212
301
|
console.log(`provider ` + providerId + ` (from ` + ProvisionProvider$ReventlessAws.describe(match[1]) + `)`);
|
|
213
|
-
|
|
302
|
+
let identityStore = identityStoreFor(providerId, args.stack);
|
|
303
|
+
Stdlib_Option.forEach(identityStore, store => {
|
|
304
|
+
console.log(`store ` + store);
|
|
305
|
+
});
|
|
306
|
+
return await provision(file, providerId, identityStore, undefined);
|
|
214
307
|
}
|
|
215
308
|
|
|
216
309
|
let cli = {
|
|
@@ -251,6 +344,9 @@ export {
|
|
|
251
344
|
checkPoolAcceptsUsernames,
|
|
252
345
|
describe,
|
|
253
346
|
ensureGroups,
|
|
347
|
+
identityStoreFor,
|
|
348
|
+
recordedUserId,
|
|
349
|
+
domainIdFor,
|
|
254
350
|
applyEntry,
|
|
255
351
|
manifestNote,
|
|
256
352
|
provision,
|
|
@@ -224,10 +224,34 @@ let resolvePool = async (~args: args): result<string, string> =>
|
|
|
224
224
|
|
|
225
225
|
// ── The store ────────────────────────────────────────────────────────────────
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
/** One store to provision: its name, its key, and the refusal for a table under
|
|
228
|
+
that name with another key. Two stores share the pool's trigger, and this is
|
|
229
|
+
the shape both are described in. */
|
|
230
|
+
type store = {
|
|
231
|
+
tableName: string,
|
|
232
|
+
keySchema: array<(string, string)>,
|
|
233
|
+
refusal: (~tableName: string, ~actual: array<(string, string)>) => option<string>,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let activeRoleStore = (~providerId: string): store => {
|
|
237
|
+
tableName: Schema.derivedStoreName(~identityProviderId=providerId),
|
|
238
|
+
keySchema: Schema.expectedKeySchema,
|
|
239
|
+
refusal: Schema.keySchemaRefusal,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let identityStore = (~providerId: string): store => {
|
|
243
|
+
tableName: Auth_IdentityStore_Schema.derivedStoreName(~identityProviderId=providerId),
|
|
244
|
+
keySchema: Auth_IdentityStore_Schema.expectedKeySchema,
|
|
245
|
+
refusal: Auth_IdentityStore_Schema.keySchemaRefusal,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let keySchemaOf = (pairs: array<(string, string)>): array<
|
|
249
|
+
DynamoDb.DynamoDb.DescribeTableCommand.keySchemaElement,
|
|
250
|
+
> =>
|
|
251
|
+
pairs->Array.map(((attributeName, keyType)) => {
|
|
252
|
+
DynamoDb.DynamoDb.DescribeTableCommand.attributeName,
|
|
253
|
+
keyType,
|
|
254
|
+
})
|
|
231
255
|
|
|
232
256
|
/** The SDK's shape mapped onto the plain pairs [Schema.keySchemaRefusal] compares.
|
|
233
257
|
The refusal itself lives with the schema it is about, since the schema is what
|
|
@@ -248,13 +272,10 @@ let describeTable = async (~tableName: string): option<
|
|
|
248
272
|
| exn if Util_AwsError.isNotFound(exn) => None
|
|
249
273
|
}
|
|
250
274
|
|
|
251
|
-
let provisionStore = async (
|
|
275
|
+
let provisionStore = async ({tableName, keySchema, refusal}: store): result<unit, string> =>
|
|
252
276
|
switch await describeTable(~tableName) {
|
|
253
277
|
| Some(described) =>
|
|
254
|
-
switch
|
|
255
|
-
~tableName,
|
|
256
|
-
~actual=described.keySchema->Option.getOr([])->asPairs,
|
|
257
|
-
) {
|
|
278
|
+
switch refusal(~tableName, ~actual=described.keySchema->Option.getOr([])->asPairs) {
|
|
258
279
|
| Some(message) => Error(message)
|
|
259
280
|
| None =>
|
|
260
281
|
Console.log(`store ${tableName} (adopted)`)
|
|
@@ -263,11 +284,11 @@ let provisionStore = async (~tableName: string): result<unit, string> =>
|
|
|
263
284
|
| None =>
|
|
264
285
|
let _ = await DynamoDb.DynamoDb.CreateTableCommand.make({
|
|
265
286
|
tableName,
|
|
266
|
-
keySchema,
|
|
267
|
-
attributeDefinitions:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
287
|
+
keySchema: keySchemaOf(keySchema),
|
|
288
|
+
attributeDefinitions: keySchema->Array.map(((attributeName, _)) => {
|
|
289
|
+
DynamoDb.DynamoDb.CreateTableCommand.attributeName,
|
|
290
|
+
attributeType: "S",
|
|
291
|
+
}),
|
|
271
292
|
billingMode: "PAY_PER_REQUEST",
|
|
272
293
|
})->DynamoDb.DynamoDb.CreateTableCommand.send
|
|
273
294
|
|
|
@@ -315,12 +336,15 @@ let run = async (args: args): result<unit, string> =>
|
|
|
315
336
|
switch await resolvePool(~args) {
|
|
316
337
|
| Error(_) as e => e
|
|
317
338
|
| Ok(providerId) =>
|
|
318
|
-
|
|
319
|
-
switch await provisionStore(~tableName) {
|
|
339
|
+
switch await provisionStore(activeRoleStore(~providerId)) {
|
|
320
340
|
| Error(_) as e => e
|
|
321
341
|
| Ok() =>
|
|
322
|
-
|
|
323
|
-
|
|
342
|
+
switch await provisionStore(identityStore(~providerId)) {
|
|
343
|
+
| Error(_) as e => e
|
|
344
|
+
| Ok() =>
|
|
345
|
+
Console.log(nextSteps(~providerId))
|
|
346
|
+
Ok()
|
|
347
|
+
}
|
|
324
348
|
}
|
|
325
349
|
}
|
|
326
350
|
|
|
@@ -10,6 +10,7 @@ import * as Util_AwsError$ReventlessAws from "../src/util/Util_AwsError.res.mjs"
|
|
|
10
10
|
import * as Auth_SignUpMode$ReventlessAws from "../src/adapter/Auth/Auth_SignUpMode.res.mjs";
|
|
11
11
|
import * as Auth_LoginIdentifier$ReventlessAws from "../src/adapter/Auth/Auth_LoginIdentifier.res.mjs";
|
|
12
12
|
import * as CognitoIdentityServiceProvider$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/CognitoIdentityServiceProvider.res.mjs";
|
|
13
|
+
import * as Auth_IdentityStore_Schema$ReventlessAws from "../src/adapter/Auth/Auth_IdentityStore_Schema.res.mjs";
|
|
13
14
|
import * as ClientCognitoIdentityProvider from "@aws-sdk/client-cognito-identity-provider";
|
|
14
15
|
import * as Auth_ActiveRoleStore_Schema$ReventlessAws from "../src/adapter/Auth/Auth_ActiveRoleStore_Schema.res.mjs";
|
|
15
16
|
|
|
@@ -185,16 +186,28 @@ async function resolvePool(args) {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
189
|
+
function activeRoleStore(providerId) {
|
|
190
|
+
return {
|
|
191
|
+
tableName: Auth_ActiveRoleStore_Schema$ReventlessAws.derivedStoreName(providerId),
|
|
192
|
+
keySchema: Auth_ActiveRoleStore_Schema$ReventlessAws.expectedKeySchema,
|
|
193
|
+
refusal: Auth_ActiveRoleStore_Schema$ReventlessAws.keySchemaRefusal
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function identityStore(providerId) {
|
|
198
|
+
return {
|
|
199
|
+
tableName: Auth_IdentityStore_Schema$ReventlessAws.derivedStoreName(providerId),
|
|
200
|
+
keySchema: Auth_IdentityStore_Schema$ReventlessAws.expectedKeySchema,
|
|
201
|
+
refusal: Auth_IdentityStore_Schema$ReventlessAws.keySchemaRefusal
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function keySchemaOf(pairs) {
|
|
206
|
+
return pairs.map(param => ({
|
|
207
|
+
AttributeName: param[0],
|
|
208
|
+
KeyType: param[1]
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
198
211
|
|
|
199
212
|
function asPairs(elements) {
|
|
200
213
|
return elements.map(k => [
|
|
@@ -217,10 +230,12 @@ async function describeTable(tableName) {
|
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
|
|
220
|
-
async function provisionStore(
|
|
233
|
+
async function provisionStore(param) {
|
|
234
|
+
let keySchema = param.keySchema;
|
|
235
|
+
let tableName = param.tableName;
|
|
221
236
|
let described = await describeTable(tableName);
|
|
222
237
|
if (described !== undefined) {
|
|
223
|
-
let message =
|
|
238
|
+
let message = param.refusal(tableName, asPairs(Stdlib_Option.getOr(described.KeySchema, [])));
|
|
224
239
|
if (message !== undefined) {
|
|
225
240
|
return {
|
|
226
241
|
TAG: "Error",
|
|
@@ -236,17 +251,11 @@ async function provisionStore(tableName) {
|
|
|
236
251
|
}
|
|
237
252
|
await DynamoDb_DynamoDb$AwsSdk.CreateTableCommand.send(new ClientDynamodb.CreateTableCommand({
|
|
238
253
|
TableName: tableName,
|
|
239
|
-
KeySchema: keySchema,
|
|
240
|
-
AttributeDefinitions:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
AttributeName: Auth_ActiveRoleStore_Schema$ReventlessAws.sortKey,
|
|
247
|
-
AttributeType: "S"
|
|
248
|
-
}
|
|
249
|
-
],
|
|
254
|
+
KeySchema: keySchemaOf(keySchema),
|
|
255
|
+
AttributeDefinitions: keySchema.map(param => ({
|
|
256
|
+
AttributeName: param[0],
|
|
257
|
+
AttributeType: "S"
|
|
258
|
+
})),
|
|
250
259
|
BillingMode: "PAY_PER_REQUEST"
|
|
251
260
|
}));
|
|
252
261
|
let waited = await DynamoDb_DynamoDb$AwsSdk.TableExistsWaiter.wait(tableName, undefined);
|
|
@@ -296,11 +305,14 @@ async function run(args) {
|
|
|
296
305
|
return e;
|
|
297
306
|
}
|
|
298
307
|
let providerId = e._0;
|
|
299
|
-
let
|
|
300
|
-
let e$1 = await provisionStore(tableName);
|
|
308
|
+
let e$1 = await provisionStore(activeRoleStore(providerId));
|
|
301
309
|
if (e$1.TAG !== "Ok") {
|
|
302
310
|
return e$1;
|
|
303
311
|
}
|
|
312
|
+
let e$2 = await provisionStore(identityStore(providerId));
|
|
313
|
+
if (e$2.TAG !== "Ok") {
|
|
314
|
+
return e$2;
|
|
315
|
+
}
|
|
304
316
|
console.log(nextSteps(providerId));
|
|
305
317
|
return {
|
|
306
318
|
TAG: "Ok",
|
|
@@ -347,7 +359,9 @@ export {
|
|
|
347
359
|
poolSettings,
|
|
348
360
|
findPoolByName,
|
|
349
361
|
resolvePool,
|
|
350
|
-
|
|
362
|
+
activeRoleStore,
|
|
363
|
+
identityStore,
|
|
364
|
+
keySchemaOf,
|
|
351
365
|
asPairs,
|
|
352
366
|
describeTable,
|
|
353
367
|
provisionStore,
|
package/src/Platform.res
CHANGED
|
@@ -1287,9 +1287,9 @@ module MakeWithConfig = (
|
|
|
1287
1287
|
// door: unlike geocoding, a browser has no business sending mail from the
|
|
1288
1288
|
// deployment's identity. Unset ⇒ no export, no grant.
|
|
1289
1289
|
messagingSender?: ReventlessInfra.Platform.messagingSender,
|
|
1290
|
-
// Ignored here: an in-process provider cannot reach a Lambda
|
|
1291
|
-
// pool
|
|
1292
|
-
//
|
|
1290
|
+
// Ignored here: an in-process provider cannot reach a Lambda. Every AWS
|
|
1291
|
+
// platform has a pool, and a plugin whose slice declares the need is handed
|
|
1292
|
+
// a provider over it with no root opt-in — see `deployPlugin`.
|
|
1293
1293
|
identityProvider?: Reventless.IdentityProvider.t,
|
|
1294
1294
|
// Optional object store for direct-to-S3 uploads, as returned by
|
|
1295
1295
|
// `Capability_ObjectStore_S3.make`. When set, the deploy registers it with
|
|
@@ -2760,6 +2760,37 @@ module MakeWithConfig = (
|
|
|
2760
2760
|
}
|
|
2761
2761
|
PluginRuntime_Builder.registerCapabilityEnv("MESSAGING_EMAIL_PROVIDER", messagingEmailProvider)
|
|
2762
2762
|
|
|
2763
|
+
// The pool and its identity store, read across the same two halves. Every
|
|
2764
|
+
// AWS platform has a pool, so unlike the two above there is no root opt-in:
|
|
2765
|
+
// what a plugin declares decides whether its slices are granted the admin
|
|
2766
|
+
// calls, in the slice runtime's finalizer.
|
|
2767
|
+
let platformOutput = (name: string, ~local: unit => Pulumi.Output.t<string>) =>
|
|
2768
|
+
switch platformStackRef {
|
|
2769
|
+
| Some(stackRef) =>
|
|
2770
|
+
(
|
|
2771
|
+
stackRef->Pulumi.StackReference.getOutput(name): Pulumi.Output.t<option<string>>
|
|
2772
|
+
)->Pulumi.Output.apply(o => o->Option.getOr(""))
|
|
2773
|
+
| None => local()
|
|
2774
|
+
}
|
|
2775
|
+
let identityProviderId = platformOutput("identityProviderId", ~local=() =>
|
|
2776
|
+
Platform_Stack.resolveCognitoUserPool().poolId
|
|
2777
|
+
)
|
|
2778
|
+
PluginRuntime_Builder.registerCapabilityEnv("IDENTITY_PROVIDER_ID", identityProviderId)
|
|
2779
|
+
PluginRuntime_Builder.registerCapabilityEnv(
|
|
2780
|
+
"IDENTITY_STORE_TABLE",
|
|
2781
|
+
platformOutput("identityStore", ~local=() =>
|
|
2782
|
+
Platform_Stack.resolveCognitoUserPool().identityTable.name
|
|
2783
|
+
),
|
|
2784
|
+
)
|
|
2785
|
+
PluginRuntime_Builder.registerIdentityProvider({
|
|
2786
|
+
poolArn: platformOutput("identityProviderArn", ~local=() =>
|
|
2787
|
+
Platform_Stack.resolveCognitoUserPool().poolArn
|
|
2788
|
+
),
|
|
2789
|
+
storeArn: platformOutput("identityStoreArn", ~local=() =>
|
|
2790
|
+
Platform_Stack.resolveCognitoUserPool().identityTable.arn
|
|
2791
|
+
),
|
|
2792
|
+
})
|
|
2793
|
+
|
|
2763
2794
|
module P = unpack(plugin)
|
|
2764
2795
|
let pluginComponent = P.make()
|
|
2765
2796
|
ReventlessCore.Plugin_Helpers.clearOffload()
|
|
@@ -2895,10 +2926,17 @@ module MakeWithConfig = (
|
|
|
2895
2926
|
stackRef->Pulumi.StackReference.getOutput("objectStores"),
|
|
2896
2927
|
geocoderPlaceIndex,
|
|
2897
2928
|
messagingEmailSender,
|
|
2929
|
+
identityProviderId,
|
|
2898
2930
|
)
|
|
2899
|
-
->Pulumi.Output.
|
|
2931
|
+
->Pulumi.Output.all5
|
|
2900
2932
|
->Pulumi.Output.apply((
|
|
2901
|
-
(structure, objectStores, placeIndex, emailSender): (
|
|
2933
|
+
(structure, objectStores, placeIndex, emailSender, poolId): (
|
|
2934
|
+
_,
|
|
2935
|
+
option<JSON.t>,
|
|
2936
|
+
string,
|
|
2937
|
+
string,
|
|
2938
|
+
string,
|
|
2939
|
+
),
|
|
2902
2940
|
) => {
|
|
2903
2941
|
// ── Declared capabilities ──────────────────────────────────────────
|
|
2904
2942
|
//
|
|
@@ -2922,10 +2960,11 @@ module MakeWithConfig = (
|
|
|
2922
2960
|
// The values the slice Lambdas are actually handed, so this reads
|
|
2923
2961
|
// what decides the outcome rather than a second record of the same
|
|
2924
2962
|
// intent. `""` is what an unprovisioned platform exports.
|
|
2925
|
-
~provisioned=
|
|
2963
|
+
~provisioned=[
|
|
2926
2964
|
placeIndex == "" ? [] : [Reventless.CapabilityNeed.Geocoding],
|
|
2927
2965
|
emailSender == "" ? [] : [Reventless.CapabilityNeed.Messaging],
|
|
2928
|
-
|
|
2966
|
+
poolId == "" ? [] : [Reventless.CapabilityNeed.IdentityProvider],
|
|
2967
|
+
]->Array.flat,
|
|
2929
2968
|
) {
|
|
2930
2969
|
| [] => ()
|
|
2931
2970
|
| missing =>
|
|
@@ -2942,9 +2981,7 @@ module MakeWithConfig = (
|
|
|
2942
2981
|
`default: set \`platform:messagingEmailSender\` in Pulumi.<stack>.yaml (or ` ++
|
|
2943
2982
|
`REVENTLESS_MESSAGING_EMAIL_SENDER), then verify the address with SES — or ` ++ `\`platform:messagingEmailProvider: log\` to log every message and send none.`
|
|
2944
2983
|
| IdentityProvider =>
|
|
2945
|
-
`\n IdentityProvider
|
|
2946
|
-
`refusal exist, the Cognito and local implementations do not. A plugin ` ++
|
|
2947
|
-
`declaring this need cannot be deployed until one lands; nothing about this ` ++ `deployment's configuration will change that.`
|
|
2984
|
+
`\n IdentityProvider is the platform's Cognito pool, exported as ` ++ `\`identityProviderId\`. This platform stack exports none — deploy it first.`
|
|
2948
2985
|
}
|
|
2949
2986
|
)
|
|
2950
2987
|
// One line per capability, not per declaring component: two slices
|