@proteinjs/user-server 1.8.0 → 1.9.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 (76) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/generated/index.d.ts.map +1 -1
  3. package/dist/generated/index.js +3 -1
  4. package/dist/generated/index.js.map +1 -1
  5. package/dist/src/authentication/DbSessionStore.d.ts +3 -2
  6. package/dist/src/authentication/DbSessionStore.d.ts.map +1 -1
  7. package/dist/src/authentication/DbSessionStore.js +3 -2
  8. package/dist/src/authentication/DbSessionStore.js.map +1 -1
  9. package/dist/src/authentication/establishSession.d.ts +30 -0
  10. package/dist/src/authentication/establishSession.d.ts.map +1 -0
  11. package/dist/src/authentication/establishSession.js +83 -0
  12. package/dist/src/authentication/establishSession.js.map +1 -0
  13. package/dist/src/routes/devLogin.d.ts.map +1 -1
  14. package/dist/src/routes/devLogin.js +6 -12
  15. package/dist/src/routes/devLogin.js.map +1 -1
  16. package/dist/src/routes/login.d.ts.map +1 -1
  17. package/dist/src/routes/login.js +2 -3
  18. package/dist/src/routes/login.js.map +1 -1
  19. package/dist/src/routes/logout.d.ts +10 -0
  20. package/dist/src/routes/logout.d.ts.map +1 -1
  21. package/dist/src/routes/logout.js +13 -32
  22. package/dist/src/routes/logout.js.map +1 -1
  23. package/dist/src/routes/signup.d.ts +19 -0
  24. package/dist/src/routes/signup.d.ts.map +1 -0
  25. package/dist/src/routes/signup.js +102 -0
  26. package/dist/src/routes/signup.js.map +1 -0
  27. package/dist/src/routes/validateResetPasswordToken.d.ts.map +1 -1
  28. package/dist/src/routes/validateResetPasswordToken.js +5 -1
  29. package/dist/src/routes/validateResetPasswordToken.js.map +1 -1
  30. package/dist/src/services/AccessInvite.d.ts.map +1 -1
  31. package/dist/src/services/AccessInvite.js +32 -15
  32. package/dist/src/services/AccessInvite.js.map +1 -1
  33. package/dist/src/services/Signup.d.ts +18 -1
  34. package/dist/src/services/Signup.d.ts.map +1 -1
  35. package/dist/src/services/Signup.js +8 -2
  36. package/dist/src/services/Signup.js.map +1 -1
  37. package/dist/test/AccountDeletion.integration.test.js +24 -16
  38. package/dist/test/AccountDeletion.integration.test.js.map +1 -1
  39. package/dist/test/DevLogin.test.js +15 -20
  40. package/dist/test/DevLogin.test.js.map +1 -1
  41. package/dist/test/LogoutRoute.test.d.ts +2 -0
  42. package/dist/test/LogoutRoute.test.d.ts.map +1 -0
  43. package/dist/test/LogoutRoute.test.js +81 -0
  44. package/dist/test/LogoutRoute.test.js.map +1 -0
  45. package/dist/test/Signup.test.js +4 -2
  46. package/dist/test/Signup.test.js.map +1 -1
  47. package/dist/test/SignupRoute.test.d.ts +2 -0
  48. package/dist/test/SignupRoute.test.d.ts.map +1 -0
  49. package/dist/test/SignupRoute.test.js +338 -0
  50. package/dist/test/SignupRoute.test.js.map +1 -0
  51. package/dist/test/ValidateResetToken.test.d.ts +2 -0
  52. package/dist/test/ValidateResetToken.test.d.ts.map +1 -0
  53. package/dist/test/ValidateResetToken.test.js +170 -0
  54. package/dist/test/ValidateResetToken.test.js.map +1 -0
  55. package/dist/test/passportSessionHarness.d.ts +31 -0
  56. package/dist/test/passportSessionHarness.d.ts.map +1 -0
  57. package/dist/test/passportSessionHarness.js +107 -0
  58. package/dist/test/passportSessionHarness.js.map +1 -0
  59. package/generated/index.ts +3 -1
  60. package/package.json +11 -9
  61. package/src/authentication/DbSessionStore.ts +3 -2
  62. package/src/authentication/establishSession.ts +33 -0
  63. package/src/routes/devLogin.ts +4 -6
  64. package/src/routes/login.ts +2 -3
  65. package/src/routes/logout.ts +13 -18
  66. package/src/routes/signup.ts +55 -0
  67. package/src/routes/validateResetPasswordToken.ts +5 -1
  68. package/src/services/AccessInvite.ts +36 -12
  69. package/src/services/Signup.ts +21 -2
  70. package/test/AccountDeletion.integration.test.ts +15 -11
  71. package/test/DevLogin.test.ts +11 -14
  72. package/test/LogoutRoute.test.ts +33 -0
  73. package/test/Signup.test.ts +4 -2
  74. package/test/SignupRoute.test.ts +219 -0
  75. package/test/ValidateResetToken.test.ts +86 -0
  76. package/test/passportSessionHarness.ts +58 -0
