@reventlessdev/reventless-aws 3.0.0-alpha.345 → 3.0.0-alpha.346

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 (42) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +12 -9
  3. package/run-provision-accounts.mjs +3 -0
  4. package/run-provision-admin.mjs +3 -0
  5. package/scripts/ProvisionAccounts.res +325 -0
  6. package/scripts/ProvisionAccounts.res.mjs +337 -0
  7. package/scripts/ProvisionAdmin.res +275 -0
  8. package/scripts/ProvisionAdmin.res.mjs +289 -0
  9. package/scripts/ProvisionCognito.res +149 -0
  10. package/scripts/ProvisionCognito.res.mjs +98 -0
  11. package/scripts/ProvisionIdentity.res +76 -11
  12. package/scripts/ProvisionIdentity.res.mjs +72 -7
  13. package/scripts/ProvisionProvider.res +104 -0
  14. package/scripts/ProvisionProvider.res.mjs +123 -0
  15. package/src/Platform.res +32 -2
  16. package/src/Platform.res.mjs +20 -10
  17. package/src/Platform_Stack.res +119 -9
  18. package/src/Platform_Stack.res.mjs +42 -4
  19. package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
  20. package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
  21. package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
  22. package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
  23. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
  24. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
  25. package/src/components/Api/AppSync_Adapter.res +56 -72
  26. package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
  27. package/src/components/Api/AppSync_SdlDecorate.res +80 -1
  28. package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
  29. package/tests/AppSync_AdapterTest.res +83 -0
  30. package/tests/AppSync_AdapterTest.res.mjs +69 -0
  31. package/tests/Auth_LoginIdentifierTest.res +51 -0
  32. package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
  33. package/tests/Auth_SignUpModeTest.res +56 -0
  34. package/tests/Auth_SignUpModeTest.res.mjs +53 -0
  35. package/tests/ProvisionAccountsTest.res +142 -0
  36. package/tests/ProvisionAccountsTest.res.mjs +153 -0
  37. package/tests/ProvisionAdminTest.res +55 -0
  38. package/tests/ProvisionAdminTest.res.mjs +66 -0
  39. package/tests/ProvisionIdentityTest.res +89 -3
  40. package/tests/ProvisionIdentityTest.res.mjs +66 -1
  41. package/tests/Util_OwnerScopeEnvTest.res +25 -0
  42. package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
