@shipfox/api-auth 5.0.0 → 6.0.0

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 (154) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +52 -0
  3. package/README.md +71 -37
  4. package/dist/config.d.ts +0 -12
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +0 -62
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/auth.d.ts +21 -2
  9. package/dist/core/auth.d.ts.map +1 -1
  10. package/dist/core/auth.js +109 -77
  11. package/dist/core/auth.js.map +1 -1
  12. package/dist/core/email-owner.d.ts +12 -0
  13. package/dist/core/email-owner.d.ts.map +1 -0
  14. package/dist/core/email-owner.js +20 -0
  15. package/dist/core/email-owner.js.map +1 -0
  16. package/dist/core/job-lease-token.d.ts.map +1 -1
  17. package/dist/core/job-lease-token.js +3 -2
  18. package/dist/core/job-lease-token.js.map +1 -1
  19. package/dist/core/jwt.d.ts +2 -2
  20. package/dist/core/jwt.d.ts.map +1 -1
  21. package/dist/core/jwt.js.map +1 -1
  22. package/dist/core/rate-limit.d.ts.map +1 -1
  23. package/dist/core/rate-limit.js +2 -7
  24. package/dist/core/rate-limit.js.map +1 -1
  25. package/dist/core/runner-session-token.d.ts.map +1 -1
  26. package/dist/core/runner-session-token.js +3 -2
  27. package/dist/core/runner-session-token.js.map +1 -1
  28. package/dist/db/db.d.ts +0 -218
  29. package/dist/db/db.d.ts.map +1 -1
  30. package/dist/db/db.js +0 -2
  31. package/dist/db/db.js.map +1 -1
  32. package/dist/db/users.d.ts +3 -0
  33. package/dist/db/users.d.ts.map +1 -1
  34. package/dist/db/users.js +28 -10
  35. package/dist/db/users.js.map +1 -1
  36. package/dist/index.d.ts +5 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +40 -34
  39. package/dist/index.js.map +1 -1
  40. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  41. package/dist/presentation/auth/jwt-auth.js +2 -2
  42. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  43. package/dist/presentation/e2eRoutes/create-session.d.ts +2 -1
  44. package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -1
  45. package/dist/presentation/e2eRoutes/create-session.js +42 -39
  46. package/dist/presentation/e2eRoutes/create-session.js.map +1 -1
  47. package/dist/presentation/e2eRoutes/index.d.ts +2 -1
  48. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -1
  49. package/dist/presentation/e2eRoutes/index.js +12 -10
  50. package/dist/presentation/e2eRoutes/index.js.map +1 -1
  51. package/dist/presentation/inter-module.d.ts +4 -0
  52. package/dist/presentation/inter-module.d.ts.map +1 -0
  53. package/dist/presentation/inter-module.js +16 -0
  54. package/dist/presentation/inter-module.js.map +1 -0
  55. package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -1
  56. package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -1
  57. package/dist/presentation/routes/email-verification/verify-email-confirm.js +36 -25
  58. package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -1
  59. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
  60. package/dist/presentation/routes/email-verification/verify-email-resend.js +20 -4
  61. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
  62. package/dist/presentation/routes/index.d.ts +2 -2
  63. package/dist/presentation/routes/index.d.ts.map +1 -1
  64. package/dist/presentation/routes/index.js +11 -13
  65. package/dist/presentation/routes/index.js.map +1 -1
  66. package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -1
  67. package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -1
  68. package/dist/presentation/routes/password/password-reset-confirm.js +29 -26
  69. package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -1
  70. package/dist/presentation/routes/registration/signup.d.ts +2 -1
  71. package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
  72. package/dist/presentation/routes/registration/signup.js +86 -68
  73. package/dist/presentation/routes/registration/signup.js.map +1 -1
  74. package/dist/presentation/routes/session/login.d.ts +2 -1
  75. package/dist/presentation/routes/session/login.d.ts.map +1 -1
  76. package/dist/presentation/routes/session/login.js +40 -37
  77. package/dist/presentation/routes/session/login.js.map +1 -1
  78. package/dist/presentation/routes/session/refresh.d.ts +2 -1
  79. package/dist/presentation/routes/session/refresh.d.ts.map +1 -1
  80. package/dist/presentation/routes/session/refresh.js +42 -39
  81. package/dist/presentation/routes/session/refresh.js.map +1 -1
  82. package/dist/presentation/subscribers/index.d.ts +0 -1
  83. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  84. package/dist/presentation/subscribers/index.js +0 -1
  85. package/dist/presentation/subscribers/index.js.map +1 -1
  86. package/dist/presentation/subscribers/on-password-reset-send-requested.js +1 -1
  87. package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -1
  88. package/dist/tsconfig.test.tsbuildinfo +1 -1
  89. package/drizzle/0000_initial.sql +1 -13
  90. package/drizzle/meta/0000_snapshot.json +0 -92
  91. package/package.json +17 -13
  92. package/src/config.ts +0 -56
  93. package/src/core/auth.test.ts +106 -187
  94. package/src/core/auth.ts +131 -87
  95. package/src/core/email-owner.test.ts +86 -0
  96. package/src/core/email-owner.ts +23 -0
  97. package/src/core/job-lease-token.test.ts +2 -1
  98. package/src/core/job-lease-token.ts +3 -2
  99. package/src/core/jwt.ts +2 -2
  100. package/src/core/rate-limit.test.ts +11 -28
  101. package/src/core/rate-limit.ts +3 -11
  102. package/src/core/runner-session-token.test.ts +2 -1
  103. package/src/core/runner-session-token.ts +3 -2
  104. package/src/db/db.ts +0 -2
  105. package/src/db/users.ts +32 -14
  106. package/src/index.test.ts +15 -11
  107. package/src/index.ts +23 -20
  108. package/src/presentation/auth/jwt-auth.test.ts +2 -1
  109. package/src/presentation/auth/jwt-auth.ts +2 -2
  110. package/src/presentation/e2eRoutes/create-session.ts +34 -30
  111. package/src/presentation/e2eRoutes/index.test.ts +9 -6
  112. package/src/presentation/e2eRoutes/index.ts +8 -5
  113. package/src/presentation/inter-module.ts +13 -0
  114. package/src/presentation/routes/email-verification/verify-email-confirm.ts +42 -24
  115. package/src/presentation/routes/email-verification/verify-email-resend.ts +18 -4
  116. package/src/presentation/routes/index.test.ts +10 -3
  117. package/src/presentation/routes/index.ts +22 -12
  118. package/src/presentation/routes/password/password-reset-confirm.ts +25 -22
  119. package/src/presentation/routes/password/password-reset-request.test.ts +1 -0
  120. package/src/presentation/routes/rate-limit.test.ts +2 -2
  121. package/src/presentation/routes/registration/signup.test.ts +39 -74
  122. package/src/presentation/routes/registration/signup.ts +89 -70
  123. package/src/presentation/routes/session/login.ts +38 -35
  124. package/src/presentation/routes/session/refresh.ts +43 -40
  125. package/src/presentation/subscribers/email-send-requested.test.ts +2 -30
  126. package/src/presentation/subscribers/index.ts +0 -1
  127. package/src/presentation/subscribers/on-password-reset-send-requested.ts +1 -1
  128. package/test/env.ts +1 -3
  129. package/test/globalSetup.ts +10 -1
  130. package/test/routes.ts +51 -84
  131. package/tsconfig.build.tsbuildinfo +1 -1
  132. package/dist/core/entities/email-verification.d.ts +0 -9
  133. package/dist/core/entities/email-verification.d.ts.map +0 -1
  134. package/dist/core/entities/email-verification.js +0 -3
  135. package/dist/core/entities/email-verification.js.map +0 -1
  136. package/dist/db/email-verifications.d.ts +0 -39
  137. package/dist/db/email-verifications.d.ts.map +0 -1
  138. package/dist/db/email-verifications.js +0 -95
  139. package/dist/db/email-verifications.js.map +0 -1
  140. package/dist/db/schema/email-verifications.d.ts +0 -114
  141. package/dist/db/schema/email-verifications.d.ts.map +0 -1
  142. package/dist/db/schema/email-verifications.js +0 -35
  143. package/dist/db/schema/email-verifications.js.map +0 -1
  144. package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +0 -3
  145. package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +0 -1
  146. package/dist/presentation/subscribers/on-email-verification-send-requested.js +0 -13
  147. package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +0 -1
  148. package/src/core/entities/email-verification.ts +0 -8
  149. package/src/db/email-verifications.test.ts +0 -61
  150. package/src/db/email-verifications.ts +0 -160
  151. package/src/db/schema/email-verifications.ts +0 -37
  152. package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +0 -66
  153. package/src/presentation/routes/email-verification/verify-email-resend.test.ts +0 -100
  154. package/src/presentation/subscribers/on-email-verification-send-requested.ts +0 -10
