@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
@@ -0,0 +1,106 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
+
4
+ import { testServer } from '../../../../../tests/helpers/TestServer';
5
+ import { PatchApiUserEndpoint } from './PatchApiUserEndpoint';
6
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
7
+ import { ApiUser, ApiUserRateLimits, PermissionLevel, Permissions, UserMeta, UserPermissions } from '@stamhoofd/structures';
8
+ import { CreateApiUserEndpoint } from './CreateApiUserEndpoint';
9
+
10
+ describe('Endpoint.CreateApiUserEndpoint', () => {
11
+ // Test endpoint
12
+ const endpoint = new CreateApiUserEndpoint();
13
+
14
+ beforeEach(() => {
15
+ TestUtils.setEnvironment('userMode', 'platform');
16
+ });
17
+
18
+ test('Only a platform admin can set the rate limits of a key', async () => {
19
+ const organization = await new OrganizationFactory({}).create();
20
+ const user = await new UserFactory({
21
+ globalPermissions: Permissions.create({
22
+ level: PermissionLevel.Full,
23
+ }),
24
+ }).create();
25
+ const token = await Token.createToken(user);
26
+
27
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
28
+ permissions: UserPermissions.create({
29
+ organizationPermissions: new Map([
30
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
31
+ ]),
32
+ }),
33
+ meta: UserMeta.create({
34
+ rateLimits: ApiUserRateLimits.High,
35
+ }),
36
+ }));
37
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
38
+ const response = await testServer.test(endpoint, createRequest);
39
+
40
+ expect(response.body).toBeDefined();
41
+ expect(response.body.meta?.rateLimits).toEqual(ApiUserRateLimits.High);
42
+ });
43
+
44
+ test('An organization admin cannot set rate limits', async () => {
45
+ const organization = await new OrganizationFactory({}).create();
46
+ const user = await new UserFactory({
47
+ permissions: Permissions.create({
48
+ level: PermissionLevel.Full,
49
+ }),
50
+ organization,
51
+ }).create();
52
+ const token = await Token.createToken(user);
53
+
54
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
55
+ permissions: UserPermissions.create({
56
+ organizationPermissions: new Map([
57
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
58
+ ]),
59
+ }),
60
+ meta: UserMeta.create({
61
+ rateLimits: ApiUserRateLimits.High,
62
+ }),
63
+ }));
64
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
65
+
66
+ await expect(testServer.test(endpoint, createRequest)).rejects.toThrow(SHExpect.simpleError({
67
+ code: 'permission_denied',
68
+ }));
69
+ });
70
+
71
+ test('An API-key cannot have permissions outside its organization', async () => {
72
+ const organization = await new OrganizationFactory({}).create();
73
+ const user = await new UserFactory({
74
+ permissions: Permissions.create({
75
+ level: PermissionLevel.Full,
76
+ }),
77
+ organization,
78
+ }).create();
79
+ const token = await Token.createToken(user);
80
+
81
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
82
+ permissions: UserPermissions.create({
83
+ organizationPermissions: new Map([
84
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
85
+ ['other', Permissions.create({ level: PermissionLevel.Full })],
86
+ ]),
87
+ globalPermissions: Permissions.create({
88
+ level: PermissionLevel.Full,
89
+ }),
90
+ }),
91
+ }));
92
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
93
+
94
+ const response = await testServer.test(endpoint, createRequest);
95
+
96
+ expect(response.body).toBeDefined();
97
+ expect(response.body.permissions).toEqual(
98
+ UserPermissions.create({
99
+ organizationPermissions: new Map([
100
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
101
+ ]),
102
+ globalPermissions: null,
103
+ }),
104
+ );
105
+ });
106
+ });
@@ -2,15 +2,15 @@ import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Token, User } from '@stamhoofd/models';
5
- import { ApiUser, ApiUserWithToken, UserPermissions } from '@stamhoofd/structures';
5
+ import { ApiUser, ApiUserWithToken, UserMeta, UserPermissions } from '@stamhoofd/structures';
6
6
 
