@proteinjs/user-server 1.20.2 → 1.21.1

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 (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/generated/index.js +1 -1
  3. package/dist/generated/index.js.map +1 -1
  4. package/dist/src/authentication/PasswordResetToken.d.ts +45 -0
  5. package/dist/src/authentication/PasswordResetToken.d.ts.map +1 -0
  6. package/dist/src/authentication/PasswordResetToken.js +138 -0
  7. package/dist/src/authentication/PasswordResetToken.js.map +1 -0
  8. package/dist/src/routes/executePasswordReset.d.ts +4 -4
  9. package/dist/src/routes/executePasswordReset.d.ts.map +1 -1
  10. package/dist/src/routes/executePasswordReset.js +27 -26
  11. package/dist/src/routes/executePasswordReset.js.map +1 -1
  12. package/dist/src/routes/initiatePasswordReset.js +2 -2
  13. package/dist/src/routes/initiatePasswordReset.js.map +1 -1
  14. package/dist/src/routes/validateResetPasswordToken.d.ts.map +1 -1
  15. package/dist/src/routes/validateResetPasswordToken.js +13 -16
  16. package/dist/src/routes/validateResetPasswordToken.js.map +1 -1
  17. package/dist/src/services/Signup.d.ts +14 -0
  18. package/dist/src/services/Signup.d.ts.map +1 -1
  19. package/dist/src/services/Signup.js +102 -27
  20. package/dist/src/services/Signup.js.map +1 -1
  21. package/dist/test/ExecutePasswordReset.test.d.ts +2 -0
  22. package/dist/test/ExecutePasswordReset.test.d.ts.map +1 -0
  23. package/dist/test/ExecutePasswordReset.test.js +394 -0
  24. package/dist/test/ExecutePasswordReset.test.js.map +1 -0
  25. package/dist/test/SignupResendInvite.integration.test.d.ts +2 -0
  26. package/dist/test/SignupResendInvite.integration.test.d.ts.map +1 -0
  27. package/dist/test/SignupResendInvite.integration.test.js +235 -0
  28. package/dist/test/SignupResendInvite.integration.test.js.map +1 -0
  29. package/dist/test/ValidateResetToken.test.js +30 -7
  30. package/dist/test/ValidateResetToken.test.js.map +1 -1
  31. package/generated/index.ts +1 -1
  32. package/package.json +3 -3
  33. package/src/authentication/PasswordResetToken.ts +96 -0
  34. package/src/routes/executePasswordReset.ts +25 -24
  35. package/src/routes/initiatePasswordReset.ts +2 -2
  36. package/src/routes/validateResetPasswordToken.ts +12 -15
  37. package/src/services/Signup.ts +71 -21
  38. package/test/ExecutePasswordReset.test.ts +224 -0
  39. package/test/SignupResendInvite.integration.test.ts +163 -0
  40. package/test/ValidateResetToken.test.ts +24 -6
@@ -2,6 +2,7 @@ import moment from 'moment';
2
2
  import { getDbAsSystem } from '@proteinjs/db';
3
3
  import { tables } from '@proteinjs/user';
4
4
  import { validateResetPasswordToken } from '../src/routes/validateResetPasswordToken';
5
+ import { PasswordResetToken } from '../src/authentication/PasswordResetToken';
5
6
  import { UserServerTestEnvironment } from './UserServerTestEnvironment';
6
7
 
7
8
  const testEnv = new UserServerTestEnvironment();
@@ -12,7 +13,9 @@ const testEnv = new UserServerTestEnvironment();
12
13
  * `autocomplete="username"` field so password managers can associate the updated password
13
14
  * with the stored credential. The email only ever rides a VALID token's response — the
14
15
  * token was delivered to that very inbox, so it reveals nothing the holder doesn't know —
15
- * while invalid/expired verdicts stay email-free (no account-probing oracle).
16
+ * while invalid/expired verdicts stay email-free (no account-probing oracle). A query value
17
+ * that is not a well-formed token (a parsed object or array, a string of another shape) is
18
+ * invalid without a lookup.
16
19
  */
17
20
 
18
21
  type RouteOutcome = { status?: number; body?: any };
@@ -32,6 +35,8 @@ const invokeValidate = async (query: Record<string, unknown>): Promise<RouteOutc
32
35
  return outcome;
33
36
  };
34
37
 
38
+ const mintToken = () => new PasswordResetToken().mint();
39
+
35
40
  const armResetToken = async (email: string, token: string, expiration: moment.Moment) => {
36
41
  const user = await testEnv.createUser({ name: 'Reset User', email });
37
42
  await getDbAsSystem().update(tables.User, {
@@ -51,18 +56,20 @@ describe('validateResetPasswordToken route', () => {
51
56
  });
52
57
 
53
58
  it('a valid token resolves isValid WITH the account email (the reset form identifier)', async () => {
54
- await armResetToken('reset-valid@test.local', 'tok-valid-1', moment().add(1, 'hour'));
59
+ const token = mintToken();
60
+ await armResetToken('reset-valid@test.local', token, moment().add(1, 'hour'));
55
61
 
56
- const outcome = await invokeValidate({ token: 'tok-valid-1' });
62
+ const outcome = await invokeValidate({ token });
57
63
 
58
64
  expect(outcome.status).toBe(200);
59
65
  expect(outcome.body).toEqual({ isValid: true, email: 'reset-valid@test.local' });
60
66
  });
61
67
 
62
68
  it('an expired token resolves invalid and leaks no email', async () => {
63
- await armResetToken('reset-expired@test.local', 'tok-expired-1', moment().subtract(1, 'minute'));
69
+ const token = mintToken();
70
+ await armResetToken('reset-expired@test.local', token, moment().subtract(1, 'minute'));
64
71
 
65
- const outcome = await invokeValidate({ token: 'tok-expired-1' });
72
+ const outcome = await invokeValidate({ token });
66
73
 
67
74
  expect(outcome.status).toBe(200);
68
75
  expect(outcome.body.isValid).toBe(false);
@@ -70,13 +77,24 @@ describe('validateResetPasswordToken route', () => {
70
77
  });
71
78
 
72
79
  it('an unknown token resolves invalid and leaks no email', async () => {
73
- const outcome = await invokeValidate({ token: 'tok-never-issued' });
80
+ const outcome = await invokeValidate({ token: mintToken() });
74
81
 
75
82
  expect(outcome.status).toBe(200);
76
83
  expect(outcome.body.isValid).toBe(false);
77
84
  expect(outcome.body.email).toBeUndefined();
78
85
  });
79
86
 
87
+ it.each([
88
+ ['a string of another shape', 'tok-never-issued'],
89
+ ['a parsed array', ['a', 'b']],
90
+ ['a parsed object', { passwordResetToken: null }],
91
+ ])('%s resolves invalid without a lookup and leaks no email', async (_label, token) => {
92
+ const outcome = await invokeValidate({ token });
93
+
94
+ expect(outcome.status).toBe(200);
95
+ expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
96
+ });
97
+
80
98
  it('a missing token is a 400', async () => {
81
99
  const outcome = await invokeValidate({});
82
100