@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,188 @@
|
|
|
1
|
+
/* eslint-disable jest/no-conditional-expect */
|
|
2
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Organization, OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
4
|
+
|
|
5
|
+
import { PatchMap } from '@simonbackx/simple-encoding';
|
|
6
|
+
import { ApiUser, ApiUserRateLimits, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, UserMeta, UserPermissions } from '@stamhoofd/structures';
|
|
7
|
+
import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
8
|
+
import { CreateApiUserEndpoint } from '../../src/endpoints/organization/dashboard/users/CreateApiUserEndpoint';
|
|
9
|
+
import { testServer } from '../helpers/TestServer';
|
|
10
|
+
import { GetUserEndpoint } from '../../src/endpoints/auth/GetUserEndpoint';
|
|
11
|
+
|
|
12
|
+
describe('E2E.APIRateLimits', () => {
|
|
13
|
+
// Test endpoint
|
|
14
|
+
const createEndpoint = new CreateApiUserEndpoint();
|
|
15
|
+
const getUserEndpoint = new GetUserEndpoint();
|
|
16
|
+
let organization: Organization;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
20
|
+
organization = await new OrganizationFactory({}).create();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Note: we don't use a factory because this is an E2E test and
|
|
25
|
+
* we also want to check if the created tokens with the API are actually marked as API-keys and not normal users.
|
|
26
|
+
*/
|
|
27
|
+
async function createAPIToken(rateLimits: ApiUserRateLimits | null) {
|
|
28
|
+
const user = await new UserFactory({
|
|
29
|
+
globalPermissions: Permissions.create({
|
|
30
|
+
level: PermissionLevel.Full,
|
|
31
|
+
}),
|
|
32
|
+
}).create();
|
|
33
|
+
const token = await Token.createToken(user);
|
|
34
|
+
|
|
35
|
+
const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
|
|
36
|
+
permissions: UserPermissions.create({
|
|
37
|
+
organizationPermissions: new Map([
|
|
38
|
+
[organization.id, Permissions.create({ level: PermissionLevel.Read })],
|
|
39
|
+
]),
|
|
40
|
+
}),
|
|
41
|
+
meta: UserMeta.create({
|
|
42
|
+
rateLimits,
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
createRequest.headers.authorization = 'Bearer ' + token.accessToken;
|
|
46
|
+
const createResponse = await testServer.test(createEndpoint, createRequest);
|
|
47
|
+
|
|
48
|
+
expect(createResponse.body.meta?.rateLimits ?? undefined).toEqual(rateLimits ?? undefined);
|
|
49
|
+
|
|
50
|
+
return createResponse.body.token;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test('By default throws after 25 requests in less than 5s', async () => {
|
|
54
|
+
const token = await createAPIToken(null);
|
|
55
|
+
|
|
56
|
+
// Start firing
|
|
57
|
+
for (let i = 0; i < 30; i++) {
|
|
58
|
+
const request = Request.buildJson('GET', '/user', organization.getApiHost());
|
|
59
|
+
request.headers.authorization = 'Bearer ' + token;
|
|
60
|
+
const promise = testServer.test(getUserEndpoint, request);
|
|
61
|
+
|
|
62
|
+
if (i < 25) {
|
|
63
|
+
try {
|
|
64
|
+
await expect(promise).toResolve();
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
let error: any = null;
|
|
68
|
+
try {
|
|
69
|
+
await promise;
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
error = e;
|
|
73
|
+
}
|
|
74
|
+
throw new Error('The endpoint rejected at call ' + i + ' with error message ' + error?.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await expect(promise).rejects.toThrow(
|
|
79
|
+
SHExpect.simpleError({
|
|
80
|
+
code: 'rate_limit',
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('Normal rate limit throws after 25 requests in less than 5s', async () => {
|
|
88
|
+
const token = await createAPIToken(ApiUserRateLimits.Normal);
|
|
89
|
+
|
|
90
|
+
// Start firing
|
|
91
|
+
for (let i = 0; i < 30; i++) {
|
|
92
|
+
const request = Request.buildJson('GET', '/user', organization.getApiHost());
|
|
93
|
+
request.headers.authorization = 'Bearer ' + token;
|
|
94
|
+
const promise = testServer.test(getUserEndpoint, request);
|
|
95
|
+
|
|
96
|
+
if (i < 25) {
|
|
97
|
+
try {
|
|
98
|
+
await expect(promise).toResolve();
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
let error: any = null;
|
|
102
|
+
try {
|
|
103
|
+
await promise;
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
error = e;
|
|
107
|
+
}
|
|
108
|
+
throw new Error('The endpoint rejected at call ' + i + ' with error message ' + error?.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
await expect(promise).rejects.toThrow(
|
|
113
|
+
SHExpect.simpleError({
|
|
114
|
+
code: 'rate_limit',
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('Medium rate limits throw after 50 requests in less than 5s', async () => {
|
|
122
|
+
const token = await createAPIToken(ApiUserRateLimits.Medium);
|
|
123
|
+
|
|
124
|
+
// Start firing
|
|
125
|
+
for (let i = 0; i < 60; i++) {
|
|
126
|
+
const request = Request.buildJson('GET', '/user', organization.getApiHost());
|
|
127
|
+
request.headers.authorization = 'Bearer ' + token;
|
|
128
|
+
const promise = testServer.test(getUserEndpoint, request);
|
|
129
|
+
|
|
130
|
+
if (i < 50) {
|
|
131
|
+
try {
|
|
132
|
+
await expect(promise).toResolve();
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
let error: any = null;
|
|
136
|
+
try {
|
|
137
|
+
await promise;
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
error = e;
|
|
141
|
+
}
|
|
142
|
+
throw new Error('The endpoint rejected at call ' + i + ' with error message ' + error?.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
await expect(promise).rejects.toThrow(
|
|
147
|
+
SHExpect.simpleError({
|
|
148
|
+
code: 'rate_limit',
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('High rate limits throw after 125 requests in less than 5s', async () => {
|
|
156
|
+
const token = await createAPIToken(ApiUserRateLimits.High);
|
|
157
|
+
|
|
158
|
+
// Start firing
|
|
159
|
+
for (let i = 0; i < 140; i++) {
|
|
160
|
+
const request = Request.buildJson('GET', '/user', organization.getApiHost());
|
|
161
|
+
request.headers.authorization = 'Bearer ' + token;
|
|
162
|
+
const promise = testServer.test(getUserEndpoint, request);
|
|
163
|
+
|
|
164
|
+
if (i < 125) {
|
|
165
|
+
try {
|
|
166
|
+
await expect(promise).toResolve();
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
let error: any = null;
|
|
170
|
+
try {
|
|
171
|
+
await promise;
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
error = e;
|
|
175
|
+
}
|
|
176
|
+
throw new Error('The endpoint rejected at call ' + i + ' with error message ' + error?.message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await expect(promise).rejects.toThrow(
|
|
181
|
+
SHExpect.simpleError({
|
|
182
|
+
code: 'rate_limit',
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -165,9 +165,15 @@ export class StripeMocker {
|
|
|
165
165
|
const endpoint = new StripeWebookEndpoint();
|
|
166
166
|
|
|
167
167
|
const r = Request.buildJson('POST', `/stripe/webhooks`, undefined, payload);
|
|
168
|
+
const secret = payload.account ? STAMHOOFD.STRIPE_CONNECT_ENDPOINT_SECRET : STAMHOOFD.STRIPE_ENDPOINT_SECRET;
|
|
169
|
+
|
|
170
|
+
if (!secret) {
|
|
171
|
+
throw new Error('No stripe secret set in env');
|
|
172
|
+
}
|
|
173
|
+
|
|
168
174
|
r.headers['stripe-signature'] = stripe.webhooks.generateTestHeaderString({
|
|
169
175
|
payload: await r.body,
|
|
170
|
-
secret:
|
|
176
|
+
secret: secret,
|
|
171
177
|
});
|
|
172
178
|
await testServer.test(endpoint, r);
|
|
173
179
|
}
|
|
File without changes
|