@stamhoofd/backend 2.81.0 → 2.82.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 (25) hide show
  1. package/package.json +10 -10
  2. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +139 -0
  3. package/src/endpoints/global/email/PatchEmailEndpoint.ts +28 -5
  4. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +16 -35
  5. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +8 -8
  6. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -1
  7. package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +68 -0
  8. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -7
  9. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
  10. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +1 -1
  11. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
  12. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +1 -1
  13. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +106 -0
  14. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +16 -3
  15. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +247 -0
  16. package/src/endpoints/{auth → organization/dashboard/users}/PatchApiUserEndpoint.ts +24 -5
  17. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +5 -0
  18. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -1
  19. package/src/helpers/AuthenticatedStructures.ts +8 -0
  20. package/src/helpers/CheckSettlements.ts +1 -1
  21. package/src/helpers/Context.ts +28 -12
  22. package/src/helpers/StripeHelper.ts +12 -1
  23. package/tests/e2e/api-rate-limits.test.ts +188 -0
  24. package/tests/helpers/StripeMocker.ts +7 -1
  25. /package/src/endpoints/global/platform/{GetPlatformEnpoint.ts → GetPlatformEndpoint.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -38,14 +38,14 @@
38
38
  "@simonbackx/simple-encoding": "2.22.0",
39
39
  "@simonbackx/simple-endpoints": "1.19.1",
40
40
  "@simonbackx/simple-logging": "^1.0.1",
41
- "@stamhoofd/backend-i18n": "2.81.0",
42
- "@stamhoofd/backend-middleware": "2.81.0",
43
- "@stamhoofd/email": "2.81.0",
44
- "@stamhoofd/models": "2.81.0",
45
- "@stamhoofd/queues": "2.81.0",
46
- "@stamhoofd/sql": "2.81.0",
47
- "@stamhoofd/structures": "2.81.0",
48
- "@stamhoofd/utility": "2.81.0",
41
+ "@stamhoofd/backend-i18n": "2.82.0",
42
+ "@stamhoofd/backend-middleware": "2.82.0",
43
+ "@stamhoofd/email": "2.82.0",
44
+ "@stamhoofd/models": "2.82.0",
45
+ "@stamhoofd/queues": "2.82.0",
46
+ "@stamhoofd/sql": "2.82.0",
47
+ "@stamhoofd/structures": "2.82.0",
48
+ "@stamhoofd/utility": "2.82.0",
49
49
  "archiver": "^7.0.1",
50
50
  "aws-sdk": "^2.885.0",
51
51
  "axios": "1.6.8",
@@ -65,5 +65,5 @@
65
65
  "publishConfig": {
66
66
  "access": "public"
67
67
  },
68
- "gitHead": "5a0faf4813aeb5c3d7216536278acb65ba2361e0"
68
+ "gitHead": "ce2a8fa52bc4881cc00f1c5d61c737cf19f3a62e"
69
69
  }
