@reventlessdev/reventless-local 3.0.0-alpha.297 → 3.0.0-alpha.299

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 (59) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +10 -10
  3. package/src/EmitCapabilities.res +3 -3
  4. package/src/LocalPlatformRegistry.res +1 -1
  5. package/src/LocalSeedTarget.res +1 -1
  6. package/src/Platform.res +57 -3
  7. package/src/Platform.res.mjs +61 -6
  8. package/src/ShellConfig.res +11 -1
  9. package/src/ShellConfig.res.mjs +10 -4
  10. package/src/adapter/Auth/Auth_GraphqlContext.res +18 -2
  11. package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +8 -1
  12. package/src/adapter/Auth/UserStore.res +102 -3
  13. package/src/adapter/Auth/UserStore.res.mjs +128 -2
  14. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +33 -0
  15. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +26 -0
  16. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +11 -3
  17. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +3 -0
  18. package/src/adapter/DomainGraphQL_Server.res +1 -1
  19. package/src/adapter/GraphQL_CallerError.res +4 -0
  20. package/src/adapter/GraphQL_CallerError.res.mjs +9 -0
  21. package/src/adapter/LocalAnonymousLimit.res +35 -0
  22. package/src/adapter/LocalAnonymousLimit.res.mjs +52 -0
  23. package/src/adapter/LocalCapabilities.res +14 -2
  24. package/src/adapter/LocalCapabilities.res.mjs +21 -5
  25. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +22 -0
  26. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +7 -0
  27. package/src/capability/Capability_IdentityProvider_UserStore.res +6 -2
  28. package/src/capability/Capability_IdentityProvider_UserStore.res.mjs +6 -2
  29. package/src/components/InboundTranslationSlice_Builder.res +1 -0
  30. package/src/components/InboundTranslationSlice_Builder.res.mjs +4 -1
  31. package/tests/EmitCapabilitiesTest.res +4 -6
  32. package/tests/EmitCapabilitiesTest.res.mjs +1 -2
  33. package/tests/HostShellDistTest.res.mjs +1 -1
  34. package/tests/LocalPlatformRegistryTest.res +3 -5
  35. package/tests/LocalPlatformRegistryTest.res.mjs +2 -4
  36. package/tests/LocalPlatformStartTest.res +2 -2
  37. package/tests/LocalPlatformStartTest.res.mjs +1 -2
  38. package/tests/ShellConfigTest.res.mjs +27 -27
  39. package/tests/adapter/InboundTranslationMutationTest.res +13 -2
  40. package/tests/adapter/InboundTranslationMutationTest.res.mjs +7 -4
  41. package/tests/adapter/LocalAnonymousLimitTest.res +70 -0
  42. package/tests/adapter/LocalAnonymousLimitTest.res.mjs +85 -0
  43. package/tests/adapter/LocalAuthLoginTest.res +2 -2
  44. package/tests/adapter/LocalAuthUserStoreTest.res +88 -0
  45. package/tests/adapter/LocalAuthUserStoreTest.res.mjs +83 -0
  46. package/tests/capability/Capability_IdentityProvider_UserStoreTest.res +6 -0
  47. package/tests/capability/Capability_IdentityProvider_UserStoreTest.res.mjs +4 -0
  48. package/tests/capability/LocalCapabilitiesTest.res +2 -1
  49. package/tests/capability/LocalCapabilitiesTest.res.mjs +3 -2
  50. package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res +136 -0
  51. package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res.mjs +183 -0
  52. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +143 -11
  53. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +129 -22
  54. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +18 -5
  55. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +12 -3
  56. package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res +26 -6
  57. package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res.mjs +48 -13
  58. package/tests/plugin/Plugin_Fixtures.res +0 -4
  59. package/tests/plugin/Plugin_Fixtures.res.mjs +13 -33
@@ -233,3 +233,91 @@ describe("UserStore.providedGroups", () => {
233
233
  expect(loaded)->toBe(true)
234
234
  })
235
235
  })