@@ -0,0 +1,86 @@
1
+ import moment from 'moment';
2
+ import { getDbAsSystem } from '@proteinjs/db';
3
+ import { tables } from '@proteinjs/user';
4
+ import { validateResetPasswordToken } from '../src/routes/validateResetPasswordToken';
5
+ import { UserServerTestEnvironment } from './UserServerTestEnvironment';
6
+
7
+ const testEnv = new UserServerTestEnvironment();
8
+
9
+ /**
10
+ * `GET /user/validate-reset-token`. Beyond the valid/expired/unknown verdicts, the valid
11
+ * response carries the account EMAIL: the reset page renders it as the read-only
12
+ * `autocomplete="username"` field so password managers can associate the updated password
13
+ * with the stored credential. The email only ever rides a VALID token's response — the
14
+ * 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
+ */
17
+
18
+ type RouteOutcome = { status?: number; body?: any };
19
+
20
+ const invokeValidate = async (query: Record<string, unknown>): Promise<RouteOutcome> => {
21
+ const outcome: RouteOutcome = {};
22
+ const response = {
23
+ status(code: number) {
24
+ outcome.status = code;
25
+ return this;
26
+ },
27
+ send(body?: unknown) {
28
+ outcome.body = body;
29
+ },
30
+ };
31
+ await validateResetPasswordToken.onRequest({ query } as never, response as never);
32
+ return outcome;
33
+ };
34
+
35
+ const armResetToken = async (email: string, token: string, expiration: moment.Moment) => {
36
+ const user = await testEnv.createUser({ name: 'Reset User', email });
37
+ await getDbAsSystem().update(tables.User, {
38
+ id: user.id,
39
+ passwordResetToken: token,
40
+ passwordResetTokenExpiration: expiration,
41
+ });
42
+ };
43
+
44
+ describe('validateResetPasswordToken route', () => {
45
+ beforeAll(async () => {
46
+ await testEnv.beforeAll();
47
+ });
48
+
49
+ afterAll(async () => {
50
+ await testEnv.afterAll();
51
+ });
52
+
53
+ 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'));
55
+
56
+ const outcome = await invokeValidate({ token: 'tok-valid-1' });
57
+
58
+ expect(outcome.status).toBe(200);
59
+ expect(outcome.body).toEqual({ isValid: true, email: 'reset-valid@test.local' });
60
+ });
61
+
62
+ 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'));
64
+
65
+ const outcome = await invokeValidate({ token: 'tok-expired-1' });
66
+
67
+ expect(outcome.status).toBe(200);
68
+ expect(outcome.body.isValid).toBe(false);
69
+ expect(outcome.body.email).toBeUndefined();
70
+ });
71
+
72
+ it('an unknown token resolves invalid and leaks no email', async () => {
73
+ const outcome = await invokeValidate({ token: 'tok-never-issued' });
74
+
75
+ expect(outcome.status).toBe(200);
76
+ expect(outcome.body.isValid).toBe(false);
77
+ expect(outcome.body.email).toBeUndefined();
78
+ });
79
+
80
+ it('a missing token is a 400', async () => {
81
+ const outcome = await invokeValidate({});
82
+
83
+ expect(outcome.status).toBe(400);
84
+ expect(outcome.body.isValid).toBe(false);
85
+ });
86
+ });
@@ -0,0 +1,58 @@
1
+ import passport from 'passport';
2
+
3
+ /** Session-lifecycle events in call order — the session-establishment contract under test. */
4
+ export type SessionEvent = 'regenerate' | 'login' | 'save';
5
+
6
+ export type PassportRequest = {
7
+ /** Express-shaped request whose `login`/`logout` are REAL passport machinery. */
8
+ request: any;
9
+ /**
10
+ * 'regenerate' / 'login' / 'save' in call order. The full contract of session establishment:
11
+ * regenerate FIRST (a fresh session id on privilege change — session fixation), then the bind
12
+ * of the account onto the fresh session, then save (the row must commit before the response).
13
+ */
14
+ events: SessionEvent[];
15
+ };
16
+
17
+ /**
18
+ * Builds a request whose `request.login` / `request.logout` are the GENUINE passport 0.6
19
+ * machinery, wired through `authenticator.initialize()` — the same middleware the server runs —
20
+ * over an express-session-shaped session mock.
21
+ *
22
+ * Why real passport and not a hand-mocked `login`: since passport 0.6 (the CVE-2022-25896
23
+ * session-fixation fix), the regenerate and the commit-before-response save live INSIDE
24
+ * `request.login` (SessionManager.logIn: regenerate → serializeUser → bind → save). A mocked
25
+ * `login` would assert nothing about either — the door suites must drive the real owner to
26
+ * prove the doors inherit the contract.
27
+ *
28
+ * serializeUser mirrors @proteinjs/server's identity pass-through (the session stores the
29
+ * account email directly), instrumented to record the bind ('login') between 'regenerate' and
30
+ * 'save'. The session mock replicates the one express-session behavior passport depends on:
31
+ * `regenerate` REPLACES `request.session` with a fresh session (new id, no passport state), so
32
+ * the bind provably lands on the post-regeneration session.
33
+ */
34
+ export const createPassportRequest = async (extra: Record<string, unknown> = {}): Promise<PassportRequest> => {
35
+ const events: SessionEvent[] = [];
36
+ const authenticator = new passport.Passport();
37
+ authenticator.serializeUser((user: unknown, done: (err: unknown, id?: unknown) => void) => {
38
+ events.push('login');
39
+ done(null, user);
40
+ });
41
+
42
+ const request: any = { ...extra };
43
+ const makeSession = (): any => ({
44
+ regenerate: (done: (err?: unknown) => void) => {
45
+ events.push('regenerate');
46
+ request.session = makeSession();
47
+ done();
48
+ },
49
+ save: (done: (err?: unknown) => void) => {
50
+ events.push('save');
51
+ done();
52
+ },
53
+ });
54
+ request.session = makeSession();
55
+
56
+ await new Promise<void>((resolve) => authenticator.initialize()(request, {} as never, () => resolve()));
57
+ return { request, events };
58
+ };