@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +71 -37
- package/dist/config.d.ts +0 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -62
- package/dist/config.js.map +1 -1
- package/dist/core/auth.d.ts +21 -2
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +109 -77
- package/dist/core/auth.js.map +1 -1
- package/dist/core/email-owner.d.ts +12 -0
- package/dist/core/email-owner.d.ts.map +1 -0
- package/dist/core/email-owner.js +20 -0
- package/dist/core/email-owner.js.map +1 -0
- package/dist/core/job-lease-token.d.ts.map +1 -1
- package/dist/core/job-lease-token.js +3 -2
- package/dist/core/job-lease-token.js.map +1 -1
- package/dist/core/jwt.d.ts +2 -2
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/jwt.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/rate-limit.js +2 -7
- package/dist/core/rate-limit.js.map +1 -1
- package/dist/core/runner-session-token.d.ts.map +1 -1
- package/dist/core/runner-session-token.js +3 -2
- package/dist/core/runner-session-token.js.map +1 -1
- package/dist/db/db.d.ts +0 -218
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +0 -2
- package/dist/db/db.js.map +1 -1
- package/dist/db/users.d.ts +3 -0
- package/dist/db/users.d.ts.map +1 -1
- package/dist/db/users.js +28 -10
- package/dist/db/users.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -34
- package/dist/index.js.map +1 -1
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
- package/dist/presentation/auth/jwt-auth.js +2 -2
- package/dist/presentation/auth/jwt-auth.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts +2 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.js +42 -39
- package/dist/presentation/e2eRoutes/create-session.js.map +1 -1
- package/dist/presentation/e2eRoutes/index.d.ts +2 -1
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/index.js +12 -10
- package/dist/presentation/e2eRoutes/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +16 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.js +36 -25
- package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.js +20 -4
- package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -2
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +11 -13
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.js +29 -26
- package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -1
- package/dist/presentation/routes/registration/signup.d.ts +2 -1
- package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
- package/dist/presentation/routes/registration/signup.js +86 -68
- package/dist/presentation/routes/registration/signup.js.map +1 -1
- package/dist/presentation/routes/session/login.d.ts +2 -1
- package/dist/presentation/routes/session/login.d.ts.map +1 -1
- package/dist/presentation/routes/session/login.js +40 -37
- package/dist/presentation/routes/session/login.js.map +1 -1
- package/dist/presentation/routes/session/refresh.d.ts +2 -1
- package/dist/presentation/routes/session/refresh.d.ts.map +1 -1
- package/dist/presentation/routes/session/refresh.js +42 -39
- package/dist/presentation/routes/session/refresh.js.map +1 -1
- package/dist/presentation/subscribers/index.d.ts +0 -1
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +0 -1
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +1 -13
- package/drizzle/meta/0000_snapshot.json +0 -92
- package/package.json +17 -13
- package/src/config.ts +0 -56
- package/src/core/auth.test.ts +106 -187
- package/src/core/auth.ts +131 -87
- package/src/core/email-owner.test.ts +86 -0
- package/src/core/email-owner.ts +23 -0
- package/src/core/job-lease-token.test.ts +2 -1
- package/src/core/job-lease-token.ts +3 -2
- package/src/core/jwt.ts +2 -2
- package/src/core/rate-limit.test.ts +11 -28
- package/src/core/rate-limit.ts +3 -11
- package/src/core/runner-session-token.test.ts +2 -1
- package/src/core/runner-session-token.ts +3 -2
- package/src/db/db.ts +0 -2
- package/src/db/users.ts +32 -14
- package/src/index.test.ts +15 -11
- package/src/index.ts +23 -20
- package/src/presentation/auth/jwt-auth.test.ts +2 -1
- package/src/presentation/auth/jwt-auth.ts +2 -2
- package/src/presentation/e2eRoutes/create-session.ts +34 -30
- package/src/presentation/e2eRoutes/index.test.ts +9 -6
- package/src/presentation/e2eRoutes/index.ts +8 -5
- package/src/presentation/inter-module.ts +13 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.ts +42 -24
- package/src/presentation/routes/email-verification/verify-email-resend.ts +18 -4
- package/src/presentation/routes/index.test.ts +10 -3
- package/src/presentation/routes/index.ts +22 -12
- package/src/presentation/routes/password/password-reset-confirm.ts +25 -22
- package/src/presentation/routes/password/password-reset-request.test.ts +1 -0
- package/src/presentation/routes/rate-limit.test.ts +2 -2
- package/src/presentation/routes/registration/signup.test.ts +39 -74
- package/src/presentation/routes/registration/signup.ts +89 -70
- package/src/presentation/routes/session/login.ts +38 -35
- package/src/presentation/routes/session/refresh.ts +43 -40
- package/src/presentation/subscribers/email-send-requested.test.ts +2 -30
- package/src/presentation/subscribers/index.ts +0 -1
- package/src/presentation/subscribers/on-password-reset-send-requested.ts +1 -1
- package/test/env.ts +1 -3
- package/test/globalSetup.ts +10 -1
- package/test/routes.ts +51 -84
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/email-verification.d.ts +0 -9
- package/dist/core/entities/email-verification.d.ts.map +0 -1
- package/dist/core/entities/email-verification.js +0 -3
- package/dist/core/entities/email-verification.js.map +0 -1
- package/dist/db/email-verifications.d.ts +0 -39
- package/dist/db/email-verifications.d.ts.map +0 -1
- package/dist/db/email-verifications.js +0 -95
- package/dist/db/email-verifications.js.map +0 -1
- package/dist/db/schema/email-verifications.d.ts +0 -114
- package/dist/db/schema/email-verifications.d.ts.map +0 -1
- package/dist/db/schema/email-verifications.js +0 -35
- package/dist/db/schema/email-verifications.js.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +0 -3
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.js +0 -13
- package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +0 -1
- package/src/core/entities/email-verification.ts +0 -8
- package/src/db/email-verifications.test.ts +0 -61
- package/src/db/email-verifications.ts +0 -160
- package/src/db/schema/email-verifications.ts +0 -37
- package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +0 -66
- package/src/presentation/routes/email-verification/verify-email-resend.test.ts +0 -100
- 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 {
|
|
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('
|
|
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,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 '
|
|
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.
|
|
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';
|
package/test/globalSetup.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
4
|
-
|
|
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 {
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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/
|
|
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 =
|
|
110
|
-
export const acceptWorkspaceInvitationMock
|
|
111
|
-
|
|
112
|
-
export const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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: [
|
|
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
|
-
|
|
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(
|
|
208
|
-
|
|
209
|
-
|
|
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: {
|
|
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
|
-
|
|
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,
|