@reventlessdev/reventless-aws 3.0.0-alpha.395 → 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 +8 -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 +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/src/Platform.res.mjs
CHANGED
|
@@ -1412,6 +1412,20 @@ function MakeWithConfig(Config) {
|
|
|
1412
1412
|
PluginRuntime_Builder$ReventlessAws.registerMessagingSender(messagingEmailSender);
|
|
1413
1413
|
let messagingEmailProvider = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("messagingEmailProvider").apply(o => Stdlib_Option.getOr(o, "")) : messagingEmailProviderRef.contents;
|
|
1414
1414
|
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("MESSAGING_EMAIL_PROVIDER", messagingEmailProvider);
|
|
1415
|
+
let platformOutput = (name, local) => {
|
|
1416
|
+
if (platformStackRef !== undefined) {
|
|
1417
|
+
return Primitive_option.valFromOption(platformStackRef).getOutput(name).apply(o => Stdlib_Option.getOr(o, ""));
|
|
1418
|
+
} else {
|
|
1419
|
+
return local();
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
let identityProviderId = platformOutput("identityProviderId", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().poolId);
|
|
1423
|
+
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("IDENTITY_PROVIDER_ID", identityProviderId);
|
|
1424
|
+
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("IDENTITY_STORE_TABLE", platformOutput("identityStore", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().identityTable.name));
|
|
1425
|
+
PluginRuntime_Builder$ReventlessAws.registerIdentityProvider({
|
|
1426
|
+
poolArn: platformOutput("identityProviderArn", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().poolArn),
|
|
1427
|
+
storeArn: platformOutput("identityStoreArn", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().identityTable.arn)
|
|
1428
|
+
});
|
|
1415
1429
|
let pluginComponent = plugin.make();
|
|
1416
1430
|
Plugin_Helpers$ReventlessCore.clearOffload();
|
|
1417
1431
|
currentDeployTarget.contents = "Domain";
|
|
@@ -1469,15 +1483,18 @@ function MakeWithConfig(Config) {
|
|
|
1469
1483
|
pluginOutputs.pluginStructure,
|
|
1470
1484
|
stackRef.getOutput("objectStores"),
|
|
1471
1485
|
geocoderPlaceIndex,
|
|
1472
|
-
messagingEmailSender
|
|
1486
|
+
messagingEmailSender,
|
|
1487
|
+
identityProviderId
|
|
1473
1488
|
]).apply(param => {
|
|
1474
1489
|
let structure = param[0];
|
|
1475
1490
|
let missing = CapabilityNeed$Reventless.unmet(Stdlib_Option.getOr(Stdlib_Option.flatMap(structure, s => s.requiredCapabilities), []).map(d => [
|
|
1476
1491
|
d.capability,
|
|
1477
1492
|
d.component
|
|
1478
|
-
]),
|
|
1479
|
-
param[2] === "" ? [] : ["Geocoding"]
|
|
1480
|
-
|
|
1493
|
+
]), [
|
|
1494
|
+
param[2] === "" ? [] : ["Geocoding"],
|
|
1495
|
+
param[3] === "" ? [] : ["Messaging"],
|
|
1496
|
+
param[4] === "" ? [] : ["IdentityProvider"]
|
|
1497
|
+
].flat());
|
|
1481
1498
|
if (missing.length !== 0) {
|
|
1482
1499
|
Stdlib_JsError.throwWithMessage(CapabilityNeed$Reventless.unmetMessage(missing) + Belt_SetString.toArray(Belt_SetString.fromArray(missing.map(u => {
|
|
1483
1500
|
let match = u.need;
|
|
@@ -1487,7 +1504,7 @@ function MakeWithConfig(Config) {
|
|
|
1487
1504
|
case "Messaging" :
|
|
1488
1505
|
return `\n Messaging is \`Capability_Messaging.make\`, passed to the platform as \`~messagingSender\`. The sender itself is configuration and has no default: set \`platform:messagingEmailSender\` in Pulumi.<stack>.yaml (or REVENTLESS_MESSAGING_EMAIL_SENDER), then verify the address with SES — or \`platform:messagingEmailProvider: log\` to log every message and send none.`;
|
|
1489
1506
|
case "IdentityProvider" :
|
|
1490
|
-
return `\n IdentityProvider
|
|
1507
|
+
return `\n IdentityProvider is the platform's Cognito pool, exported as \`identityProviderId\`. This platform stack exports none — deploy it first.`;
|
|
1491
1508
|
}
|
|
1492
1509
|
}))).join(""));
|
|
1493
1510
|
}
|
|
@@ -2804,6 +2821,20 @@ function Make($star) {
|
|
|
2804
2821
|
PluginRuntime_Builder$ReventlessAws.registerMessagingSender(messagingEmailSender);
|
|
2805
2822
|
let messagingEmailProvider = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("messagingEmailProvider").apply(o => Stdlib_Option.getOr(o, "")) : messagingEmailProviderRef.contents;
|
|
2806
2823
|
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("MESSAGING_EMAIL_PROVIDER", messagingEmailProvider);
|
|
2824
|
+
let platformOutput = (name, local) => {
|
|
2825
|
+
if (platformStackRef !== undefined) {
|
|
2826
|
+
return Primitive_option.valFromOption(platformStackRef).getOutput(name).apply(o => Stdlib_Option.getOr(o, ""));
|
|
2827
|
+
} else {
|
|
2828
|
+
return local();
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
let identityProviderId = platformOutput("identityProviderId", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().poolId);
|
|
2832
|
+
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("IDENTITY_PROVIDER_ID", identityProviderId);
|
|
2833
|
+
PluginRuntime_Builder$ReventlessAws.registerCapabilityEnv("IDENTITY_STORE_TABLE", platformOutput("identityStore", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().identityTable.name));
|
|
2834
|
+
PluginRuntime_Builder$ReventlessAws.registerIdentityProvider({
|
|
2835
|
+
poolArn: platformOutput("identityProviderArn", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().poolArn),
|
|
2836
|
+
storeArn: platformOutput("identityStoreArn", () => Platform_Stack$ReventlessAws.resolveCognitoUserPool().identityTable.arn)
|
|
2837
|
+
});
|
|
2807
2838
|
let pluginComponent = plugin.make();
|
|
2808
2839
|
Plugin_Helpers$ReventlessCore.clearOffload();
|
|
2809
2840
|
currentDeployTarget.contents = "Domain";
|
|
@@ -2861,15 +2892,18 @@ function Make($star) {
|
|
|
2861
2892
|
pluginOutputs.pluginStructure,
|
|
2862
2893
|
stackRef.getOutput("objectStores"),
|
|
2863
2894
|
geocoderPlaceIndex,
|
|
2864
|
-
messagingEmailSender
|
|
2895
|
+
messagingEmailSender,
|
|
2896
|
+
identityProviderId
|
|
2865
2897
|
]).apply(param => {
|
|
2866
2898
|
let structure = param[0];
|
|
2867
2899
|
let missing = CapabilityNeed$Reventless.unmet(Stdlib_Option.getOr(Stdlib_Option.flatMap(structure, s => s.requiredCapabilities), []).map(d => [
|
|
2868
2900
|
d.capability,
|
|
2869
2901
|
d.component
|
|
2870
|
-
]),
|
|
2871
|
-
param[2] === "" ? [] : ["Geocoding"]
|
|
2872
|
-
|
|
2902
|
+
]), [
|
|
2903
|
+
param[2] === "" ? [] : ["Geocoding"],
|
|
2904
|
+
param[3] === "" ? [] : ["Messaging"],
|
|
2905
|
+
param[4] === "" ? [] : ["IdentityProvider"]
|
|
2906
|
+
].flat());
|
|
2873
2907
|
if (missing.length !== 0) {
|
|
2874
2908
|
Stdlib_JsError.throwWithMessage(CapabilityNeed$Reventless.unmetMessage(missing) + Belt_SetString.toArray(Belt_SetString.fromArray(missing.map(u => {
|
|
2875
2909
|
let match = u.need;
|
|
@@ -2879,7 +2913,7 @@ function Make($star) {
|
|
|
2879
2913
|
case "Messaging" :
|
|
2880
2914
|
return `\n Messaging is \`Capability_Messaging.make\`, passed to the platform as \`~messagingSender\`. The sender itself is configuration and has no default: set \`platform:messagingEmailSender\` in Pulumi.<stack>.yaml (or REVENTLESS_MESSAGING_EMAIL_SENDER), then verify the address with SES — or \`platform:messagingEmailProvider: log\` to log every message and send none.`;
|
|
2881
2915
|
case "IdentityProvider" :
|
|
2882
|
-
return `\n IdentityProvider
|
|
2916
|
+
return `\n IdentityProvider is the platform's Cognito pool, exported as \`identityProviderId\`. This platform stack exports none — deploy it first.`;
|
|
2883
2917
|
}
|
|
2884
2918
|
}))).join(""));
|
|
2885
2919
|
}
|
package/src/Platform_Stack.res
CHANGED
|
@@ -12,6 +12,9 @@ type cognitoUserPool = {
|
|
|
12
12
|
is declared with the trigger's ARN), while the mutation that writes it is
|
|
13
13
|
provisioned after the API — see [Auth_ActiveRoleStore.res]. */
|
|
14
14
|
activeRoleTable: Auth_ActiveRoleStore.storeTable,
|
|
15
|
+
/** Rows mapping each subject to the domain's own user id, read by the same
|
|
16
|
+
trigger and written when an account is created — see [Auth_IdentityStore.res]. */
|
|
17
|
+
identityTable: Auth_IdentityStore.storeTable,
|
|
15
18
|
/** The sign-in attribute this stack created the pool with. `None` in BYO mode:
|
|
16
19
|
the pool's own choice was made elsewhere and no lookup here reads it back,
|
|
17
20
|
so reporting one would be a guess. */
|
|
@@ -179,9 +182,16 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
179
182
|
~choice=Auth_ActiveRoleStore.chooseStore(~identityProviderId=existingPoolId),
|
|
180
183
|
~opts={},
|
|
181
184
|
)
|
|
185
|
+
// Same scope rule, same reason: the trigger that reads it is the pool's.
|
|
186
|
+
let identityTable = Auth_IdentityStore.resolveTable(
|
|
187
|
+
~choice=Auth_IdentityStore.chooseStore(~identityProviderId=existingPoolId),
|
|
188
|
+
~opts={},
|
|
189
|
+
)
|
|
182
190
|
let activeRoleTrigger = Auth_ActiveRoleTrigger.make(
|
|
183
191
|
~activeRoleTableName=activeRoleTable.name->Pulumi.Output.asInput,
|
|
184
192
|
~activeRoleTableArn=activeRoleTable.arn->Pulumi.Output.asInput,
|
|
193
|
+
~identityTableName=identityTable.name->Pulumi.Output.asInput,
|
|
194
|
+
~identityTableArn=identityTable.arn->Pulumi.Output.asInput,
|
|
185
195
|
~opts={},
|
|
186
196
|
)
|
|
187
197
|
|
|
@@ -239,6 +249,7 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
239
249
|
poolArn: lookup->Pulumi.Output.apply(r => r.arn),
|
|
240
250
|
managed: false,
|
|
241
251
|
activeRoleTable,
|
|
252
|
+
identityTable,
|
|
242
253
|
loginIdentifier: None,
|
|
243
254
|
}
|
|
244
255
|
|
|
@@ -334,6 +345,7 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
334
345
|
poolArn: pool.arn,
|
|
335
346
|
managed: true,
|
|
336
347
|
activeRoleTable,
|
|
348
|
+
identityTable,
|
|
337
349
|
loginIdentifier: Some(loginIdentifier),
|
|
338
350
|
}
|
|
339
351
|
}
|
|
@@ -409,6 +421,11 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
409
421
|
// the name is derived, not configured, so this is a convenience for checking
|
|
410
422
|
// rather than a value anything is meant to copy into a config.
|
|
411
423
|
Pulumi.Pulumi.export("activeRoleStore", result.activeRoleTable.name)
|
|
424
|
+
// Read by `provision-accounts` on a stack-owned pool, whose table carries a
|
|
425
|
+
// generated suffix no derivation can produce.
|
|
426
|
+
Pulumi.Pulumi.export("identityStore", result.identityTable.name)
|
|
427
|
+
// What a plugin stack grants its principal-making slices write access to.
|
|
428
|
+
Pulumi.Pulumi.export("identityStoreArn", result.identityTable.arn)
|
|
412
429
|
|
|
413
430
|
result
|
|
414
431
|
}
|
|
@@ -10,6 +10,7 @@ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/
|
|
|
10
10
|
import * as AWS_Tags$ReventlessAws from "./adapter/AWS_Tags.res.mjs";
|
|
11
11
|
import * as Auth_SignUpMode$ReventlessAws from "./adapter/Auth/Auth_SignUpMode.res.mjs";
|
|
12
12
|
import * as Util_LocalConfig$ReventlessAws from "./util/Util_LocalConfig.res.mjs";
|
|
13
|
+
import * as Auth_IdentityStore$ReventlessAws from "./adapter/Auth/Auth_IdentityStore.res.mjs";
|
|
13
14
|
import * as Auth_ActiveRoleStore$ReventlessAws from "./adapter/Auth/Auth_ActiveRoleStore.res.mjs";
|
|
14
15
|
import * as Auth_LoginIdentifier$ReventlessAws from "./adapter/Auth/Auth_LoginIdentifier.res.mjs";
|
|
15
16
|
import * as Util_CognitoGroupUser$ReventlessAws from "./util/Util_CognitoGroupUser.res.mjs";
|
|
@@ -66,7 +67,8 @@ function _resolveUncached() {
|
|
|
66
67
|
let cfg = new Pulumi.Config("platform");
|
|
67
68
|
let existingPoolId = _identityProviderId(cfg);
|
|
68
69
|
let activeRoleTable = Auth_ActiveRoleStore$ReventlessAws.resolveTable(Auth_ActiveRoleStore$ReventlessAws.chooseStore(existingPoolId), {});
|
|
69
|
-
let
|
|
70
|
+
let identityTable = Auth_IdentityStore$ReventlessAws.resolveTable(Auth_IdentityStore$ReventlessAws.chooseStore(existingPoolId), {});
|
|
71
|
+
let activeRoleTrigger = Auth_ActiveRoleTrigger$ReventlessAws.make(activeRoleTable.name, activeRoleTable.arn, identityTable.name, identityTable.arn, undefined, {});
|
|
70
72
|
let result;
|
|
71
73
|
if (existingPoolId !== undefined) {
|
|
72
74
|
let tokenUnits_accessToken = "minutes";
|
|
@@ -105,6 +107,7 @@ function _resolveUncached() {
|
|
|
105
107
|
poolArn: lookup.apply(r => r.arn),
|
|
106
108
|
managed: false,
|
|
107
109
|
activeRoleTable: activeRoleTable,
|
|
110
|
+
identityTable: identityTable,
|
|
108
111
|
loginIdentifier: undefined
|
|
109
112
|
};
|
|
110
113
|
} else {
|
|
@@ -161,6 +164,7 @@ function _resolveUncached() {
|
|
|
161
164
|
poolArn: pool.arn,
|
|
162
165
|
managed: true,
|
|
163
166
|
activeRoleTable: activeRoleTable,
|
|
167
|
+
identityTable: identityTable,
|
|
164
168
|
loginIdentifier: loginIdentifier
|
|
165
169
|
};
|
|
166
170
|
}
|
|
@@ -183,6 +187,8 @@ function _resolveUncached() {
|
|
|
183
187
|
Pulumi$Pulumi.$$export("cognitoUserPoolManaged", managedStr);
|
|
184
188
|
Pulumi$Pulumi.$$export("identityProviderAdminGroup", Pulumi.output(Role$Reventless.adminGroup()));
|
|
185
189
|
Pulumi$Pulumi.$$export("activeRoleStore", result.activeRoleTable.name);
|
|
190
|
+
Pulumi$Pulumi.$$export("identityStore", result.identityTable.name);
|
|
191
|
+
Pulumi$Pulumi.$$export("identityStoreArn", result.identityTable.arn);
|
|
186
192
|
return result;
|
|
187
193
|
}
|
|
188
194
|
|
|
@@ -30,6 +30,8 @@ type triggerOutputs = {
|
|
|
30
30
|
let make = (
|
|
31
31
|
~activeRoleTableName: Pulumi.Input.t<string>,
|
|
32
32
|
~activeRoleTableArn: Pulumi.Input.t<string>,
|
|
33
|
+
~identityTableName: Pulumi.Input.t<string>,
|
|
34
|
+
~identityTableArn: Pulumi.Input.t<string>,
|
|
33
35
|
~name: string="ActiveRoleTrigger",
|
|
34
36
|
~opts: Pulumi.ComponentResource.options,
|
|
35
37
|
): triggerOutputs => {
|
|
@@ -47,14 +49,14 @@ let make = (
|
|
|
47
49
|
~opts,
|
|
48
50
|
)
|
|
49
51
|
|
|
50
|
-
// Logs and
|
|
51
|
-
// permission at all, because the membership it checks
|
|
52
|
-
// event as `request.groupConfiguration.groupsToOverride`.
|
|
53
|
-
// this function must never be able to write
|
|
52
|
+
// Logs and single-item reads of the two stores. Nothing else: the trigger
|
|
53
|
+
// needs no Cognito permission at all, because the membership it checks
|
|
54
|
+
// against arrives in the event as `request.groupConfiguration.groupsToOverride`.
|
|
55
|
+
// `GetItem` alone — this function must never be able to write what it reads.
|
|
54
56
|
let _policy =
|
|
55
|
-
activeRoleTableArn
|
|
56
|
-
->Pulumi.Output.
|
|
57
|
-
->Pulumi.Output.apply(tableArn => {
|
|
57
|
+
(activeRoleTableArn->Pulumi.Output.fromInput, identityTableArn->Pulumi.Output.fromInput)
|
|
58
|
+
->Pulumi.Output.all2
|
|
59
|
+
->Pulumi.Output.apply(((tableArn, identityArn)) => {
|
|
58
60
|
let _ = IAM.RolePolicy.make(
|
|
59
61
|
~name=name ++ "Policy",
|
|
60
62
|
~args={
|
|
@@ -77,6 +79,12 @@ let make = (
|
|
|
77
79
|
actions: Action("dynamodb:GetItem"),
|
|
78
80
|
resources: Resource(tableArn),
|
|
79
81
|
},
|
|
82
|
+
{
|
|
83
|
+
sid: "AllowReadingDomainId",
|
|
84
|
+
effect: Allow,
|
|
85
|
+
actions: Action("dynamodb:GetItem"),
|
|
86
|
+
resources: Resource(identityArn),
|
|
87
|
+
},
|
|
80
88
|
],
|
|
81
89
|
)
|
|
82
90
|
->PolicyDocument.toJsonString
|
|
@@ -140,6 +148,7 @@ let make = (
|
|
|
140
148
|
Lambda.Function.variables: Dict.fromArray([
|
|
141
149
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
142
150
|
("ACTIVE_ROLE_TABLE", activeRoleTableName),
|
|
151
|
+
("IDENTITY_STORE_TABLE", identityTableName),
|
|
143
152
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
144
153
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
145
154
|
Util_LambdaLogging.logLevelEntry(),
|
|
@@ -11,11 +11,14 @@ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
|
11
11
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
12
12
|
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
13
13
|
|
|
14
|
-
function make(activeRoleTableName, activeRoleTableArn, nameOpt, opts) {
|
|
14
|
+
function make(activeRoleTableName, activeRoleTableArn, identityTableName, identityTableArn, nameOpt, opts) {
|
|
15
15
|
let name = nameOpt !== undefined ? nameOpt : "ActiveRoleTrigger";
|
|
16
16
|
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
17
17
|
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
18
|
-
|
|
18
|
+
Pulumi.all([
|
|
19
|
+
activeRoleTableArn,
|
|
20
|
+
identityTableArn
|
|
21
|
+
]).apply(param => {
|
|
19
22
|
new (Aws.iam.RolePolicy)(name + "Policy", {
|
|
20
23
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "Policy", [
|
|
21
24
|
{
|
|
@@ -32,7 +35,13 @@ function make(activeRoleTableName, activeRoleTableArn, nameOpt, opts) {
|
|
|
32
35
|
Sid: "AllowReadingStoredRole",
|
|
33
36
|
Effect: "Allow",
|
|
34
37
|
Action: "dynamodb:GetItem",
|
|
35
|
-
Resource:
|
|
38
|
+
Resource: param[0]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
Sid: "AllowReadingDomainId",
|
|
42
|
+
Effect: "Allow",
|
|
43
|
+
Action: "dynamodb:GetItem",
|
|
44
|
+
Resource: param[1]
|
|
36
45
|
}
|
|
37
46
|
])),
|
|
38
47
|
role: lambdaRole.id
|
|
@@ -65,6 +74,10 @@ function make(activeRoleTableName, activeRoleTableArn, nameOpt, opts) {
|
|
|
65
74
|
"ACTIVE_ROLE_TABLE",
|
|
66
75
|
activeRoleTableName
|
|
67
76
|
],
|
|
77
|
+
[
|
|
78
|
+
"IDENTITY_STORE_TABLE",
|
|
79
|
+
identityTableName
|
|
80
|
+
],
|
|
68
81
|
[
|
|
69
82
|
"NODE_OPTIONS",
|
|
70
83
|
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
@@ -41,6 +41,11 @@ let tableName = (): string =>
|
|
|
41
41
|
| None => JsError.throwWithMessage("ACTIVE_ROLE_TABLE is not configured")
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/** The identity store, read for the domain id claim. Optional where the role
|
|
45
|
+
store is not: a trigger deployed before the store existed must keep minting
|
|
46
|
+
tokens, and an absent store means exactly what an absent row means. */
|
|
47
|
+
let identityTableName = (): option<string> => getEnv("IDENTITY_STORE_TABLE")
|
|
48
|
+
|
|
44
49
|
// ── Cognito pre-token-generation event (V1_0) ───────────────────────────────
|
|
45
50
|
|
|
46
51
|
type userAttributes = {sub?: string}
|
|
@@ -124,16 +129,24 @@ exactly one thing.
|
|
|
124
129
|
`claimsOverrideDetails` is not the same as none, and the regression line for this
|
|
125
130
|
whole feature is that a caller with no stored role gets byte-identical output.
|
|
126
131
|
*/
|
|
127
|
-
let respond = (~event: event, ~decision: decision): event => {
|
|
132
|
+
let respond = (~event: event, ~decision: decision, ~userId: option<string>=?): event => {
|
|
128
133
|
let incoming = event.request->Option.flatMap(r => r.groupConfiguration)
|
|
129
134
|
let iamRolesToOverride = incoming->Option.flatMap(g => g.iamRolesToOverride)->Option.getOr([])
|
|
130
135
|
let preferredRole = incoming->Option.flatMap(g => g.preferredRole)
|
|
131
136
|
|
|
137
|
+
// The domain id rides on whatever else the mint adds. On its own it adds only
|
|
138
|
+
// the claim and leaves the group configuration alone, so a caller with a
|
|
139
|
+
// domain id and no stored role keeps exactly the groups the pool granted.
|
|
140
|
+
let withUserId = (claims: dict<string>) => {
|
|
141
|
+
userId->Option.forEach(id => claims->Dict.set(ReventlessCore.Auth_UserId.claim, id))
|
|
142
|
+
claims
|
|
143
|
+
}
|
|
144
|
+
|
|
132
145
|
let overrideWith = (~groups: array<string>, ~claims: dict<string>) => {
|
|
133
146
|
...event,
|
|
134
147
|
response: {
|
|
135
148
|
claimsOverrideDetails: {
|
|
136
|
-
claimsToAddOrOverride: claims,
|
|
149
|
+
claimsToAddOrOverride: withUserId(claims),
|
|
137
150
|
groupOverrideDetails: {
|
|
138
151
|
groupsToOverride: groups,
|
|
139
152
|
iamRolesToOverride,
|
|
@@ -143,9 +156,13 @@ let respond = (~event: event, ~decision: decision): event => {
|
|
|
143
156
|
},
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
switch decision {
|
|
147
|
-
| Unchanged => event
|
|
148
|
-
|
|
|
159
|
+
switch (decision, userId) {
|
|
160
|
+
| (Unchanged, None) => event
|
|
161
|
+
| (Unchanged, Some(_)) => {
|
|
162
|
+
...event,
|
|
163
|
+
response: {claimsOverrideDetails: {claimsToAddOrOverride: withUserId(Dict.make())}},
|
|
164
|
+
}
|
|
165
|
+
| (Narrow({role, membership}), _) =>
|
|
149
166
|
overrideWith(
|
|
150
167
|
~groups=[role],
|
|
151
168
|
~claims=Dict.fromArray([
|
|
@@ -153,7 +170,7 @@ let respond = (~event: event, ~decision: decision): event => {
|
|
|
153
170
|
(ReventlessCore.Auth_ActiveRole.availableRolesClaim, membership->Array.join(",")),
|
|
154
171
|
]),
|
|
155
172
|
)
|
|
156
|
-
| Stale({role, membership}) =>
|
|
173
|
+
| (Stale({role, membership}), _) =>
|
|
157
174
|
// The full set, as the pool granted it — plus the marker that explains why the
|
|
158
175
|
// stored choice did not apply.
|
|
159
176
|
overrideWith(
|
|
@@ -194,6 +211,33 @@ let storedRoleFor = async (~sub: string, ~clientId: string, ~table: string): opt
|
|
|
194
211
|
| _ => None
|
|
195
212
|
}
|
|
196
213
|
|
|
214
|
+
/**
|
|
215
|
+
The domain id minted for this subject, or `None` for an account created before
|
|
216
|
+
the store existed — whose id is its `sub`, and must stay so: every row it has
|
|
217
|
+
written is keyed by it.
|
|
218
|
+
|
|
219
|
+
Fails to `None` for the reason [storedRoleFor] does: a claim this trigger could
|
|
220
|
+
not read is a login that must still succeed, and a caller without the claim
|
|
221
|
+
lands on the fallback every older account already uses.
|
|
222
|
+
*/
|
|
223
|
+
let userIdFor = async (~sub: string, ~table: option<string>): option<string> =>
|
|
224
|
+
switch table {
|
|
225
|
+
| None => None
|
|
226
|
+
| Some(table) =>
|
|
227
|
+
try {
|
|
228
|
+
let out = await DynamoDb_DocumentClient.GetCommand.make({
|
|
229
|
+
tableName: table,
|
|
230
|
+
key: Dict.fromArray([(Auth_IdentityStore_Schema.partitionKey, JSON.Encode.string(sub))]),
|
|
231
|
+
})->DynamoDb_DocumentClient.GetCommand.send
|
|
232
|
+
out.item
|
|
233
|
+
->Option.flatMap(JSON.Decode.object)
|
|
234
|
+
->Option.flatMap(o => o->Dict.get(Auth_IdentityStore_Schema.userIdAttribute))
|
|
235
|
+
->Option.flatMap(JSON.Decode.string)
|
|
236
|
+
} catch {
|
|
237
|
+
| _ => None
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
197
241
|
// ── Handler ─────────────────────────────────────────────────────────────────
|
|
198
242
|
|
|
199
243
|
let handler = async (event: event): event => {
|
|
@@ -216,10 +260,16 @@ let handler = async (event: event): event => {
|
|
|
216
260
|
// granted — the safe direction, and the same one a read failure takes. An
|
|
217
261
|
// absent client id is not worth failing a login over: the caller lands on their
|
|
218
262
|
// full membership, which is their existing privileges and not more.
|
|
219
|
-
if sub == ""
|
|
263
|
+
if sub == "" {
|
|
220
264
|
event
|
|
265
|
+
} else if clientId == "" {
|
|
266
|
+
respond(~event, ~decision=Unchanged, ~userId=?await userIdFor(~sub, ~table=identityTableName()))
|
|
221
267
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
268
|
+
// Both reads at once: this function is a login's latency.
|
|
269
|
+
let (storedRole, userId) = await Promise.all2((
|
|
270
|
+
storedRoleFor(~sub, ~clientId, ~table=tableName()),
|
|
271
|
+
userIdFor(~sub, ~table=identityTableName()),
|
|
272
|
+
))
|
|
273
|
+
respond(~event, ~decision=decide(~membership, ~storedRole), ~userId?)
|
|
224
274
|
}
|
|
225
275
|
}
|
|
@@ -4,8 +4,10 @@ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
6
|
import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
|
|
7
|
+
import * as Auth_UserId$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_UserId.res.mjs";
|
|
7
8
|
import * as Auth_ActiveRole$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_ActiveRole.res.mjs";
|
|
8
9
|
import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
|
|
10
|
+
import * as Auth_IdentityStore_Schema$ReventlessAws from "./Auth_IdentityStore_Schema.res.mjs";
|
|
9
11
|
import * as Auth_ActiveRoleStore_Schema$ReventlessAws from "./Auth_ActiveRoleStore_Schema.res.mjs";
|
|
10
12
|
|
|
11
13
|
function getEnv(k) {
|
|
@@ -24,6 +26,10 @@ function tableName() {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
function identityTableName() {
|
|
30
|
+
return getEnv("IDENTITY_STORE_TABLE");
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
function decide(membership, storedRole) {
|
|
28
34
|
if (storedRole !== undefined && storedRole !== "") {
|
|
29
35
|
if (membership.includes(storedRole)) {
|
|
@@ -44,15 +50,21 @@ function decide(membership, storedRole) {
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
function respond(event, decision) {
|
|
53
|
+
function respond(event, decision, userId) {
|
|
48
54
|
let incoming = Stdlib_Option.flatMap(event.request, r => r.groupConfiguration);
|
|
49
55
|
let iamRolesToOverride = Stdlib_Option.getOr(Stdlib_Option.flatMap(incoming, g => g.iamRolesToOverride), []);
|
|
50
56
|
let preferredRole = Stdlib_Option.flatMap(incoming, g => g.preferredRole);
|
|
57
|
+
let withUserId = claims => {
|
|
58
|
+
Stdlib_Option.forEach(userId, id => {
|
|
59
|
+
claims[Auth_UserId$ReventlessCore.claim] = id;
|
|
60
|
+
});
|
|
61
|
+
return claims;
|
|
62
|
+
};
|
|
51
63
|
let overrideWith = (groups, claims) => {
|
|
52
64
|
let newrecord = {...event};
|
|
53
65
|
newrecord.response = {
|
|
54
66
|
claimsOverrideDetails: {
|
|
55
|
-
claimsToAddOrOverride: claims,
|
|
67
|
+
claimsToAddOrOverride: withUserId(claims),
|
|
56
68
|
groupOverrideDetails: {
|
|
57
69
|
groupsToOverride: groups,
|
|
58
70
|
iamRolesToOverride: iamRolesToOverride,
|
|
@@ -63,7 +75,16 @@ function respond(event, decision) {
|
|
|
63
75
|
return newrecord;
|
|
64
76
|
};
|
|
65
77
|
if (typeof decision !== "object") {
|
|
66
|
-
|
|
78
|
+
if (userId === undefined) {
|
|
79
|
+
return event;
|
|
80
|
+
}
|
|
81
|
+
let newrecord = {...event};
|
|
82
|
+
newrecord.response = {
|
|
83
|
+
claimsOverrideDetails: {
|
|
84
|
+
claimsToAddOrOverride: withUserId({})
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return newrecord;
|
|
67
88
|
}
|
|
68
89
|
if (decision.TAG === "Narrow") {
|
|
69
90
|
let role = decision.role;
|
|
@@ -112,23 +133,49 @@ async function storedRoleFor(sub, clientId, table) {
|
|
|
112
133
|
}
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
async function userIdFor(sub, table) {
|
|
137
|
+
if (table === undefined) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
let out = await DynamoDb_DocumentClient$AwsSdk.GetCommand.send(new LibDynamodb.GetCommand({
|
|
142
|
+
TableName: table,
|
|
143
|
+
Key: Object.fromEntries([[
|
|
144
|
+
Auth_IdentityStore_Schema$ReventlessAws.partitionKey,
|
|
145
|
+
sub
|
|
146
|
+
]])
|
|
147
|
+
}));
|
|
148
|
+
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);
|
|
149
|
+
} catch (exn) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
115
154
|
async function handler(event) {
|
|
116
155
|
let membership = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(event.request, r => r.groupConfiguration), g => g.groupsToOverride), []);
|
|
117
156
|
let sub = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(event.request, r => r.userAttributes), u => u.sub), "");
|
|
118
157
|
let clientId = Stdlib_Option.getOr(Stdlib_Option.flatMap(event.callerContext, c => c.clientId), "");
|
|
119
|
-
if (sub === ""
|
|
158
|
+
if (sub === "") {
|
|
120
159
|
return event;
|
|
121
160
|
}
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
if (clientId === "") {
|
|
162
|
+
return respond(event, "Unchanged", await userIdFor(sub, getEnv("IDENTITY_STORE_TABLE")));
|
|
163
|
+
}
|
|
164
|
+
let match = await Promise.all([
|
|
165
|
+
storedRoleFor(sub, clientId, tableName()),
|
|
166
|
+
userIdFor(sub, getEnv("IDENTITY_STORE_TABLE"))
|
|
167
|
+
]);
|
|
168
|
+
return respond(event, decide(membership, match[0]), match[1]);
|
|
124
169
|
}
|
|
125
170
|
|
|
126
171
|
export {
|
|
127
172
|
getEnv,
|
|
128
173
|
tableName,
|
|
174
|
+
identityTableName,
|
|
129
175
|
decide,
|
|
130
176
|
respond,
|
|
131
177
|
storedRoleFor,
|
|
178
|
+
userIdFor,
|
|
132
179
|
handler,
|
|
133
180
|
}
|
|
134
181
|
/* @aws-sdk/lib-dynamodb Not a pure module */
|
|
@@ -58,13 +58,24 @@ let _getHeader = (headers: dict<string>, name: string): option<string> =>
|
|
|
58
58
|
| None => headers->Dict.get(name)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// The domain id when the token carries one, else the `sub`. The fallback is
|
|
62
|
+
// what every account created before the identity store existed relies on: its
|
|
63
|
+
// rows are keyed by the `sub`, and a different answer here would orphan them.
|
|
64
|
+
let _userIdOf = (~claims: dict<JSON.t>, ~sub: string): string =>
|
|
65
|
+
claims
|
|
66
|
+
->Dict.get(ReventlessCore.Auth_UserId.claim)
|
|
67
|
+
->Option.flatMap(JSON.Decode.string)
|
|
68
|
+
->Option.filter(id => id != "")
|
|
69
|
+
->Option.getOr(sub)
|
|
70
|
+
|
|
61
71
|
let _identityFromClaims = (claims: 'a): Identity.t => {
|
|
62
72
|
let claimsDict: dict<JSON.t> = claims->Obj.magic
|
|
63
|
-
let
|
|
73
|
+
let sub =
|
|
64
74
|
claimsDict
|
|
65
75
|
->Dict.get("sub")
|
|
66
76
|
->Option.flatMap(JSON.Decode.string)
|
|
67
77
|
->Option.getOr("anonymous")
|
|
78
|
+
let userId = _userIdOf(~claims=claimsDict, ~sub)
|
|
68
79
|
let username =
|
|
69
80
|
claimsDict
|
|
70
81
|
->Dict.get("cognito:username")
|
|
@@ -154,7 +165,7 @@ let fromAppSyncIdentity = (id: option<appSyncIdentity>): Identity.authResult =>
|
|
|
154
165
|
->Array.map(((k, v)) => (k, v->JSON.Decode.string->Option.getOr(v->JSON.stringify)))
|
|
155
166
|
->Dict.fromArray
|
|
156
167
|
Authenticated({
|
|
157
|
-
userId: sub,
|
|
168
|
+
userId: _userIdOf(~claims=rawClaims, ~sub),
|
|
158
169
|
username,
|
|
159
170
|
groups,
|
|
160
171
|
claims,
|
|
@@ -4,6 +4,7 @@ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as Pulumi from "@pulumi/pulumi";
|
|
6
6
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
|
+
import * as Auth_UserId$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_UserId.res.mjs";
|
|
7
8
|
import * as Platform_Stack$ReventlessAws from "../../Platform_Stack.res.mjs";
|
|
8
9
|
|
|
9
10
|
let _decodeJwtClaims = (function(token) {
|
|
@@ -31,8 +32,13 @@ function _getHeader(headers, name) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function _userIdOf(claims, sub) {
|
|
36
|
+
return Stdlib_Option.getOr(Stdlib_Option.filter(Stdlib_Option.flatMap(claims[Auth_UserId$ReventlessCore.claim], Stdlib_JSON.Decode.string), id => id !== ""), sub);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
function _identityFromClaims(claims) {
|
|
35
|
-
let
|
|
40
|
+
let sub = Stdlib_Option.getOr(Stdlib_Option.flatMap(claims["sub"], Stdlib_JSON.Decode.string), "anonymous");
|
|
41
|
+
let userId = _userIdOf(claims, sub);
|
|
36
42
|
let username = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.orElse(claims["cognito:username"], claims["username"]), Stdlib_JSON.Decode.string), userId);
|
|
37
43
|
let groups = Stdlib_Option.getOr(Stdlib_Option.map(claims["cognito:groups"], g => g), []);
|
|
38
44
|
return {
|
|
@@ -81,7 +87,7 @@ function fromAppSyncIdentity(id) {
|
|
|
81
87
|
return {
|
|
82
88
|
TAG: "Authenticated",
|
|
83
89
|
_0: {
|
|
84
|
-
userId: sub,
|
|
90
|
+
userId: _userIdOf(rawClaims, sub),
|
|
85
91
|
username: username,
|
|
86
92
|
groups: groups,
|
|
87
93
|
claims: claims,
|
|
@@ -127,6 +133,7 @@ export {
|
|
|
127
133
|
_decodeJwtClaims,
|
|
128
134
|
_bearerToken,
|
|
129
135
|
_getHeader,
|
|
136
|
+
_userIdOf,
|
|
130
137
|
_identityFromClaims,
|
|
131
138
|
authenticate,
|
|
132
139
|
fromAppSyncIdentity,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// The rows mapping a Cognito `sub` to the domain's own user id, and where they
|
|
2
|
+
// live. Read by the pre-token-generation trigger on every mint, written only
|
|
3
|
+
// when an account is created (`provision-accounts`, and the Cognito identity
|
|
4
|
+
// provider when it exists).
|
|
5
|
+
//
|
|
6
|
+
// 🚨 **The rows follow the identity provider, not the stack**, exactly as the
|
|
7
|
+
// active-role store's do and for the same reason: a pool holds one pre-token
|
|
8
|
+
// trigger, so two stacks sharing a pool with a table each would have the winning
|
|
9
|
+
// trigger reading rows the other stack wrote nowhere near. A stack that owns its
|
|
10
|
+
// pool owns this table; a stack handed an `identityProviderId` looks it up at
|
|
11
|
+
// [Auth_IdentityStore_Schema.derivedStoreName] and never creates it.
|
|
12
|
+
|
|
13
|
+
type storeTable = Auth_ActiveRoleStore.storeTable
|
|
14
|
+
|
|
15
|
+
/** Which store a deployment's trigger reads. Total and pure, like the active-role
|
|
16
|
+
store's: whether the provider is ours decides it, and nothing else. */
|
|
17
|
+
let chooseStore = (~identityProviderId: option<string>): Auth_ActiveRoleStore.storeChoice =>
|
|
18
|
+
switch identityProviderId {
|
|
19
|
+
| None => StackScoped
|
|
20
|
+
| Some(id) => ProviderScoped(Auth_IdentityStore_Schema.derivedStoreName(~identityProviderId=id))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One row per subject — the key schema is [Auth_IdentityStore_Schema]'s — plus
|
|
24
|
+
`userId`, the domain id minted for it. */
|
|
25
|
+
let makeTable = (
|
|
26
|
+
~name: string="IdentityStore",
|
|
27
|
+
~opts: Pulumi.ComponentResource.options,
|
|
28
|
+
): storeTable => {
|
|
29
|
+
let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
30
|
+
let table = Util_DynamoDb.makeTable(
|
|
31
|
+
~attributes=[{name: Auth_IdentityStore_Schema.partitionKey, type_: "S"}],
|
|
32
|
+
~tags=AWS.Tags.make(
|
|
33
|
+
~name=name ++ "Table",
|
|
34
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
35
|
+
~role=Auth,
|
|
36
|
+
~scope=Platform,
|
|
37
|
+
),
|
|
38
|
+
~opts,
|
|
39
|
+
name,
|
|
40
|
+
)
|
|
41
|
+
{name: table.name, arn: table.arn}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The store a resolved provider calls for — created or looked up. */
|
|
45
|
+
let resolveTable = (
|
|
46
|
+
~choice: Auth_ActiveRoleStore.storeChoice,
|
|
47
|
+
~opts: Pulumi.ComponentResource.options,
|
|
48
|
+
): storeTable =>
|
|
49
|
+
switch choice {
|
|
50
|
+
| StackScoped => makeTable(~opts)
|
|
51
|
+
| ProviderScoped(tableName) => Auth_ActiveRoleStore.lookupTable(~tableName)
|
|
52
|
+
}
|