@@ -0,0 +1,139 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { Email, Organization, OrganizationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from "@stamhoofd/models";
3
+ import { EmailStatus, Email as EmailStruct, PermissionLevel, Permissions, Version } from "@stamhoofd/structures";
4
+ import { TestUtils } from "@stamhoofd/test-utils";
5
+ import { testServer } from "../../../../tests/helpers/TestServer";
6
+ import { PatchEmailEndpoint } from "./PatchEmailEndpoint";
7
+
8
+ const baseUrl = `/v${Version}/email`;
9
+
10
+ describe('Endpoint.PatchEmailEndpoint', () => {
11
+ const endpoint = new PatchEmailEndpoint();
12
+ let period: RegistrationPeriod;
13
+ let organization: Organization;
14
+ let token: Token;
15
+ let user: User;
16
+
17
+ const patchEmail = async (email: EmailStruct, token: Token, organization?: Organization) => {
18
+ const id = email.id;
19
+ const request = Request.buildJson('PATCH', `${baseUrl}/${id}`, organization?.getApiHost(), email);
20
+ request.headers.authorization = 'Bearer ' + token.accessToken;
21
+ return await testServer.test(endpoint, request);
22
+ };
23
+
24
+ beforeEach(async () => {
25
+ TestUtils.setEnvironment('userMode', 'platform');
26
+ });
27
+
28
+ beforeAll(async () => {
29
+ period = await new RegistrationPeriodFactory({
30
+ startDate: new Date(2023, 0, 1),
31
+ endDate: new Date(2023, 11, 31),
32
+ }).create();
33
+
34
+ organization = await new OrganizationFactory({ period })
35
+ .create();
36
+
37
+ user = await new UserFactory({
38
+ organization,
39
+ permissions: Permissions.create({
40
+ level: PermissionLevel.Read,
41
+ }),
42
+ })
43
+ .create();
44
+
45
+ token = await Token.createToken(user);
46
+ });
47
+
48
+ test('Should throw error if no unsubscribe button in email html', async () => {
49
+ const email = new Email();
50
+ email.subject = 'test subject';
51
+ email.status = EmailStatus.Draft;
52
+ email.text = 'test email {{unsubscribeUrl}}';
53
+ email.html = `<!DOCTYPE html>
54
+ <html>
55
+
56
+ <head>
57
+ <meta charset="utf-8" />
58
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
59
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
60
+ <title>test</title>
61
+ </head>
62
+
63
+ <body>
64
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
65
+ </body>
66
+
67
+ </html>`;
68
+ email.json = {
69
+ "content": [
70
+ {
71
+ "content": [
72
+ {
73
+ "text": "test email",
74
+ "type": "text"
75
+ }
76
+ ],
77
+ "type": "paragraph"
78
+ }
79
+ ],
80
+ "type": "doc"
81
+ };
82
+ email.userId = user.id;
83
+ email.organizationId = organization.id;
84
+
85
+ await email.save();
86
+
87
+ const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
88
+
89
+ await expect(async () => await patchEmail(body, token, organization))
90
+ .rejects
91
+ .toThrow('Missing unsubscribe button');
92
+ })
93
+
94
+ test('Should throw error if no unsubscribe button in email text', async () => {
95
+ const email = new Email();
96
+ email.subject = 'test subject';
97
+ email.status = EmailStatus.Draft;
98
+ email.text = 'test email';
99
+ email.html = `<!DOCTYPE html>
100
+ <html>
101
+
102
+ <head>
103
+ <meta charset="utf-8" />
104
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
105
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
106
+ <title>test</title>
107
+ </head>
108
+
109
+ <body>
110
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email {{unsubscribeUrl}}</p>
111
+ </body>
112
+
113
+ </html>`;
114
+ email.json = {
115
+ "content": [
116
+ {
117
+ "content": [
118
+ {
119
+ "text": "test email",
120
+ "type": "text"
121
+ }
122
+ ],
123
+ "type": "paragraph"
124
+ }
125
+ ],
126
+ "type": "doc"
127
+ };
128
+ email.userId = user.id;
129
+ email.organizationId = organization.id;
130
+
131
+ await email.save();
132
+
133
+ const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
134
+
135
+ await expect(async () => await patchEmail(body, token, organization))
136
+ .rejects
137
+ .toThrow('Missing unsubscribe button');
138
+ })
139
+ })
@@ -2,19 +2,15 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
2
2
  import { Email } from '@stamhoofd/models';
3
3
  import { EmailPreview, EmailStatus, Email as EmailStruct } from '@stamhoofd/structures';
4
4
 
5
+ import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
5
6
  import { SimpleError } from '@simonbackx/simple-errors';
6
7
  import { Context } from '../../../helpers/Context';
7
- import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
8
8
 
9
9
  type Params = { id: string };
10
10
  type Query = undefined;
11
11
  type Body = AutoEncoderPatchType<EmailStruct>;
12
12
  type ResponseBody = EmailPreview;
13
13
 
14
- /**
15
- * One endpoint to create, patch and delete groups. Usefull because on organization setup, we need to create multiple groups at once. Also, sometimes we need to link values and update multiple groups at once
16
- */
17
-
18
14
  export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
19
15
  bodyDecoder = EmailStruct.patchType() as Decoder<AutoEncoderPatchType<EmailStruct>>;
20
16
 
@@ -107,6 +103,33 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
107
103
 
108
104
  if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent) {
109
105
  model.throwIfNotReadyToSend();
106
+
107
+ const replacement = '{{unsubscribeUrl}}';
108
+
109
+ if (model.html) {
110
+ // Check email contains an unsubscribe button
111
+ if (!model.html.includes(replacement)) {
112
+ throw new SimpleError({
113
+ code: "missing_unsubscribe_button",
114
+ message: "Missing unsubscribe button",
115
+ human: "Je moet een ‘uitschrijven’-knop of link toevoegen onderaan je e-mail. Klik daarvoor onderaan op het ‘toverstaf’ icoontje en kies voor ‘Knop om uit te schrijven voor e-mails’. Dit is verplicht volgens de GDPR-wetgeving, maar het zorgt ook voor een betere e-mail reputatie omdat minder e-mails als spam worden gemarkeerd.",
116
+ field: "html"
117
+ })
118
+ }
119
+ }
120
+
121
+ if (model.text) {
122
+ // Check email contains an unsubscribe button
123
+ if (!model.text.includes(replacement)) {
124
+ throw new SimpleError({
125
+ code: "missing_unsubscribe_button",
126
+ message: "Missing unsubscribe button",
127
+ human: "Je moet een ‘uitschrijven’-knop of link toevoegen onderaan je e-mail. Klik daarvoor onderaan op het ‘toverstaf’ icoontje en kies voor ‘Knop om uit te schrijven voor e-mails’. Dit is verplicht volgens de GDPR-wetgeving, maar het zorgt ook voor een betere e-mail reputatie omdat minder e-mails als spam worden gemarkeerd.",
128
+ field: "text"
129
+ })
130
+ }
131
+ }
132
+
110
133
  model.send().catch(console.error);
111
134
  }