@@ -0,0 +1,66 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as ProvisionAdmin$ReventlessAws from "../scripts/ProvisionAdmin.res.mjs";
4
+
5
+ globalThis.describe("ProvisionAdmin.parseArgs", () => {
6
+ globalThis.test("both arguments parse", () => {
7
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([
8
+ "--provider-id",
9
+ "eu-west-1_AbCdEfGhI",
10
+ "--email",
11
+ "me@example.com"
12
+ ])).toEqual({
13
+ TAG: "Ok",
14
+ _0: {
15
+ providerId: "eu-west-1_AbCdEfGhI",
16
+ stack: undefined,
17
+ email: "me@example.com",
18
+ help: false
19
+ }
20
+ });
21
+ });
22
+ globalThis.test("no arguments leaves both unanswered", () => {
23
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([])).toEqual({
24
+ TAG: "Ok",
25
+ _0: {
26
+ providerId: undefined,
27
+ stack: undefined,
28
+ email: undefined,
29
+ help: false
30
+ }
31
+ });
32
+ });
33
+ globalThis.test("an unknown flag refuses rather than being ignored", () => {
34
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs([
35
+ "--provider",
36
+ "eu-west-1_AbCdEfGhI"
37
+ ])).toEqual({
38
+ TAG: "Error",
39
+ _0: `unknown argument "--provider"`
40
+ });
41
+ });
42
+ globalThis.test("a flag with no value refuses", () => {
43
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs(["--email"])).toEqual({
44
+ TAG: "Error",
45
+ _0: "--email needs a value"
46
+ });
47
+ });
48
+ globalThis.test("-h asks for the usage", () => {
49
+ globalThis.expect(ProvisionAdmin$ReventlessAws.parseArgs(["-h"])).toEqual({
50
+ TAG: "Ok",
51
+ _0: {
52
+ providerId: undefined,
53
+ stack: undefined,
54
+ email: undefined,
55
+ help: true
56
+ }
57
+ });
58
+ });
59
+ });
60
+
61
+ let Provision;
62
+
63
+ export {
64
+ Provision,
65
+ }
66
+ /* Not a pure module */
@@ -16,6 +16,8 @@ describe("ProvisionIdentity.parseArgs", () => {
16
16
  Ok({
17
17
  Provision.poolName: Provision.defaultPoolName,
18
18
  providerId: None,
19
+ loginIdentifier: Auth_LoginIdentifier.Email,
20
+ signUpMode: Auth_SignUpMode.AdminOnly,
19
21
  help: false,
20
22
  }),
21
23
  )
@@ -26,6 +28,8 @@ describe("ProvisionIdentity.parseArgs", () => {
26
28
  Ok({
27
29
  Provision.poolName: Provision.defaultPoolName,
28
30
  providerId: Some("eu-west-1_AbCdEfGhI"),
31
+ loginIdentifier: Auth_LoginIdentifier.Email,
32
+ signUpMode: Auth_SignUpMode.AdminOnly,
29
33
  help: false,
30
34
  }),
31
35
  )
@@ -33,13 +37,60 @@ describe("ProvisionIdentity.parseArgs", () => {
33
37
 
34
38
  testSync("--name selects which pool to adopt by name", () =>
35
39
  expect(Provision.parseArgs(["--name", "examples-dev"]))->toEqual(
36
- Ok({Provision.poolName: "examples-dev", providerId: None, help: false}),
40
+ Ok({
41
+ Provision.poolName: "examples-dev",
42
+ providerId: None,
43
+ loginIdentifier: Auth_LoginIdentifier.Email,
44
+ signUpMode: Auth_SignUpMode.AdminOnly,
45
+ help: false,
46
+ }),
37
47
  )
38
48
  )
39
49
 
40
50
  testSync("both together are accepted, and the id wins at resolve time", () =>
41
51
  expect(Provision.parseArgs(["--name", "ignored", "--provider-id", "eu-west-1_x"]))->toEqual(
42
- Ok({Provision.poolName: "ignored", providerId: Some("eu-west-1_x"), help: false}),
52
+ Ok({
53
+ Provision.poolName: "ignored",
54
+ providerId: Some("eu-west-1_x"),
55
+ loginIdentifier: Auth_LoginIdentifier.Email,
56
+ signUpMode: Auth_SignUpMode.AdminOnly,
57
+ help: false,
58
+ }),
59
+ )
60
+ )
61
+
62
+ testSync("--login-identifier picks the sign-in attribute", () =>
63
+ expect(
64
+ Provision.parseArgs(["--login-identifier", "emailOrPhone"])->Result.map(
65
+ a => a.loginIdentifier,
66
+ ),
67
+ )->toEqual(Ok(Auth_LoginIdentifier.EmailOrPhone))
68
+ )
69
+
70
+ // 🚨 The sign-in attribute cannot be changed once the pool exists, so a typo
71
+ // that fell back to the default would create a pool the operator has to
72
+ // rebuild — and re-register every account in — to correct.
73
+ testSync("an unrecognised sign-in attribute is refused, never defaulted", () =>
74
+ expect(Provision.parseArgs(["--login-identifier", "e-mail"])->Result.isError)->toBe(true)
75
+ )
76
+
77
+ testSync("--sign-up-mode opens the pool to self-registration", () =>
78
+ expect(
79
+ Provision.parseArgs(["--sign-up-mode", "selfService"])->Result.map(a => a.signUpMode),
80
+ )->toEqual(Ok(Auth_SignUpMode.SelfService))
81
+ )
82
+
83
+ // This one IS correctable later, unlike the sign-in attribute — it is refused
84
+ // because the mistake is silent, not because it is permanent. Defaulting past
85
+ // a typo leaves a pool that refuses every registration while the command line
86
+ // reads as though it allows them.
87
+ testSync("an unrecognised sign-up mode is refused, never defaulted", () =>
88
+ expect(Provision.parseArgs(["--sign-up-mode", "self-service"])->Result.isError)->toBe(true)
89
+ )
90
+
91
+ testSync("sign-up mode defaults to admin-only, so an existing pool is unchanged", () =>
92
+ expect(Provision.parseArgs([])->Result.map(a => a.signUpMode))->toEqual(
93
+ Ok(Auth_SignUpMode.AdminOnly),
43
94
  )
44
95
  )
45
96
 
@@ -62,6 +113,12 @@ describe("ProvisionIdentity.parseArgs", () => {
62
113
  expect(Provision.parseArgs(["--name"]))->toEqual(Error("--name needs a value"))
63
114
  )
64
115
 
116
+ testSync("--login-identifier with no value is refused too", () =>
117
+ expect(Provision.parseArgs(["--login-identifier"]))->toEqual(
118
+ Error("--login-identifier needs a value"),
119
+ )
120
+ )
121
+
65
122
  testSync("--help and -h both ask for the usage text", () =>
66
123
  expect((
67
124
  Provision.parseArgs(["--help"])->Result.map(a => a.help),
@@ -83,7 +140,11 @@ describe("ProvisionIdentity.parseArgs", () => {
83
140
  // touches it — so its settings are not a detail an operator can revise cheaply
84
141
  // once accounts exist in it.
85
142
  describe("ProvisionIdentity.poolSettings", () => {
86
- let settings = Provision.poolSettings(~poolName="MyIdentity")
143
+ let settings = Provision.poolSettings(
144
+ ~poolName="MyIdentity",
145
+ ~loginIdentifier=Auth_LoginIdentifier.default,
146
+ ~signUpMode=Auth_SignUpMode.default,
147
+ )
87
148
 
88
149
  testSync("carries the name it was asked for", () => expect(settings.poolName)->toBe("MyIdentity"))
89
150
 
@@ -95,6 +156,31 @@ describe("ProvisionIdentity.poolSettings", () => {
95
156
  ))->toEqual((Some(["email"]), Some("OFF"), Some(true)))
96
157
  )
97
158
 
159
+ testSync("a phone pool is created signing in on phone_number", () =>
160
+ expect(
161
+ Provision.poolSettings(
162
+ ~poolName="MyIdentity",
163
+ ~loginIdentifier=Auth_LoginIdentifier.Phone,
164
+ ~signUpMode=Auth_SignUpMode.default,
165
+ ).usernameAttributes,
166
+ )->toEqual(Some(["phone_number"]))
167
+ )
168
+
169
+ // The negation is the part worth pinning: this type asks who may sign up, and
170
+ // Cognito's field asks the opposite question. Reading it backwards would open a
171
+ // pool that was meant to stay closed.
172
+ testSync("selfService clears allowAdminCreateUserOnly, adminOnly sets it", () => {
173
+ let openPool = Provision.poolSettings(
174
+ ~poolName="MyIdentity",
175
+ ~loginIdentifier=Auth_LoginIdentifier.default,
176
+ ~signUpMode=Auth_SignUpMode.SelfService,
177
+ )
178
+ expect((
179
+ openPool.adminCreateUserConfig->Option.flatMap(c => c.allowAdminCreateUserOnly),
180
+ settings.adminCreateUserConfig->Option.flatMap(c => c.allowAdminCreateUserOnly),
181
+ ))->toEqual((Some(false), Some(true)))
182
+ })
183
+
98
184
  testSync("keeps the 12-character password policy", () =>
99
185
  expect(
100
186
  settings.policies
@@ -2,7 +2,9 @@
2
2
 
3
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
4
  import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
5
+ import * as Auth_SignUpMode$ReventlessAws from "../src/adapter/Auth/Auth_SignUpMode.res.mjs";
5
6
  import * as ProvisionIdentity$ReventlessAws from "../scripts/ProvisionIdentity.res.mjs";
7
+ import * as Auth_LoginIdentifier$ReventlessAws from "../src/adapter/Auth/Auth_LoginIdentifier.res.mjs";
6
8
 
7
9
  globalThis.describe("ProvisionIdentity.parseArgs", () => {
8
10
  globalThis.test("no arguments means adopt-or-create the default name", () => {
@@ -11,6 +13,8 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
11
13
  _0: {
12
14
  poolName: ProvisionIdentity$ReventlessAws.defaultPoolName,
13
15
  providerId: undefined,
16
+ loginIdentifier: "Email",
17
+ signUpMode: "AdminOnly",
14
18
  help: false
15
19
  }
16
20
  });
@@ -24,6 +28,8 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
24
28
  _0: {
25
29
  poolName: ProvisionIdentity$ReventlessAws.defaultPoolName,
26
30
  providerId: "eu-west-1_AbCdEfGhI",
31
+ loginIdentifier: "Email",
32
+ signUpMode: "AdminOnly",
27
33
  help: false
28
34
  }
29
35
  });
@@ -37,6 +43,8 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
37
43
  _0: {
38
44
  poolName: "examples-dev",
39
45
  providerId: undefined,
46
+ loginIdentifier: "Email",
47
+ signUpMode: "AdminOnly",
40
48
  help: false
41
49
  }
42
50
  });
@@ -52,10 +60,48 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
52
60
  _0: {
53
61
  poolName: "ignored",
54
62
  providerId: "eu-west-1_x",
63
+ loginIdentifier: "Email",
64
+ signUpMode: "AdminOnly",
55
65
  help: false
56
66
  }
57
67
  });
58
68
  });
69
+ globalThis.test("--login-identifier picks the sign-in attribute", () => {
70
+ globalThis.expect(Stdlib_Result.map(ProvisionIdentity$ReventlessAws.parseArgs([
71
+ "--login-identifier",
72
+ "emailOrPhone"
73
+ ]), a => a.loginIdentifier)).toEqual({
74
+ TAG: "Ok",
75
+ _0: "EmailOrPhone"
76
+ });
77
+ });
78
+ globalThis.test("an unrecognised sign-in attribute is refused, never defaulted", () => {
79
+ globalThis.expect(Stdlib_Result.isError(ProvisionIdentity$ReventlessAws.parseArgs([
80
+ "--login-identifier",
81
+ "e-mail"
82
+ ]))).toBe(true);
83
+ });
84
+ globalThis.test("--sign-up-mode opens the pool to self-registration", () => {
85
+ globalThis.expect(Stdlib_Result.map(ProvisionIdentity$ReventlessAws.parseArgs([
86
+ "--sign-up-mode",
87
+ "selfService"
88
+ ]), a => a.signUpMode)).toEqual({
89
+ TAG: "Ok",
90
+ _0: "SelfService"
91
+ });
92
+ });
93
+ globalThis.test("an unrecognised sign-up mode is refused, never defaulted", () => {
94
+ globalThis.expect(Stdlib_Result.isError(ProvisionIdentity$ReventlessAws.parseArgs([
95
+ "--sign-up-mode",
96
+ "self-service"
97
+ ]))).toBe(true);
98
+ });
99
+ globalThis.test("sign-up mode defaults to admin-only, so an existing pool is unchanged", () => {
100
+ globalThis.expect(Stdlib_Result.map(ProvisionIdentity$ReventlessAws.parseArgs([]), a => a.signUpMode)).toEqual({
101
+ TAG: "Ok",
102
+ _0: "AdminOnly"
103
+ });
104
+ });
59
105
  globalThis.test("a misspelled flag is refused, not ignored", () => {
60
106
  globalThis.expect(ProvisionIdentity$ReventlessAws.parseArgs([
61
107
  "--provider_id",
@@ -77,6 +123,12 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
77
123
  _0: "--name needs a value"
78
124
  });
79
125
  });
126
+ globalThis.test("--login-identifier with no value is refused too", () => {
127
+ globalThis.expect(ProvisionIdentity$ReventlessAws.parseArgs(["--login-identifier"])).toEqual({
128
+ TAG: "Error",
129
+ _0: "--login-identifier needs a value"
130
+ });
131
+ });
80
132
  globalThis.test("--help and -h both ask for the usage text", () => {
81
133
  globalThis.expect([
82
134
  Stdlib_Result.map(ProvisionIdentity$ReventlessAws.parseArgs(["--help"]), a => a.help),
@@ -104,7 +156,7 @@ globalThis.describe("ProvisionIdentity.parseArgs", () => {
104
156
  });
105
157
 
106
158
  globalThis.describe("ProvisionIdentity.poolSettings", () => {
107
- let settings = ProvisionIdentity$ReventlessAws.poolSettings("MyIdentity");
159
+ let settings = ProvisionIdentity$ReventlessAws.poolSettings("MyIdentity", Auth_LoginIdentifier$ReventlessAws.default, Auth_SignUpMode$ReventlessAws.default);
108
160
  globalThis.test("carries the name it was asked for", () => {
109
161
  globalThis.expect(settings.PoolName).toBe("MyIdentity");
110
162
  });
@@ -119,6 +171,19 @@ globalThis.describe("ProvisionIdentity.poolSettings", () => {
119
171
  true
120
172
  ]);
121
173
  });
174
+ globalThis.test("a phone pool is created signing in on phone_number", () => {
175
+ globalThis.expect(ProvisionIdentity$ReventlessAws.poolSettings("MyIdentity", "Phone", Auth_SignUpMode$ReventlessAws.default).UsernameAttributes).toEqual(["phone_number"]);
176
+ });
177
+ globalThis.test("selfService clears allowAdminCreateUserOnly, adminOnly sets it", () => {
178
+ let openPool = ProvisionIdentity$ReventlessAws.poolSettings("MyIdentity", Auth_LoginIdentifier$ReventlessAws.default, "SelfService");
179
+ globalThis.expect([
180
+ Stdlib_Option.flatMap(openPool.AdminCreateUserConfig, c => c.AllowAdminCreateUserOnly),
181
+ Stdlib_Option.flatMap(settings.AdminCreateUserConfig, c => c.AllowAdminCreateUserOnly)
182
+ ]).toEqual([
183
+ false,
184
+ true
185
+ ]);
186
+ });
122
187
  globalThis.test("keeps the 12-character password policy", () => {
123
188
  globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(settings.Policies, p => p.PasswordPolicy), p => p.MinimumLength)).toEqual(12);
124
189
  });
@@ -59,4 +59,29 @@ describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
59
59
  withElevated([], () => Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables))
60
60
  expect(variables->Dict.get(Util_OwnerScopeEnv.key)->Option.isNone)->toBe(true)
61
61
  })
62
+
63
+ // The whole path the "administrator sees empty screens" trap ran through: the
64
+ // platform entry point defaults the list, and this composes it into a Lambda's
65
+ // environment. Asserted end to end rather than at either half, because both
66
+ // halves were already correct while the trap was open — the pipe was laid and
67
+ // nothing ever put a value in.
68
+ testSync("the platform's administrator default reaches a composed environment", () => {
69
+ Reventless.OwnerScope.clearElevatedGroups()
70
+ NodeProcess.env->Dict.delete(Util_OwnerScopeEnv.key)
71
+ Reventless.OwnerScope.defaultElevatedGroups([Reventless.AdminGroup.name])
72
+
73
+ let variables = Dict.make()
74
+ Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
75
+ let encoded = switch variables->Dict.get(Util_OwnerScopeEnv.key) {
76
+ | Some(value) => value->Obj.magic
77
+ | None => ""
78
+ }
79
+ Reventless.OwnerScope.clearElevatedGroups()
80
+
81
+ // Round-tripped, like the first case above: the runtime reads this back with
82
+ // `parseElevatedGroups`, so the encoding is asserted rather than the spelling.
83
+ expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual([
84
+ Reventless.AdminGroup.name,
85
+ ])
86
+ })
62
87
  })
@@ -1,6 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
3
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
4
6
  import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
5
7
  import * as Util_OwnerScopeEnv$ReventlessAws from "../src/util/Util_OwnerScopeEnv.res.mjs";
6
8
 
@@ -54,6 +56,17 @@ globalThis.describe("Util_OwnerScopeEnv — the elevated-groups carrier", () =>
54
56
  withElevated([], () => Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables));
55
57
  globalThis.expect(Stdlib_Option.isNone(variables[Util_OwnerScopeEnv$ReventlessAws.key])).toBe(true);
56
58
  });
59
+ globalThis.test("the platform's administrator default reaches a composed environment", () => {
60
+ OwnerScope$Reventless.clearElevatedGroups();
61
+ Stdlib_Dict.$$delete(process.env, Util_OwnerScopeEnv$ReventlessAws.key);
62
+ OwnerScope$Reventless.defaultElevatedGroups([AdminGroup$Reventless.name]);
63
+ let variables = {};
64
+ Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
65
+ let value = variables[Util_OwnerScopeEnv$ReventlessAws.key];
66
+ let encoded = value !== undefined ? value : "";
67
+ OwnerScope$Reventless.clearElevatedGroups();
68
+ globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([AdminGroup$Reventless.name]);
69
+ });
57
70
  });
58
71
 
59
72
  export {