@stamhoofd/backend 2.144.0 → 2.145.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/package.json +19 -19
- package/src/crons/mollie-chargebacks.ts +31 -29
- package/src/crons/mollie-refunds.ts +61 -59
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +16 -5
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +210 -4
- package/src/endpoints/auth/CreateTokenEndpoint.ts +9 -20
- package/src/endpoints/auth/DeleteTokenEndpoint.test.ts +29 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +2 -1
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -11
- package/src/endpoints/auth/MFA.security.test.ts +9 -7
- package/src/endpoints/auth/MFA.test.ts +42 -15
- package/src/endpoints/auth/PatchUserEndpoint.ts +3 -2
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +5 -4
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +4 -3
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +6 -5
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +5 -4
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +5 -4
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +2 -1
- package/src/endpoints/global/files/UploadFile.test.ts +2 -1
- package/src/endpoints/global/files/UploadImage.test.ts +2 -1
- package/src/endpoints/global/files/upload-security.test.ts +3 -2
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +4 -3
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +26 -25
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +57 -56
- package/src/endpoints/global/members/SendMemberSecurityCodeEndpoint.test.ts +4 -3
- package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +6 -4
- package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +6 -6
- package/src/endpoints/global/platform/SignOutPlatformAdminsEndpoint.test.ts +9 -8
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +14 -13
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +24 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +4 -3
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +20 -19
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +0 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +23 -22
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +4 -4
- package/src/endpoints/organization/dashboard/payments/GetPaymentBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +7 -6
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +9 -8
- package/src/endpoints/organization/dashboard/settlements/SettlementsExportEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/settlements/SettlementsSyncEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +6 -5
- package/src/endpoints/organization/dashboard/users/SignOutOrganizationAdminsEndpoint.test.ts +16 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +2 -1
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -3
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -5
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +94 -8
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +18 -18
- package/src/helpers/Context.ts +13 -6
- package/src/helpers/ContextStorage.ts +5 -0
- package/src/helpers/StripeSettlementSync.ts +10 -7
- package/src/helpers/TwoFactorHelper.ts +21 -22
- package/src/services/AdminSessionService.ts +5 -3
- package/src/services/InvoiceService.ts +2 -2
- package/src/services/MollieService.ts +168 -126
- package/src/services/PaymentService.ts +2 -2
- package/src/services/SSOService.test.ts +25 -1
- package/src/services/SSOService.ts +17 -6
- package/src/services/SessionService.test.ts +319 -0
- package/src/services/SessionService.ts +357 -0
- package/src/services/WebshopCrowdfundingService.test.ts +4 -3
- package/src/sql-filters/registrations.ts +59 -1
- package/tests/actions/patchUserMember.ts +2 -1
- package/tests/e2e/api-rate-limits.test.ts +2 -1
- package/tests/e2e/bundle-discounts.test.ts +4 -3
- package/tests/e2e/charge-members.test.ts +6 -5
- package/tests/e2e/documents.test.ts +4 -3
- package/tests/e2e/private-files.test.ts +9 -8
- package/tests/e2e/register.test.ts +4 -3
- package/tests/e2e/stock.test.ts +4 -3
- package/tests/e2e/tickets.test.ts +4 -3
- package/tests/helpers/ExportSlice.ts +2 -1
- package/tests/helpers/MollieMocker.ts +85 -23
- package/tests/init/initAdmin.ts +2 -1
- package/tests/init/initPlatformAdmin.ts +2 -1
- package/src/helpers/ViesHelper.test.ts +0 -401
- package/src/helpers/ViesHelper.ts +0 -222
- package/src/services/PeppolDirectoryService.test.ts +0 -139
- package/src/services/PeppolDirectoryService.ts +0 -111
|
@@ -3,13 +3,14 @@ import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { EmailVerificationCode, MFATOTP, MFAToken, PasswordToken, Platform, Token, User, WebauthnCredential } from '@stamhoofd/models';
|
|
5
5
|
import type { ChallengeGrantStruct, MFAGrantStruct, PasswordGrantStruct, PasswordTokenGrantStruct, RefreshTokenGrantStruct, RequestChallengeGrantStruct } from '@stamhoofd/structures';
|
|
6
|
-
import { CreateTokenStruct, LoginMethod, MFAMethodType, SignupResponse, Token as TokenStruct } from '@stamhoofd/structures';
|
|
6
|
+
import { CreateTokenStruct, LoginMethod, MFAMethodType, SessionLoginMethod, SignupResponse, Token as TokenStruct } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../helpers/Context.js';
|
|
9
9
|
import { RecoveryCodeHelper } from '../../helpers/RecoveryCodeHelper.js';
|
|
10
10
|
import { TOTPHelper } from '../../helpers/TOTPHelper.js';
|
|
11
11
|
import { mfaVerificationRateLimiter, TwoFactorHelper } from '../../helpers/TwoFactorHelper.js';
|
|
12
12
|
import { WebauthnHelper } from '../../helpers/WebauthnHelper.js';
|
|
13
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
13
14
|
import { VerificationCodeService } from '../../services/VerificationCodeService.js';
|
|
14
15
|
|
|
15
16
|
type Params = Record<string, never>;
|
|
@@ -45,7 +46,7 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
45
46
|
|
|
46
47
|
switch (request.body.grantType) {
|
|
47
48
|
case 'refresh_token': {
|
|
48
|
-
const oldToken = await
|
|
49
|
+
const oldToken = await SessionService.getByRefreshToken(request.body.refreshToken);
|
|
49
50
|
if (!oldToken) {
|
|
50
51
|
throw new SimpleError({
|
|
51
52
|
code: 'invalid_refresh_token',
|
|
@@ -63,19 +64,7 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
const token = await Token.createToken(oldToken.user);
|
|
68
|
-
|
|
69
|
-
// In the rare event our response doesn't reach the client anymore, we don't want the client to sign out...
|
|
70
|
-
// So we allow a small rotation overlap period
|
|
71
|
-
const leeway = 60 * 1000;
|
|
72
|
-
oldToken.refreshTokenValidUntil = new Date(Math.min(oldToken.refreshTokenValidUntil.getTime(), Date.now() + leeway));
|
|
73
|
-
|
|
74
|
-
// Invalidate the corresponding access token
|
|
75
|
-
oldToken.accessTokenValidUntil = new Date(Date.now() - 60 * 60 * 1000);
|
|
76
|
-
|
|
77
|
-
// Do not delete the old one, only expire it fast so it will get deleted in the future
|
|
78
|
-
await oldToken.save();
|
|
67
|
+
const token = await SessionService.rotateSession(oldToken);
|
|
79
68
|
|
|
80
69
|
if (!token) {
|
|
81
70
|
throw new SimpleError({
|
|
@@ -150,9 +139,9 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
150
139
|
// Second factor / forced enrollment: block the session until the user
|
|
151
140
|
// completes (or first sets up) a second factor. Shared with every other
|
|
152
141
|
// grant that mints a session from a single primary credential.
|
|
153
|
-
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod:
|
|
142
|
+
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod: SessionLoginMethod.Password, i18n: request.i18n });
|
|
154
143
|
|
|
155
|
-
const token = await
|
|
144
|
+
const token = await SessionService.createSession(user, { loginMethod: SessionLoginMethod.Password, authenticatedAt: new Date() });
|
|
156
145
|
|
|
157
146
|
if (!token) {
|
|
158
147
|
throw new SimpleError({
|
|
@@ -264,7 +253,7 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
264
253
|
});
|
|
265
254
|
}
|
|
266
255
|
|
|
267
|
-
const token = await
|
|
256
|
+
const token = await SessionService.createSession(user, { loginMethod: mfaToken.loginMethod, authenticatedAt: new Date() });
|
|
268
257
|
await user.markActive();
|
|
269
258
|
|
|
270
259
|
const st = new TokenStruct(token);
|
|
@@ -314,10 +303,10 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
314
303
|
//
|
|
315
304
|
// The link was emailed to the account, so this is also how a long-inactive
|
|
316
305
|
// admin confirms their email address and gets to enroll after all.
|
|
317
|
-
await TwoFactorHelper.assertSecondFactorOrThrow(passwordToken.user, organization, request.request.getVersion(), { loginMethod:
|
|
306
|
+
await TwoFactorHelper.assertSecondFactorOrThrow(passwordToken.user, organization, request.request.getVersion(), { loginMethod: SessionLoginMethod.Email, i18n: request.i18n, allowTemporarySession: true });
|
|
318
307
|
|
|
319
308
|
// Important to create a new token before adjusting the old token
|
|
320
|
-
const token = await
|
|
309
|
+
const token = await SessionService.createSession(passwordToken.user, { loginMethod: SessionLoginMethod.Email, authenticatedAt: new Date() });
|
|
321
310
|
|
|
322
311
|
// TODO: make token short lived until renewal
|
|
323
312
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { OrganizationFactory, Token, UserFactory, UserSession } from '@stamhoofd/models';
|
|
3
|
+
|
|
4
|
+
import { testServer } from '../../../tests/helpers/TestServer.js';
|
|
5
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
6
|
+
import { DeleteTokenEndpoint } from './DeleteTokenEndpoint.js';
|
|
7
|
+
|
|
8
|
+
describe('Endpoint.DeleteToken', () => {
|
|
9
|
+
const endpoint = new DeleteTokenEndpoint();
|
|
10
|
+
|
|
11
|
+
test('ends the session and deletes every token in it', async () => {
|
|
12
|
+
const organization = await new OrganizationFactory({}).create();
|
|
13
|
+
const user = await new UserFactory({ organization }).create();
|
|
14
|
+
const original = await SessionService.createSession(user);
|
|
15
|
+
const renewed = await SessionService.rotateSession(original);
|
|
16
|
+
const otherSession = await SessionService.createSession(user);
|
|
17
|
+
|
|
18
|
+
const request = Request.buildJson('DELETE', '/oauth/token', organization.getApiHost());
|
|
19
|
+
request.headers.authorization = 'Bearer ' + original.accessToken;
|
|
20
|
+
await testServer.test(endpoint, request);
|
|
21
|
+
|
|
22
|
+
expect(await UserSession.getByID(original.sessionId)).toBeUndefined();
|
|
23
|
+
expect(await Token.getByAccessToken(original.accessToken, true)).toBeUndefined();
|
|
24
|
+
expect(await Token.getByAccessToken(renewed.accessToken, true)).toBeUndefined();
|
|
25
|
+
expect(await UserSession.getByID(otherSession.sessionId)).toBeDefined();
|
|
26
|
+
expect(await Token.getByAccessToken(otherSession.accessToken, true)).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
});
|
|
@@ -2,6 +2,7 @@ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
|
2
2
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
|
|
4
4
|
import { Context } from '../../helpers/Context.js';
|
|
5
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
5
6
|
|
|
6
7
|
type Params = Record<string, never>;
|
|
7
8
|
type Query = undefined;
|
|
@@ -25,7 +26,7 @@ export class DeleteTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
25
26
|
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
26
27
|
await Context.setOptionalOrganizationScope();
|
|
27
28
|
const { token } = await Context.authenticate({ allowWithoutAccount: true });
|
|
28
|
-
await
|
|
29
|
+
await SessionService.endSession(token);
|
|
29
30
|
|
|
30
31
|
return new Response(undefined);
|
|
31
32
|
}
|
|
@@ -3,6 +3,7 @@ import { MemberFactory, OrganizationFactory, Token, UserFactory } from '@stamhoo
|
|
|
3
3
|
|
|
4
4
|
import { MFATestHelper } from '../../../tests/helpers/MFATestHelper.js';
|
|
5
5
|
import { testServer } from '../../../tests/helpers/TestServer.js';
|
|
6
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
6
7
|
import { GetUserEndpoint } from './GetUserEndpoint.js';
|
|
7
8
|
import { STExpect } from '@stamhoofd/test-utils';
|
|
8
9
|
|
|
@@ -13,7 +14,7 @@ describe('Endpoint.GetUser', () => {
|
|
|
13
14
|
test('Request user details when signed in', async () => {
|
|
14
15
|
const organization = await new OrganizationFactory({}).create();
|
|
15
16
|
const user = await new UserFactory({ organization }).create();
|
|
16
|
-
const token = await
|
|
17
|
+
const token = await SessionService.createSession(user);
|
|
17
18
|
|
|
18
19
|
const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
19
20
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
@@ -23,13 +24,32 @@ describe('Endpoint.GetUser', () => {
|
|
|
23
24
|
expect(response.body.id).toEqual(user.id);
|
|
24
25
|
});
|
|
25
26
|
|
|
27
|
+
test('using a renewed token retires the previous active token', async () => {
|
|
28
|
+
const organization = await new OrganizationFactory({}).create();
|
|
29
|
+
const user = await new UserFactory({ organization }).create();
|
|
30
|
+
const original = await SessionService.createSession(user);
|
|
31
|
+
const replacement = await SessionService.rotateSession(original);
|
|
32
|
+
|
|
33
|
+
const originalRequest = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
34
|
+
originalRequest.headers.authorization = 'Bearer ' + original.accessToken;
|
|
35
|
+
await testServer.test(endpoint, originalRequest);
|
|
36
|
+
|
|
37
|
+
const replacementRequest = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
38
|
+
replacementRequest.headers.authorization = 'Bearer ' + replacement.accessToken;
|
|
39
|
+
await testServer.test(endpoint, replacementRequest);
|
|
40
|
+
|
|
41
|
+
await expect(testServer.test(endpoint, originalRequest)).rejects.toThrow(STExpect.simpleError({
|
|
42
|
+
code: 'invalid_access_token',
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
|
|
26
46
|
test('Two-factor authentication is reported on the user and on the accounts of a member', async () => {
|
|
27
47
|
const organization = await new OrganizationFactory({}).create();
|
|
28
48
|
const user = await new UserFactory({ organization }).create();
|
|
29
49
|
await new MemberFactory({ organization, user }).create();
|
|
30
50
|
await MFATestHelper.addConfirmedTOTP(user);
|
|
31
51
|
|
|
32
|
-
const token = await
|
|
52
|
+
const token = await SessionService.createSession(user);
|
|
33
53
|
const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
34
54
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
35
55
|
|
|
@@ -44,7 +64,7 @@ describe('Endpoint.GetUser', () => {
|
|
|
44
64
|
test('A user without a second factor is not reported as having two-factor authentication', async () => {
|
|
45
65
|
const organization = await new OrganizationFactory({}).create();
|
|
46
66
|
const user = await new UserFactory({ organization }).create();
|
|
47
|
-
const token = await
|
|
67
|
+
const token = await SessionService.createSession(user);
|
|
48
68
|
|
|
49
69
|
const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
50
70
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
@@ -62,7 +82,7 @@ describe('Endpoint.GetUser', () => {
|
|
|
62
82
|
test('Request user details with invalid token is not working', async () => {
|
|
63
83
|
const organization = await new OrganizationFactory({}).create();
|
|
64
84
|
const user = await new UserFactory({ organization }).create();
|
|
65
|
-
const token = await
|
|
85
|
+
const token = await SessionService.createSession(user);
|
|
66
86
|
|
|
67
87
|
const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
68
88
|
r.headers.authorization = 'Bearer ' + token.accessToken + 'd';
|
|
@@ -73,7 +93,9 @@ describe('Endpoint.GetUser', () => {
|
|
|
73
93
|
test('Request user details with expired token is not working', async () => {
|
|
74
94
|
const organization = await new OrganizationFactory({}).create();
|
|
75
95
|
const user = await new UserFactory({ organization }).create();
|
|
76
|
-
const token = await
|
|
96
|
+
const token = await SessionService.createSession(user);
|
|
97
|
+
token.accessTokenValidUntil = new Date(Date.now() - 1000);
|
|
98
|
+
await token.save();
|
|
77
99
|
|
|
78
100
|
const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
|
|
79
101
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
@@ -84,7 +106,7 @@ describe('Endpoint.GetUser', () => {
|
|
|
84
106
|
test('Can request details of an API user by token without knowing the organization scope', async () => {
|
|
85
107
|
const organization = await new OrganizationFactory({}).create();
|
|
86
108
|
const user = await new UserFactory({ organization, apiUser: true }).create();
|
|
87
|
-
const token = await
|
|
109
|
+
const token = await SessionService.createSession(user);
|
|
88
110
|
|
|
89
111
|
const r = Request.buildJson('GET', '/v1/user');
|
|
90
112
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
@@ -98,13 +120,13 @@ describe('Endpoint.GetUser', () => {
|
|
|
98
120
|
test('Cannot request details of an normal user by token without knowing the organization scope', async () => {
|
|
99
121
|
const organization = await new OrganizationFactory({}).create();
|
|
100
122
|
const user = await new UserFactory({ organization }).create();
|
|
101
|
-
const token = await
|
|
123
|
+
const token = await SessionService.createSession(user);
|
|
102
124
|
|
|
103
125
|
const r = Request.buildJson('GET', '/v1/user');
|
|
104
126
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
105
127
|
|
|
106
128
|
await expect(testServer.test(endpoint, r)).rejects.toThrow(STExpect.simpleError({
|
|
107
|
-
code: 'invalid_access_token'
|
|
129
|
+
code: 'invalid_access_token',
|
|
108
130
|
}));
|
|
109
131
|
});
|
|
110
132
|
|
|
@@ -112,14 +134,13 @@ describe('Endpoint.GetUser', () => {
|
|
|
112
134
|
const wrongOrganization = await new OrganizationFactory({}).create();
|
|
113
135
|
const organization = await new OrganizationFactory({}).create();
|
|
114
136
|
const user = await new UserFactory({ organization, apiUser: true }).create();
|
|
115
|
-
const token = await
|
|
137
|
+
const token = await SessionService.createSession(user);
|
|
116
138
|
|
|
117
139
|
const r = Request.buildJson('GET', '/v1/user', wrongOrganization.getApiHost());
|
|
118
140
|
r.headers.authorization = 'Bearer ' + token.accessToken;
|
|
119
141
|
|
|
120
142
|
await expect(testServer.test(endpoint, r)).rejects.toThrow(STExpect.simpleError({
|
|
121
|
-
code: 'invalid_access_token'
|
|
143
|
+
code: 'invalid_access_token',
|
|
122
144
|
}));
|
|
123
145
|
});
|
|
124
|
-
|
|
125
146
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { SimpleError } from '@simonbackx/simple-errors';
|
|
3
|
+
import { isSimpleError, isSimpleErrors } from '@simonbackx/simple-errors';
|
|
4
|
+
import type { Organization } from '@stamhoofd/models';
|
|
5
|
+
import { EmailVerificationCode, MFARecoveryCode, MFATOTP, MFAToken, OrganizationFactory, PasswordToken, Token, User, UserFactory, WebauthnCredential } from '@stamhoofd/models';
|
|
4
6
|
import { NewUser, PermissionLevel, Permissions, Token as TokenStruct } from '@stamhoofd/structures';
|
|
5
7
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
8
|
import crypto from 'crypto';
|
|
7
9
|
import { authenticator } from 'otplib';
|
|
10
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
8
11
|
|
|
9
12
|
import { MFATestHelper } from '../../../tests/helpers/MFATestHelper.js';
|
|
10
13
|
import { testServer } from '../../../tests/helpers/TestServer.js';
|
|
@@ -49,8 +52,7 @@ function firstError(e: unknown): SimpleError {
|
|
|
49
52
|
async function captureError(promise: Promise<unknown>): Promise<SimpleError> {
|
|
50
53
|
try {
|
|
51
54
|
await promise;
|
|
52
|
-
}
|
|
53
|
-
catch (e) {
|
|
55
|
+
} catch (e) {
|
|
54
56
|
return firstError(e);
|
|
55
57
|
}
|
|
56
58
|
throw new Error('Expected the request to be rejected, but it succeeded');
|
|
@@ -79,7 +81,7 @@ function withSetup(request: Request, setupToken: string) {
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
async function freshToken(user: User): Promise<Token> {
|
|
82
|
-
return await
|
|
84
|
+
return await SessionService.createSession(user, { authenticatedAt: new Date() });
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
async function addConfirmedTOTP(user: User): Promise<{ id: string; secret: string }> {
|
|
@@ -649,9 +651,9 @@ describe('MFA security', () => {
|
|
|
649
651
|
// separately and are not browser sessions.
|
|
650
652
|
const organization = await new OrganizationFactory({}).create();
|
|
651
653
|
const apiUser = await new UserFactory({ organization, apiUser: true }).create();
|
|
652
|
-
const apiToken = await
|
|
654
|
+
const apiToken = await SessionService.createSession(apiUser);
|
|
653
655
|
|
|
654
|
-
expect(await
|
|
656
|
+
expect(await SessionService.deleteOtherSessions({ userId: apiUser.id, keepAccessToken: null })).toBe(0);
|
|
655
657
|
expect(await Token.getByAccessToken(apiToken.accessToken, true)).toBeDefined();
|
|
656
658
|
});
|
|
657
659
|
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { Endpoint
|
|
2
|
-
import {
|
|
1
|
+
import type { Endpoint } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import type { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { isSimpleError, isSimpleErrors } from '@simonbackx/simple-errors';
|
|
3
5
|
import { EmailMocker } from '@stamhoofd/email';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
+
import type { Organization } from '@stamhoofd/models';
|
|
7
|
+
import { AuditLog, EmailTemplateFactory, EmailVerificationCode, MFARecoveryCode, MFATOTP, MFAToken, PasswordToken, Platform, Token, User, UserFactory, OrganizationFactory, UserSession, WebauthnChallenge, WebauthnCredential } from '@stamhoofd/models';
|
|
8
|
+
import { AuditLogReplacementType, AuditLogType, EmailTemplateType, MFAMethodType, PermissionLevel, Permissions, SessionLoginMethod, Token as TokenStruct } from '@stamhoofd/structures';
|
|
6
9
|
import { authenticator } from 'otplib';
|
|
7
10
|
import crypto from 'crypto';
|
|
11
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
8
12
|
|
|
9
13
|
import { MFATestHelper } from '../../../tests/helpers/MFATestHelper.js';
|
|
10
14
|
import { testServer } from '../../../tests/helpers/TestServer.js';
|
|
@@ -43,8 +47,7 @@ function firstError(e: unknown): SimpleError {
|
|
|
43
47
|
async function captureError(promise: Promise<unknown>): Promise<SimpleError> {
|
|
44
48
|
try {
|
|
45
49
|
await promise;
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
50
|
+
} catch (e) {
|
|
48
51
|
return firstError(e);
|
|
49
52
|
}
|
|
50
53
|
throw new Error('Expected the request to throw');
|
|
@@ -106,12 +109,12 @@ function bearer(request: Request, token: Token) {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
async function freshToken(user: User): Promise<Token> {
|
|
109
|
-
return await
|
|
112
|
+
return await SessionService.createSession(user, { authenticatedAt: new Date() });
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
async function staleToken(user: User): Promise<Token> {
|
|
113
116
|
// A token as produced by a refresh_token rotation (never authenticatedAt).
|
|
114
|
-
return await
|
|
117
|
+
return await SessionService.createSession(user);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
async function requireMfa(organization: Organization | null, email: string, password: string): Promise<{ token: string; methods: string[]; webauthnAuthenticationOptions: unknown }> {
|
|
@@ -668,8 +671,7 @@ describe('MFA', () => {
|
|
|
668
671
|
const err = await captureError(testServer.test(tokenEndpoint, passwordLogin(organization, user.email, password)));
|
|
669
672
|
expect(err.code).toBe('require_email_confirmation');
|
|
670
673
|
expect(send).toHaveBeenCalledOnce();
|
|
671
|
-
}
|
|
672
|
-
finally {
|
|
674
|
+
} finally {
|
|
673
675
|
send.mockRestore();
|
|
674
676
|
}
|
|
675
677
|
});
|
|
@@ -690,7 +692,7 @@ describe('MFA', () => {
|
|
|
690
692
|
// The identity provider authenticated the user, so there is nothing to confirm.
|
|
691
693
|
const { organization, user } = await inactiveAdmin();
|
|
692
694
|
|
|
693
|
-
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod:
|
|
695
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.SSO });
|
|
694
696
|
expect(requirement.type).toBe('setup');
|
|
695
697
|
});
|
|
696
698
|
});
|
|
@@ -1414,14 +1416,14 @@ describe('MFA', () => {
|
|
|
1414
1416
|
const { organization, user } = await adminOfOrgRequiringTwoFactor({ withPassword: false });
|
|
1415
1417
|
await addConfirmedTOTP(user);
|
|
1416
1418
|
|
|
1417
|
-
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod:
|
|
1419
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.SSO });
|
|
1418
1420
|
expect(requirement.type).toBe('challenge');
|
|
1419
1421
|
});
|
|
1420
1422
|
|
|
1421
1423
|
test('an SSO-only account is not forced to enroll: the provider is the second factor', async () => {
|
|
1422
1424
|
const { organization, user } = await adminOfOrgRequiringTwoFactor({ withPassword: false });
|
|
1423
1425
|
|
|
1424
|
-
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod:
|
|
1426
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.SSO });
|
|
1425
1427
|
expect(requirement.type).toBe('none');
|
|
1426
1428
|
});
|
|
1427
1429
|
|
|
@@ -1430,16 +1432,41 @@ describe('MFA', () => {
|
|
|
1430
1432
|
// requirement is not satisfied by signing in through SSO.
|
|
1431
1433
|
const { organization, user } = await adminOfOrgRequiringTwoFactor({ withPassword: true });
|
|
1432
1434
|
|
|
1433
|
-
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod:
|
|
1435
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.SSO });
|
|
1434
1436
|
expect(requirement.type).toBe('setup');
|
|
1435
1437
|
});
|
|
1436
1438
|
|
|
1437
1439
|
test('a password login is always forced to enroll', async () => {
|
|
1438
1440
|
const { organization, user } = await adminOfOrgRequiringTwoFactor({ withPassword: true });
|
|
1439
1441
|
|
|
1440
|
-
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod:
|
|
1442
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.Password });
|
|
1441
1443
|
expect(requirement.type).toBe('setup');
|
|
1442
1444
|
});
|
|
1445
|
+
|
|
1446
|
+
test('the session of an SSO login that passed a second factor is an SSO session', async () => {
|
|
1447
|
+
// The session is only created after the second factor, so the challenge has to
|
|
1448
|
+
// remember how the user got there: it decides how long the session may live.
|
|
1449
|
+
const { organization, user } = await adminOfOrgRequiringTwoFactor({ withPassword: false });
|
|
1450
|
+
const { secret } = await addConfirmedTOTP(user);
|
|
1451
|
+
|
|
1452
|
+
const requirement = await TwoFactorHelper.getSecondFactorRequirement(user, organization, { loginMethod: SessionLoginMethod.SSO });
|
|
1453
|
+
if (requirement.type !== 'challenge') {
|
|
1454
|
+
throw new Error('Expected a challenge');
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const response = await testServer.test(tokenEndpoint, mfaGrant(organization, {
|
|
1458
|
+
mfa_token: requirement.challenge.token,
|
|
1459
|
+
method: 'TOTP',
|
|
1460
|
+
code: authenticator.generate(secret),
|
|
1461
|
+
}));
|
|
1462
|
+
if (!(response.body instanceof TokenStruct)) {
|
|
1463
|
+
throw new Error('Expected TokenStruct');
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const token = await Token.getByAccessToken(response.body.accessToken);
|
|
1467
|
+
expect((await UserSession.getByID(token!.sessionId))!.loginMethod).toBe(SessionLoginMethod.SSO);
|
|
1468
|
+
expect(token!.refreshTokenValidUntil.getTime()).toBeLessThanOrEqual(Date.now() + 3 * 60 * 60 * 1000);
|
|
1469
|
+
});
|
|
1443
1470
|
});
|
|
1444
1471
|
|
|
1445
1472
|
// -----------------------------------------------------------------------
|
|
@@ -10,6 +10,7 @@ import { LoginMethod, NewUser, PermissionLevel, SignupResponse, UserPermissions
|
|
|
10
10
|
import { Context } from '../../helpers/Context.js';
|
|
11
11
|
import { MemberUserSyncer } from '../../helpers/MemberUserSyncer.js';
|
|
12
12
|
import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures.js';
|
|
13
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
13
14
|
import { VerificationCodeService } from '../../services/VerificationCodeService.js';
|
|
14
15
|
|
|
15
16
|
type Params = { id: string };
|
|
@@ -163,7 +164,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
|
|
|
163
164
|
// password changes
|
|
164
165
|
await editUser.changePassword(request.body.password);
|
|
165
166
|
await PasswordToken.clearFor(editUser.id);
|
|
166
|
-
await
|
|
167
|
+
await SessionService.clearFor({ userId: editUser.id, keepAccessToken: token.accessToken });
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
if (request.body.hasPassword === false) {
|
|
@@ -179,7 +180,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
|
|
|
179
180
|
}
|
|
180
181
|
editUser.password = null;
|
|
181
182
|
await PasswordToken.clearFor(editUser.id);
|
|
182
|
-
await
|
|
183
|
+
await SessionService.clearFor({ userId: editUser.id, keepAccessToken: token.accessToken });
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -2,12 +2,13 @@ import type { Decoder } from '@simonbackx/simple-encoding';
|
|
|
2
2
|
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
-
import { EmailVerificationCode,
|
|
6
|
-
import { Token as TokenStruct, VerifyEmailRequest } from '@stamhoofd/structures';
|
|
5
|
+
import { EmailVerificationCode, User } from '@stamhoofd/models';
|
|
6
|
+
import { SessionLoginMethod, Token as TokenStruct, VerifyEmailRequest } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../helpers/Context.js';
|
|
9
9
|
import { TwoFactorHelper } from '../../helpers/TwoFactorHelper.js';
|
|
10
10
|
import { BalanceItemService } from '../../services/BalanceItemService.js';
|
|
11
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
11
12
|
|
|
12
13
|
type Params = Record<string, never>;
|
|
13
14
|
type Query = undefined;
|
|
@@ -130,10 +131,10 @@ export class VerifyEmailEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
130
131
|
// the request already comes from a session of that same user (changing your email
|
|
131
132
|
// address while signed in): the second factor was checked when it was created.
|
|
132
133
|
if (authenticatedUser?.id !== user.id) {
|
|
133
|
-
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod:
|
|
134
|
+
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod: SessionLoginMethod.Email, i18n: request.i18n });
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const token = await
|
|
137
|
+
const token = await SessionService.createSession(user, { loginMethod: SessionLoginMethod.Email });
|
|
137
138
|
await user.markActive();
|
|
138
139
|
|
|
139
140
|
const st = new TokenStruct(token);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
3
|
-
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory,
|
|
2
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
3
|
+
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
4
4
|
import { EmailStatus, LimitedFilteredRequest, PermissionLevel, Permissions, Replacement } from '@stamhoofd/structures';
|
|
5
5
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
6
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
7
7
|
import { GetAdminEmailsEndpoint } from './GetAdminEmailsEndpoint.js';
|
|
8
8
|
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
9
10
|
|
|
10
11
|
const baseUrl = `/email`;
|
|
11
12
|
|
|
@@ -39,7 +40,7 @@ describe('Endpoint.getAdminEmails', () => {
|
|
|
39
40
|
user,
|
|
40
41
|
}).create();
|
|
41
42
|
|
|
42
|
-
userToken = await
|
|
43
|
+
userToken = await SessionService.createSession(user);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
const getAdminEmails = async (query: LimitedFilteredRequest = new LimitedFilteredRequest({ limit: 10 }), token: Token = userToken, testOrganization: Organization = organization) => {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
3
|
-
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory,
|
|
2
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
3
|
+
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
4
4
|
import { EmailContent, EmailStatus, LimitedFilteredRequest, Replacement } from '@stamhoofd/structures';
|
|
5
5
|
import { Language } from '@stamhoofd/types/Language';
|
|
6
6
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
7
7
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
8
8
|
import { GetUserEmailsEndpoint } from './GetUserEmailsEndpoint.js';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
10
11
|
|
|
11
12
|
const baseUrl = `/user/email`;
|
|
12
13
|
|
|
@@ -39,7 +40,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
39
40
|
user,
|
|
40
41
|
}).create();
|
|
41
42
|
|
|
42
|
-
userToken = await
|
|
43
|
+
userToken = await SessionService.createSession(user);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
afterEach(async () => {
|
|
@@ -726,7 +727,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
726
727
|
expect(response.body.results.find(e => e.id === email.id)).toBeUndefined();
|
|
727
728
|
|
|
728
729
|
// Test that is IS included if we request the same data via the 'otherUser' token
|
|
729
|
-
const otherUserToken = await
|
|
730
|
+
const otherUserToken = await SessionService.createSession(otherUser);
|
|
730
731
|
const responseForOtherUser = await getUserEmails(new LimitedFilteredRequest({ limit: 10 }), otherUserToken);
|
|
731
732
|
expect(responseForOtherUser.body.results.find(e => e.id === email.id)).toBeDefined();
|
|
732
733
|
});
|
|
@@ -805,7 +806,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
805
806
|
host: organization.getApiHost(),
|
|
806
807
|
query: new LimitedFilteredRequest({ limit: 10 }),
|
|
807
808
|
headers: {
|
|
808
|
-
|
|
809
|
+
authorization: 'Bearer ' + userToken.accessToken,
|
|
809
810
|
...(language ? { 'accept-language': language } : {}),
|
|
810
811
|
},
|
|
811
812
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { AutoEncoderPatchType } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { PatchMap } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
4
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
5
|
-
import { Email, EmailRecipient, GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationPeriodFactory,
|
|
4
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
5
|
+
import { Email, EmailRecipient, GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
6
6
|
import { AccessRight, EmailContent, EmailRecipientFilter, EmailRecipientSubfilter, EmailStatus, Email as EmailStruct, OrganizationEmail, Parent, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, UserPermissions, Version } from '@stamhoofd/structures';
|
|
7
7
|
import { Language } from '@stamhoofd/types/Language';
|
|
8
8
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
9
9
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
10
10
|
import { PatchEmailEndpoint } from './PatchEmailEndpoint.js';
|
|
11
11
|
import { EmailRecipientFilterType } from '@stamhoofd/structures/email/EmailRecipientFilterType.js';
|
|
12
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
12
13
|
|
|
13
14
|
// Import recipient loaders to initialize them
|
|
14
15
|
import { Formatter } from '@stamhoofd/utility';
|
|
@@ -82,7 +83,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
82
83
|
})
|
|
83
84
|
.create();
|
|
84
85
|
|
|
85
|
-
token = await
|
|
86
|
+
token = await SessionService.createSession(user);
|
|
86
87
|
|
|
87
88
|
user2 = await new UserFactory({
|
|
88
89
|
organization,
|
|
@@ -99,7 +100,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
99
100
|
})
|
|
100
101
|
.create();
|
|
101
102
|
|
|
102
|
-
token2 = await
|
|
103
|
+
token2 = await SessionService.createSession(user2);
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
test('Should throw for invalid senderId', async () => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
2
2
|
import { GetEmailRecipientsEndpoint } from './GetEmailRecipientsEndpoint.js';
|
|
3
3
|
import { AccessRight, EmailStatus, LimitedFilteredRequest, OrganizationEmail, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
|
|
4
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
5
|
-
import { Email, EmailRecipient, OrganizationFactory, RegistrationPeriodFactory,
|
|
4
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
5
|
+
import { Email, EmailRecipient, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
6
6
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
7
7
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
8
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
8
9
|
|
|
9
10
|
const baseUrl = `/email-recipients`;
|
|
10
11
|
|
|
@@ -57,7 +58,7 @@ describe('Endpoint.GetEmailRecipients', () => {
|
|
|
57
58
|
})
|
|
58
59
|
.create();
|
|
59
60
|
|
|
60
|
-
token = await
|
|
61
|
+
token = await SessionService.createSession(user);
|
|
61
62
|
|
|
62
63
|
user2 = await new UserFactory({
|
|
63
64
|
organization,
|
|
@@ -73,7 +74,7 @@ describe('Endpoint.GetEmailRecipients', () => {
|
|
|
73
74
|
})
|
|
74
75
|
.create();
|
|
75
76
|
|
|
76
|
-
token2 = await
|
|
77
|
+
token2 = await SessionService.createSession(user2);
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
test('It can request all email recipients if read permission for all senders', async () => {
|
|
@@ -10,6 +10,7 @@ import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
|
10
10
|
import '../../../audit-logs/init.js';
|
|
11
11
|
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
12
12
|
import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint.js';
|
|
13
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
13
14
|
|
|
14
15
|
const baseUrl = `/event-notifications`;
|
|
15
16
|
const endpoint = new PatchEventNotificationsEndpoint();
|
|
@@ -34,7 +35,7 @@ const TestRequest = {
|
|
|
34
35
|
}) {
|
|
35
36
|
const request = Request.buildJson('PATCH', baseUrl, options.organization?.getApiHost(), options.body);
|
|
36
37
|
if (options.user) {
|
|
37
|
-
const token = await
|
|
38
|
+
const token = await SessionService.createSession(options.user);
|
|
38
39
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
39
40
|
}
|
|
40
41
|
return await testServer.test(endpoint, request);
|