7
7
  import { Context } from '../../../../helpers/Context';
8
8
  type Params = Record<string, never>;
9
9
  type Query = undefined;
10
10
  type Body = ApiUser;
11
- type ResponseBody = ApiUser;
11
+ type ResponseBody = ApiUserWithToken;
12
12
 
13
- export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
+ export class CreateApiUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
14
  bodyDecoder = ApiUser as Decoder<ApiUser>;
15
15
 
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
@@ -56,6 +56,19 @@ export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseB
56
56
  }
57
57
 
58
58
  admin.permissions = UserPermissions.limitedAdd(null, request.body.permissions, organization.id);
59
+
60
+ if (request.body.meta) {
61
+ const rateLimits = request.body.meta.rateLimits;
62
+ if (rateLimits) {
63
+ if (!Context.auth.hasPlatformFullAccess()) {
64
+ throw Context.auth.error($t('Je hebt geen rechten om de rate limits van API-keys te wijzigen'));
65
+ }
66
+
67
+ admin.meta = admin.meta ?? UserMeta.create({});
68
+ admin.meta.rateLimits = rateLimits;
69
+ }
70
+ }
71
+
59
72
  await admin.save();
60
73
 
61
74
  // Set id
@@ -0,0 +1,247 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
+
4
+ import { testServer } from '../../../../../tests/helpers/TestServer';
5
+ import { PatchApiUserEndpoint } from './PatchApiUserEndpoint';
6
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
7
+ import { ApiUser, ApiUserRateLimits, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, UserMeta, UserPermissions } from '@stamhoofd/structures';
8
+ import { CreateApiUserEndpoint } from './CreateApiUserEndpoint';
9
+ import { PatchMap } from '@simonbackx/simple-encoding';
10
+
11
+ describe('Endpoint.PatchApiUserEndpoint', () => {
12
+ // Test endpoint
13
+ const createEndpoint = new CreateApiUserEndpoint();
14
+ const endpoint = new PatchApiUserEndpoint();
15
+
16
+ beforeEach(() => {
17
+ TestUtils.setEnvironment('userMode', 'platform');
18
+ });
19
+
20
+ test('Only a platform admin can alter the rate limits of a key', async () => {
21
+ const organization = await new OrganizationFactory({}).create();
22
+ const user = await new UserFactory({
23
+ globalPermissions: Permissions.create({
24
+ level: PermissionLevel.Full,
25
+ }),
26
+ }).create();
27
+ const token = await Token.createToken(user);
28
+
29
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
30
+ permissions: UserPermissions.create({
31
+ organizationPermissions: new Map([
32
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
33
+ ]),
34
+ }),
35
+ }));
36
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
37
+ const createResponse = await testServer.test(createEndpoint, createRequest);
38
+
39
+ const apiUserId = createResponse.body.id;
40
+
41
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
42
+ id: apiUserId,
43
+ meta: UserMeta.patch({
44
+ rateLimits: ApiUserRateLimits.High,
45
+ }),
46
+ }));
47
+ r.headers.authorization = 'Bearer ' + token.accessToken;
48
+
49
+ const response = await testServer.test(endpoint, r);
50
+ expect(response.body).toBeDefined();
51
+ expect(response.body.id).toEqual(apiUserId);
52
+ expect(response.body.meta?.rateLimits).toEqual(ApiUserRateLimits.High);
53
+ });
54
+
55
+ test('An organization admin cannot alter rate limits', async () => {
56
+ const organization = await new OrganizationFactory({}).create();
57
+ const user = await new UserFactory({
58
+ permissions: Permissions.create({
59
+ level: PermissionLevel.Full,
60
+ }),
61
+ organization,
62
+ }).create();
63
+ const token = await Token.createToken(user);
64
+
65
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
66
+ permissions: UserPermissions.create({
67
+ organizationPermissions: new Map([
68
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
69
+ ]),
70
+ }),
71
+ }));
72
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
73
+ const createResponse = await testServer.test(createEndpoint, createRequest);
74
+
75
+ const apiUserId = createResponse.body.id;
76
+
77
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
78
+ id: apiUserId,
79
+ meta: UserMeta.patch({
80
+ rateLimits: ApiUserRateLimits.High,
81
+ }),
82
+ }));
83
+ r.headers.authorization = 'Bearer ' + token.accessToken;
84
+
85
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(SHExpect.simpleError({
86
+ code: 'permission_denied',
87
+ }));
88
+ });
89
+
90
+ test('You cannot grant an API-key permissions outside its organization using a put', async () => {
91
+ const organization = await new OrganizationFactory({}).create();
92
+ const user = await new UserFactory({
93
+ permissions: Permissions.create({
94
+ level: PermissionLevel.Full,
95
+ }),
96
+ organization,
97
+ }).create();
98
+ const token = await Token.createToken(user);
99
+
100
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
101
+ permissions: UserPermissions.create({
102
+ organizationPermissions: new Map([
103
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
104
+ ]),
105
+ }),
106
+ }));
107
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
108
+ const createResponse = await testServer.test(createEndpoint, createRequest);
109
+ const apiUserId = createResponse.body.id;
110
+
111
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
112
+ id: apiUserId,
113
+ permissions: UserPermissions.create({
114
+ organizationPermissions: new Map([
115
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
116
+ ['other', Permissions.create({ level: PermissionLevel.Full })],
117
+ ]),
118
+ globalPermissions: Permissions.create({
119
+ level: PermissionLevel.Full,
120
+ }),
121
+ }),
122
+ }));
123
+ r.headers.authorization = 'Bearer ' + token.accessToken;
124
+
125
+ const response = await testServer.test(endpoint, r);
126
+ expect(response.body).toBeDefined();
127
+ expect(response.body.id).toEqual(apiUserId);
128
+ expect(response.body.permissions).toEqual(
129
+ UserPermissions.create({
130
+ organizationPermissions: new Map([
131
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
132
+ ]),
133
+ globalPermissions: null,
134
+ }),
135
+ );
136
+ });
137
+
138
+ test('You cannot grant an API-key permissions outside its organization using a patch', async () => {
139
+ const organization = await new OrganizationFactory({}).create();
140
+ const user = await new UserFactory({
141
+ permissions: Permissions.create({
142
+ level: PermissionLevel.Full,
143
+ }),
144
+ organization,
145
+ }).create();
146
+ const token = await Token.createToken(user);
147
+
148
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
149
+ permissions: UserPermissions.create({
150
+ organizationPermissions: new Map([
151
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
152
+ ]),
153
+ }),
154
+ }));
155
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
156
+ const createResponse = await testServer.test(createEndpoint, createRequest);
157
+ const apiUserId = createResponse.body.id;
158
+
159
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
160
+ id: apiUserId,
161
+ permissions: UserPermissions.patch({
162
+ organizationPermissions: new PatchMap([
163
+ ['other', Permissions.create({ level: PermissionLevel.Full })],
164
+ ]),
165
+ globalPermissions: Permissions.create({
166
+ level: PermissionLevel.Full,
167
+ }),
168
+ }),
169
+ }));
170
+ r.headers.authorization = 'Bearer ' + token.accessToken;
171
+
172
+ const response = await testServer.test(endpoint, r);
173
+ expect(response.body).toBeDefined();
174
+ expect(response.body.id).toEqual(apiUserId);
175
+ expect(response.body.permissions).toEqual(
176
+ UserPermissions.create({
177
+ organizationPermissions: new Map([
178
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
179
+ ]),
180
+ globalPermissions: null,
181
+ }),
182
+ );
183
+ });
184
+
185
+ test('You can grant an API-key permissions using a patch', async () => {
186
+ const organization = await new OrganizationFactory({}).create();
187
+ const user = await new UserFactory({
188
+ permissions: Permissions.create({
189
+ level: PermissionLevel.Full,
190
+ }),
191
+ organization,
192
+ }).create();
193
+ const token = await Token.createToken(user);
194
+
195
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
196
+ permissions: UserPermissions.create({
197
+ organizationPermissions: new Map([
198
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
199
+ ]),
200
+ }),
201
+ }));
202
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
203
+ const createResponse = await testServer.test(createEndpoint, createRequest);
204
+ const apiUserId = createResponse.body.id;
205
+
206
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
207
+ id: apiUserId,
208
+ permissions: UserPermissions.patch({
209
+ organizationPermissions: new PatchMap([
210
+ [organization.id, Permissions.patch({
211
+ resources: new PatchMap([[
212
+ PermissionsResourceType.Groups,
213
+ new Map([
214
+ ['group-id', ResourcePermissions.create({
215
+ level: PermissionLevel.Full,
216
+ })],
217
+ ]),
218
+ ]]),
219
+ })],
220
+ ]),
221
+ }),
222
+ }));
223
+ r.headers.authorization = 'Bearer ' + token.accessToken;
224
+
225
+ const response = await testServer.test(endpoint, r);
226
+ expect(response.body).toBeDefined();
227
+ expect(response.body.id).toEqual(apiUserId);
228
+ expect(response.body.permissions).toEqual(
229
+ UserPermissions.create({
230
+ organizationPermissions: new Map([
231
+ [organization.id, Permissions.create({
232
+ level: PermissionLevel.Read,
233
+ resources: new Map([[
234
+ PermissionsResourceType.Groups,
235
+ new Map([
236
+ ['group-id', ResourcePermissions.create({
237
+ level: PermissionLevel.Full,
238
+ })],
239
+ ]),
240
+ ]]),
241
+ })],
242
+ ]),
243
+ globalPermissions: null,
244
+ }),
245
+ );
246
+ });
247
+ });
@@ -1,17 +1,16 @@
1
- import { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
1
+ import { AutoEncoderPatchType, Decoder, isPatch } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Token, User } from '@stamhoofd/models';
5
- import { ApiUser, PermissionLevel, UserPermissions } from '@stamhoofd/structures';
6
-
7
- import { Context } from '../../helpers/Context';
5
+ import { ApiUser, PermissionLevel, UserMeta, UserPermissions } from '@stamhoofd/structures';
6
+ import { Context } from '../../../../helpers/Context';
8
7
 
