@proteinjs/user-server 1.22.0 → 1.22.2

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 (133) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/generated/index.js +1 -1
  3. package/dist/generated/index.js.map +1 -1
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +10 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/src/authentication/PasswordHasher.d.ts +9 -0
  9. package/dist/src/authentication/PasswordHasher.d.ts.map +1 -1
  10. package/dist/src/authentication/PasswordHasher.js +25 -0
  11. package/dist/src/authentication/PasswordHasher.js.map +1 -1
  12. package/dist/src/authentication/PasswordResetToken.d.ts +43 -12
  13. package/dist/src/authentication/PasswordResetToken.d.ts.map +1 -1
  14. package/dist/src/authentication/PasswordResetToken.js +86 -21
  15. package/dist/src/authentication/PasswordResetToken.js.map +1 -1
  16. package/dist/src/authentication/authenticate.d.ts +15 -0
  17. package/dist/src/authentication/authenticate.d.ts.map +1 -1
  18. package/dist/src/authentication/authenticate.js +55 -20
  19. package/dist/src/authentication/authenticate.js.map +1 -1
  20. package/dist/src/routes/devLogin.d.ts +1 -1
  21. package/dist/src/routes/devLogin.js +1 -1
  22. package/dist/src/routes/executePasswordReset.d.ts +6 -1
  23. package/dist/src/routes/executePasswordReset.d.ts.map +1 -1
  24. package/dist/src/routes/executePasswordReset.js +16 -7
  25. package/dist/src/routes/executePasswordReset.js.map +1 -1
  26. package/dist/src/routes/initiatePasswordReset.d.ts +10 -2
  27. package/dist/src/routes/initiatePasswordReset.d.ts.map +1 -1
  28. package/dist/src/routes/initiatePasswordReset.js +55 -34
  29. package/dist/src/routes/initiatePasswordReset.js.map +1 -1
  30. package/dist/src/routes/login.d.ts +8 -0
  31. package/dist/src/routes/login.d.ts.map +1 -1
  32. package/dist/src/routes/login.js +63 -22
  33. package/dist/src/routes/login.js.map +1 -1
  34. package/dist/src/throttle/ClientAddress.d.ts +35 -0
  35. package/dist/src/throttle/ClientAddress.d.ts.map +1 -0
  36. package/dist/src/throttle/ClientAddress.js +48 -0
  37. package/dist/src/throttle/ClientAddress.js.map +1 -0
  38. package/dist/src/throttle/PasswordResetThrottle.d.ts +39 -0
  39. package/dist/src/throttle/PasswordResetThrottle.d.ts.map +1 -0
  40. package/dist/src/throttle/PasswordResetThrottle.js +59 -0
  41. package/dist/src/throttle/PasswordResetThrottle.js.map +1 -0
  42. package/dist/src/throttle/RequestDigests.d.ts +42 -0
  43. package/dist/src/throttle/RequestDigests.d.ts.map +1 -0
  44. package/dist/src/throttle/RequestDigests.js +96 -0
  45. package/dist/src/throttle/RequestDigests.js.map +1 -0
  46. package/dist/src/throttle/SignInThrottle.d.ts +67 -0
  47. package/dist/src/throttle/SignInThrottle.d.ts.map +1 -0
  48. package/dist/src/throttle/SignInThrottle.js +91 -0
  49. package/dist/src/throttle/SignInThrottle.js.map +1 -0
  50. package/dist/src/throttle/SlidingWindow.d.ts +51 -0
  51. package/dist/src/throttle/SlidingWindow.d.ts.map +1 -0
  52. package/dist/src/throttle/SlidingWindow.js +85 -0
  53. package/dist/src/throttle/SlidingWindow.js.map +1 -0
  54. package/dist/test/ClientAddress.test.d.ts +2 -0
  55. package/dist/test/ClientAddress.test.d.ts.map +1 -0
  56. package/dist/test/ClientAddress.test.js +51 -0
  57. package/dist/test/ClientAddress.test.js.map +1 -0
  58. package/dist/test/DbTraffic.d.ts +29 -0
  59. package/dist/test/DbTraffic.d.ts.map +1 -0
  60. package/dist/test/DbTraffic.js +106 -0
  61. package/dist/test/DbTraffic.js.map +1 -0
  62. package/dist/test/DevLoginBootstrapAdmin.test.js +3 -4
  63. package/dist/test/DevLoginBootstrapAdmin.test.js.map +1 -1
  64. package/dist/test/DevLoginStaleCookieFirstHit.test.js +1 -1
  65. package/dist/test/DevLoginStaleCookieFirstHit.test.js.map +1 -1
  66. package/dist/test/ExecutePasswordReset.test.js +513 -124
  67. package/dist/test/ExecutePasswordReset.test.js.map +1 -1
  68. package/dist/test/InitiatePasswordReset.test.d.ts +2 -0
  69. package/dist/test/InitiatePasswordReset.test.d.ts.map +1 -0
  70. package/dist/test/InitiatePasswordReset.test.js +783 -0
  71. package/dist/test/InitiatePasswordReset.test.js.map +1 -0
  72. package/dist/test/LogCapture.d.ts +22 -0
  73. package/dist/test/LogCapture.d.ts.map +1 -0
  74. package/dist/test/LogCapture.js +127 -0
  75. package/dist/test/LogCapture.js.map +1 -0
  76. package/dist/test/LoginThrottle.integration.test.d.ts +2 -0
  77. package/dist/test/LoginThrottle.integration.test.d.ts.map +1 -0
  78. package/dist/test/LoginThrottle.integration.test.js +616 -0
  79. package/dist/test/LoginThrottle.integration.test.js.map +1 -0
  80. package/dist/test/MalformedResetTokens.d.ts +11 -0
  81. package/dist/test/MalformedResetTokens.d.ts.map +1 -0
  82. package/dist/test/MalformedResetTokens.js +35 -0
  83. package/dist/test/MalformedResetTokens.js.map +1 -0
  84. package/dist/test/RequestDigests.test.d.ts +2 -0
  85. package/dist/test/RequestDigests.test.d.ts.map +1 -0
  86. package/dist/test/RequestDigests.test.js +73 -0
  87. package/dist/test/RequestDigests.test.js.map +1 -0
  88. package/dist/test/SlidingWindow.test.d.ts +2 -0
  89. package/dist/test/SlidingWindow.test.d.ts.map +1 -0
  90. package/dist/test/SlidingWindow.test.js +72 -0
  91. package/dist/test/SlidingWindow.test.js.map +1 -0
  92. package/dist/test/ValidateResetToken.test.js +156 -33
  93. package/dist/test/ValidateResetToken.test.js.map +1 -1
  94. package/dist/test/emulatorLock.test.d.ts +2 -0
  95. package/dist/test/emulatorLock.test.d.ts.map +1 -0
  96. package/dist/test/emulatorLock.test.js +32 -0
  97. package/dist/test/emulatorLock.test.js.map +1 -0
  98. package/dist/test/emulatorLockName.test.d.ts +2 -0
  99. package/dist/test/emulatorLockName.test.d.ts.map +1 -0
  100. package/dist/test/emulatorLockName.test.js +36 -0
  101. package/dist/test/emulatorLockName.test.js.map +1 -0
  102. package/generated/index.ts +1 -1
  103. package/index.ts +6 -1
  104. package/jest.config.js +2 -2
  105. package/package.json +3 -3
  106. package/src/authentication/PasswordHasher.ts +18 -1
  107. package/src/authentication/PasswordResetToken.ts +74 -20
  108. package/src/authentication/authenticate.ts +36 -9
  109. package/src/routes/devLogin.ts +1 -1
  110. package/src/routes/executePasswordReset.ts +10 -2
  111. package/src/routes/initiatePasswordReset.ts +46 -25
  112. package/src/routes/login.ts +39 -8
  113. package/src/throttle/ClientAddress.ts +47 -0
  114. package/src/throttle/PasswordResetThrottle.ts +62 -0
  115. package/src/throttle/RequestDigests.ts +97 -0
  116. package/src/throttle/SignInThrottle.ts +98 -0
  117. package/src/throttle/SlidingWindow.ts +90 -0
  118. package/test/ClientAddress.test.ts +54 -0
  119. package/test/DbTraffic.ts +43 -0
  120. package/test/DevLoginBootstrapAdmin.test.ts +3 -4
  121. package/test/DevLoginStaleCookieFirstHit.test.ts +1 -1
  122. package/test/ExecutePasswordReset.test.ts +308 -70
  123. package/test/InitiatePasswordReset.test.ts +456 -0
  124. package/test/LogCapture.ts +54 -0
  125. package/test/LoginThrottle.integration.test.ts +339 -0
  126. package/test/MalformedResetTokens.ts +27 -0
  127. package/test/RequestDigests.test.ts +81 -0
  128. package/test/SlidingWindow.test.ts +85 -0
  129. package/test/ValidateResetToken.test.ts +108 -24
  130. package/test/emulatorLock.js +5 -6
  131. package/test/emulatorLock.test.ts +32 -0
  132. package/test/emulatorLockName.test.ts +33 -0
  133. package/test/setup.js +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MalformedResetTokens.d.ts","sourceRoot":"","sources":["../../test/MalformedResetTokens.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAiBlG;CACH"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MalformedResetTokens = void 0;