236
+
237
+ describe("UserStore.reload", () => {
238
+ let signsIn = async (~username, ~password) =>
239
+ switch await LocalAuth.Login.issue(~username, ~password) {
240
+ | Ok(_) => true
241
+ | Error(_) => false
242
+ }
243
+
244
+ testPromise("adds, changes and forgets accounts as the file now says", async () => {
245
+ resetAll()
246
+ let path = _writeYamlFile(`
247
+ - username: alice
248
+ password: a1
249
+ groups: [Editor]
250
+ - username: bob
251
+ password: b1
252
+ groups: []
253
+ `)
254
+ let _ = UserStore.load(~usersFile=path, ())
255
+ NodeFs.writeFileSync(
256
+ path,
257
+ `
258
+ - username: alice
259
+ password: a2
260
+ groups: [Editor]
261
+ - username: driver
262
+ password: driver
263
+ groups: [Driver]
264
+ `,
265
+ )
266
+ switch UserStore.reload(path) {
267
+ | Ok(said) => expect(said)->toEqual("2 accounts, removed: bob")
268
+ | Error(e) => JsError.throwWithMessage(e)
269
+ }
270
+ expect(await signsIn(~username="driver", ~password="driver"))->toBe(true)
271
+ expect(await signsIn(~username="alice", ~password="a2"))->toBe(true)
272
+ expect(await signsIn(~username="alice", ~password="a1"))->toBe(false)
273
+ expect(await signsIn(~username="bob", ~password="b1"))->toBe(false)
274
+ expect(LocalAuth.lookupUser("bob"))->toEqual(None)
275
+ })
276
+
277
+ testPromise("a file that does not parse changes nothing", async () => {
278
+ resetAll()
279
+ let path = _writeYamlFile(`
280
+ - username: alice
281
+ password: a1
282
+ groups: [Editor]
283
+ `)
284
+ let _ = UserStore.load(~usersFile=path, ())
285
+ NodeFs.writeFileSync(path, "- username: alice\n groups: [Editor]\n")
286
+ expect(UserStore.reload(path)->Result.isError)->toBe(true)
287
+ expect(await signsIn(~username="alice", ~password="a1"))->toBe(true)
288
+ })
289
+
290
+ testPromise("watch reads the file again once it is saved", async () => {
291
+ resetAll()
292
+ let path = _writeYamlFile(`
293
+ - username: alice
294
+ password: a1
295
+ groups: [Editor]
296
+ `)
297
+ let _ = UserStore.load(~usersFile=path, ())
298
+ UserStore.watch(path)
299
+ NodeFs.writeFileSync(
300
+ path,
301
+ `
302
+ - username: alice
303
+ password: a1
304
+ groups: [Editor]
305
+ - username: driver
306
+ password: driver
307
+ groups: [Driver]
308
+ `,
309
+ )
310
+ let rec waitFor = async (tries: int) =>
311
+ if await signsIn(~username="driver", ~password="driver") {
312
+ true
313
+ } else if tries == 0 {
314
+ false
315
+ } else {
316
+ await Promise.make((resolve, _) => setTimeout(() => resolve(), 50)->ignore)
317
+ await waitFor(tries - 1)
318
+ }
319
+ let added = await waitFor(60)
320
+ UserStore.unwatch()
321
+ expect(added)->toBe(true)
322
+ })
323
+ })
@@ -293,6 +293,89 @@ globalThis.describe("UserStore.providedGroups", () => {
293
293
  });
294
294
  });
295
295
 
