@reventlessdev/reventless-local 3.0.0-alpha.298 → 3.0.0-alpha.300
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 +37 -0
- package/package.json +12 -12
- package/src/EmitCapabilities.res +3 -3
- package/src/LocalPlatformRegistry.res +1 -1
- package/src/LocalSeedTarget.res +1 -1
- package/src/Platform.res +57 -3
- package/src/Platform.res.mjs +61 -6
- package/src/ShellConfig.res +11 -1
- package/src/ShellConfig.res.mjs +10 -4
- package/src/adapter/Auth/Auth_GraphqlContext.res +18 -2
- package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +8 -1
- package/src/adapter/Auth/UserStore.res +102 -3
- package/src/adapter/Auth/UserStore.res.mjs +128 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +33 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +26 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +11 -3
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +3 -0
- package/src/adapter/DomainGraphQL_Server.res +1 -1
- package/src/adapter/GraphQL_CallerError.res +4 -0
- package/src/adapter/GraphQL_CallerError.res.mjs +9 -0
- package/src/adapter/LocalAnonymousLimit.res +35 -0
- package/src/adapter/LocalAnonymousLimit.res.mjs +52 -0
- package/src/adapter/LocalCapabilities.res +14 -2
- package/src/adapter/LocalCapabilities.res.mjs +21 -5
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +22 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +7 -0
- package/src/components/InboundTranslationSlice_Builder.res +1 -0
- package/src/components/InboundTranslationSlice_Builder.res.mjs +4 -1
- package/tests/EmitCapabilitiesTest.res +4 -6
- package/tests/EmitCapabilitiesTest.res.mjs +1 -2
- package/tests/HostShellDistTest.res.mjs +1 -1
- package/tests/LocalPlatformRegistryTest.res +3 -5
- package/tests/LocalPlatformRegistryTest.res.mjs +2 -4
- package/tests/LocalPlatformStartTest.res +2 -2
- package/tests/LocalPlatformStartTest.res.mjs +1 -2
- package/tests/ShellConfigTest.res.mjs +27 -27
- package/tests/adapter/InboundTranslationMutationTest.res +13 -2
- package/tests/adapter/InboundTranslationMutationTest.res.mjs +7 -4
- package/tests/adapter/LocalAnonymousLimitTest.res +70 -0
- package/tests/adapter/LocalAnonymousLimitTest.res.mjs +85 -0
- package/tests/adapter/LocalAuthLoginTest.res +2 -2
- package/tests/adapter/LocalAuthUserStoreTest.res +88 -0
- package/tests/adapter/LocalAuthUserStoreTest.res.mjs +83 -0
- package/tests/capability/LocalCapabilitiesTest.res +2 -1
- package/tests/capability/LocalCapabilitiesTest.res.mjs +3 -2
- package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res +136 -0
- package/tests/components/inboundtranslationslice/InboundTranslationCredentialTest.res.mjs +183 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +143 -11
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +129 -22
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +18 -5
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +12 -3
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res +26 -6
- package/tests/components/inboundtranslationslice/InboundTranslationWebhookTest.res.mjs +48 -13
- package/tests/plugin/Plugin_Fixtures.res +0 -4
- 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,
|
|
@@ -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 */
|