@reventlessdev/reventless-local 3.0.0-alpha.217 → 3.0.0-alpha.219

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.
@@ -1,6 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
3
4
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
4
6
  import * as LocalAuth$ReventlessLocal from "../../src/adapter/Auth/LocalAuth.res.mjs";
5
7
 
6
8
  let alice_groups = ["Editor"];
@@ -36,7 +38,7 @@ function resetAll() {
36
38
  globalThis.test("issue returns Ok(token) for valid credentials", async () => {
37
39
  resetAll();
38
40
  LocalAuth$ReventlessLocal.Login.setCredentials("alice", "alice-pw", alice);
39
- let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw");
41
+ let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw", undefined);
40
42
  if (result.TAG !== "Ok") {
41
43
  return Stdlib_JsError.throwWithMessage("unexpected Error: " + result._0);
42
44
  }
@@ -46,7 +48,7 @@ globalThis.test("issue returns Ok(token) for valid credentials", async () => {
46
48
 
47
49
  globalThis.test("issue returns Error for unknown username", async () => {
48
50
  resetAll();
49
- let result = await LocalAuth$ReventlessLocal.Login.issue("ghost", "x");
51
+ let result = await LocalAuth$ReventlessLocal.Login.issue("ghost", "x", undefined);
50
52
  if (result.TAG === "Ok") {
51
53
  return Stdlib_JsError.throwWithMessage("expected Error");
52
54
  }
@@ -55,7 +57,7 @@ globalThis.test("issue returns Error for unknown username", async () => {
55
57
  globalThis.test("issue returns Error for wrong password", async () => {
56
58
  resetAll();
57
59
  LocalAuth$ReventlessLocal.Login.setCredentials("alice", "alice-pw", alice);
58
- let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "WRONG");
60
+ let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "WRONG", undefined);
59
61
  if (result.TAG === "Ok") {
60
62
  return Stdlib_JsError.throwWithMessage("expected Error");
61
63
  }
@@ -64,7 +66,7 @@ globalThis.test("issue returns Error for wrong password", async () => {
64
66
  globalThis.test("verifyAndDecode round-trips the identity for a fresh token", async () => {
65
67
  resetAll();
66
68
  LocalAuth$ReventlessLocal.Login.setCredentials("alice", "alice-pw", alice);
67
- let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw");
69
+ let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw", undefined);
68
70
  let token;
69
71
  token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage("issue failed: " + t._0);
70
72
  let identity = LocalAuth$ReventlessLocal.Login.verifyAndDecode(token);
@@ -81,7 +83,7 @@ globalThis.test("verifyAndDecode round-trips the identity for a fresh token", as
81
83
  globalThis.test("verifyAndDecode rejects a tampered payload", async () => {
82
84
  resetAll();
83
85
  LocalAuth$ReventlessLocal.Login.setCredentials("alice", "alice-pw", alice);
84
- let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw");
86
+ let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw", undefined);
85
87
  let token;
86
88
  token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage("issue failed: " + t._0);
87
89
  let parts = token.split(".");
@@ -100,7 +102,7 @@ globalThis.test("verifyAndDecode rejects a tampered payload", async () => {
100
102
  globalThis.test("verifyAndDecode rejects a tampered signature", async () => {
101
103
  resetAll();
102
104
  LocalAuth$ReventlessLocal.Login.setCredentials("alice", "alice-pw", alice);
103
- let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw");
105
+ let t = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw", undefined);
104
106
  let token;
105
107
  token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage("issue failed: " + t._0);
106
108
  let match = LocalAuth$ReventlessLocal.Login.verifyAndDecode(token + "x");
@@ -120,7 +122,7 @@ globalThis.test("verifyAndDecode rejects a malformed token (no dot)", async () =
120
122
  globalThis.test("authenticate accepts a valid Bearer token", async () => {
121
123
  resetAll();
122
124
  LocalAuth$ReventlessLocal.Login.setCredentials("bob", "bob-pw", bob);
123
- let t = await LocalAuth$ReventlessLocal.Login.issue("bob", "bob-pw");
125
+ let t = await LocalAuth$ReventlessLocal.Login.issue("bob", "bob-pw", undefined);
124
126
  let token;
125
127
  token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage("issue failed: " + t._0);
126
128
  let result = await LocalAuth$ReventlessLocal.authenticate(buildContext([[
@@ -158,7 +160,7 @@ globalThis.test("authenticate rejects invalid Bearer even when X-User is present
158
160
  globalThis.test("Bearer outranks X-User when both present and Bearer is valid", async () => {
159
161
  resetAll();
160
162
  LocalAuth$ReventlessLocal.Login.setCredentials("bob", "bob-pw", bob);
161
- let t = await LocalAuth$ReventlessLocal.Login.issue("bob", "bob-pw");
163
+ let t = await LocalAuth$ReventlessLocal.Login.issue("bob", "bob-pw", undefined);
162
164
  let token;
163
165
  token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage("issue failed: " + t._0);
164
166
  let result = await LocalAuth$ReventlessLocal.authenticate(buildContext([
@@ -198,10 +200,191 @@ globalThis.test("setCredentials mirrors identity into the X-User registry", asyn
198
200
  globalThis.expect(identity.groups).toEqual(["Editor"]);
199
201
  });
200
202
 
203
+ let multiRole_groups = [
204
+ "Fulfilment",
205
+ "Shopper"
206
+ ];
207
+
208
+ let multiRole = {
209
+ userId: "u-carol",
210
+ username: "carol",
211
+ groups: multiRole_groups,
212
+ provider: "InMemory"
213
+ };
214
+
215
+ function decodeOrThrow(token) {
216
+ let i = LocalAuth$ReventlessLocal.Login.verifyAndDecode(token);
217
+ if (i !== undefined) {
218
+ return i;
219
+ } else {
220
+ return Stdlib_JsError.throwWithMessage("expected a verifiable token");
221
+ }
222
+ }
223
+
224
+ function carolLoggedIn() {
225
+ resetAll();
226
+ LocalAuth$ReventlessLocal.Login.setCredentials("carol", "carol-pw", multiRole);
227
+ }
228
+
229
+ globalThis.test("a login naming no role mints exactly what it minted before", async () => {
230
+ carolLoggedIn();
231
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", undefined);
232
+ let plain;
233
+ plain = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
234
+ let identity = decodeOrThrow(plain);
235
+ globalThis.expect(identity.groups).toEqual([
236
+ "Fulfilment",
237
+ "Shopper"
238
+ ]);
239
+ globalThis.expect(identity.claims).toEqual(undefined);
240
+ });
241
+
242
+ globalThis.test("a login naming a held role mints that role alone", async () => {
243
+ carolLoggedIn();
244
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Shopper");
245
+ let token;
246
+ token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
247
+ let identity = decodeOrThrow(token);
248
+ globalThis.expect(identity.groups).toEqual(["Shopper"]);
249
+ });
250
+
251
+ globalThis.test("a narrowed token remembers the choice and what it gave up", async () => {
252
+ carolLoggedIn();
253
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Shopper");
254
+ let token;
255
+ token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
256
+ let identity = decodeOrThrow(token);
257
+ globalThis.expect([
258
+ Identity$Reventless.getClaim(identity, "activeRole"),
259
+ Identity$Reventless.getClaim(identity, "availableRoles")
260
+ ]).toEqual([
261
+ "Shopper",
262
+ "Fulfilment,Shopper"
263
+ ]);
264
+ });
265
+
266
+ globalThis.test("a login naming a role the user does not hold is REFUSED", async () => {
267
+ carolLoggedIn();
268
+ let msg = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Admin");
269
+ if (msg.TAG === "Ok") {
270
+ return Stdlib_JsError.throwWithMessage("expected a request to widen to be refused");
271
+ }
272
+ globalThis.expect(msg._0.includes("Admin")).toEqual(true);
273
+ });
274
+
275
+ globalThis.test("narrowing cannot smuggle a group in through the claims bag", async () => {
276
+ resetAll();
277
+ let newrecord = {...multiRole};
278
+ newrecord.claims = Object.fromEntries([[
279
+ "availableRoles",
280
+ "Admin,Fulfilment,Shopper"
281
+ ]]);
282
+ LocalAuth$ReventlessLocal.Login.setCredentials("carol", "carol-pw", newrecord);
283
+ let match = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Admin");
284
+ if (match.TAG === "Ok") {
285
+ return Stdlib_JsError.throwWithMessage("expected membership to be judged by groups, not by a claim");
286
+ }
287
+ globalThis.expect(true).toEqual(true);
288
+ });
289
+
290
+ globalThis.test("a narrowed token authenticates as the narrowed identity", async () => {
291
+ carolLoggedIn();
292
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Shopper");
293
+ let token;
294
+ token = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
295
+ let result = await LocalAuth$ReventlessLocal.authenticate(buildContext([[
296
+ "authorization",
297
+ "Bearer " + token
298
+ ]]));
299
+ if (typeof result !== "object") {
300
+ return Stdlib_JsError.throwWithMessage("expected the narrowed token to authenticate");
301
+ }
302
+ if (result.TAG !== "Authenticated") {
303
+ return Stdlib_JsError.throwWithMessage("expected the narrowed token to authenticate");
304
+ }
305
+ globalThis.expect(result._0.groups).toEqual(["Shopper"]);
306
+ });
307
+
308
+ globalThis.test("the minted identity matches the token, not the stored user", async () => {
309
+ carolLoggedIn();
310
+ let minted = LocalAuth$ReventlessLocal.Login.mintedIdentity("carol", "Shopper");
311
+ let stored = LocalAuth$ReventlessLocal.lookupUser("carol");
312
+ globalThis.expect([
313
+ Stdlib_Option.mapOr(minted, [], i => i.groups),
314
+ Stdlib_Option.mapOr(stored, [], i => i.groups)
315
+ ]).toEqual([
316
+ ["Shopper"],
317
+ [
318
+ "Fulfilment",
319
+ "Shopper"
320
+ ]
321
+ ]);
322
+ });
323
+
324
+ globalThis.test("a session can be re-minted as one of its roles", async () => {
325
+ carolLoggedIn();
326
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", undefined);
327
+ let wide;
328
+ wide = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
329
+ let t$1 = LocalAuth$ReventlessLocal.Login.reissue(wide, "Shopper");
330
+ if (t$1.TAG !== "Ok") {
331
+ return Stdlib_JsError.throwWithMessage(t$1._0);
332
+ }
333
+ globalThis.expect(decodeOrThrow(t$1._0).groups).toEqual(["Shopper"]);
334
+ });
335
+
336
+ globalThis.test("a narrowed session can widen back to its full membership", async () => {
337
+ carolLoggedIn();
338
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Shopper");
339
+ let narrow;
340
+ narrow = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
341
+ let t$1 = LocalAuth$ReventlessLocal.Login.reissue(narrow, undefined);
342
+ if (t$1.TAG !== "Ok") {
343
+ return Stdlib_JsError.throwWithMessage(t$1._0);
344
+ }
345
+ let identity = decodeOrThrow(t$1._0);
346
+ globalThis.expect([
347
+ identity.groups,
348
+ identity.claims
349
+ ]).toEqual([
350
+ [
351
+ "Fulfilment",
352
+ "Shopper"
353
+ ],
354
+ undefined
355
+ ]);
356
+ });
357
+
358
+ globalThis.test("switching judges membership from the store, not the token's claim", async () => {
359
+ carolLoggedIn();
360
+ let t = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", "Shopper");
361
+ let narrow;
362
+ narrow = t.TAG === "Ok" ? t._0 : Stdlib_JsError.throwWithMessage(t._0);
363
+ let newrecord = {...multiRole};
364
+ LocalAuth$ReventlessLocal.Login.setCredentials("carol", "carol-pw", (newrecord.groups = ["Shopper"], newrecord));
365
+ let match = LocalAuth$ReventlessLocal.Login.reissue(narrow, "Fulfilment");
366
+ if (match.TAG === "Ok") {
367
+ return Stdlib_JsError.throwWithMessage("expected a revoked role to be unreachable via the token's claim");
368
+ }
369
+ globalThis.expect(true).toEqual(true);
370
+ });
371
+
372
+ globalThis.test("switching refuses a token this server did not sign", async () => {
373
+ carolLoggedIn();
374
+ let msg = LocalAuth$ReventlessLocal.Login.reissue("not.a-real-token", "Shopper");
375
+ if (msg.TAG === "Ok") {
376
+ return Stdlib_JsError.throwWithMessage("expected an unverifiable token to be refused");
377
+ }
378
+ globalThis.expect(msg._0).toEqual("Invalid token");
379
+ });
380
+
201
381
  export {
202
382
  alice,
203
383
  bob,
204
384
  buildContext,
205
385
  resetAll,
386
+ multiRole,
387
+ decodeOrThrow,
388
+ carolLoggedIn,
206
389
  }
207
390
  /* Not a pure module */
@@ -81,7 +81,7 @@ globalThis.test("load(~users) registers credentials and returns InlineUsers", as
81
81
  } else {
82
82
  Stdlib_JsError.throwWithMessage("load failed: " + msg._0);
83
83
  }
84
- let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw");
84
+ let result = await LocalAuth$ReventlessLocal.Login.issue("alice", "alice-pw", undefined);
85
85
  if (result.TAG !== "Ok") {
86
86
  Stdlib_JsError.throwWithMessage("issue rejected loaded credentials: " + result._0);
87
87
  }
@@ -112,7 +112,7 @@ globalThis.test("load(~usersFile) reads a YAML file from disk", async () => {
112
112
  } else {
113
113
  Stdlib_JsError.throwWithMessage("load failed: " + msg._0);
114
114
  }
115
- let result = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw");
115
+ let result = await LocalAuth$ReventlessLocal.Login.issue("carol", "carol-pw", undefined);
116
116
  if (result.TAG !== "Ok") {
117
117
  Stdlib_JsError.throwWithMessage("issue rejected file-loaded credentials: " + result._0);
118
118
  }