112
135
 
@@ -1,36 +1,17 @@
1
1
  import { PatchableArray, PatchMap, patchObject } from '@simonbackx/simple-encoding';
2
2
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
3
- import { EventNotificationFactory, EventFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, Token, User, UserFactory, EmailTemplateFactory, RecordCategoryFactory, RegistrationPeriodFactory, RecordAnswerFactory, EventNotification } from '@stamhoofd/models';
4
- import { AccessRight, BaseOrganization, EmailTemplateType, Event, EventNotificationStatus, EventNotification as EventNotificationStruct, PermissionLevel, Permissions, PermissionsResourceType, RecordAnswer, RecordType, ResourcePermissions } from '@stamhoofd/structures';
5
- import { TestUtils } from '@stamhoofd/test-utils';
6
- import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint';
7
- import { testServer } from '../../../../tests/helpers/TestServer';
8
3
  import { EmailMocker } from '@stamhoofd/email';
4
+ import { EmailTemplateFactory, EventFactory, EventNotification, EventNotificationFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, RecordAnswerFactory, RecordCategoryFactory, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
5
+ import { AccessRight, BaseOrganization, EmailTemplateType, Event, EventNotificationStatus, EventNotification as EventNotificationStruct, Permissions, PermissionsResourceType, RecordType, ResourcePermissions } from '@stamhoofd/structures';
6
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
7
+ import { testServer } from '../../../../tests/helpers/TestServer';
8
+ import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint';
9
9
 
10
10
  const baseUrl = `/event-notifications`;
11
11
  const endpoint = new PatchEventNotificationsEndpoint();
12
12
  type EndpointType = typeof endpoint;
13
13
  type Body = EndpointType extends Endpoint<any, any, infer B, any> ? B : never;
14
14
 
15
- const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
16
- const errorWithMessage = (message: string) => expect.objectContaining({ message }) as jest.Constructable;
17
- const simpleError = (data: {
18
- code?: string;
19
- message?: string;
20
- field?: string;
21
- }) => {
22
- const d = {
23
- code: data.code ?? expect.any(String),
24
- message: data.message ?? expect.any(String),
25
- field: data.field ?? expect.anything(),
26
- };
27
-
28
- if (!data.field) {
29
- delete d.field;
30
- }
31
- return expect.objectContaining(d) as jest.Constructable;
32
- };
33
-
34
15
  const minimumUserPermissions = Permissions.create({
35
16
  resources: new Map([
36
17
  [PermissionsResourceType.Groups, new Map([
@@ -169,7 +150,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
169
150
  );
170
151
 
171
152
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
172
- simpleError({ code: 'invalid_field', field: 'typeId' }),
153
+ SHExpect.simpleError({ code: 'invalid_field', field: 'typeId' }),
173
154
  );
174
155
  });
175
156
 
@@ -194,7 +175,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
194
175
  );
195
176
 
196
177
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
197
- simpleError({ code: 'invalid_field', field: 'events' }),
178
+ SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
198
179
  );
199
180
  });
200
181
 
@@ -228,7 +209,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
228
209
  }),
229
210
  );