4
+ /**
5
+ * The `token` field of a request that does not carry a well-formed reset token: every type a
6
+ * parsed body or query string can deliver, and every near-miss of the token's shape. The
7
+ * near-misses are cut from a LIVE token, so a shape check that loosens (a lost anchor, a wider
8
+ * length, a case-insensitive class, a missing type check) would be looking up something derived
9
+ * from a real credential — and the suites that use this table assert that no lookup is built.
10
+ */
11
+ var MalformedResetTokens = /** @class */ (function () {
12
+ function MalformedResetTokens() {
13
+ }
14
+ MalformedResetTokens.CASES = [
15
+ ['absent', function () { return ({}); }],
16
+ ['undefined', function () { return ({ token: undefined }); }],
17
+ ['null', function () { return ({ token: null }); }],
18
+ ['empty', function () { return ({ token: '' }); }],
19
+ ['a string of another shape', function () { return ({ token: 'tok-valid-1' }); }],
20
+ ['a number', function () { return ({ token: 123 }); }],
21
+ ['true', function () { return ({ token: true }); }],
22
+ ['an array', function () { return ({ token: ['a', 'b'] }); }],
23
+ ['an array holding the live token', function (liveToken) { return ({ token: [liveToken] }); }],
24
+ ['an object', function () { return ({ token: { passwordResetToken: null } }); }],
25
+ ['63 hex characters of the live token', function (liveToken) { return ({ token: liveToken.slice(0, 63) }); }],
26
+ ['the live token and one more hex character', function (liveToken) { return ({ token: "".concat(liveToken, "a") }); }],
27
+ ['the live token in uppercase hex', function (liveToken) { return ({ token: liveToken.toUpperCase() }); }],
28
+ ['the live token and a trailing newline', function (liveToken) { return ({ token: "".concat(liveToken, "\n") }); }],
29
+ ['the live token after a leading space', function (liveToken) { return ({ token: " ".concat(liveToken) }); }],
30
+ ['64 characters that are not hex', function () { return ({ token: 'g'.repeat(64) }); }],
31
+ ];
32
+ return MalformedResetTokens;
33
+ }());
34
+ exports.MalformedResetTokens = MalformedResetTokens;
35
+ //# sourceMappingURL=MalformedResetTokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MalformedResetTokens.js","sourceRoot":"","sources":["../../test/MalformedResetTokens.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACH;IAAA;IAmBA,CAAC;IAlBiB,0BAAK,GAAkF;QACrG,CAAC,QAAQ,EAAE,cAAM,OAAA,CAAC,EAAE,CAAC,EAAJ,CAAI,CAAC;QACtB,CAAC,WAAW,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAtB,CAAsB,CAAC;QAC3C,CAAC,MAAM,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAjB,CAAiB,CAAC;QACjC,CAAC,OAAO,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAf,CAAe,CAAC;QAChC,CAAC,2BAA2B,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAA1B,CAA0B,CAAC;QAC/D,CAAC,UAAU,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAhB,CAAgB,CAAC;QACpC,CAAC,MAAM,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAjB,CAAiB,CAAC;QACjC,CAAC,UAAU,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAvB,CAAuB,CAAC;QAC3C,CAAC,iCAAiC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,EAAxB,CAAwB,CAAC;QAC5E,CAAC,WAAW,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC,EAAzC,CAAyC,CAAC;QAC9D,CAAC,qCAAqC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAnC,CAAmC,CAAC;QAC3F,CAAC,2CAA2C,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,UAAG,SAAS,MAAG,EAAE,CAAC,EAA5B,CAA4B,CAAC;QAC1F,CAAC,iCAAiC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,EAApC,CAAoC,CAAC;QACxF,CAAC,uCAAuC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,UAAG,SAAS,OAAI,EAAE,CAAC,EAA7B,CAA6B,CAAC;QACvF,CAAC,sCAAsC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,WAAI,SAAS,CAAE,EAAE,CAAC,EAA5B,CAA4B,CAAC;QACrF,CAAC,gCAAgC,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAA3B,CAA2B,CAAC;KACtE,CAAC;IACJ,2BAAC;CAAA,AAnBD,IAmBC;AAnBY,oDAAoB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RequestDigests.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestDigests.test.d.ts","sourceRoot":"","sources":["../../test/RequestDigests.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var crypto_1 = require("crypto");
4
+ var RequestDigests_1 = require("../src/throttle/RequestDigests");
5
+ /**
6
+ * The two keyed digests the throttled doors key their windows on and write on their log lines:
7
+ * the account digest (a keyed hash of the lowercased address) and the coarse IP hash. Both are
8
+ * stable for one key (so an operator can say "one account, one device" across lines, pods and
9
+ * restarts) and useless without it (never the address, never a plain hash a list of addresses
10
+ * could reverse).
11
+ */
12
+ describe('RequestDigests', function () {
13
+ var digests = new RequestDigests_1.RequestDigests({ secret: 'test-session-secret' });
14
+ it('the account digest is stable for one address whatever its case or surrounding spaces', function () {
15
+ var digest = digests.account('Ada.Lovelace@Example.com');
16
+ expect(digest).toMatch(/^[0-9a-f]{16}$/);
17
+ expect(digests.account('ada.lovelace@example.com')).toBe(digest);
18
+ expect(digests.account(' ADA.LOVELACE@EXAMPLE.COM ')).toBe(digest);
19
+ expect(digests.account('ada.lovelace@example.org')).not.toBe(digest);
20
+ });
21
+ it('the account digest is keyed: another key gives another digest, and it is no plain hash of the address', function () {
22
+ var address = 'ada.lovelace@example.com';
23
+ var digest = digests.account(address);
24
+ expect(new RequestDigests_1.RequestDigests({ secret: 'another-secret' }).account(address)).not.toBe(digest);
25
+ expect((0, crypto_1.createHash)('sha256').update(address).digest('hex')).not.toContain(digest);
26
+ expect(digest).not.toContain('ada');
27
+ });
28
+ it('the account digest and the coarse IP hash are separate: one value never reads as the other', function () {
29
+ expect(digests.account('203.0.113.7')).not.toBe(digests.coarseIp('203.0.113.7'));
30
+ });
31
+ it('the coarse IP hash is stable for one IPv4 address and differs for the next', function () {
32
+ var hash = digests.coarseIp('203.0.113.7');
33
+ expect(hash).toMatch(/^[0-9a-f]{16}$/);
34
+ expect(digests.coarseIp('203.0.113.7')).toBe(hash);
35
+ expect(digests.coarseIp('::ffff:203.0.113.7')).toBe(hash);
36
+ expect(digests.coarseIp('203.0.113.8')).not.toBe(hash);
37
+ });
38
+ it('IPv6 is coarsened to its /64 — one device rotates freely inside its own /64', function () {
39
+ var hash = digests.coarseIp('2001:db8:1:2:aaaa:bbbb:cccc:dddd');
40
+ expect(digests.coarseIp('2001:db8:1:2::1')).toBe(hash);
41
+ expect(digests.coarseIp('2001:0db8:0001:0002:ffff:0:0:1')).toBe(hash);
42
+ expect(digests.coarseIp('2001:db8:1:3::1')).not.toBe(hash);
43
+ });
44
+ it('with no key configured the digests refuse to run — never a plain hash, never a key of their own', function () {
45
+ var original = process.env.SESSION_SECRET;
46
+ delete process.env.SESSION_SECRET;
47
+ try {
48
+ expect(function () { return new RequestDigests_1.RequestDigests().account('ada.lovelace@example.com'); }).toThrow(/SESSION_SECRET/);
49
+ expect(function () { return new RequestDigests_1.RequestDigests({ secret: '' }).coarseIp('203.0.113.7'); }).toThrow(/SESSION_SECRET/);
50
+ }
51
+ finally {
52
+ if (original !== undefined) {
53
+ process.env.SESSION_SECRET = original;
54
+ }
55
+ }
56
+ });
57
+ it('reads the key the deployment configures (SESSION_SECRET) when none is passed', function () {
58
+ var original = process.env.SESSION_SECRET;
59
+ process.env.SESSION_SECRET = 'test-session-secret';
60
+ try {
61
+ expect(new RequestDigests_1.RequestDigests().account('ada.lovelace@example.com')).toBe(digests.account('ada.lovelace@example.com'));
62
+ }
63
+ finally {
64
+ if (original === undefined) {
65
+ delete process.env.SESSION_SECRET;
66
+ }
67
+ else {
68
+ process.env.SESSION_SECRET = original;
69
+ }
70
+ }
71
+ });
72
+ });
73
+ //# sourceMappingURL=RequestDigests.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestDigests.test.js","sourceRoot":"","sources":["../../test/RequestDigests.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SlidingWindow.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlidingWindow.test.d.ts","sourceRoot":"","sources":["../../test/SlidingWindow.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var SlidingWindow_1 = require("../src/throttle/SlidingWindow");
4
+ /**
5
+ * The per-key sliding window every throttled door shares (the sign-in and reset doors here, the
6
+ * invite-request door in the consuming app): `hit` records and answers whether the key is over,
7
+ * `isOver` only reads, `record` only counts, `clear` forgets. Attempts refused as over the window
8
+ * are not counted, so a key drains exactly one window after its last counted attempt.
9
+ */
10
+ describe('SlidingWindow', function () {
11
+ var MINUTE = 60 * 1000;
12
+ var now = 1700000000000;
13
+ var clock = function () { return now; };
14
+ it('admits up to the limit inside the window and refuses the next', function () {
15
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 3, now: clock });
16
+ expect([window.hit('a'), window.hit('a'), window.hit('a')]).toEqual([false, false, false]);
17
+ expect(window.hit('a')).toBe(true);
18
+ expect(window.isOver('a')).toBe(true);
19
+ });
20
+ it('keeps each key in its own window', function () {
21
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
22
+ window.hit('a');
23
+ window.hit('a');
24
+ expect(window.hit('a')).toBe(true);
25
+ expect(window.hit('b')).toBe(false);
26
+ });
27
+ it('drains: an attempt older than the window no longer counts, and refused attempts never counted', function () {
28
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
29
+ window.hit('a');
30
+ now += 4 * MINUTE;
31
+ window.hit('a');
32
+ now += 5 * MINUTE;
33
+ expect(window.hit('a')).toBe(true); // both counted attempts are still inside the window
34
+ now += 1 * MINUTE + 1; // the first counted attempt left the window; the refused one never counted
35
+ expect(window.hit('a')).toBe(false);
36
+ expect(window.hit('a')).toBe(true);
37
+ });
38
+ it('isOver reads without counting; record counts without answering; clear forgets the key', function () {
39
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
40
+ expect(window.isOver('a')).toBe(false);
41
+ expect(window.isOver('a')).toBe(false);
42
+ window.record('a');
43
+ expect(window.isOver('a')).toBe(false);
44
+ window.record('a');
45
+ expect(window.isOver('a')).toBe(true);
46
+ window.clear('a');
47
+ expect(window.isOver('a')).toBe(false);
48
+ });
49
+ it('forgive uncounts the latest attempt only', function () {
50
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 2, now: clock });
51
+ window.hit('a');
52
+ window.hit('a');
53
+ expect(window.isOver('a')).toBe(true);
54
+ window.forgive('a');
55
+ expect(window.isOver('a')).toBe(false);
56
+ expect(window.hit('a')).toBe(false);
57
+ expect(window.hit('a')).toBe(true);
58
+ window.forgive('b'); // a key never seen: nothing to uncount, nothing thrown
59
+ expect(window.isOver('b')).toBe(false);
60
+ });
61
+ it('bounds the keys it tracks: at the bound the oldest-touched key is forgotten first', function () {
62
+ var window = new SlidingWindow_1.SlidingWindow({ windowMs: 10 * MINUTE, limit: 1, maxKeys: 2, now: clock });
63
+ window.hit('a');
64
+ window.hit('b');
65
+ window.hit('a'); // refused, but 'a' is now the most recently touched
66
+ window.hit('c'); // at the bound: 'b' (oldest-touched) is forgotten
67
+ expect(window.isOver('a')).toBe(true);
68
+ expect(window.isOver('b')).toBe(false);
69
+ expect(window.isOver('c')).toBe(true);
70
+ });
71
+ });
72
+ //# sourceMappingURL=SlidingWindow.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlidingWindow.test.js","sourceRoot":"","sources":["../../test/SlidingWindow.test.ts"],"names":[],"mappings":"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"}
@@ -39,12 +39,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ var crypto_1 = require("crypto");
42
43
  var moment_1 = __importDefault(require("moment"));
