@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.
- package/package.json +10 -10
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +139 -0
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +28 -5
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +16 -35
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +8 -8
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -1
- package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +68 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -7
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +106 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +16 -3
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +247 -0
- package/src/endpoints/{auth → organization/dashboard/users}/PatchApiUserEndpoint.ts +24 -5
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +5 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -1
- package/src/helpers/AuthenticatedStructures.ts +8 -0
- package/src/helpers/CheckSettlements.ts +1 -1
- package/src/helpers/Context.ts +28 -12
- package/src/helpers/StripeHelper.ts +12 -1
- package/tests/e2e/api-rate-limits.test.ts +188 -0
- package/tests/helpers/StripeMocker.ts +7 -1
- /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 =
|
|
11
|
+
type ResponseBody = ApiUserWithToken;
|
|
12
12
|
|
|
13
|
-
export class
|
|
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
|
|
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
|
|
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
|
}
|
package/src/helpers/Context.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
95
|
+
if (testMode && !STAMHOOFD.STRIPE_SECRET_KEY?.startsWith('sk_test_')) {
|
|
85
96
|
// Do not query anything
|
|
86
97
|
return payment.status;
|
|
87
98
|
}
|