230
211
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
231
- simpleError({ code: 'invalid_period', field: 'startDate' }),
212
+ SHExpect.simpleError({ code: 'invalid_period', field: 'startDate' }),
232
213
  );
233
214
  });
234
215
 
@@ -256,7 +237,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
256
237
  );
257
238
 
258
239
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
259
- simpleError({ code: 'invalid_field', field: 'events' }),
240
+ SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
260
241
  );
261
242
  });
262
243
 
@@ -416,7 +397,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
416
397
  );
417
398
 
418
399
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
419
- simpleError({ code: 'permission_denied' }),
400
+ SHExpect.simpleError({ code: 'permission_denied' }),
420
401
  );
421
402
  });
422
403
 
@@ -442,7 +423,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
442
423
  );
443
424
 
444
425
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
445
- simpleError({ code: 'permission_denied' }),
426
+ SHExpect.simpleError({ code: 'permission_denied' }),
446
427
  );
447
428
  });
448
429
 
@@ -468,7 +449,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
468
449
  );
469
450
 
470
451
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
471
- simpleError({ code: 'permission_denied' }),
452
+ SHExpect.simpleError({ code: 'permission_denied' }),
472
453
  );
473
454
  });
474
455
 
@@ -494,7 +475,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
494
475
  );
495
476
 
496
477
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
497
- simpleError({ code: 'permission_denied' }),
478
+ SHExpect.simpleError({ code: 'permission_denied' }),
498
479
  );
499
480
  });
500
481
 
@@ -520,7 +501,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
520
501
  );
521
502
 
522
503
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
523
- simpleError({ code: 'permission_denied' }),
504
+ SHExpect.simpleError({ code: 'permission_denied' }),
524
505
  );
525
506
  });
526
507
 
@@ -603,7 +584,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
603
584
  );
604
585
 
605
586
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
606
- simpleError({ code: 'permission_denied' }),
587
+ SHExpect.simpleError({ code: 'permission_denied' }),
607
588
  );
608
589
  });
609
590
 
@@ -991,7 +972,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
991
972
  );
992
973
 
993
974
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
994
- simpleError({ code: 'permission_denied' }),
975
+ SHExpect.simpleError({ code: 'permission_denied' }),
995
976
  );
996
977
  });
997
978
  });
@@ -3,7 +3,7 @@ import { PatchableArray, PatchableArrayAutoEncoder, PatchMap } from '@simonbackx
3
3
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
4
4
  import { GroupFactory, MemberFactory, OrganizationFactory, OrganizationTagFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
5
5
  import { Address, Country, EmergencyContact, MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, Permissions, PermissionsResourceType, RecordCategory, RecordSettings, RecordTextAnswer, ResourcePermissions, ReviewTime, ReviewTimes } from '@stamhoofd/structures';
6
- import { TestUtils } from '@stamhoofd/test-utils';
6
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
8
8
  import { PatchOrganizationMembersEndpoint } from './PatchOrganizationMembersEndpoint';
9
9
 
@@ -16,8 +16,6 @@ const firstName = 'John';
16
16
  const lastName = 'Doe';
17
17
  const birthDay = { year: 1993, month: 4, day: 5 };
18
18
 
19
- const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
20
-
21
19
  describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
22
20
  beforeEach(async () => {
23
21
  TestUtils.setEnvironment('userMode', 'platform');
@@ -59,7 +57,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
59
57
  request.headers.authorization = 'Bearer ' + token.accessToken;
60
58
  await expect(testServer.test(endpoint, request))
61
59
  .rejects
62
- .toThrow(errorWithCode('known_member_missing_rights'));
60
+ .toThrow(SHExpect.errorWithCode('known_member_missing_rights'));
63
61
  });
64
62
 
65
63
  test('The security code is not a requirement for members without additional data', async () => {
@@ -219,7 +217,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
219
217
 
220
218
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
221
219
  request.headers.authorization = 'Bearer ' + token.accessToken;
222
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('not_found'));
220
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('not_found'));
223
221
  });
224
222
 
225
223
  test('An admin can edit members registered in its own organization', async () => {
@@ -495,7 +493,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
495
493
 
496
494
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
497
495
  request.headers.authorization = 'Bearer ' + token.accessToken;
498
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
496
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
499
497
  });
500
498
 