43
44
  var db_1 = require("@proteinjs/db");
44
45
  var user_1 = require("@proteinjs/user");
45
46
  var validateResetPasswordToken_1 = require("../src/routes/validateResetPasswordToken");
46
47
  var PasswordResetToken_1 = require("../src/authentication/PasswordResetToken");
47
48
  var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
49
+ var DbTraffic_1 = require("./DbTraffic");
50
+ var LogCapture_1 = require("./LogCapture");
51
+ var MalformedResetTokens_1 = require("./MalformedResetTokens");
48
52
  var testEnv = new UserServerTestEnvironment_1.UserServerTestEnvironment();
49
53
  var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0, function () {
50
54
  var outcome, response;
@@ -68,22 +72,26 @@ var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0,
68
72
  }
69
73
  });
70
74
  }); };
71
- var mintToken = function () { return new PasswordResetToken_1.PasswordResetToken().mint(); };
72
- var armResetToken = function (email, token, expiration) { return __awaiter(void 0, void 0, void 0, function () {
73
- var user;
75
+ var sha256Hex = function (value) { return (0, crypto_1.createHash)('sha256').update(value).digest('hex'); };
76
+ /** A well-formed token that was never issued to anyone. */
77
+ var unissuedToken = function () { return sha256Hex("never issued ".concat(Math.random())); };
78
+ /** A user with a token minted by the owner; `expiration` (when given) replaces the expiry the mint stored. */
79
+ var armResetToken = function (email, expiration) { return __awaiter(void 0, void 0, void 0, function () {
80
+ var user, token;
74
81
  return __generator(this, function (_a) {
75
82
  switch (_a.label) {
76
83
  case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Reset User', email: email })];
77
84
  case 1:
78
85
  user = _a.sent();
79
- return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, {
80
- id: user.id,
81
- passwordResetToken: token,
82
- passwordResetTokenExpiration: expiration,
83
- })];
86
+ return [4 /*yield*/, new PasswordResetToken_1.PasswordResetToken().mint(user)];
84
87
  case 2:
88
+ token = _a.sent();
89
+ if (!(expiration !== undefined)) return [3 /*break*/, 4];
90
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, { id: user.id, passwordResetTokenExpiration: expiration })];
91
+ case 3:
85
92
  _a.sent();
86
- return [2 /*return*/];
93
+ _a.label = 4;
94
+ case 4: return [2 /*return*/, token];
87
95
  }
88
96
  });
89
97
  }); };
