@reventlessdev/reventless-aws 3.0.0-alpha.319 → 3.0.0-alpha.321
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 +30 -0
- package/package.json +12 -8
- package/rescript.json +4 -0
- package/run-provision-identity.mjs +3 -0
- package/scripts/ProvisionIdentity.res +303 -0
- package/scripts/ProvisionIdentity.res.mjs +376 -0
- package/src/Platform.res +54 -30
- package/src/Platform.res.mjs +34 -32
- package/src/Platform_Stack.res +102 -19
- package/src/Platform_Stack.res.mjs +43 -7
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +267 -32
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +124 -14
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +93 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +53 -4
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res +50 -4
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs +24 -3
- package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res +92 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res.mjs +49 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +21 -7
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +15 -7
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -18
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +14 -4
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +29 -14
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +11 -1
- package/src/util/Util_AwsError.res +56 -0
- package/src/util/Util_AwsError.res.mjs +59 -0
- package/src/util/Util_ShellConfig.res +28 -0
- package/src/util/Util_ShellConfig.res.mjs +22 -0
- package/tests/AppSync_RetirementNarrowingTest.res +10 -12
- package/tests/AppSync_RetirementNarrowingTest.res.mjs +4 -2
- package/tests/Auth_ActiveRolePoolAttachmentTest.res +175 -0
- package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +136 -0
- package/tests/Auth_ActiveRoleStoreTest.res +65 -0
- package/tests/Auth_ActiveRoleStoreTest.res.mjs +48 -0
- package/tests/Auth_ActiveRoleStore_SchemaTest.res +98 -0
- package/tests/Auth_ActiveRoleStore_SchemaTest.res.mjs +97 -0
- package/tests/AwsErrorFixtures.mjs +16 -0
- package/tests/ProvisionIdentityTest.res +109 -0
- package/tests/ProvisionIdentityTest.res.mjs +132 -0
- package/tests/QueryDbOwnerIndexTableTest.res +53 -0
- package/tests/QueryDbOwnerIndexTableTest.res.mjs +50 -0
- package/tests/QueryDbResolvers_AppSyncTest.res +19 -26
- package/tests/QueryDbResolvers_AppSyncTest.res.mjs +15 -17
- package/tests/Util_AwsErrorTest.res +127 -0
- package/tests/Util_AwsErrorTest.res.mjs +76 -0
- package/tests/Util_ShellConfigTest.res +51 -0
- package/tests/Util_ShellConfigTest.res.mjs +42 -0
package/src/Platform_Stack.res
CHANGED
|
@@ -17,39 +17,78 @@ type cognitoUserPool = {
|
|
|
17
17
|
/**
|
|
18
18
|
* Resolve the Cognito UserPool used by AppSync auth. Two modes:
|
|
19
19
|
*
|
|
20
|
-
* - **Auto**: no `platform:
|
|
20
|
+
* - **Auto**: no `platform:identityProviderId` config — create a fresh UserPool
|
|
21
21
|
* with SPA-friendly defaults (email username, 12-char password policy, no
|
|
22
22
|
* MFA, admin-only user creation). Caller is responsible for creating
|
|
23
23
|
* groups (`Admin`, `User`, …) and users via the AWS console / CLI.
|
|
24
24
|
*
|
|
25
|
-
* - **BYO**:
|
|
25
|
+
* - **BYO**: provider ID provided — skip pool creation, look up the existing
|
|
26
26
|
* pool via `aws.cognito.getUserPool` for its ARN. Lookup precedence
|
|
27
27
|
* (highest → lowest):
|
|
28
|
-
* 1. `
|
|
29
|
-
* 2. `Pulumi.local.yaml` sidecar key `
|
|
28
|
+
* 1. `REVENTLESS_IDENTITY_PROVIDER_ID` env var (typically a CI secret).
|
|
29
|
+
* 2. `Pulumi.local.yaml` sidecar key `identityProviderId` (gitignored;
|
|
30
30
|
* per-dev override).
|
|
31
|
-
* 3. `platform:
|
|
31
|
+
* 3. `platform:identityProviderId` in the checked-in `Pulumi.<stack>.yaml`.
|
|
32
|
+
*
|
|
33
|
+
* The former `cognitoUserPoolId` spelling is still read at every level, and
|
|
34
|
+
* warns — see `_deprecatedPoolIdKey` for why dropping it is its own act.
|
|
32
35
|
*
|
|
33
36
|
* In both modes the framework owns the `UserPoolClient` (SPA settings would
|
|
34
37
|
* not reliably exist on a pre-existing client; the client is a child resource
|
|
35
38
|
* Pulumi can destroy without touching the parent pool).
|
|
36
39
|
*
|
|
37
|
-
* Always exports `
|
|
38
|
-
* `
|
|
39
|
-
* stack outputs so downstream stacks (and Stage D AppSync
|
|
40
|
-
* them via `StackReference
|
|
40
|
+
* Always exports `identityProviderId`, `identityProviderClientId`,
|
|
41
|
+
* `identityProviderArn`, `identityProviderRegion`, `identityProviderManaged` and
|
|
42
|
+
* `activeRoleStore` as stack outputs so downstream stacks (and Stage D AppSync
|
|
43
|
+
* wiring) can read them via `StackReference` — plus, for one release, the
|
|
44
|
+
* deprecated `cognito*` spellings of the first five.
|
|
41
45
|
*
|
|
42
46
|
* Also provisions the role-state table and the pre-token-generation trigger that
|
|
43
47
|
* narrows `cognito:groups` to a caller's chosen role. They belong here rather
|
|
44
48
|
* than beside the mutation that writes the table because the pool must be
|
|
45
49
|
* declared carrying the trigger's ARN — see the ordering note below.
|
|
50
|
+
*
|
|
51
|
+
* In BYO mode the store follows the provider rather than the stack: its name is
|
|
52
|
+
* derived from the provider id, it is looked up rather than created, and it is
|
|
53
|
+
* provisioned outside every stack alongside the pool. Either lookup finding
|
|
54
|
+
* nothing fails the deploy — the provider and its store exist together or not
|
|
55
|
+
* at all.
|
|
46
56
|
*/
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
let log = ReventlessCore.Logger.fromEnv()
|
|
58
|
+
|
|
59
|
+
/** The key that was `cognitoUserPoolId` before the concept was named for what it
|
|
60
|
+
is rather than for the service implementing it.
|
|
61
|
+
|
|
62
|
+
🚨 **Still read, and it must stay read until every caller has moved.** An absent
|
|
63
|
+
provider id is not an error here — it is auto mode, and auto mode *creates a
|
|
64
|
+
user pool*. So a caller left on the old key after this stopped reading it would
|
|
65
|
+
not fail: it would deploy green, mint a fresh pool, and orphan every account in
|
|
66
|
+
the old one. Dropping this fallback is a separate act, done once the callers are
|
|
67
|
+
known to have moved, not a tidy-up. */
|
|
68
|
+
let _deprecatedPoolIdKey = "cognitoUserPoolId"
|
|
69
|
+
|
|
70
|
+
let _identityProviderId = (~cfg: Pulumi.Config.t): option<string> => {
|
|
71
|
+
let read = key =>
|
|
72
|
+
switch Util_LocalConfig.get(key) {
|
|
73
|
+
| Some(_) as v => v
|
|
74
|
+
| None => cfg->Pulumi.Config.get(key)
|
|
75
|
+
}
|
|
76
|
+
switch read("identityProviderId") {
|
|
50
77
|
| Some(_) as v => v
|
|
51
|
-
| None =>
|
|
78
|
+
| None =>
|
|
79
|
+
read(_deprecatedPoolIdKey)->Option.map(id => {
|
|
80
|
+
log.warn(
|
|
81
|
+
~comp="Platform_Stack",
|
|
82
|
+
`platform:${_deprecatedPoolIdKey} (REVENTLESS_COGNITO_USER_POOL_ID) is deprecated — rename it to platform:identityProviderId (REVENTLESS_IDENTITY_PROVIDER_ID). Still honoured, because dropping it silently would deploy in auto mode and create a NEW user pool.`,
|
|
83
|
+
)
|
|
84
|
+
id
|
|
85
|
+
})
|
|
52
86
|
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let _resolveUncached = (): cognitoUserPool => {
|
|
90
|
+
let cfg = Pulumi.Config.make(Some("platform"))
|
|
91
|
+
let existingPoolId = _identityProviderId(~cfg)
|
|
53
92
|
|
|
54
93
|
// Provisioned ahead of the pool in both pool modes: the pre-token-generation
|
|
55
94
|
// trigger reads these rows, and in auto mode the pool is declared carrying the
|
|
@@ -71,7 +110,19 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
71
110
|
// Saving one GetItem in a legacy mode is not worth a switch whose failure mode
|
|
72
111
|
// is a feature that quietly is not there. If the cost ever matters, the fix is
|
|
73
112
|
// a design that does not hinge on call order — not this parameter again.
|
|
74
|
-
|
|
113
|
+
//
|
|
114
|
+
// 🚨 **On a provider we do not own, the rows do not belong to this stack.** A
|
|
115
|
+
// pool holds one pre-token-generation trigger, so two stacks sharing a pool with
|
|
116
|
+
// a table each have the winning trigger reading rows the serving resolver never
|
|
117
|
+
// wrote — every role switch succeeds and does nothing. So the store follows the
|
|
118
|
+
// provider: named by derivation from the provider id, looked up rather than
|
|
119
|
+
// created, and provisioned outside every stack alongside the pool itself. See
|
|
120
|
+
// [Auth_ActiveRoleStore.chooseStore] and
|
|
121
|
+
// [docs/plans/active-role-store-scoped-to-the-pool.md].
|
|
122
|
+
let activeRoleTable = Auth_ActiveRoleStore.resolveTable(
|
|
123
|
+
~choice=Auth_ActiveRoleStore.chooseStore(~identityProviderId=existingPoolId),
|
|
124
|
+
~opts={},
|
|
125
|
+
)
|
|
75
126
|
let activeRoleTrigger = Auth_ActiveRoleTrigger.make(
|
|
76
127
|
~activeRoleTableName=activeRoleTable.name->Pulumi.Output.asInput,
|
|
77
128
|
~activeRoleTableArn=activeRoleTable.arn->Pulumi.Output.asInput,
|
|
@@ -112,11 +163,16 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
112
163
|
// The code hash is an input so a new build of the trigger reaches this
|
|
113
164
|
// resource: the function ARN is stable, so it alone would let a broken
|
|
114
165
|
// version onto the pool unchecked after the first deploy.
|
|
166
|
+
//
|
|
167
|
+
// The store travels with it because the pool has one trigger slot: the
|
|
168
|
+
// resource compares this against the store read by whatever already holds the
|
|
169
|
+
// slot, and refuses rather than taking it from a trigger that disagrees.
|
|
115
170
|
let _attachment = Auth_ActiveRolePoolAttachment.make(
|
|
116
171
|
~props={
|
|
117
172
|
userPoolId: Pulumi.Input.make(poolId),
|
|
118
173
|
preTokenGenerationArn: activeRoleTrigger.functionArn->Pulumi.Output.asInput,
|
|
119
174
|
codeHash: Pulumi.Input.make(activeRoleTrigger.sourceCodeHash),
|
|
175
|
+
activeRoleStore: activeRoleTable.name->Pulumi.Output.asInput,
|
|
120
176
|
},
|
|
121
177
|
~opts={},
|
|
122
178
|
)
|
|
@@ -205,14 +261,41 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
205
261
|
let regionStr =
|
|
206
262
|
Pulumi.Config.make(Some("aws"))->Pulumi.Config.get("region")->Option.getOr("unknown")
|
|
207
263
|
|
|
264
|
+
// 🚨 **Both spellings, deliberately, and for one release only.**
|
|
265
|
+
//
|
|
266
|
+
// The `cognito*` names are what every consumer reads today; the
|
|
267
|
+
// `identityProvider*` names are what they are becoming, for the reason the
|
|
268
|
+
// config key changed — the concept is not AWS-specific. A stack that exported
|
|
269
|
+
// only the new ones would break any plugin stack pinned to an older
|
|
270
|
+
// `reventless-aws`, which reads the old names through `StackReference`.
|
|
271
|
+
//
|
|
272
|
+
// Exporting both costs nothing and removes the ordering problem entirely
|
|
273
|
+
// rather than managing it: every consumer, old or new, finds a name it knows.
|
|
274
|
+
// The old set goes in a later release, once no pinned consumer reads them.
|
|
275
|
+
//
|
|
276
|
+
// `Arn` survives the rename on purpose — the value *is* an ARN, and `urn` in a
|
|
277
|
+
// Pulumi codebase already means the Pulumi URN every resource carries. See
|
|
278
|
+
// [docs/plans/active-role-store-scoped-to-the-pool.md] step 7.
|
|
279
|
+
let managedStr = Pulumi.Output.make(result.managed ? "true" : "false")
|
|
280
|
+
let regionOutput = Pulumi.Output.make(regionStr)
|
|
281
|
+
|
|
282
|
+
Pulumi.Pulumi.export("identityProviderId", result.poolId)
|
|
283
|
+
Pulumi.Pulumi.export("identityProviderClientId", result.clientId)
|
|
284
|
+
Pulumi.Pulumi.export("identityProviderArn", result.poolArn)
|
|
285
|
+
Pulumi.Pulumi.export("identityProviderRegion", regionOutput)
|
|
286
|
+
Pulumi.Pulumi.export("identityProviderManaged", managedStr)
|
|
287
|
+
|
|
288
|
+
// Deprecated spellings — see above.
|
|
208
289
|
Pulumi.Pulumi.export("cognitoUserPoolId", result.poolId)
|
|
209
290
|
Pulumi.Pulumi.export("cognitoUserPoolClientId", result.clientId)
|
|
210
291
|
Pulumi.Pulumi.export("cognitoUserPoolArn", result.poolArn)
|
|
211
|
-
Pulumi.Pulumi.export("cognitoRegion",
|
|
212
|
-
Pulumi.Pulumi.export(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
292
|
+
Pulumi.Pulumi.export("cognitoRegion", regionOutput)
|
|
293
|
+
Pulumi.Pulumi.export("cognitoUserPoolManaged", managedStr)
|
|
294
|
+
|
|
295
|
+
// Exported so an operator can read back the store this provider resolved to —
|
|
296
|
+
// the name is derived, not configured, so this is a convenience for checking
|
|
297
|
+
// rather than a value anything is meant to copy into a config.
|
|
298
|
+
Pulumi.Pulumi.export("activeRoleStore", result.activeRoleTable.name)
|
|
216
299
|
|
|
217
300
|
result
|
|
218
301
|
}
|
|
@@ -4,17 +4,41 @@ import * as Aws from "@pulumi/aws";
|
|
|
4
4
|
import * as Pulumi$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Pulumi.res.mjs";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
|
+
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
7
8
|
import * as AWS_Tags$ReventlessAws from "./adapter/AWS_Tags.res.mjs";
|
|
8
9
|
import * as Util_LocalConfig$ReventlessAws from "./util/Util_LocalConfig.res.mjs";
|
|
9
10
|
import * as Auth_ActiveRoleStore$ReventlessAws from "./adapter/Auth/Auth_ActiveRoleStore.res.mjs";
|
|
10
11
|
import * as Auth_ActiveRoleTrigger$ReventlessAws from "./adapter/Auth/Auth_ActiveRoleTrigger.res.mjs";
|
|
11
12
|
import * as Auth_ActiveRolePoolAttachment$ReventlessAws from "./adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs";
|
|
12
13
|
|
|
14
|
+
let log = Logger$ReventlessCore.fromEnv();
|
|
15
|
+
|
|
16
|
+
let _deprecatedPoolIdKey = "cognitoUserPoolId";
|
|
17
|
+
|
|
18
|
+
function _identityProviderId(cfg) {
|
|
19
|
+
let read = key => {
|
|
20
|
+
let v = Util_LocalConfig$ReventlessAws.get(key);
|
|
21
|
+
if (v !== undefined) {
|
|
22
|
+
return v;
|
|
23
|
+
} else {
|
|
24
|
+
return cfg.get(key);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
let v = read("identityProviderId");
|
|
28
|
+
if (v !== undefined) {
|
|
29
|
+
return v;
|
|
30
|
+
} else {
|
|
31
|
+
return Stdlib_Option.map(read(_deprecatedPoolIdKey), id => {
|
|
32
|
+
log.warn("Platform_Stack", undefined, `platform:` + _deprecatedPoolIdKey + ` (REVENTLESS_COGNITO_USER_POOL_ID) is deprecated — rename it to platform:identityProviderId (REVENTLESS_IDENTITY_PROVIDER_ID). Still honoured, because dropping it silently would deploy in auto mode and create a NEW user pool.`);
|
|
33
|
+
return id;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
13
38
|
function _resolveUncached() {
|
|
14
39
|
let cfg = new Pulumi.Config("platform");
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
let activeRoleTable = Auth_ActiveRoleStore$ReventlessAws.makeTable(undefined, {});
|
|
40
|
+
let existingPoolId = _identityProviderId(cfg);
|
|
41
|
+
let activeRoleTable = Auth_ActiveRoleStore$ReventlessAws.resolveTable(Auth_ActiveRoleStore$ReventlessAws.chooseStore(existingPoolId), {});
|
|
18
42
|
let activeRoleTrigger = Auth_ActiveRoleTrigger$ReventlessAws.make(activeRoleTable.name, activeRoleTable.arn, undefined, {});
|
|
19
43
|
let result;
|
|
20
44
|
if (existingPoolId !== undefined) {
|
|
@@ -45,7 +69,8 @@ function _resolveUncached() {
|
|
|
45
69
|
Auth_ActiveRolePoolAttachment$ReventlessAws.make(undefined, {
|
|
46
70
|
userPoolId: existingPoolId,
|
|
47
71
|
preTokenGenerationArn: activeRoleTrigger.functionArn,
|
|
48
|
-
codeHash: activeRoleTrigger.sourceCodeHash
|
|
72
|
+
codeHash: activeRoleTrigger.sourceCodeHash,
|
|
73
|
+
activeRoleStore: activeRoleTable.name
|
|
49
74
|
}, {});
|
|
50
75
|
result = {
|
|
51
76
|
poolId: Pulumi.output(existingPoolId),
|
|
@@ -109,11 +134,19 @@ function _resolveUncached() {
|
|
|
109
134
|
}
|
|
110
135
|
Auth_ActiveRoleTrigger$ReventlessAws.grantInvoke(activeRoleTrigger, result.poolArn, undefined, {});
|
|
111
136
|
let regionStr = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
|
|
137
|
+
let managedStr = Pulumi.output(result.managed ? "true" : "false");
|
|
138
|
+
let regionOutput = Pulumi.output(regionStr);
|
|
139
|
+
Pulumi$Pulumi.$$export("identityProviderId", result.poolId);
|
|
140
|
+
Pulumi$Pulumi.$$export("identityProviderClientId", result.clientId);
|
|
141
|
+
Pulumi$Pulumi.$$export("identityProviderArn", result.poolArn);
|
|
142
|
+
Pulumi$Pulumi.$$export("identityProviderRegion", regionOutput);
|
|
143
|
+
Pulumi$Pulumi.$$export("identityProviderManaged", managedStr);
|
|
112
144
|
Pulumi$Pulumi.$$export("cognitoUserPoolId", result.poolId);
|
|
113
145
|
Pulumi$Pulumi.$$export("cognitoUserPoolClientId", result.clientId);
|
|
114
146
|
Pulumi$Pulumi.$$export("cognitoUserPoolArn", result.poolArn);
|
|
115
|
-
Pulumi$Pulumi.$$export("cognitoRegion",
|
|
116
|
-
Pulumi$Pulumi.$$export("cognitoUserPoolManaged",
|
|
147
|
+
Pulumi$Pulumi.$$export("cognitoRegion", regionOutput);
|
|
148
|
+
Pulumi$Pulumi.$$export("cognitoUserPoolManaged", managedStr);
|
|
149
|
+
Pulumi$Pulumi.$$export("activeRoleStore", result.activeRoleTable.name);
|
|
117
150
|
return result;
|
|
118
151
|
}
|
|
119
152
|
|
|
@@ -132,8 +165,11 @@ function resolveCognitoUserPool() {
|
|
|
132
165
|
}
|
|
133
166
|
|
|
134
167
|
export {
|
|
168
|
+
log,
|
|
169
|
+
_deprecatedPoolIdKey,
|
|
170
|
+
_identityProviderId,
|
|
135
171
|
_resolveUncached,
|
|
136
172
|
_cached,
|
|
137
173
|
resolveCognitoUserPool,
|
|
138
174
|
}
|
|
139
|
-
/*
|
|
175
|
+
/* log Not a pure module */
|