@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +12 -8
  3. package/rescript.json +4 -0
  4. package/run-provision-identity.mjs +3 -0
  5. package/scripts/ProvisionIdentity.res +303 -0
  6. package/scripts/ProvisionIdentity.res.mjs +376 -0
  7. package/src/Platform.res +54 -30
  8. package/src/Platform.res.mjs +34 -32
  9. package/src/Platform_Stack.res +102 -19
  10. package/src/Platform_Stack.res.mjs +43 -7
  11. package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +267 -32
  12. package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +124 -14
  13. package/src/adapter/Auth/Auth_ActiveRoleStore.res +93 -5
  14. package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +53 -4
  15. package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res +50 -4
  16. package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs +24 -3
  17. package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res +92 -0
  18. package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res.mjs +49 -0
  19. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +21 -7
  20. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +15 -7
  21. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -18
  22. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +14 -4
  23. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +29 -14
  24. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +11 -1
  25. package/src/util/Util_AwsError.res +56 -0
  26. package/src/util/Util_AwsError.res.mjs +59 -0
  27. package/src/util/Util_ShellConfig.res +28 -0
  28. package/src/util/Util_ShellConfig.res.mjs +22 -0
  29. package/tests/AppSync_RetirementNarrowingTest.res +10 -12
  30. package/tests/AppSync_RetirementNarrowingTest.res.mjs +4 -2
  31. package/tests/Auth_ActiveRolePoolAttachmentTest.res +175 -0
  32. package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +136 -0
  33. package/tests/Auth_ActiveRoleStoreTest.res +65 -0
  34. package/tests/Auth_ActiveRoleStoreTest.res.mjs +48 -0
  35. package/tests/Auth_ActiveRoleStore_SchemaTest.res +98 -0
  36. package/tests/Auth_ActiveRoleStore_SchemaTest.res.mjs +97 -0
  37. package/tests/AwsErrorFixtures.mjs +16 -0
  38. package/tests/ProvisionIdentityTest.res +109 -0
  39. package/tests/ProvisionIdentityTest.res.mjs +132 -0
  40. package/tests/QueryDbOwnerIndexTableTest.res +53 -0
  41. package/tests/QueryDbOwnerIndexTableTest.res.mjs +50 -0
  42. package/tests/QueryDbResolvers_AppSyncTest.res +19 -26
  43. package/tests/QueryDbResolvers_AppSyncTest.res.mjs +15 -17
  44. package/tests/Util_AwsErrorTest.res +127 -0
  45. package/tests/Util_AwsErrorTest.res.mjs +76 -0
  46. package/tests/Util_ShellConfigTest.res +51 -0
  47. package/tests/Util_ShellConfigTest.res.mjs +42 -0
@@ -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:cognitoUserPoolId` config — create a fresh UserPool
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**: pool ID provided — skip pool creation, look up the existing
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. `REVENTLESS_COGNITO_USER_POOL_ID` env var (typically a CI secret).
29
- * 2. `Pulumi.local.yaml` sidecar key `cognitoUserPoolId` (gitignored;
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:cognitoUserPoolId` in the checked-in `Pulumi.<stack>.yaml`.
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 `cognitoUserPoolId`, `cognitoUserPoolClientId`,
38
- * `cognitoUserPoolArn`, `cognitoRegion`, and `cognitoUserPoolManaged` as
39
- * stack outputs so downstream stacks (and Stage D AppSync wiring) can read
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 _resolveUncached = (): cognitoUserPool => {
48
- let cfg = Pulumi.Config.make(Some("platform"))
49
- let existingPoolId = switch Util_LocalConfig.get("cognitoUserPoolId") {
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 => cfg->Pulumi.Config.get("cognitoUserPoolId")
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
- let activeRoleTable = Auth_ActiveRoleStore.makeTable(~opts={})
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", Pulumi.Output.make(regionStr))
212
- Pulumi.Pulumi.export(
213
- "cognitoUserPoolManaged",
214
- Pulumi.Output.make(result.managed ? "true" : "false"),
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 v = Util_LocalConfig$ReventlessAws.get("cognitoUserPoolId");
16
- let existingPoolId = v !== undefined ? v : cfg.get("cognitoUserPoolId");
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", Pulumi.output(regionStr));
116
- Pulumi$Pulumi.$$export("cognitoUserPoolManaged", Pulumi.output(result.managed ? "true" : "false"));
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
- /* @pulumi/aws Not a pure module */
175
+ /* log Not a pure module */