@@ -1,5 +1,5 @@
1
1
  import type {Mailer, MailMessage} from '@shipfox/node-mailer';
2
- import {onEmailVerificationSendRequested, onPasswordResetSendRequested} from './index.js';
2
+ import {onPasswordResetSendRequested} from './index.js';
3
3
 
4
4
  const testMailer = vi.hoisted(
5
5
  (): {
@@ -16,7 +16,7 @@ const testMailer = vi.hoisted(
16
16
  },
17
17
  );
18
18
 
19
- vi.mock('#config.js', () => ({
19
+ vi.mock('@shipfox/node-mailer', () => ({
20
20
  mailer: testMailer.mailer,
21
21
  }));
22
22
 
@@ -30,22 +30,6 @@ describe('auth email subscribers', () => {
30
30
  });
31
31
  });
32
32
 
33
- test('sends a rendered email verification message', async () => {
34
- await onEmailVerificationSendRequested({
35
- email: 'verify@example.com',
36
- verifyLink: 'https://app.example.test/auth/verify-email?token=verify-token',
37
- });
38
-
39
- expect(testMailer.captured).toHaveLength(1);
40
- expect(testMailer.captured[0]).toMatchObject({
41
- to: 'verify@example.com',
42
- subject: 'Verify your email',
43
- });
44
- expect(testMailer.captured[0]?.text).toContain('/auth/verify-email?token=verify-token');
45
- expect(testMailer.captured[0]?.html).toContain('/auth/verify-email?token');
46
- expect(testMailer.captured[0]?.html).toContain('verify-token');
47
- });
48
-
49
33
  test('sends a rendered password reset message', async () => {
50
34
  await onPasswordResetSendRequested({
51
35
  email: 'reset@example.com',
@@ -62,16 +46,4 @@ describe('auth email subscribers', () => {
62
46
  expect(testMailer.captured[0]?.html).toContain('/auth/reset?token');
63
47
  expect(testMailer.captured[0]?.html).toContain('reset-token');
64
48
  });
65
-
66
- test('rethrows mailer failures so the outbox dispatcher retries', async () => {
67
- const failure = new Error('smtp unavailable');
68
- testMailer.send.mockRejectedValueOnce(failure);
69
-
70
- const promise = onEmailVerificationSendRequested({
71
- email: 'verify-failure@example.com',
72
- verifyLink: 'https://app.example.test/auth/verify-email?token=verify-token',
73
- });
74
-
75
- await expect(promise).rejects.toBe(failure);
76
- });
77
49
  });
@@ -1,2 +1 @@
1
- export {onEmailVerificationSendRequested} from './on-email-verification-send-requested.js';
2
1
  export {onPasswordResetSendRequested} from './on-password-reset-send-requested.js';
@@ -1,6 +1,6 @@
1
1
  import type {AuthPasswordResetSendRequestedEvent} from '@shipfox/api-auth-dto';
2
2
  import {renderEmail} from '@shipfox/node-email';
3
- import {mailer} from '#config.js';
3
+ import {mailer} from '@shipfox/node-mailer';
4
4
 
5
5
  export async function onPasswordResetSendRequested(
6
6
  payload: AuthPasswordResetSendRequestedEvent,
package/test/env.ts CHANGED
@@ -4,7 +4,5 @@ process.env.POSTGRES_USERNAME ??= 'shipfox';
4
4
  process.env.POSTGRES_PASSWORD ??= 'password';
5
5
  process.env.POSTGRES_DATABASE = 'api_test';
6
6
  process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
7
- process.env.AUTH_JWT_SECRET = 'test-secret';
8
- process.env.AUTH_JOB_LEASE_TOKEN_SECRET = 'test-lease-secret';
9
- process.env.AUTH_RUNNER_SESSION_TOKEN_SECRET = 'test-runner-session-secret';
7
+ process.env.AUTH_ROOT_KEY = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
10
8
  process.env.TZ = 'UTC';
@@ -1,4 +1,5 @@
1
1
  import './env.js';
2
+ import {emailChallengesModule} from '@shipfox/api-email-challenges';
2
3
  import {runMigrations} from '@shipfox/node-drizzle';
3
4
  import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
4
5
  import {sql} from 'drizzle-orm';
@@ -9,8 +10,16 @@ export async function setup() {
9
10
  createPostgresClient();
10
11
 
11
12
  await runMigrations(db(), migrationsPath, '__drizzle_migrations_auth');
13
+ const emailChallengesDatabase = emailChallengesModule.database;
14
+ const emailChallengesMigrations =
15
+ emailChallengesDatabase && !Array.isArray(emailChallengesDatabase)
16
+ ? emailChallengesDatabase.migrationsPath
17
+ : undefined;
18
+ if (!emailChallengesMigrations)
19
+ throw new Error('Email challenges module has no database migrations');
20
+ await runMigrations(db(), emailChallengesMigrations, '__drizzle_migrations_email_challenges');
12
21
  await db().execute(
13
- sql`TRUNCATE auth_outbox, auth_email_verifications, auth_password_resets, auth_rate_limits, auth_refresh_tokens, auth_users CASCADE`,
22
+ sql`TRUNCATE email_challenges_challenges, email_challenges_send_limits, auth_outbox, auth_password_resets, auth_rate_limits, auth_refresh_tokens, auth_users CASCADE`,
14
23
  );
15
24
 
16
25
  closeDb();
package/test/routes.ts CHANGED
@@ -1,21 +1,20 @@
1
1
  import type {OutgoingHttpHeaders} from 'node:http';
2
- import {
3
- AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,
4
- type AUTH_PASSWORD_RESET_SEND_REQUESTED,
5
- } from '@shipfox/api-auth-dto';
2
+ import type {AUTH_PASSWORD_RESET_SEND_REQUESTED} from '@shipfox/api-auth-dto';
3
+ import type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';
4
+ import {userAccessTokenKey} from '@shipfox/node-auth-root-key';
6
5
  import {type AppConfig, createApp, type FastifyInstance} from '@shipfox/node-fastify';
7
6
  import type {Mailer, MailMessage} from '@shipfox/node-mailer';
8
7
  import {and, desc, eq, sql} from 'drizzle-orm';
9
8
  import {db} from '#db/db.js';
10
9
  import {authOutbox} from '#db/schema/outbox.js';
11
10
  import {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';
12
- import {authRoutes} from '#presentation/routes/index.js';
11
+ import {buildAuthRoutes} from '#presentation/routes/index.js';
13
12
 
14
13
  const testConfig = vi.hoisted(
15
14
  (): {
16
15
  captured: MailMessage[];
16
+ challenges: Map<string, string>;
17
17
  mailer: Mailer;
18
- secret: string;
19
18
  clientBaseUrl: string;
20
19
  } => {
21
20
  const captured: MailMessage[] = [];
@@ -27,56 +26,27 @@ const testConfig = vi.hoisted(
27
26
  };
28
27
  return {
29
28
  captured,
29
+ challenges: new Map(),
30
30
  mailer,
31
- secret: 'route-tests-secret',
32
31
  clientBaseUrl: 'https://app.example.test',
33
32
  };
34
33
  },
35
34
  );
36
35
 
37
36
  const workspaceTestDoubles = vi.hoisted(() => {
38
- class InvitationEmailMismatchError extends Error {
39
- constructor(message = 'Invitation email does not match') {
40
- super(message);
41
- this.name = 'InvitationEmailMismatchError';
42
- }
43
- }
44
-
45
- class TokenAlreadyUsedError extends Error {
46
- constructor(message = 'Invitation token has already been used') {
47
- super(message);
48
- this.name = 'TokenAlreadyUsedError';
49
- }
50
- }
51
-
52
- class TokenExpiredError extends Error {
53
- constructor(message = 'Invitation token has expired') {
54
- super(message);
55
- this.name = 'TokenExpiredError';
56
- }
57
- }
58
-
59
- class TokenInvalidError extends Error {
60
- constructor(message = 'Invitation token is invalid') {
61
- super(message);
62
- this.name = 'TokenInvalidError';
63
- }
64
- }
65
-
66
37
  return {
67
- acceptWorkspaceInvitation: vi.fn(),
68
- peekInvitationByRawToken: vi.fn(),
69
- listMembershipsByUser: vi.fn(() => Promise.resolve([])),
70
- InvitationEmailMismatchError,
71
- TokenAlreadyUsedError,
72
- TokenExpiredError,
73
- TokenInvalidError,
38
+ acceptInvitation: vi.fn(),
39
+ preflightInvitationAcceptance: vi.fn(),
40
+ listMembershipsForTokenClaims: vi.fn(() => Promise.resolve({memberships: []})),
41
+ requireActiveMembership: vi.fn(),
74
42
  };
75
- });
43
+ }) as {
44
+ [Method in keyof WorkspacesInterModuleClient]: ReturnType<typeof vi.fn>;
45
+ };
46
+ const workspaces = workspaceTestDoubles as unknown as WorkspacesInterModuleClient;
76
47
 
77
48
  vi.mock('#config.js', () => ({
78
49
  config: {
79
- AUTH_JWT_SECRET: testConfig.secret,
80
50
  AUTH_JWT_EXPIRES_IN: '15m',
81
51
  AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS: 14,
82
52
  AUTH_REFRESH_ROTATION_GRACE_SECONDS: 30,
@@ -87,42 +57,27 @@ vi.mock('#config.js', () => ({
87
57
  mailer: testConfig.mailer,
88
58
  }));
89
59
 
90
- vi.mock('@shipfox/api-workspaces', () => ({
91
- acceptWorkspaceInvitation: workspaceTestDoubles.acceptWorkspaceInvitation,
92
- peekInvitationByRawToken: workspaceTestDoubles.peekInvitationByRawToken,
93
- InvitationEmailMismatchError: workspaceTestDoubles.InvitationEmailMismatchError,
94
- listMembershipsByUser: workspaceTestDoubles.listMembershipsByUser,
95
- TokenAlreadyUsedError: workspaceTestDoubles.TokenAlreadyUsedError,
96
- TokenExpiredError: workspaceTestDoubles.TokenExpiredError,
97
- TokenInvalidError: workspaceTestDoubles.TokenInvalidError,
98
- }));
99
-
100
- const {acceptWorkspaceInvitation, peekInvitationByRawToken, listMembershipsByUser} = await import(
101
- '@shipfox/api-workspaces'
102
- );
60
+ vi.mock('@shipfox/node-mailer', () => ({mailer: testConfig.mailer}));
103
61
 
62
+ const CODE_RE = /\b\d{8}\b/u;
104
63
  const TOKEN_RE = /token=([\w\-_=]+)/;
105
- type AuthEmailEventType =
106
- | typeof AUTH_EMAIL_VERIFICATION_SEND_REQUESTED
107
- | typeof AUTH_PASSWORD_RESET_SEND_REQUESTED;
64
+ type AuthEmailEventType = typeof AUTH_PASSWORD_RESET_SEND_REQUESTED;
108
65
 
109
- export const ROUTE_TEST_SECRET = testConfig.secret;
110
- export const acceptWorkspaceInvitationMock = vi.mocked(acceptWorkspaceInvitation);
111
- export const peekInvitationByRawTokenMock = vi.mocked(peekInvitationByRawToken);
112
- export const listMembershipsByUserMock = vi.mocked(listMembershipsByUser);
113
- export const workspaceErrors = {
114
- InvitationEmailMismatchError: workspaceTestDoubles.InvitationEmailMismatchError,
115
- TokenAlreadyUsedError: workspaceTestDoubles.TokenAlreadyUsedError,
116
- TokenExpiredError: workspaceTestDoubles.TokenExpiredError,
117
- TokenInvalidError: workspaceTestDoubles.TokenInvalidError,
118
- };
66
+ export const ROUTE_TEST_SECRET = userAccessTokenKey();
67
+ export const acceptWorkspaceInvitationMock: ReturnType<typeof vi.fn> =
68
+ workspaceTestDoubles.acceptInvitation;
69
+ export const peekInvitationByRawTokenMock: ReturnType<typeof vi.fn> =
70
+ workspaceTestDoubles.preflightInvitationAcceptance;
71
+ export const listMembershipsByUserMock: ReturnType<typeof vi.fn> =
72
+ workspaceTestDoubles.listMembershipsForTokenClaims;
119
73
 
120
74
  export function resetCapturedMail(): void {
121
75
  testConfig.captured.length = 0;
76
+ testConfig.challenges.clear();
122
77
  acceptWorkspaceInvitationMock.mockReset();
123
78
  peekInvitationByRawTokenMock.mockReset();
124
79
  listMembershipsByUserMock.mockReset();
125
- listMembershipsByUserMock.mockResolvedValue([]);
80
+ listMembershipsByUserMock.mockResolvedValue({memberships: []});
126
81
  }
127
82
 
128
83
  export function capturedMail(): MailMessage[] {
@@ -143,6 +98,12 @@ export function uniqueEmail(prefix: string): string {
143
98
  return `${prefix}-${crypto.randomUUID()}@example.com`;
144
99
  }
145
100
 
101
+ export function latestVerificationCode(email: string): string {
102
+ const code = latestMailTo(email).text?.match(CODE_RE)?.[0];
103
+ expect(code).toBeDefined();
104
+ return code ?? '';
105
+ }
106
+
146
107
  export function extractToken(url: string): string {
147
108
  const match = url.match(TOKEN_RE);
148
109
  expect(match?.[1]).toBeDefined();
@@ -175,8 +136,7 @@ export async function latestEmailLinkTo(
175
136
  const event = (await outboxEventsTo(email, eventType))[0];
176
137
  expect(event).toBeDefined();
177
138
  const payload = event?.payload as {verifyLink?: string; resetLink?: string} | undefined;
178
- const link =
179
- eventType === AUTH_EMAIL_VERIFICATION_SEND_REQUESTED ? payload?.verifyLink : payload?.resetLink;
139
+ const link = payload?.resetLink;
180
140
  expect(link).toBeDefined();
181
141
  return link ?? '';
182
142
  }
@@ -186,7 +146,7 @@ export async function createAuthTestApp(params?: {
186
146
  }): Promise<FastifyInstance> {
187
147
  const appConfig: AppConfig = {
188
148
  auth: [createJwtAuthMethod()],
189
- routes: [authRoutes],
149
+ routes: [buildAuthRoutes(true, workspaces)],
190
150
  swagger: false,
191
151
  };
192
152
  if (params?.fastifyOptions) appConfig.fastifyOptions = params.fastifyOptions;
@@ -197,22 +157,26 @@ export async function signup(
197
157
  app: FastifyInstance,
198
158
  params: {email: string; password: string; name?: string},
199
159
  ) {
200
- return await app.inject({
160
+ const result = await app.inject({
201
161
  method: 'POST',
202
162
  url: '/auth/signup',
203
163
  payload: {name: 'Test User', ...params},
204
164
  });
165
+ const challengeId = result.json().email_challenge?.id;
166
+ if (typeof challengeId === 'string')
167
+ testConfig.challenges.set(params.email.trim().toLowerCase(), challengeId);
168
+ return result;
205
169
  }
206
170
 
207
- export async function verifyEmail(app: FastifyInstance, email: string): Promise<void> {
208
- const token = extractToken(
209
- await latestEmailLinkTo(email, AUTH_EMAIL_VERIFICATION_SEND_REQUESTED),
210
- );
211
-
171
+ export async function verifyEmail(
172
+ app: FastifyInstance,
173
+ email: string,
174
+ challengeId = testConfig.challenges.get(email) ?? '',
175
+ ): Promise<void> {
212
176
  const res = await app.inject({
213
177
  method: 'POST',
214
178
  url: '/auth/verify-email/confirm',
215
- payload: {token},
179
+ payload: {email, challenge_id: challengeId, code: latestVerificationCode(email)},
216
180
  });
217
181
 
218
182
  expect(res.statusCode).toBe(200);
@@ -235,11 +199,13 @@ export async function signupVerifyLogin(
235
199
  refreshCookie: string;
236
200
  token: string;
237
201
  userId: string;
202
+ emailChallengeId: string;
238
203
  }> {
239
204
  const email = uniqueEmail(prefix);
240
205
  const password = 'correct horse battery staple';
241
- await signup(app, {email, password, name: prefix});
242
- await verifyEmail(app, email);
206
+ const signupResult = await signup(app, {email, password, name: prefix});
207
+ const emailChallengeId = signupResult.json().email_challenge.id;
208
+ await verifyEmail(app, email, emailChallengeId);
243
209
 
244
210
  const loginRes = await login(app, {email, password});
245
211
 
@@ -250,6 +216,7 @@ export async function signupVerifyLogin(
250
216
  refreshCookie: getSetCookie(loginRes),
251
217
  token: loginRes.json().token,
252
218
  userId: loginRes.json().user.id,
219
+ emailChallengeId,
253
220
  };
254
221
  }
255
222
 
@@ -264,7 +231,7 @@ export async function createVerifiedSession(prefix: string): Promise<{
264
231
  const email = uniqueEmail(prefix);
265
232
  const password = 'correct horse battery staple';
266
233
  const user = await createUser({email, password, name: prefix, verified: true});
267
- const session = await createSessionForUser({userId: user.id});
234
+ const session = await createSessionForUser({userId: user.id, workspaces});
268
235
 
269
236
  return {
270
237
  email,