@stamhoofd/backend 2.143.1 → 2.145.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -19
- package/src/crons/fake-settlements.test.ts +13 -11
- package/src/crons/fake-settlements.ts +22 -38
- package/src/crons/mollie-chargebacks.ts +31 -29
- package/src/crons/mollie-refunds.ts +61 -59
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +16 -5
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +210 -4
- package/src/endpoints/auth/CreateTokenEndpoint.ts +9 -20
- package/src/endpoints/auth/DeleteTokenEndpoint.test.ts +29 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +2 -1
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -11
- package/src/endpoints/auth/MFA.security.test.ts +9 -7
- package/src/endpoints/auth/MFA.test.ts +42 -15
- package/src/endpoints/auth/PatchUserEndpoint.ts +3 -2
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +5 -4
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +4 -3
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +6 -5
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +5 -4
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +5 -4
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +2 -1
- package/src/endpoints/global/files/UploadFile.test.ts +2 -1
- package/src/endpoints/global/files/UploadFile.ts +53 -25
- package/src/endpoints/global/files/UploadImage.test.ts +2 -1
- package/src/endpoints/global/files/UploadImage.ts +32 -6
- package/src/endpoints/global/files/upload-security.test.ts +3 -2
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +4 -3
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +26 -25
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +57 -56
- package/src/endpoints/global/members/SendMemberSecurityCodeEndpoint.test.ts +4 -3
- package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +6 -4
- package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +6 -6
- package/src/endpoints/global/platform/SignOutPlatformAdminsEndpoint.test.ts +9 -8
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +14 -13
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +24 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +4 -3
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +20 -19
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +0 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +23 -22
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +4 -4
- package/src/endpoints/organization/dashboard/payments/GetPaymentBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +7 -6
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +9 -8
- package/src/endpoints/organization/dashboard/settlements/SettlementsExportEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/settlements/SettlementsSyncEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +6 -5
- package/src/endpoints/organization/dashboard/users/SignOutOrganizationAdminsEndpoint.test.ts +16 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +2 -1
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -3
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -5
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +94 -8
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +18 -18
- package/src/helpers/ApplicationFeeInvoicer.ts +1 -1
- package/src/helpers/Context.ts +14 -21
- package/src/helpers/ContextStorage.ts +5 -0
- package/src/helpers/MollieSettlementSync.test.ts +94 -41
- package/src/helpers/MollieSettlementSync.ts +174 -78
- package/src/helpers/StripeSettlementSync.test.ts +0 -85
- package/src/helpers/StripeSettlementSync.ts +47 -70
- package/src/helpers/TwoFactorHelper.ts +21 -22
- package/src/helpers/streamForBreakdown.ts +1 -1
- package/src/services/AdminSessionService.ts +5 -3
- package/src/services/InvoiceService.ts +2 -2
- package/src/services/MollieService.ts +168 -126
- package/src/services/PaymentService.ts +2 -2
- package/src/services/SSOService.test.ts +25 -1
- package/src/services/SSOService.ts +17 -6
- package/src/services/SessionService.test.ts +319 -0
- package/src/services/SessionService.ts +357 -0
- package/src/services/SettlementService.test.ts +13 -85
- package/src/services/SettlementService.ts +5 -113
- package/src/services/WebshopCrowdfundingService.test.ts +4 -3
- package/src/sql-filters/balance-items.ts +10 -0
- package/src/sql-filters/registrations.ts +59 -1
- package/tests/actions/patchUserMember.ts +2 -1
- package/tests/e2e/api-rate-limits.test.ts +2 -1
- package/tests/e2e/bundle-discounts.test.ts +4 -3
- package/tests/e2e/charge-members.test.ts +6 -5
- package/tests/e2e/documents.test.ts +4 -3
- package/tests/e2e/private-files.test.ts +9 -8
- package/tests/e2e/register.test.ts +4 -3
- package/tests/e2e/stock.test.ts +4 -3
- package/tests/e2e/tickets.test.ts +4 -3
- package/tests/helpers/ExportSlice.ts +2 -1
- package/tests/helpers/MollieMocker.ts +173 -38
- package/tests/init/initAdmin.ts +2 -1
- package/tests/init/initPlatformAdmin.ts +2 -1
- package/src/helpers/ViesHelper.test.ts +0 -401
- package/src/helpers/ViesHelper.ts +0 -215
- package/src/services/PeppolDirectoryService.test.ts +0 -139
- package/src/services/PeppolDirectoryService.ts +0 -111
|
@@ -10,6 +10,7 @@ import { LoginMethod, NewUser, PermissionLevel, SignupResponse, UserPermissions
|
|
|
10
10
|
import { Context } from '../../helpers/Context.js';
|
|
11
11
|
import { MemberUserSyncer } from '../../helpers/MemberUserSyncer.js';
|
|
12
12
|
import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures.js';
|
|
13
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
13
14
|
import { VerificationCodeService } from '../../services/VerificationCodeService.js';
|
|
14
15
|
|
|
15
16
|
type Params = { id: string };
|
|
@@ -163,7 +164,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
|
|
|
163
164
|
// password changes
|
|
164
165
|
await editUser.changePassword(request.body.password);
|
|
165
166
|
await PasswordToken.clearFor(editUser.id);
|
|
166
|
-
await
|
|
167
|
+
await SessionService.clearFor({ userId: editUser.id, keepAccessToken: token.accessToken });
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
if (request.body.hasPassword === false) {
|
|
@@ -179,7 +180,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
|
|
|
179
180
|
}
|
|
180
181
|
editUser.password = null;
|
|
181
182
|
await PasswordToken.clearFor(editUser.id);
|
|
182
|
-
await
|
|
183
|
+
await SessionService.clearFor({ userId: editUser.id, keepAccessToken: token.accessToken });
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -2,12 +2,13 @@ import type { Decoder } from '@simonbackx/simple-encoding';
|
|
|
2
2
|
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
-
import { EmailVerificationCode,
|
|
6
|
-
import { Token as TokenStruct, VerifyEmailRequest } from '@stamhoofd/structures';
|
|
5
|
+
import { EmailVerificationCode, User } from '@stamhoofd/models';
|
|
6
|
+
import { SessionLoginMethod, Token as TokenStruct, VerifyEmailRequest } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../helpers/Context.js';
|
|
9
9
|
import { TwoFactorHelper } from '../../helpers/TwoFactorHelper.js';
|
|
10
10
|
import { BalanceItemService } from '../../services/BalanceItemService.js';
|
|
11
|
+
import { SessionService } from '../../services/SessionService.js';
|
|
11
12
|
|
|
12
13
|
type Params = Record<string, never>;
|
|
13
14
|
type Query = undefined;
|
|
@@ -130,10 +131,10 @@ export class VerifyEmailEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
130
131
|
// the request already comes from a session of that same user (changing your email
|
|
131
132
|
// address while signed in): the second factor was checked when it was created.
|
|
132
133
|
if (authenticatedUser?.id !== user.id) {
|
|
133
|
-
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod:
|
|
134
|
+
await TwoFactorHelper.assertSecondFactorOrThrow(user, organization, request.request.getVersion(), { loginMethod: SessionLoginMethod.Email, i18n: request.i18n });
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const token = await
|
|
137
|
+
const token = await SessionService.createSession(user, { loginMethod: SessionLoginMethod.Email });
|
|
137
138
|
await user.markActive();
|
|
138
139
|
|
|
139
140
|
const st = new TokenStruct(token);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
3
|
-
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory,
|
|
2
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
3
|
+
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
4
4
|
import { EmailStatus, LimitedFilteredRequest, PermissionLevel, Permissions, Replacement } from '@stamhoofd/structures';
|
|
5
5
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
6
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
7
7
|
import { GetAdminEmailsEndpoint } from './GetAdminEmailsEndpoint.js';
|
|
8
8
|
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
9
10
|
|
|
10
11
|
const baseUrl = `/email`;
|
|
11
12
|
|
|
@@ -39,7 +40,7 @@ describe('Endpoint.getAdminEmails', () => {
|
|
|
39
40
|
user,
|
|
40
41
|
}).create();
|
|
41
42
|
|
|
42
|
-
userToken = await
|
|
43
|
+
userToken = await SessionService.createSession(user);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
const getAdminEmails = async (query: LimitedFilteredRequest = new LimitedFilteredRequest({ limit: 10 }), token: Token = userToken, testOrganization: Organization = organization) => {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
3
|
-
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory,
|
|
2
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
3
|
+
import { Email, EmailRecipient, MemberFactory, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
4
4
|
import { EmailContent, EmailStatus, LimitedFilteredRequest, Replacement } from '@stamhoofd/structures';
|
|
5
5
|
import { Language } from '@stamhoofd/types/Language';
|
|
6
6
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
7
7
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
8
8
|
import { GetUserEmailsEndpoint } from './GetUserEmailsEndpoint.js';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
10
11
|
|
|
11
12
|
const baseUrl = `/user/email`;
|
|
12
13
|
|
|
@@ -39,7 +40,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
39
40
|
user,
|
|
40
41
|
}).create();
|
|
41
42
|
|
|
42
|
-
userToken = await
|
|
43
|
+
userToken = await SessionService.createSession(user);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
afterEach(async () => {
|
|
@@ -726,7 +727,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
726
727
|
expect(response.body.results.find(e => e.id === email.id)).toBeUndefined();
|
|
727
728
|
|
|
728
729
|
// Test that is IS included if we request the same data via the 'otherUser' token
|
|
729
|
-
const otherUserToken = await
|
|
730
|
+
const otherUserToken = await SessionService.createSession(otherUser);
|
|
730
731
|
const responseForOtherUser = await getUserEmails(new LimitedFilteredRequest({ limit: 10 }), otherUserToken);
|
|
731
732
|
expect(responseForOtherUser.body.results.find(e => e.id === email.id)).toBeDefined();
|
|
732
733
|
});
|
|
@@ -805,7 +806,7 @@ describe('Endpoint.GetUserEmails', () => {
|
|
|
805
806
|
host: organization.getApiHost(),
|
|
806
807
|
query: new LimitedFilteredRequest({ limit: 10 }),
|
|
807
808
|
headers: {
|
|
808
|
-
|
|
809
|
+
authorization: 'Bearer ' + userToken.accessToken,
|
|
809
810
|
...(language ? { 'accept-language': language } : {}),
|
|
810
811
|
},
|
|
811
812
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { AutoEncoderPatchType } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { PatchMap } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
4
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
5
|
-
import { Email, EmailRecipient, GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationPeriodFactory,
|
|
4
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
5
|
+
import { Email, EmailRecipient, GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
6
6
|
import { AccessRight, EmailContent, EmailRecipientFilter, EmailRecipientSubfilter, EmailStatus, Email as EmailStruct, OrganizationEmail, Parent, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, UserPermissions, Version } from '@stamhoofd/structures';
|
|
7
7
|
import { Language } from '@stamhoofd/types/Language';
|
|
8
8
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
9
9
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
10
10
|
import { PatchEmailEndpoint } from './PatchEmailEndpoint.js';
|
|
11
11
|
import { EmailRecipientFilterType } from '@stamhoofd/structures/email/EmailRecipientFilterType.js';
|
|
12
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
12
13
|
|
|
13
14
|
// Import recipient loaders to initialize them
|
|
14
15
|
import { Formatter } from '@stamhoofd/utility';
|
|
@@ -82,7 +83,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
82
83
|
})
|
|
83
84
|
.create();
|
|
84
85
|
|
|
85
|
-
token = await
|
|
86
|
+
token = await SessionService.createSession(user);
|
|
86
87
|
|
|
87
88
|
user2 = await new UserFactory({
|
|
88
89
|
organization,
|
|
@@ -99,7 +100,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
99
100
|
})
|
|
100
101
|
.create();
|
|
101
102
|
|
|
102
|
-
token2 = await
|
|
103
|
+
token2 = await SessionService.createSession(user2);
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
test('Should throw for invalid senderId', async () => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
2
2
|
import { GetEmailRecipientsEndpoint } from './GetEmailRecipientsEndpoint.js';
|
|
3
3
|
import { AccessRight, EmailStatus, LimitedFilteredRequest, OrganizationEmail, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
|
|
4
|
-
import type { Organization, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
5
|
-
import { Email, EmailRecipient, OrganizationFactory, RegistrationPeriodFactory,
|
|
4
|
+
import type { Organization, RegistrationPeriod, User, Token } from '@stamhoofd/models';
|
|
5
|
+
import { Email, EmailRecipient, OrganizationFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
6
6
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
7
7
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
8
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
8
9
|
|
|
9
10
|
const baseUrl = `/email-recipients`;
|
|
10
11
|
|
|
@@ -57,7 +58,7 @@ describe('Endpoint.GetEmailRecipients', () => {
|
|
|
57
58
|
})
|
|
58
59
|
.create();
|
|
59
60
|
|
|
60
|
-
token = await
|
|
61
|
+
token = await SessionService.createSession(user);
|
|
61
62
|
|
|
62
63
|
user2 = await new UserFactory({
|
|
63
64
|
organization,
|
|
@@ -73,7 +74,7 @@ describe('Endpoint.GetEmailRecipients', () => {
|
|
|
73
74
|
})
|
|
74
75
|
.create();
|
|
75
76
|
|
|
76
|
-
token2 = await
|
|
77
|
+
token2 = await SessionService.createSession(user2);
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
test('It can request all email recipients if read permission for all senders', async () => {
|
|
@@ -10,6 +10,7 @@ import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
|
10
10
|
import '../../../audit-logs/init.js';
|
|
11
11
|
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
12
12
|
import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint.js';
|
|
13
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
13
14
|
|
|
14
15
|
const baseUrl = `/event-notifications`;
|
|
15
16
|
const endpoint = new PatchEventNotificationsEndpoint();
|
|
@@ -34,7 +35,7 @@ const TestRequest = {
|
|
|
34
35
|
}) {
|
|
35
36
|
const request = Request.buildJson('PATCH', baseUrl, options.organization?.getApiHost(), options.body);
|
|
36
37
|
if (options.user) {
|
|
37
|
-
const token = await
|
|
38
|
+
const token = await SessionService.createSession(options.user);
|
|
38
39
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
39
40
|
}
|
|
40
41
|
return await testServer.test(endpoint, request);
|
|
@@ -8,6 +8,7 @@ import { AccessRight, Event, Group, GroupSettings, GroupType, OrganizationEventT
|
|
|
8
8
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
9
9
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
10
10
|
import { PatchEventsEndpoint } from './PatchEventsEndpoint.js';
|
|
11
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
11
12
|
|
|
12
13
|
const baseUrl = `/events`;
|
|
13
14
|
const endpoint = new PatchEventsEndpoint();
|
|
@@ -32,7 +33,7 @@ const TestRequest = {
|
|
|
32
33
|
}) {
|
|
33
34
|
const request = Request.buildJson('PATCH', baseUrl, options.organization?.getApiHost(), options.body);
|
|
34
35
|
if (options.user) {
|
|
35
|
-
const token = await
|
|
36
|
+
const token = await SessionService.createSession(options.user);
|
|
36
37
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
37
38
|
}
|
|
38
39
|
return await testServer.test(endpoint, request);
|
|
@@ -6,6 +6,7 @@ import { TestUtils } from '@stamhoofd/test-utils';
|
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
8
|
import { Readable } from 'node:stream';
|
|
9
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
9
10
|
|
|
10
11
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
11
12
|
import { UploadFile } from './UploadFile.js';
|
|
@@ -59,7 +60,7 @@ describe('Endpoint.UploadFile', () => {
|
|
|
59
60
|
organization,
|
|
60
61
|
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
61
62
|
}).create();
|
|
62
|
-
const token = await
|
|
63
|
+
const token = await SessionService.createSession(user);
|
|
63
64
|
|
|
64
65
|
const boundary = '--------------------------StamhoofdTest';
|
|
65
66
|
const body = Buffer.concat([
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PutObjectCommand
|
|
1
|
+
import { PutObjectCommand } from '@aws-sdk/client-s3'; // ES Modules import
|
|
2
2
|
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
@@ -11,7 +11,8 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
11
11
|
import type { Decoder } from '@simonbackx/simple-encoding';
|
|
12
12
|
import { AutoEncoder, BooleanDecoder, field } from '@simonbackx/simple-encoding';
|
|
13
13
|
import { Context } from '../../../helpers/Context.js';
|
|
14
|
-
import { limiter } from './UploadImage.js';
|
|
14
|
+
import { limiter, organizationLimiter } from './UploadImage.js';
|
|
15
|
+
import type { Organization } from '@stamhoofd/models';
|
|
15
16
|
import { Image } from '@stamhoofd/models';
|
|
16
17
|
|
|
17
18
|
type Params = Record<string, never>;
|
|
@@ -58,11 +59,20 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
61
|
-
await Context.setOptionalOrganizationScope();
|
|
62
|
-
const { user } = await Context.
|
|
62
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
63
|
+
const { user } = await Context.optionalAuthenticate();
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
// Webshops allow for file upload, but don't have a user attached
|
|
66
|
+
if (user) {
|
|
67
|
+
if (!Context.auth?.canUpload({ private: request.query.isPrivate })) {
|
|
68
|
+
throw Context.auth.error();
|
|
69
|
+
}
|
|
70
|
+
} else if (!organization) {
|
|
71
|
+
throw new SimpleError({
|
|
72
|
+
code: 'permission_denied',
|
|
73
|
+
message: 'Endpoints needs organization if no user is present',
|
|
74
|
+
human: $t('%ZlM'),
|
|
75
|
+
});
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
@@ -77,7 +87,14 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
77
87
|
throw new Error('Not supported without real request');
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
if (user) {
|
|
91
|
+
limiter.track(user.id, 1);
|
|
92
|
+
} else {
|
|
93
|
+
limiter.track(request.request.getIP(), 1);
|
|
94
|
+
if (organization) {
|
|
95
|
+
organizationLimiter.track(organization.id, 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
81
98
|
|
|
82
99
|
const form = formidable({ maxFileSize: 20 * 1024 * 1024, maxFields: 1, keepExtensions: true });
|
|
83
100
|
const file = await new Promise<FormidableFile>((resolve, reject) => {
|
|
@@ -89,34 +106,43 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
89
106
|
}));
|
|
90
107
|
return;
|
|
91
108
|
}
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
return;
|
|
109
|
+
try {
|
|
110
|
+
if (request.request.request?.destroyed) {
|
|
111
|
+
throw new Error('Request destroyed before parsing');
|
|
96
112
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
form.parse(request.request.request, (err, fields, files) => {
|
|
114
|
+
console.log('form.parse', err, fields, files);
|
|
115
|
+
if (err) {
|
|
116
|
+
reject(err);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!files.file || !Array.isArray(files.file) || files.file.length !== 1) {
|
|
121
|
+
reject(new SimpleError({
|
|
122
|
+
code: 'missing_field',
|
|
123
|
+
message: 'Missing file',
|
|
124
|
+
field: 'file',
|
|
125
|
+
}));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resolve(files.file[0]);
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error(e);
|
|
133
|
+
reject(e);
|
|
134
|
+
}
|
|
109
135
|
});
|
|
110
136
|
|
|
111
137
|
try {
|
|
112
|
-
return await this.upload(request, file, user);
|
|
138
|
+
return await this.upload(request, file, organization, user);
|
|
113
139
|
} finally {
|
|
114
140
|
// Formidable wrote the upload to a temporary file
|
|
115
141
|
await fs.rm(file.filepath, { force: true }).catch(() => { /* we can't do anything about this */ });
|
|
116
142
|
}
|
|
117
143
|
}
|
|
118
144
|
|
|
119
|
-
private async upload(request: DecodedRequest<Params, Query, Body>, file: FormidableFile,
|
|
145
|
+
private async upload(request: DecodedRequest<Params, Query, Body>, file: FormidableFile, organization: Organization | null, user?: { id: string }) {
|
|
120
146
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
121
147
|
throw new SimpleError({
|
|
122
148
|
code: 'not_available',
|
|
@@ -156,6 +182,8 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
156
182
|
if (request.query.isPrivate && user) {
|
|
157
183
|
// Private files
|
|
158
184
|
prefix += 'users/' + user.id + '/';
|
|
185
|
+
} else if (request.query.isPrivate && !user && organization) {
|
|
186
|
+
prefix += 'anonymous/' + organization.id + '/';
|
|
159
187
|
} else {
|
|
160
188
|
// Public files
|
|
161
189
|
prefix += 'p/';
|
|
@@ -5,6 +5,7 @@ import { PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
|
5
5
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
6
|
import type { IncomingMessage } from 'node:http';
|
|
7
7
|
import { Readable } from 'node:stream';
|
|
8
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
8
9
|
|
|
9
10
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
10
11
|
import { UploadImage } from './UploadImage.js';
|
|
@@ -52,7 +53,7 @@ describe('Endpoint.UploadImage', () => {
|
|
|
52
53
|
organization,
|
|
53
54
|
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
54
55
|
}).create();
|
|
55
|
-
const token = await
|
|
56
|
+
const token = await SessionService.createSession(user);
|
|
56
57
|
|
|
57
58
|
const boundary = '--------------------------StamhoofdTest';
|
|
58
59
|
const body = Buffer.concat([
|
|
@@ -3,6 +3,7 @@ import { AutoEncoder, BooleanDecoder, field, ObjectData } from '@simonbackx/simp
|
|
|
3
3
|
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
+
import type { Organization } from '@stamhoofd/models';
|
|
6
7
|
import { Image, RateLimiter } from '@stamhoofd/models';
|
|
7
8
|
import { Image as ImageStruct, ResolutionRequest, supportedImageTypes } from '@stamhoofd/structures';
|
|
8
9
|
import formidable from 'formidable';
|
|
@@ -44,6 +45,15 @@ export const limiter = new RateLimiter({
|
|
|
44
45
|
},
|
|
45
46
|
],
|
|
46
47
|
});
|
|
48
|
+
export const organizationLimiter = new RateLimiter({
|
|
49
|
+
limits: [
|
|
50
|
+
{
|
|
51
|
+
// Max 200 per day
|
|
52
|
+
limit: 200,
|
|
53
|
+
duration: 60 * 1000 * 60 * 24,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
});
|
|
47
57
|
|
|
48
58
|
export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
49
59
|
queryDecoder = Query as Decoder<Query>;
|
|
@@ -63,11 +73,20 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
66
|
-
await Context.setOptionalOrganizationScope();
|
|
67
|
-
const { user } = await Context.
|
|
76
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
77
|
+
const { user } = await Context.optionalAuthenticate();
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
// Webshops allow for file upload, but don't have a user attached
|
|
80
|
+
if (user) {
|
|
81
|
+
if (!Context.auth?.canUpload({ private: request.query.isPrivate })) {
|
|
82
|
+
throw Context.auth.error();
|
|
83
|
+
}
|
|
84
|
+
} else if (!organization) {
|
|
85
|
+
throw new SimpleError({
|
|
86
|
+
code: 'permission_denied',
|
|
87
|
+
message: 'Endpoints needs organization if no user is present',
|
|
88
|
+
human: $t('%ZlM'),
|
|
89
|
+
});
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
@@ -82,7 +101,14 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
82
101
|
throw new Error('Not supported without real request');
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
|
|
104
|
+
if (user) {
|
|
105
|
+
limiter.track(user.id, 1);
|
|
106
|
+
} else {
|
|
107
|
+
limiter.track(request.request.getIP(), 1);
|
|
108
|
+
if (organization) {
|
|
109
|
+
organizationLimiter.track(organization.id, 1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
86
112
|
|
|
87
113
|
const form = formidable({
|
|
88
114
|
maxFileSize: 5 * 1024 * 1024,
|
|
@@ -147,7 +173,7 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
147
173
|
}
|
|
148
174
|
|
|
149
175
|
const fileContent = await fs.readFile(file.filepath);
|
|
150
|
-
const image = await Image.create(fileContent, imageType.contentType, resolutions, request.query.isPrivate, user);
|
|
176
|
+
const image = await Image.create(fileContent, imageType.contentType, resolutions, request.query.isPrivate, user, organization as Organization | null | undefined);
|
|
151
177
|
return new Response(ImageStruct.create(image));
|
|
152
178
|
} finally {
|
|
153
179
|
// Formidable wrote the upload to a temporary file
|
|
@@ -7,6 +7,7 @@ import { File, PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
|
7
7
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
8
8
|
import type { IncomingMessage } from 'node:http';
|
|
9
9
|
import { Readable } from 'node:stream';
|
|
10
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
10
11
|
|
|
11
12
|
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
12
13
|
import { FileSignService } from '../../../services/FileSignService.js';
|
|
@@ -116,12 +117,12 @@ describe('Upload security', () => {
|
|
|
116
117
|
permissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
117
118
|
}).create();
|
|
118
119
|
adminUserId = admin.id;
|
|
119
|
-
adminToken = (await
|
|
120
|
+
adminToken = (await SessionService.createSession(admin)).accessToken;
|
|
120
121
|
|
|
121
122
|
// A user without any permissions: it may only upload private files
|
|
122
123
|
const member = await new UserFactory({ organization }).create();
|
|
123
124
|
memberUserId = member.id;
|
|
124
|
-
memberToken = (await
|
|
125
|
+
memberToken = (await SessionService.createSession(member)).accessToken;
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
beforeEach(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import type { Organization, RegistrationPeriod } from '@stamhoofd/models';
|
|
3
|
-
import { GroupFactory, OrganizationFactory, OrganizationRegistrationPeriodFactory, RegistrationPeriodFactory,
|
|
2
|
+
import type { Organization, RegistrationPeriod, Token } from '@stamhoofd/models';
|
|
3
|
+
import { GroupFactory, OrganizationFactory, OrganizationRegistrationPeriodFactory, RegistrationPeriodFactory, UserFactory } from '@stamhoofd/models';
|
|
4
4
|
import type { StamhoofdFilter } from '@stamhoofd/structures';
|
|
5
5
|
import { BundleDiscountGroupPriceSettings, GroupPrice, GroupType, LimitedFilteredRequest } from '@stamhoofd/structures';
|
|
6
6
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
@@ -8,6 +8,7 @@ import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
|
8
8
|
import { initAdmin, initBundleDiscount, initPlatformAdmin } from '../../../../tests/init/index.js';
|
|
9
9
|
import { GetGroupsEndpoint } from './GetGroupsEndpoint.js';
|
|
10
10
|
import { Database } from '@simonbackx/simple-database';
|
|
11
|
+
import { SessionService } from '../../../services/SessionService.js';
|
|
11
12
|
|
|
12
13
|
const baseUrl = `/groups`;
|
|
13
14
|
|
|
@@ -68,7 +69,7 @@ describe('Endpoint.GetGroupsEndpoint', () => {
|
|
|
68
69
|
const { organization: otherOrganization } = await initOrganization(registrationPeriod1);
|
|
69
70
|
|
|
70
71
|
const user = await new UserFactory({ organization }).create();
|
|
71
|
-
const token = await
|
|
72
|
+
const token = await SessionService.createSession(user);
|
|
72
73
|
|
|
73
74
|
const waitingList = await new GroupFactory({
|
|
74
75
|
organization,
|