296
+ globalThis.describe("UserStore.reload", () => {
297
+ let signsIn = async (username, password) => {
298
+ let match = await LocalAuth$ReventlessLocal.Login.issue(username, password, undefined);
299
+ return match.TAG === "Ok";
300
+ };
301
+ globalThis.test("adds, changes and forgets accounts as the file now says", async () => {
302
+ resetAll();
303
+ let path = _writeYamlFile(`
304
+ - username: alice
305
+ password: a1
306
+ groups: [Editor]
307
+ - username: bob
308
+ password: b1
309
+ groups: []
310
+ `);
311
+ UserStore$ReventlessLocal.load(undefined, path, undefined);
312
+ Nodefs.writeFileSync(path, `
313
+ - username: alice
314
+ password: a2
315
+ groups: [Editor]
316
+ - username: driver
317
+ password: driver
318
+ groups: [Driver]
319
+ `, "utf8");
320
+ let said = UserStore$ReventlessLocal.reload(path);
321
+ if (said.TAG === "Ok") {
322
+ globalThis.expect(said._0).toEqual("2 accounts, removed: bob");
323
+ } else {
324
+ Stdlib_JsError.throwWithMessage(said._0);
325
+ }
326
+ globalThis.expect(await signsIn("driver", "driver")).toBe(true);
327
+ globalThis.expect(await signsIn("alice", "a2")).toBe(true);
328
+ globalThis.expect(await signsIn("alice", "a1")).toBe(false);
329
+ globalThis.expect(await signsIn("bob", "b1")).toBe(false);
330
+ globalThis.expect(LocalAuth$ReventlessLocal.lookupUser("bob")).toEqual(undefined);
331
+ });
332
+ globalThis.test("a file that does not parse changes nothing", async () => {
333
+ resetAll();
334
+ let path = _writeYamlFile(`
335
+ - username: alice
336
+ password: a1
337
+ groups: [Editor]
338
+ `);
339
+ UserStore$ReventlessLocal.load(undefined, path, undefined);
340
+ Nodefs.writeFileSync(path, "- username: alice\n groups: [Editor]\n", "utf8");
341
+ globalThis.expect(Stdlib_Result.isError(UserStore$ReventlessLocal.reload(path))).toBe(true);
342
+ globalThis.expect(await signsIn("alice", "a1")).toBe(true);
343
+ });
344
+ globalThis.test("watch reads the file again once it is saved", async () => {
345
+ resetAll();
346
+ let path = _writeYamlFile(`
347
+ - username: alice
348
+ password: a1
349
+ groups: [Editor]
350
+ `);
351
+ UserStore$ReventlessLocal.load(undefined, path, undefined);
352
+ UserStore$ReventlessLocal.watch(path);
353
+ Nodefs.writeFileSync(path, `
354
+ - username: alice
355
+ password: a1
356
+ groups: [Editor]
357
+ - username: driver
358
+ password: driver
359
+ groups: [Driver]
360
+ `, "utf8");
361
+ let waitFor = async tries => {
362
+ if (await signsIn("driver", "driver")) {
363
+ return true;
364
+ } else if (tries === 0) {
365
+ return false;
366
+ } else {
367
+ await new Promise((resolve, param) => {
368
+ setTimeout(() => resolve(), 50);
369
+ });
370
+ return await waitFor(tries - 1 | 0);
371
+ }
372
+ };
373
+ let added = await waitFor(60);
374
+ UserStore$ReventlessLocal.unwatch();
375
+ globalThis.expect(added).toBe(true);
376
+ });
377
+ });
378
+
296
379
  export {
297
380
  resetAll,
298
381
  _writeYamlFile,
@@ -49,6 +49,8 @@ describe("Capability_IdentityProvider_UserStore", () => {
49
49
  (await LocalAuth.Login.issue(~username="new@example.com", ~password="secret-1"))->Result.isOk,
50
50
  )->toBe(true)
51
51
 
52
+ let mintedNow = LocalAuth.lookupUser("new@example.com")->Option.map(i => i.userId)
53
+
52
54
  restartFrom(file)
53
55
  expect(
54
56
  (await LocalAuth.Login.issue(~username="new@example.com", ~password="secret-1"))->Result.isOk,
@@ -56,6 +58,10 @@ describe("Capability_IdentityProvider_UserStore", () => {
56
58
  expect(LocalAuth.lookupUser("new@example.com")->Option.map(i => i.groups))->toEqual(
57
59
  Some(["Shopper"]),
58
60
  )
61
+ // The domain id is minted, not the login name, and the file carries the
62
+ // same one the running process handed out.
63
+ expect(mintedNow->Option.map(ReventlessCore.Auth_UserId.isMinted))->toEqual(Some(true))
64
+ expect(LocalAuth.lookupUser("new@example.com")->Option.map(i => i.userId))->toEqual(mintedNow)
59
65
  })
60
66
 
61
67
  testPromise("the same contact twice is a Conflict, not a second account", async () => {
@@ -8,6 +8,7 @@ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
8
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
9
  import * as LocalAuth$ReventlessLocal from "../../src/adapter/Auth/LocalAuth.res.mjs";
10
10
  import * as UserStore$ReventlessLocal from "../../src/adapter/Auth/UserStore.res.mjs";
11
+ import * as Auth_UserId$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Auth/Auth_UserId.res.mjs";
11
12
  import * as Capability_IdentityProvider_UserStore$ReventlessLocal from "../../src/capability/Capability_IdentityProvider_UserStore.res.mjs";
12
13
 
13
14
  function email(address) {
@@ -65,9 +66,12 @@ globalThis.describe("Capability_IdentityProvider_UserStore", () => {
65
66
  }
66
67
  });
67
68
  globalThis.expect(Stdlib_Result.isOk(await LocalAuth$ReventlessLocal.Login.issue("new@example.com", "secret-1", undefined))).toBe(true);
69
+ let mintedNow = Stdlib_Option.map(LocalAuth$ReventlessLocal.lookupUser("new@example.com"), i => i.userId);
68
70
  restartFrom(file);
69
71
  globalThis.expect(Stdlib_Result.isOk(await LocalAuth$ReventlessLocal.Login.issue("new@example.com", "secret-1", undefined))).toBe(true);
70
72
  globalThis.expect(Stdlib_Option.map(LocalAuth$ReventlessLocal.lookupUser("new@example.com"), i => i.groups)).toEqual(["Shopper"]);
73
+ globalThis.expect(Stdlib_Option.map(mintedNow, Auth_UserId$ReventlessCore.isMinted)).toEqual(true);
74
+ globalThis.expect(Stdlib_Option.map(LocalAuth$ReventlessLocal.lookupUser("new@example.com"), i => i.userId)).toEqual(mintedNow);
71
75
  });
72
76
  globalThis.test("the same contact twice is a Conflict, not a second account", async () => {
73
77
  resetAll();
@@ -18,7 +18,7 @@ let reset = () => {
18
18
  describe("LocalCapabilities.unmetWarning", () => {
19
19
  testSync("a bare platform provisions nothing, and the warning names each need once", () => {
20
20
  reset()
21
- expect(LocalCapabilities.provisioned())->toEqual([])
21
+ expect(LocalCapabilities.provisioned())->toEqual([Reventless.CapabilityNeed.Stash])
22
22
  let message = LocalCapabilities.unmetWarning(~declared)->Option.getOr("")
23
23
  expect(message->String.includes("Geocoding (GeocodeAddress)"))->toBe(true)
24
24
  expect(message->String.includes("Capability_Messaging_Log.make()"))->toBe(true)
@@ -34,6 +34,7 @@ describe("LocalCapabilities.unmetWarning", () => {
34
34
  expect(LocalCapabilities.provisioned())->toEqual([
35
35
  Reventless.CapabilityNeed.Messaging,
36
36
  IdentityProvider,
37
+ Stash,
37
38
  ])
38
39
  let message = LocalCapabilities.unmetWarning(~declared)->Option.getOr("")
39
40
  expect(message->String.includes("Messaging"))->toBe(false)
@@ -28,7 +28,7 @@ function reset() {
28
28
  globalThis.describe("LocalCapabilities.unmetWarning", () => {
29
29
  globalThis.test("a bare platform provisions nothing, and the warning names each need once", () => {
30
30
  reset();
31
- globalThis.expect(LocalCapabilities$ReventlessLocal.provisioned()).toEqual([]);
31
+ globalThis.expect(LocalCapabilities$ReventlessLocal.provisioned()).toEqual(["Stash"]);
32
32
  let message = Stdlib_Option.getOr(LocalCapabilities$ReventlessLocal.unmetWarning(declared), "");
33
33
  globalThis.expect(message.includes("Geocoding (GeocodeAddress)")).toBe(true);
34
34
  globalThis.expect(message.includes("Capability_Messaging_Log.make()")).toBe(true);
@@ -40,7 +40,8 @@ globalThis.describe("LocalCapabilities.unmetWarning", () => {
40
40
  LocalCapabilities$ReventlessLocal.registerIdentityProvider(Capability_IdentityProvider_UserStore$ReventlessLocal.make("/nonexistent/users.yaml", undefined));
41
41
  globalThis.expect(LocalCapabilities$ReventlessLocal.provisioned()).toEqual([
42
42
  "Messaging",
43
- "IdentityProvider"
43
+ "IdentityProvider",
44
+ "Stash"
44
45
  ]);
45
46
  let message = Stdlib_Option.getOr(LocalCapabilities$ReventlessLocal.unmetWarning(declared), "");
46
47
  globalThis.expect(message.includes("Messaging")).toBe(false);
@@ -0,0 +1,136 @@
1
+ // An inbound door that takes a credential: `translate` reaches a capability with
2
+ // it, and the audit row never holds it. The shape a registration's
3
+ // set-a-password step has.
4
+
5
+ open JestGlobals
6
+
7
+ module SignUpSpec = {
8
+ let name = "SignUp"
9
+ let moduleUrl = "test://sign-up"
10
+
11
+ // Hand-written marker: this inline module has no `@@reventless.spec` pass.
12
+ @schema
13
+ type externalInput = {
14
+ email: string,
15
+ password: @s.matches(Reventless.Sensitive.string) string,
16
+ }
17
+
18
+ @schema
19
+ type command = AccountOpened({email: string, principal: string})
20
+
21
+ let targetName = "Accounts"
22
+ let externalSystem = None
23
+ let capabilityNeeds = [Reventless.CapabilityNeed.IdentityProvider]
24
+ let endpoint = None
25
+ type role = Reventless.Role.name
26
+ let authorizationOf = (_: string): Reventless.Authorization.rule<role> => AllowAnonymous
27
+ }
28
+
29
+ module SignUpTranslation = {
30
+ let moduleUrl = SignUpSpec.moduleUrl
31
+ let translate = async (
32
+ input: SignUpSpec.externalInput,
33
+ ~capabilities: Reventless.Capabilities.t,
34
+ ~caller as _: option<Reventless.Identity.t>,
35
+ ) =>
36
+ switch await capabilities.identityProvider.createPrincipal(
37
+ ~contact=ToEmail(Reventless.Email.unsafe(input.email)),
38
+ ~credential=Password(input.password),
39
+ ~groups=[],
40
+ ) {
41
+ | Ok({providerId}) =>
42
+ Ok([(input.email, SignUpSpec.AccountOpened({email: input.email, principal: providerId}))])
43
+ | Error(Conflict) => Error("taken")
44
+ | Error(Unavailable(why) | Refused(why)) => Error(why)
45
+ }
46
+ }
47
+
48
+ module Callback = ReventlessCore.InboundTranslationSlice_Callback.Make(
49
+ SignUpSpec,
50
+ SignUpTranslation,
51
+ )
52
+
53
+ let input = (~password) =>
54
+ JSON.Encode.object(
55
+ Dict.fromArray([
56
+ ("email", JSON.Encode.string("new@example.com")),
57
+ ("password", JSON.Encode.string(password)),
58
+ ]),
59
+ )
60
+
61
+ let auditOf = result =>
62
+ Callback.auditLog->Dict.get(result->ReventlessCore.InboundTranslationSlice_Callback.requestIdOf)
63
+
64
+ let publishNothing: ReventlessInfra.CommandTopic.publishJsons = async _ => ()
65
+
66
+ describe("an inbound door taking a credential", () => {
67
+ testPromise("translate is handed the platform's capabilities", async () => {
68
+ let created = ref(None)
69
+ let fakes = ReventlessGwt.Capabilities_Fake.make(
70
+ ~createPrincipal=async (~contact as _, ~credential, ~groups as _) => {
71
+ created := Some(credential)
72
+ Ok({Reventless.IdentityProvider.providerId: "p-1"})
73
+ },
74
+ (),
75
+ )
76
+ let result = await Callback.receive(
77
+ publishNothing,
78
+ input(~password="correct horse"),
79
+ ~capabilities=fakes.capabilities,
80
+ )
81
+ expect(result->Result.isOk)->toBe(true)
82
+ expect(created.contents)->toEqual(Some(Reventless.IdentityProvider.Password("correct horse")))
83
+ })
84
+
85
+ testPromise("the audit row withholds the credential on success", async () => {
86
+ let result = await Callback.receive(
87
+ publishNothing,
88
+ input(~password="correct horse"),
89
+ ~capabilities=ReventlessGwt.Capabilities_Fake.make().capabilities,
90
+ )
91
+ let row = auditOf(result)->Option.map(r => r.input)->Option.getOr("")
92
+ expect(row->String.includes("correct horse"))->toBe(false)
93
+ expect(row->String.includes("[withheld]"))->toBe(true)
94
+ expect(row->String.includes("new@example.com"))->toBe(true)
95
+ })
96
+
97
+ // The refusal path writes its own row, and a refused request is the one most
98
+ // likely to be read by an operator chasing a complaint.
99
+ testPromise("the audit row withholds the credential on a refusal too", async () => {
100
+ let result = await Callback.receive(
101
+ publishNothing,
102
+ input(~password="correct horse"),
103
+ ~capabilities=Reventless.Capabilities.none,
104
+ )
105
+ expect(result->Result.isError)->toBe(true)
106
+ let row = auditOf(result)->Option.map(r => r.input)->Option.getOr("")
107
+ expect(row->String.includes("correct horse"))->toBe(false)
108
+ })
109
+
110
+ testSync("a webhook body that cannot be read keeps none of itself", () => {
111
+ let result = Callback.refuse(
112
+ ~body=`{"email": "new@example.com", "password": "correct horse"`,
113
+ ~errorCode="InvalidSignature",
114
+ "unsigned",
115
+ )
116
+ let row = auditOf(result)->Option.map(r => r.input)->Option.getOr("")
117
+ expect(row->String.includes("correct horse"))->toBe(false)
118
+ })
119
+
120
+ testSync("a webhook body that can be read keeps everything but the credential", () => {
121
+ let result = Callback.refuse(
122
+ ~body=`{"email": "new@example.com", "password": "correct horse"}`,
123
+ ~errorCode="InvalidSignature",
124
+ "unsigned",
125
+ )
126
+ let row = auditOf(result)->Option.map(r => r.input)->Option.getOr("")
127
+ expect(row->String.includes("correct horse"))->toBe(false)
128
+ expect(row->String.includes("new@example.com"))->toBe(true)
129
+ })
130
+
131
+ testSync("a slice with no sensitive field keeps its body as received", () =>
132
+ expect(
133
+ ReventlessCore.InboundTranslationSlice_Callback.redactBody("not json", ~sensitive=[]),
134
+ )->toBe("not json")
135
+ )
136
+ })
@@ -0,0 +1,183 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Sury from "sury";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
6
+ import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
7
+ import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
8
+ import * as Capabilities_Fake$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Capabilities_Fake.res.mjs";
9
+ import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
10
+
11
+ let name = "SignUp";
12
+
13
+ let moduleUrl = "test://sign-up";
14
+
15
+ let externalInputSchema = Sury.$schema(s => ({
16
+ email: s.m(Sury.string),
17
+ password: s.m(Sensitive$Reventless.string)
18
+ }));
19
+
20
+ let commandSchema = Sury.$schema(s => ({
21
+ TAG: "AccountOpened",
22
+ email: s.m(Sury.string),
23
+ principal: s.m(Sury.string)
24
+ }));
25
+
26
+ let targetName = "Accounts";
27
+
28
+ let capabilityNeeds = ["IdentityProvider"];
29
+
30
+ function authorizationOf(param) {
31
+ return "AllowAnonymous";
32
+ }
33
+
34
+ let traits = [];
35
+
36
+ let SignUpSpec = {
37
+ name: name,
38
+ moduleUrl: moduleUrl,
39
+ externalInputSchema: externalInputSchema,
40
+ commandSchema: commandSchema,
41
+ targetName: targetName,
42
+ externalSystem: undefined,
43
+ capabilityNeeds: capabilityNeeds,
44
+ endpoint: undefined,
45
+ authorizationOf: authorizationOf,
46
+ traits: traits
47
+ };
48
+
49
+ async function translate(input, capabilities, param) {
50
+ let match = await capabilities.identityProvider.createPrincipal({
51
+ TAG: "ToEmail",
52
+ _0: input.email
53
+ }, {
54
+ TAG: "Password",
55
+ _0: input.password
56
+ }, []);
57
+ if (match.TAG === "Ok") {
58
+ return {
59
+ TAG: "Ok",
60
+ _0: [[
61
+ input.email,
62
+ {
63
+ TAG: "AccountOpened",
64
+ email: input.email,
65
+ principal: match._0.providerId
66
+ }
67
+ ]]
68
+ };
69
+ }
70
+ let match$1 = match._0;
71
+ if (typeof match$1 !== "object") {
72
+ return {
73
+ TAG: "Error",
74
+ _0: "taken"
75
+ };
76
+ } else {
77
+ return {
78
+ TAG: "Error",
79
+ _0: match$1._0
80
+ };
81
+ }
82
+ }
83
+
84
+ let SignUpTranslation = {
85
+ moduleUrl: moduleUrl,
86
+ translate: translate
87
+ };
88
+
89
+ let Callback = InboundTranslationSlice_Callback$ReventlessCore.Make({
90
+ name: name,
91
+ moduleUrl: moduleUrl,
92
+ externalInputSchema: externalInputSchema,
93
+ commandSchema: commandSchema,
94
+ targetName: targetName,
95
+ externalSystem: undefined,
96
+ authorizationOf: authorizationOf,
97
+ endpoint: undefined,
98
+ capabilityNeeds: capabilityNeeds
99
+ })({
100
+ translate: translate,
101
+ moduleUrl: moduleUrl
102
+ });
103
+
104
+ function input(password) {
105
+ return Object.fromEntries([
106
+ [
107
+ "email",
108
+ "new@example.com"
109
+ ],
110
+ [
111
+ "password",
112
+ password
113
+ ]
114
+ ]);
115
+ }
116
+
117
+ function auditOf(result) {
118
+ return Callback.auditLog[InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result)];
119
+ }
120
+
121
+ async function publishNothing(param) {
122
+
123
+ }
124
+
125
+ globalThis.describe("an inbound door taking a credential", () => {
126
+ globalThis.test("translate is handed the platform's capabilities", async () => {
127
+ let created = {
128
+ contents: undefined
129
+ };
130
+ let fakes = Capabilities_Fake$ReventlessGwt.make(undefined, undefined, undefined, undefined, async (param, credential, param$1) => {
131
+ created.contents = credential;
132
+ return {
133
+ TAG: "Ok",
134
+ _0: {
135
+ providerId: "p-1"
136
+ }
137
+ };
138
+ }, undefined, undefined, undefined, undefined, undefined, undefined);
139
+ let result = await Callback.receive(publishNothing, input("correct horse"), fakes.capabilities, undefined, undefined);
140
+ globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
141
+ globalThis.expect(created.contents).toEqual({
142
+ TAG: "Password",
143
+ _0: "correct horse"
144
+ });
145
+ });
146
+ globalThis.test("the audit row withholds the credential on success", async () => {
147
+ let result = await Callback.receive(publishNothing, input("correct horse"), Capabilities_Fake$ReventlessGwt.make(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined).capabilities, undefined, undefined);
148
+ let row = Stdlib_Option.getOr(Stdlib_Option.map(Callback.auditLog[InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result)], r => r.input), "");
149
+ globalThis.expect(row.includes("correct horse")).toBe(false);
150
+ globalThis.expect(row.includes("[withheld]")).toBe(true);
151
+ globalThis.expect(row.includes("new@example.com")).toBe(true);
152
+ });
153
+ globalThis.test("the audit row withholds the credential on a refusal too", async () => {
154
+ let result = await Callback.receive(publishNothing, input("correct horse"), Capabilities$Reventless.none, undefined, undefined);
155
+ globalThis.expect(Stdlib_Result.isError(result)).toBe(true);
156
+ let row = Stdlib_Option.getOr(Stdlib_Option.map(Callback.auditLog[InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result)], r => r.input), "");
157
+ globalThis.expect(row.includes("correct horse")).toBe(false);
158
+ });
159
+ globalThis.test("a webhook body that cannot be read keeps none of itself", () => {
160
+ let result = Callback.refuse(`{"email": "new@example.com", "password": "correct horse"`, "InvalidSignature", "unsigned");
161
+ let row = Stdlib_Option.getOr(Stdlib_Option.map(Callback.auditLog[InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result)], r => r.input), "");
162
+ globalThis.expect(row.includes("correct horse")).toBe(false);
163
+ });
164
+ globalThis.test("a webhook body that can be read keeps everything but the credential", () => {
165
+ let result = Callback.refuse(`{"email": "new@example.com", "password": "correct horse"}`, "InvalidSignature", "unsigned");
166
+ let row = Stdlib_Option.getOr(Stdlib_Option.map(Callback.auditLog[InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result)], r => r.input), "");
167
+ globalThis.expect(row.includes("correct horse")).toBe(false);
168
+ globalThis.expect(row.includes("new@example.com")).toBe(true);
169
+ });
170
+ globalThis.test("a slice with no sensitive field keeps its body as received", () => {
171
+ globalThis.expect(InboundTranslationSlice_Callback$ReventlessCore.redactBody("not json", [])).toBe("not json");
172
+ });
173
+ });
174
+
175
+ export {
176
+ SignUpSpec,
177
+ SignUpTranslation,
178
+ Callback,
179
+ input,
180
+ auditOf,
181
+ publishNothing,
182
+ }
183
+ /* externalInputSchema Not a pure module */