501
499
  test('An admin without record category permission cannot set the records in that category', async () => {
@@ -564,7 +562,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
564
562
 
565
563
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
566
564
  request.headers.authorization = 'Bearer ' + token.accessToken;
567
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
565
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
568
566
  });
569
567
 
570
568
  test('An admin can set records of the platform', async () => {
@@ -1458,7 +1456,9 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
1458
1456
  await member3.refresh();
1459
1457
 
1460
1458
  // Check all parents equal
1461
- const expectedParent = parent3;
1459
+ const expectedParent = parent3.patch({
1460
+ createdAt: parent1.createdAt, // parent1 created at can be 1ms smaller, and oldest will be used
1461
+ });
1462
1462
 
1463
1463
  expect(member1.details.parents).toEqual([expectedParent]);
1464
1464
  expect(member2.details.parents).toEqual([expectedParent]);
@@ -52,6 +52,11 @@ export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, Response
52
52
 
53
53
  async handle(request: DecodedRequest<Params, Query, Body>) {
54
54
  console.log('Received Stripe Webhook', request.body.type);
55
+ const secret = request.body.account ? STAMHOOFD.STRIPE_CONNECT_ENDPOINT_SECRET : STAMHOOFD.STRIPE_ENDPOINT_SECRET;
56
+
57
+ if (!secret) {
58
+ throw StripeHelper.notConfiguredError;
59
+ }
55
60
 
56
61
  // Verify webhook signature and extract the event.
57
62
  // See https://stripe.com/docs/webhooks/signatures for more information.
@@ -66,7 +71,6 @@ export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, Response
66
71
  statusCode: 400,
67
72
  });
68
73
  }
69
- const secret = request.body.account ? STAMHOOFD.STRIPE_CONNECT_ENDPOINT_SECRET : STAMHOOFD.STRIPE_ENDPOINT_SECRET;
70
74
  event = await stripe.webhooks.constructEventAsync(await request.request.bodyPromise!, sig, secret);
71
75
  }