9
8
  type Params = { id: string };
10
9
  type Query = undefined;
11
10
  type Body = AutoEncoderPatchType<ApiUser>;
12
11
  type ResponseBody = ApiUser;
13
12
 
14
- export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
+ export class PatchApiUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
14
  bodyDecoder = ApiUser.patchType() as Decoder<AutoEncoderPatchType<ApiUser>>;
16
15
 
17
16
  protected doesMatch(request: Request): [true, Params] | [false] {
@@ -85,6 +84,26 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
85
84
  }
86
85
  }
87
86
 
87
+ if (request.body.meta) {
88
+ if (!isPatch(request.body.meta)) {
89
+ throw new SimpleError({
90
+ code: 'invalid_request',
91
+ message: 'Invalid request: meta is not a patch',
92
+ statusCode: 400,
93
+ });
94
+ }
95
+
96
+ const rateLimits = request.body.meta.rateLimits;
97
+ if (rateLimits && rateLimits !== editUser.meta?.rateLimits) {
98
+ if (!Context.auth.hasPlatformFullAccess()) {
99
+ throw Context.auth.error($t('Je hebt geen rechten om de rate limits van API-keys te wijzigen'));
100
+ }
101
+
102
+ editUser.meta = editUser.meta ?? UserMeta.create({});
103
+ editUser.meta.rateLimits = rateLimits;
104
+ }
105
+ }
106
+
88
107
  await editUser.save();