@@ -112,11 +120,9 @@ describe('validateResetPasswordToken route', function () {
112
120
  var token, outcome;
113
121
  return __generator(this, function (_a) {
114
122
  switch (_a.label) {
115
- case 0:
116
- token = mintToken();
117
- return [4 /*yield*/, armResetToken('reset-valid@test.local', token, (0, moment_1.default)().add(1, 'hour'))];
123
+ case 0: return [4 /*yield*/, armResetToken('reset-valid@test.local')];
118
124
  case 1:
119
- _a.sent();
125
+ token = _a.sent();
120
126
  return [4 /*yield*/, invokeValidate({ token: token })];
121
127
  case 2:
122
128
  outcome = _a.sent();
@@ -130,11 +136,9 @@ describe('validateResetPasswordToken route', function () {
130
136
  var token, outcome;
131
137
  return __generator(this, function (_a) {
132
138
  switch (_a.label) {
133
- case 0:
134
- token = mintToken();
135
- return [4 /*yield*/, armResetToken('reset-expired@test.local', token, (0, moment_1.default)().subtract(1, 'minute'))];
139
+ case 0: return [4 /*yield*/, armResetToken('reset-expired@test.local', (0, moment_1.default)().subtract(1, 'minute'))];
136
140
  case 1:
137
- _a.sent();
141
+ token = _a.sent();
138
142
  return [4 /*yield*/, invokeValidate({ token: token })];
139
143
  case 2:
140
144
  outcome = _a.sent();
@@ -149,7 +153,7 @@ describe('validateResetPasswordToken route', function () {
149
153
  var outcome;
150
154
  return __generator(this, function (_a) {
151
155
  switch (_a.label) {
152
- case 0: return [4 /*yield*/, invokeValidate({ token: mintToken() })];
156
+ case 0: return [4 /*yield*/, invokeValidate({ token: unissuedToken() })];
153
157
  case 1:
154
158
  outcome = _a.sent();
155
159
  expect(outcome.status).toBe(200);
@@ -159,32 +163,151 @@ describe('validateResetPasswordToken route', function () {
159
163
  }
160
164
  });
161
165
  }); });
162
- it.each([
163
- ['a string of another shape', 'tok-never-issued'],
164
- ['a parsed array', ['a', 'b']],
165
- ['a parsed object', { passwordResetToken: null }],
166
- ])('%s resolves invalid without a lookup and leaks no email', function (_label, token) { return __awaiter(void 0, void 0, void 0, function () {
167
- var outcome;
168
- return __generator(this, function (_a) {
169
- switch (_a.label) {
170
- case 0: return [4 /*yield*/, invokeValidate({ token: token })];
166
+ it('the stored digest presented as a token resolves invalid — the row holds nothing that can be presented', function () { return __awaiter(void 0, void 0, void 0, function () {
167
+ var token, row, _a, outcome, traffic;
168
+ return __generator(this, function (_b) {
169
+ switch (_b.label) {
170
+ case 0: return [4 /*yield*/, armResetToken('reset-digest@test.local')];
171
171
  case 1:
172
- outcome = _a.sent();
172
+ token = _b.sent();
173
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { email: 'reset-digest@test.local' })];
174
+ case 2:
175
+ row = _b.sent();
176
+ expect(row.passwordResetToken).toBe(sha256Hex(token));
177
+ return [4 /*yield*/, DbTraffic_1.DbTraffic.during(testEnv.spannerDriver, function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
178
+ switch (_a.label) {
179
+ case 0: return [4 /*yield*/, invokeValidate({ token: row.passwordResetToken })];
180
+ case 1: return [2 /*return*/, _a.sent()];
181
+ }
182
+ }); }); })];
183
+ case 3:
184
+ _a = _b.sent(), outcome = _a.result, traffic = _a.traffic;
173
185
  expect(outcome.status).toBe(200);
174
186
  expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
187
+ expect(traffic.lookups).toEqual([{ passwordResetToken: sha256Hex(sha256Hex(token)) }]);
188
+ expect(traffic.statements).toBe(1);
189
+ expect(traffic.writes).toBe(0);
175
190
  return [2 /*return*/];
176
191
  }
177
192
  });
178
193
  }); });
179
- it('a missing token is a 400', function () { return __awaiter(void 0, void 0, void 0, function () {
180
- var outcome;
194
+ it('a token an earlier release stored in clear resolves invalid — no migration, the person asks again', function () { return __awaiter(void 0, void 0, void 0, function () {
195
+ var clearToken, user, outcome;
181
196
  return __generator(this, function (_a) {
182
197
  switch (_a.label) {
183
- case 0: return [4 /*yield*/, invokeValidate({})];
198
+ case 0:
199
+ clearToken = unissuedToken();
200
+ return [4 /*yield*/, testEnv.createUser({ name: 'Reset User', email: 'reset-stored-in-clear@test.local' })];
184
201
  case 1:
202
+ user = _a.sent();
203
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, {
204
+ id: user.id,
205
+ passwordResetToken: clearToken,
206
+ passwordResetTokenExpiration: (0, moment_1.default)().add(1, 'hour'),
207
+ })];
208
+ case 2:
209
+ _a.sent();
210
+ return [4 /*yield*/, invokeValidate({ token: clearToken })];
211
+ case 3:
185
212
  outcome = _a.sent();
186
- expect(outcome.status).toBe(400);
187
- expect(outcome.body.isValid).toBe(false);
213
+ expect(outcome.status).toBe(200);
214
+ expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
215
+ return [2 /*return*/];
216
+ }
217
+ });
218
+ }); });
219
+ describe('a query without a well-formed token is invalid without a lookup', function () {
220
+ var liveToken;
221
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
222
+ return __generator(this, function (_a) {
223
+ switch (_a.label) {
224
+ case 0: return [4 /*yield*/, armResetToken('reset-shapes@test.local')];
225
+ case 1:
226
+ liveToken = _a.sent();
227
+ return [2 /*return*/];
228
+ }
229
+ });
230
+ }); });
231
+ it.each(MalformedResetTokens_1.MalformedResetTokens.CASES)('%s: invalid, no email, and nothing reaches the database', function (_label, tokenField) { return __awaiter(void 0, void 0, void 0, function () {
232
+ var query, _a, outcome, traffic;
233
+ return __generator(this, function (_b) {
234
+ switch (_b.label) {
235
+ case 0:
236
+ query = tokenField(liveToken);
237
+ return [4 /*yield*/, DbTraffic_1.DbTraffic.during(testEnv.spannerDriver, function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
238
+ switch (_a.label) {
239
+ case 0: return [4 /*yield*/, invokeValidate(query)];
240
+ case 1: return [2 /*return*/, _a.sent()];
241
+ }
242
+ }); }); })];
243
+ case 1:
244
+ _a = _b.sent(), outcome = _a.result, traffic = _a.traffic;
245
+ // Nothing usable in the query is a 400; anything else that is not a token is an invalid token.
246
+ expect(outcome).toEqual(query.token
247
+ ? { status: 200, body: { isValid: false, message: 'Invalid token' } }
248
+ : { status: 400, body: { isValid: false, message: 'No token provided' } });
249
+ expect(traffic).toEqual(DbTraffic_1.DbTraffic.NONE);
250
+ return [2 /*return*/];
251
+ }
252
+ });
253
+ }); });
254
+ it('after every refusal the live token is still valid', function () { return __awaiter(void 0, void 0, void 0, function () {
255
+ var outcome;
256
+ return __generator(this, function (_a) {
257
+ switch (_a.label) {
258
+ case 0: return [4 /*yield*/, invokeValidate({ token: liveToken })];
259
+ case 1:
260
+ outcome = _a.sent();
261
+ expect(outcome.body).toEqual({ isValid: true, email: 'reset-shapes@test.local' });
262
+ return [2 /*return*/];
263
+ }
264
+ });
265
+ }); });
266
+ });
267
+ it('no token, no leading characters of one and no full digest reach the log — every verdict', function () { return __awaiter(void 0, void 0, void 0, function () {
268
+ var live, expired, unissued, log, _i, _a, token;
269
+ return __generator(this, function (_b) {
270
+ switch (_b.label) {
271
+ case 0: return [4 /*yield*/, armResetToken('reset-log-live@test.local')];
272
+ case 1:
273
+ live = _b.sent();
274
+ return [4 /*yield*/, armResetToken('reset-log-expired@test.local', (0, moment_1.default)().subtract(1, 'minute'))];
275
+ case 2:
276
+ expired = _b.sent();
277
+ unissued = unissuedToken();
278
+ return [4 /*yield*/, LogCapture_1.LogCapture.during(function () { return __awaiter(void 0, void 0, void 0, function () {
279
+ return __generator(this, function (_a) {
280
+ switch (_a.label) {
281
+ case 0: return [4 /*yield*/, invokeValidate({ token: unissued })];
282
+ case 1:
283
+ _a.sent();
284
+ return [4 /*yield*/, invokeValidate({ token: live.toUpperCase() })];
285
+ case 2:
286
+ _a.sent();
287
+ return [4 /*yield*/, invokeValidate({ token: sha256Hex(live) })];
288
+ case 3:
289
+ _a.sent();
290
+ return [4 /*yield*/, invokeValidate({ token: expired })];
291
+ case 4:
292
+ _a.sent();
293
+ return [4 /*yield*/, invokeValidate({ token: live })];
294
+ case 5:
295
+ _a.sent();
296
+ return [2 /*return*/];
297
+ }
298
+ });
299
+ }); })];
300
+ case 3:
301
+ log = _b.sent();
302
+ // The capture saw the route's lines: the refusals above logged.
303
+ expect(log.text).toContain('Invalid reset token used');
304
+ expect(log.text).toContain('Expired reset token used');
305
+ expect(log.lines.length).toBeGreaterThanOrEqual(4);
306
+ // The digest presented as a token is covered by `live`: its full digest is what was presented.
307
+ for (_i = 0, _a = [unissued, live, expired]; _i < _a.length; _i++) {
308
+ token = _a[_i];
309
+ log.expectFreeOf(token);
310
+ }
188
311
  return [2 /*return*/];
189
312
  }
190
313
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=emulatorLock.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulatorLock.test.d.ts","sourceRoot":"","sources":["../../test/emulatorLock.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var crypto_1 = __importDefault(require("crypto"));
7
+ var os_1 = __importDefault(require("os"));
8
+ var path_1 = __importDefault(require("path"));
9
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10
+ var lockFilePathFor = require('./emulatorLock').lockFilePathFor;
11
+ /**
12
+ * The lockfile path is a CONTRACT between every copy of the emulator lock: two jest runs only
13
+ * serialize when they compute the same file for the same emulator host. The formula is pinned
14
+ * here byte for byte — a neutral `spanner-emulator-` name (no application's name belongs in a
15
+ * shared lock), the sanitized host slug, and the first 8 hex characters of the host's sha1.
16
+ */
17
+ describe('emulatorLock.lockFilePathFor', function () {
18
+ var expectedFor = function (host, slug) {
19
+ return path_1.default.join(os_1.default.tmpdir(), "spanner-emulator-".concat(slug, "-").concat(crypto_1.default.createHash('sha1').update(host).digest('hex').slice(0, 8), ".lock"));
20
+ };
21
+ it('names the lock by the emulator alone: spanner-emulator-<slug>-<sha1[0..8]>.lock in os.tmpdir()', function () {
22
+ expect(lockFilePathFor('localhost:9010')).toBe(expectedFor('localhost:9010', 'localhost-9010'));
23
+ expect(lockFilePathFor('127.0.0.1:9710')).toBe(expectedFor('127.0.0.1:9710', '127-0-0-1-9710'));
24
+ });
25
+ it('gives two emulators two files, and one emulator the same file every time', function () {
26
+ expect(lockFilePathFor('localhost:9010')).toBe(lockFilePathFor('localhost:9010'));
27
+ expect(lockFilePathFor('localhost:9010')).not.toBe(lockFilePathFor('localhost:9011'));
28
+ // The hash keeps hosts apart even when their slugs collide.
29
+ expect(lockFilePathFor('localhost:9010')).not.toBe(lockFilePathFor('localhost-9010'));
30
+ });
31
+ });
32
+ //# sourceMappingURL=emulatorLock.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulatorLock.test.js","sourceRoot":"","sources":["../../test/emulatorLock.test.ts"],"names":[],"mappings":";;;;;AAAA,kDAA4B;AAC5B,0CAAoB;AACpB,8CAAwB;AAExB,8DAA8D;AACtD,IAAA,eAAe,GAAK,OAAO,CAAC,gBAAgB,CAAkD,gBAA/E,CAAgF;AAEvG;;;;;GAKG;AACH,QAAQ,CAAC,8BAA8B,EAAE;IACvC,IAAM,WAAW,GAAG,UAAC,IAAY,EAAE,IAAY;QAC7C,OAAA,cAAI,CAAC,IAAI,CACP,YAAE,CAAC,MAAM,EAAE,EACX,2BAAoB,IAAI,cAAI,gBAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,UAAO,CACpG;IAHD,CAGC,CAAC;IAEJ,EAAE,CAAC,gGAAgG,EAAE;QACnG,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAChG,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE;QAC7E,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACtF,4DAA4D;QAC5D,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=emulatorLockName.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulatorLockName.test.d.ts","sourceRoot":"","sources":["../../test/emulatorLockName.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var os_1 = __importDefault(require("os"));
7
+ var path_1 = __importDefault(require("path"));
8
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
9
+ var lockFilePathFor = require('./emulatorLock').lockFilePathFor;
10
+ /**
11
+ * The emulator lock's FILE NAME is a contract between every copy of the lock: two jest runs
12
+ * serialize on a shared emulator only when they compute the same file for the same host. No
13
+ * shared package carries the lock yet, so the contract is pinned by value — this table is the
14
+ * same, row for row, in every repository that keeps a copy. A change to a row here is a change
15
+ * to all of them, made in the same window; a copy that drifts stops contending, silently.
16
+ */
17
+ // [emulator host, lock file name, what the row pins]
18
+ var LOCK_FILE_NAMES = [
19
+ ['localhost:9010', 'spanner-emulator-localhost-9010-0091e79a.lock', 'the default shared emulator'],
20
+ [
21
+ 'localhost-9010',
22
+ 'spanner-emulator-localhost-9010-493b66d1.lock',
23
+ 'the same slug as the row above: the hash alone keeps two hosts apart',
24
+ ],
25
+ [
26
+ '[emulator.a-host-name-long-enough-to-run-past-the-sixty-four-cap.example.test]:9010',
27
+ 'spanner-emulator-emulator-a-host-name-long-enough-to-run-past-the-sixty-four-cap--fe958a1a.lock',
28
+ 'edge dashes trimmed first, the slug capped at 64 second (the cap lands on a dash, and it stays)',
29
+ ],
30
+ ];
31
+ describe('the emulator lock file name (the contract every copy of the lock shares)', function () {
32
+ it.each(LOCK_FILE_NAMES)('%s locks on %s (%s)', function (host, fileName) {
33
+ expect(lockFilePathFor(host)).toBe(path_1.default.join(os_1.default.tmpdir(), fileName));
34
+ });
35
+ });
36
+ //# sourceMappingURL=emulatorLockName.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulatorLockName.test.js","sourceRoot":"","sources":["../../test/emulatorLockName.test.ts"],"names":[],"mappings":";;;;;AAAA,0CAAoB;AACpB,8CAAwB;AAExB,8DAA8D;AACtD,IAAA,eAAe,GAAK,OAAO,CAAC,gBAAgB,CAAkD,gBAA/E,CAAgF;AAEvG;;;;;;GAMG;AACH,qDAAqD;AACrD,IAAM,eAAe,GAA+B;IAClD,CAAC,gBAAgB,EAAE,+CAA+C,EAAE,6BAA6B,CAAC;IAClG;QACE,gBAAgB;QAChB,+CAA+C;QAC/C,sEAAsE;KACvE;IACD;QACE,qFAAqF;QACrF,iGAAiG;QACjG,iGAAiG;KAClG;CACF,CAAC;AAEF,QAAQ,CAAC,0EAA0E,EAAE;IACnF,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,qBAAqB,EAAE,UAAC,IAAI,EAAE,QAAQ;QAC7D,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}