72
76
  catch (err) {
@@ -0,0 +1,68 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { Token, UserFactory } from '@stamhoofd/models';
3
+ import { PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
4
+
5
+ import { TestUtils } from '@stamhoofd/test-utils';
6
+ import { testServer } from '../../../../tests/helpers/TestServer';
7
+ import { GetPlatformEndpoint } from './GetPlatformEndpoint';
8
+
9
+ describe('Endpoint.GetPlatformEndpoint', () => {
10
+ const endpoint = new GetPlatformEndpoint();
11
+
12
+ const getPlatform = async (token?: Token) => {
13
+ const request = Request.buildJson('GET', `/v${Version}/platform`);
14
+ if (token) {
15
+ request.headers.authorization = 'Bearer ' + token.accessToken;
16
+ }
17
+ return await testServer.test(endpoint, request);
18
+ };
19
+
20
+ beforeEach(async () => {
21
+ TestUtils.setEnvironment('userMode', 'platform');
22
+ });
23
+
24
+ test('Should return platform without private config if not authenticated', async () => {
25
+ const platform = await getPlatform();
26
+ expect(platform.body.privateConfig).toBeNull();
27
+ });
28
+
29
+ test('Should return platform without private config if no platform access', async () => {
30
+ const user = await new UserFactory({
31
+ globalPermissions: Permissions.create({
32
+ level: PermissionLevel.None,
33
+ }),
34
+ })
35
+ .create();
36
+
37
+ const token = await Token.createToken(user);
38
+ const platform = await getPlatform(token);
39
+ expect(platform.body.privateConfig).toBeNull();
40
+ });
41
+
42
+ test('Should return platform with private config if authenticated and has platform access', async () => {
43
+ const user = await new UserFactory({
44
+ globalPermissions: Permissions.create({
45
+ level: PermissionLevel.Full,
46
+ }),
47
+ })
48
+ .create();
49
+
50
+ const token = await Token.createToken(user);
51
+ const platform = await getPlatform(token);
52
+ expect(platform.body.privateConfig).not.toBeNull();
53
+ });
54
+
55
+ test('Should throw if invalid token', async () => {
56
+ const user = await new UserFactory({
57
+ globalPermissions: Permissions.create({
58
+ level: PermissionLevel.Full,
59
+ }),
60
+ })
61
+ .create();
62
+
63
+ const token = await Token.createToken(user);
64
+ token.accessToken = 'invalid-token';
65
+ await token.save();
66
+ await expect(getPlatform(token)).rejects.toThrow('The access token is invalid');
67
+ });
68
+ });
@@ -5,7 +5,7 @@ import { MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, Organ
5
5
  import { testServer } from '../../../../tests/helpers/TestServer';
6
6
  import { PatchUserMembersEndpoint } from './PatchUserMembersEndpoint';
7
7
  import { Database } from '@simonbackx/simple-database';
8
- import { TestUtils } from '@stamhoofd/test-utils';
8
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
9
9
 
10
10
  const baseUrl = `/members`;
11
11
  const endpoint = new PatchUserMembersEndpoint();
@@ -16,8 +16,6 @@ const firstName = 'John';
16
16
  const lastName = 'Doe';
17
17
  const birthDay = { year: 1993, month: 4, day: 5 };
18
18
 
19
- const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
20
-
21
19
  describe('Endpoint.PatchUserMembersEndpoint', () => {
22
20
  beforeEach(async () => {
23
21
  TestUtils.setEnvironment('userMode', 'platform');
@@ -55,7 +53,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
55
53
  request.headers.authorization = 'Bearer ' + token.accessToken;
56
54
  await expect(testServer.test(endpoint, request))
57
55
  .rejects
58
- .toThrow(errorWithCode('known_member_missing_rights'));
56
+ .toThrow(SHExpect.errorWithCode('known_member_missing_rights'));
59
57
  });
60
58
 
61
59
  test('The security code is not a requirement for members without additional data', async () => {
@@ -280,7 +278,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
280
278
 
281
279
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
282
280
  request.headers.authorization = 'Bearer ' + token.accessToken;
283
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
281
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
284
282
  });
285
283
 
286
284
  test('A user can save answers of records of the platform', async () => {
@@ -391,7 +389,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
391
389
 
392
390
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
393
391
  request.headers.authorization = 'Bearer ' + token.accessToken;
394
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
392
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
395
393
  });
396
394
 
397
395
  test('A user can not save anwers to inexisting records', async () => {
@@ -437,7 +435,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
437
435
 
438
436
  const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
439
437
  request.headers.authorization = 'Bearer ' + token.accessToken;
440
- await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
438
+ await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
441
439
  });
442
440
  });
443
441
  });
@@ -26,8 +26,8 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
26
26
  queryDecoder = Query as Decoder<Query>;
27
27
  webshopDomains = [
28
28
  ...new Set([
29
- ...Object.values(STAMHOOFD.domains.webshop),
30
- ...Object.values(STAMHOOFD.domains.marketing),
29
+ ...Object.values(STAMHOOFD.domains.webshop ?? {}),
30
+ ...Object.values(STAMHOOFD.domains.marketing ?? {}),
31
31
  ]),
32
32
  ];
33
33
 
@@ -113,7 +113,7 @@ export class SetOrganizationDomainEndpoint extends Endpoint<Params, Query, Body,
113
113
  type: DNSRecordType.CNAME,
114
114
  name: organization.privateMeta.mailFromDomain + '.',
115
115
  // Use shops for mail domain, to allow reuse
116
- value: STAMHOOFD.domains.webshopCname + '.',
116
+ value: (STAMHOOFD.domains.webshopCname ?? STAMHOOFD.domains.registrationCname) + '.',
117
117
  }));
118
118
 
119
119
  if (STAMHOOFD.domains.registration && organization.privateMeta.pendingRegisterDomain) {
@@ -46,7 +46,7 @@ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, Respons
46
46
  });
47
47
  }
48
48
 
49
- const type = STAMHOOFD.STRIPE_CONNECT_METHOD;
49
+ const type = STAMHOOFD.STRIPE_CONNECT_METHOD ?? 'standard';
50
50
 
51
51
  const sharedData: Stripe.AccountCreateParams = {
52
52
  capabilities: {
@@ -42,7 +42,7 @@ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
42
42
  throw Context.auth.notFoundOrNoAccess('Account niet gevonden');
43
43
  }
44
44
 
45
- if (model.accountId === STAMHOOFD.STRIPE_ACCOUNT_ID) {
45
+ if (STAMHOOFD.STRIPE_ACCOUNT_ID && model.accountId === STAMHOOFD.STRIPE_ACCOUNT_ID) {
46
46
  throw new SimpleError({
47
47
  code: 'invalid_request',
48
48
  message: 'Je kan het hoofdaccount van het platform niet verwijderen.',