89
108
 
90
109
  return new Response(await Token.getAPIUserWithToken(editUser));
@@ -2,6 +2,7 @@ import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
3
  import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
4
4
 
5
+ import { TestUtils } from '@stamhoofd/test-utils';
5
6
  import { testServer } from '../../../../../tests/helpers/TestServer';
6
7
  import { GetOrganizationEndpoint } from './GetOrganizationEndpoint';
7
8
 
@@ -9,6 +10,10 @@ describe('Endpoint.GetOrganization', () => {
9
10
  // Test endpoint
10
11
  const endpoint = new GetOrganizationEndpoint();
11
12
 
13
+ beforeEach(async () => {
14
+ TestUtils.setEnvironment('userMode', 'platform');
15
+ });
16
+
12
17
  test('Get organization as signed in user', async () => {
13
18
  const organization = await new OrganizationFactory({}).create();
14
19
  const user = await new UserFactory({ organization }).create();
@@ -1,7 +1,8 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory, WebshopFactory } from '@stamhoofd/models';
3
- import { PermissionLevel, Permissions, PrivateWebshop, Webshop as WebshopStruct } from '@stamhoofd/structures';
3
+ import { PermissionLevel, Permissions } from '@stamhoofd/structures';
4
4
 
5
+ import { TestUtils } from '@stamhoofd/test-utils';
5
6
  import { testServer } from '../../../../tests/helpers/TestServer';
6
7
  import { GetWebshopEndpoint } from './GetWebshopEndpoint';
7
8
 
@@ -9,6 +10,10 @@ describe('Endpoint.GetWebshop', () => {
9
10
  // Test endpoint
10
11
  const endpoint = new GetWebshopEndpoint();
11
12
 
13
+ beforeEach(async () => {
14
+ TestUtils.setEnvironment('userMode', 'platform');
15
+ });
16
+
12
17
  test('Get webshop as signed in user', async () => {
13
18
  const organization = await new OrganizationFactory({}).create();
14
19
  const user = await new UserFactory({ organization }).create();
@@ -423,6 +423,14 @@ export class AuthenticatedStructures {
423
423
  }
424
424
  }
425
425
 
426
+ if (includeContextOrganization && STAMHOOFD.singleOrganization && !Context.auth.organization) {
427
+ const found = organizations.get(STAMHOOFD.singleOrganization);
428
+ if (!found) {
429
+ const organization = await Context.auth.getOrganization(STAMHOOFD.singleOrganization);
430
+ organizations.set(organization.id, organization);
431
+ }
432
+ }
433
+
426
434
  const memberBlobs: MemberWithRegistrationsBlob[] = [];
427
435
  for (const member of members) {
428
436
  for (const registration of member.registrations) {
@@ -23,7 +23,7 @@ type MolliePaymentJSON = {
23
23
  let lastSettlementCheck: Date | null = null;
24
24
 
25
25
  export async function checkAllStripePayouts(checkAll = false) {
26
- if (STAMHOOFD.environment !== 'production') {
26
+ if (STAMHOOFD.environment !== 'production' || !STAMHOOFD.STRIPE_SECRET_KEY) {
27
27
  console.log('Skip settlement check');
28
28
  return;
29
29
  }
@@ -4,29 +4,42 @@ import { I18n } from '@stamhoofd/backend-i18n';
4
4
  import { Organization, Platform, RateLimiter, Token, User } from '@stamhoofd/models';
5
5
  import { AsyncLocalStorage } from 'async_hooks';
6
6
 
7
+ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
8
+ import { ApiUserRateLimits } from '@stamhoofd/structures';
7
9
  import { AdminPermissionChecker } from './AdminPermissionChecker';
8
- import { AutoEncoder, field, Decoder, StringDecoder } from '@simonbackx/simple-encoding';
9
10
 
10
11
  export const apiUserRateLimiter = new RateLimiter({
11
12
  limits: [
12
13
  {
13
- // Block heavy bursts (5req/s for 5s)
14
- limit: 25,
14
+ limit: {
15
+ '': 25, // (5req/s for 5s)
16
+ [ApiUserRateLimits.Medium]: 10 * 5, // (10req/s for 5s)
17
+ [ApiUserRateLimits.High]: 25 * 5, // (100req/s for 5s)
18
+ },
15
19
  duration: 5 * 1000,
16
20
  },
17
21
  {
18
- // max 1req/s during 150s
19
- limit: 150,
20
- duration: 150 * 1000,
22
+ limit: {
23
+ '': 120, // max 1req/s during 150s
24
+ [ApiUserRateLimits.Medium]: 240, // (2req/s for 150s)
25
+ [ApiUserRateLimits.High]: 480, // (4req/s for 150s)
26
+ },
27
+ duration: 120 * 1000,
21
28
  },
22
29
  {
23
- // 1000 requests per hour
24
- limit: 1000,
30
+ limit: {
31
+ '': 1000, // ± 0.27 request/s sustained for an hour = 3.6s between each request
32
+ [ApiUserRateLimits.Medium]: 2000, // ± 0.56 request/s sustained for an hour
33
+ [ApiUserRateLimits.High]: 4000, // ± 1.11 request/s sustained for an hour
34
+ },
25
35
  duration: 60 * 1000 * 60,
26
36
  },
27
37
  {
28
- // 2000 requests per day
29
- limit: 2000,
38
+ limit: {
39
+ '': 2_000, // max 2000 requests per day
40
+ [ApiUserRateLimits.Medium]: 14_400, // max 4000 requests per day
41
+ [ApiUserRateLimits.High]: 18_000, // max 10 requests per minute, sustained for a full day
42
+ },
30
43
  duration: 24 * 60 * 1000 * 60,
31
44
  },
32
45
  ],
@@ -169,7 +182,10 @@ export class ContextInstance {
169
182
  return await this.authenticate({ allowWithoutAccount });
170
183
  }
171
184
  catch (e) {
172
- return {};
185
+ if (e.code === 'not_authenticated') {
186
+ return {};
187
+ }
188
+ throw e;
173
189
  }
174
190
  }
175
191
 
@@ -235,7 +251,7 @@ export class ContextInstance {
235
251
 
236
252
  // Rate limits for api users
237
253
  if (token.user.isApiUser) {
238
- apiUserRateLimiter.track(this.organization?.id ?? token.user.id);
254
+ apiUserRateLimiter.track(this.organization?.id ?? token.user.id, 1, token.user.meta?.rateLimits ?? undefined);
239
255
  }
240
256
 
241
257
  const user = token.user;
@@ -6,7 +6,18 @@ import { Formatter } from '@stamhoofd/utility';
6
6
  import Stripe from 'stripe';
7
7
 
8
8
  export class StripeHelper {
9
+ static get notConfiguredError() {
10
+ return new SimpleError({
11
+ code: 'not_configured',
12
+ message: 'Stripe is not yet configured for this platform',
13
+ human: $t('Stripe is nog niet geconfigureerd voor dit platform'),
14
+ });
15
+ }
16
+
9
17
  static getInstance(accountId: string | null = null) {
18
+ if (!STAMHOOFD.STRIPE_SECRET_KEY) {
19
+ throw this.notConfiguredError;
20
+ }
10
21
  return new Stripe(STAMHOOFD.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20', typescript: true, maxNetworkRetries: 0, timeout: 10000, stripeAccount: accountId ?? undefined });
11
22
  }
12
23
 
@@ -81,7 +92,7 @@ export class StripeHelper {
81
92
  }
82
93
 
83
94
  static async getStatus(payment: Payment, cancel = false, testMode = false): Promise<PaymentStatus> {
84
- if (testMode && !STAMHOOFD.STRIPE_SECRET_KEY.startsWith('sk_test_')) {
95
+ if (testMode && !STAMHOOFD.STRIPE_SECRET_KEY?.startsWith('sk_test_')) {
85
96
  // Do not query anything
86
97
  return payment.status;
87
98
  }