@stamhoofd/backend 2.39.0 → 2.40.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 (198) hide show
  1. package/eslint.config.mjs +5 -0
  2. package/index.ts +81 -74
  3. package/jest.config.cjs +10 -0
  4. package/migrations.ts +16 -14
  5. package/package.json +11 -11
  6. package/src/crons/clear-excel-cache.test.ts +48 -50
  7. package/src/crons/clear-excel-cache.ts +18 -18
  8. package/src/crons/setup-steps.ts +2 -2
  9. package/src/crons.ts +325 -306
  10. package/src/decoders/StringArrayDecoder.ts +7 -7
  11. package/src/decoders/StringNullableDecoder.ts +1 -2
  12. package/src/email-recipient-loaders/members.ts +22 -22
  13. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
  14. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
  15. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
  16. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
  17. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
  18. package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
  19. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
  20. package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
  21. package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
  22. package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
  23. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
  24. package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
  25. package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
  26. package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
  27. package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
  28. package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
  29. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
  30. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
  31. package/src/endpoints/auth/SignupEndpoint.ts +37 -36
  32. package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
  33. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
  34. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
  35. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
  36. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
  37. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
  38. package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
  39. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
  40. package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
  41. package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
  42. package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
  43. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
  44. package/src/endpoints/global/files/GetFileCache.ts +13 -13
  45. package/src/endpoints/global/files/UploadFile.ts +51 -54
  46. package/src/endpoints/global/files/UploadImage.ts +53 -53
  47. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
  48. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
  49. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
  50. package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
  51. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
  52. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
  53. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
  54. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
  55. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
  56. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
  57. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
  58. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
  59. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
  60. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
  61. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
  62. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
  63. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
  64. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
  65. package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
  66. package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
  67. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
  68. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
  69. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
  70. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
  71. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
  72. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
  73. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
  74. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
  75. package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
  76. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
  77. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
  78. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
  79. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
  80. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
  81. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
  82. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
  83. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
  84. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
  85. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
  86. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
  87. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
  88. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
  89. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
  90. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
  91. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
  92. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
  93. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
  94. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
  95. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
  96. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
  97. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
  98. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
  99. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
  100. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
  101. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
  102. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
  103. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
  104. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
  105. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
  106. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
  107. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
  108. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
  109. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
  110. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
  111. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
  112. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
  113. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
  114. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
  115. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
  116. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
  117. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
  118. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
  119. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
  120. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
  121. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
  122. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
  123. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
  124. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
  125. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
  126. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
  127. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
  128. package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
  129. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
  130. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
  131. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
  132. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
  133. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
  134. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
  135. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
  136. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
  137. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
  138. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
  139. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
  140. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
  141. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
  142. package/src/excel-loaders/members.ts +102 -103
  143. package/src/excel-loaders/payments.ts +155 -156
  144. package/src/helpers/AddressValidator.test.ts +32 -32
  145. package/src/helpers/AddressValidator.ts +128 -122
  146. package/src/helpers/AdminPermissionChecker.ts +339 -236
  147. package/src/helpers/AuthenticatedStructures.ts +233 -134
  148. package/src/helpers/BuckarooHelper.ts +134 -134
  149. package/src/helpers/CheckSettlements.ts +94 -88
  150. package/src/helpers/Context.ts +87 -86
  151. package/src/helpers/CookieHelper.ts +23 -22
  152. package/src/helpers/EmailResumer.ts +10 -10
  153. package/src/helpers/FileCache.ts +62 -62
  154. package/src/helpers/ForwardHandler.test.ts +122 -124
  155. package/src/helpers/ForwardHandler.ts +76 -70
  156. package/src/helpers/MemberUserSyncer.ts +101 -96
  157. package/src/helpers/MembershipCharger.ts +69 -69
  158. package/src/helpers/MembershipHelper.ts +11 -12
  159. package/src/helpers/OpenIDConnectHelper.ts +85 -82
  160. package/src/helpers/PeriodHelper.ts +65 -70
  161. package/src/helpers/StripeHelper.ts +146 -137
  162. package/src/helpers/StripePayoutChecker.ts +51 -52
  163. package/src/helpers/ViesHelper.ts +46 -44
  164. package/src/helpers/fetchToAsyncIterator.ts +14 -14
  165. package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
  166. package/src/middleware/ContextMiddleware.ts +5 -5
  167. package/src/migrations/1646578856-validate-addresses.ts +6 -9
  168. package/src/seeds/0000000000-example.ts +3 -5
  169. package/src/seeds/1715028563-user-permissions.ts +16 -18
  170. package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
  171. package/src/seeds/1722344162-sync-member-users.ts +14 -15
  172. package/src/seeds/1722344162-update-membership.ts +6 -6
  173. package/src/seeds/1726055544-balance-item-paid.ts +4 -4
  174. package/src/seeds/1726055545-balance-item-pending.ts +4 -4
  175. package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
  176. package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
  177. package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
  178. package/src/seeds/1726847064-setup-steps.ts +16 -0
  179. package/src/sql-filters/balance-item-payments.ts +7 -7
  180. package/src/sql-filters/events.ts +14 -14
  181. package/src/sql-filters/members.ts +96 -96
  182. package/src/sql-filters/organizations.ts +139 -75
  183. package/src/sql-filters/payments.ts +28 -28
  184. package/src/sql-filters/registrations.ts +14 -14
  185. package/src/sql-sorters/events.ts +25 -25
  186. package/src/sql-sorters/members.ts +26 -26
  187. package/src/sql-sorters/organizations.ts +36 -36
  188. package/src/sql-sorters/payments.ts +26 -26
  189. package/tests/e2e/stock.test.ts +616 -621
  190. package/tests/e2e/tickets.test.ts +255 -260
  191. package/tests/helpers/StripeMocker.ts +177 -179
  192. package/tests/helpers/TestServer.ts +9 -9
  193. package/tests/jest.global.setup.ts +14 -13
  194. package/tests/jest.setup.ts +33 -32
  195. package/.eslintrc.js +0 -61
  196. package/jest.config.js +0 -11
  197. package/src/helpers/SetupStepsUpdater.ts +0 -359
  198. package/src/seeds/1724076679-setup-steps.ts +0 -16
@@ -10,13 +10,12 @@ type Body = undefined;
10
10
  type ResponseBody = UserWithMembers;
11
11
 
12
12
  export class GetUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
-
14
13
  protected doesMatch(request: Request): [true, Params] | [false] {
15
- if (request.method != "GET") {
14
+ if (request.method !== 'GET') {
16
15
  return [false];
17
16
  }
18
17
 
19
- const params = Endpoint.parseParameters(request.url, "/user", {});
18
+ const params = Endpoint.parseParameters(request.url, '/user', {});
20
19
 
21
20
  if (params) {
22
21
  return [true, params as Params];
@@ -25,11 +24,11 @@ export class GetUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
25
24
  }
26
25
 
27
26
  async handle(request: DecodedRequest<Params, Query, Body>) {
28
- await Context.setOptionalOrganizationScope()
29
- const {user} = await Context.authenticate({allowWithoutAccount: true})
27
+ await Context.setOptionalOrganizationScope();
28
+ const { user } = await Context.authenticate({ allowWithoutAccount: true });
30
29
 
31
30
  return new Response(
32
- await AuthenticatedStructures.userWithMembers(user)
31
+ await AuthenticatedStructures.userWithMembers(user),
33
32
  );
34
33
  }
35
34
  }
@@ -1,25 +1,25 @@
1
1
  import { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { SimpleError } from "@simonbackx/simple-errors";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Token, User } from '@stamhoofd/models';
5
- import { ApiUser, PermissionLevel, UserPermissions } from "@stamhoofd/structures";
5
+ import { ApiUser, PermissionLevel, UserPermissions } from '@stamhoofd/structures';
6
6
 
7
7
  import { Context } from '../../helpers/Context';
8
8
 
9
9
  type Params = { id: string };
10
10
  type Query = undefined;
11
- type Body = AutoEncoderPatchType<ApiUser>
12
- type ResponseBody = ApiUser
11
+ type Body = AutoEncoderPatchType<ApiUser>;
12
+ type ResponseBody = ApiUser;
13
13
 
14
14
  export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
- bodyDecoder = ApiUser.patchType() as Decoder<AutoEncoderPatchType<ApiUser>>
15
+ bodyDecoder = ApiUser.patchType() as Decoder<AutoEncoderPatchType<ApiUser>>;
16
16
 
17
17
  protected doesMatch(request: Request): [true, Params] | [false] {
18
- if (request.method != "PATCH") {
18
+ if (request.method !== 'PATCH') {
19
19
  return [false];
20
20
  }
21
21
 
22
- const params = Endpoint.parseParameters(request.url, "/api-keys/@id", { id: String });
22
+ const params = Endpoint.parseParameters(request.url, '/api-keys/@id', { id: String });
23
23
 
24
24
  if (params) {
25
25
  return [true, params as Params];
@@ -29,55 +29,57 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
29
29
 
30
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
31
  const organization = await Context.setOrganizationScope();
32
- const {user} = await Context.authenticate()
32
+ const { user } = await Context.authenticate();
33
33
 
34
34
  if (request.body.id !== request.params.id) {
35
35
  throw new SimpleError({
36
- code: "invalid_request",
37
- message: "Invalid request: id mismatch",
38
- statusCode: 400
39
- })
36
+ code: 'invalid_request',
37
+ message: 'Invalid request: id mismatch',
38
+ statusCode: 400,
39
+ });
40
40
  }
41
41
 
42
- const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id)
43
-
42
+ const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id);
43
+
44
44
  if (!editUser || !await Context.auth.canAccessUser(editUser, PermissionLevel.Write) || !editUser.isApiUser) {
45
- throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze API-user te wijzigen")
45
+ throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om deze API-user te wijzigen');
46
46
  }
47
47
 
48
- editUser.firstName = request.body.name ?? editUser.name
49
- editUser.lastName = null
48
+ editUser.firstName = request.body.name ?? editUser.name;
49
+ editUser.lastName = null;
50
50
 
51
51
  if (request.body.permissions !== undefined && editUser.permissions) {
52
52
  if (!await Context.auth.canAccessUser(editUser, PermissionLevel.Full)) {
53
53
  throw new SimpleError({
54
- code: "permission_denied",
55
- message: "Je hebt geen rechten om de rechten van deze API-user te wijzigen"
56
- })
54
+ code: 'permission_denied',
55
+ message: 'Je hebt geen rechten om de rechten van deze API-user te wijzigen',
56
+ });
57
57
  }
58
58
 
59
59
  if (request.body.permissions) {
60
60
  if (organization) {
61
- editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id)
61
+ editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id);
62
62
 
63
63
  if (editUser.id === user.id && (!editUser.permissions || !editUser.permissions.forOrganization(organization)?.hasFullAccess())) {
64
64
  throw new SimpleError({
65
- code: "permission_denied",
66
- message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
67
- })
65
+ code: 'permission_denied',
66
+ message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
67
+ });
68
68
  }
69
- } else {
69
+ }
70
+ else {
70
71
  if (editUser.permissions) {
71
- editUser.permissions.patchOrPut(request.body.permissions)
72
- } else {
73
- editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null
72
+ editUser.permissions.patchOrPut(request.body.permissions);
73
+ }
74
+ else {
75
+ editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null;
74
76
  }
75
77
 
76
78
  if (editUser.id === user.id && !editUser.permissions?.platform?.hasFullAccess()) {
77
79
  throw new SimpleError({
78
- code: "permission_denied",
79
- message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
80
- })
80
+ code: 'permission_denied',
81
+ message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
82
+ });
81
83
  }
82
84
  }
83
85
  }
@@ -85,6 +87,6 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
85
87
 
86
88
  await editUser.save();
87
89
 
88
- return new Response(await Token.getAPIUserWithToken(editUser));
90
+ return new Response(await Token.getAPIUserWithToken(editUser));
89
91
  }
90
92
  }
@@ -1,8 +1,8 @@
1
1
  import { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { SimpleError } from "@simonbackx/simple-errors";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { EmailVerificationCode, Member, PasswordToken, Token, User } from '@stamhoofd/models';
5
- import { NewUser, PermissionLevel, SignupResponse, UserPermissions, UserWithMembers } from "@stamhoofd/structures";
5
+ import { NewUser, PermissionLevel, SignupResponse, UserPermissions, UserWithMembers } from '@stamhoofd/structures';
6
6
 
7
7
  import { Context } from '../../helpers/Context';
8
8
  import { MemberUserSyncer } from '../../helpers/MemberUserSyncer';
@@ -10,18 +10,18 @@ import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures';
10
10
 
11
11
  type Params = { id: string };
12
12
  type Query = undefined;
13
- type Body = AutoEncoderPatchType<NewUser>
14
- type ResponseBody = UserWithMembers
13
+ type Body = AutoEncoderPatchType<NewUser>;
14
+ type ResponseBody = UserWithMembers;
15
15
 
16
16
  export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
17
- bodyDecoder = NewUser.patchType() as Decoder<AutoEncoderPatchType<NewUser>>
17
+ bodyDecoder = NewUser.patchType() as Decoder<AutoEncoderPatchType<NewUser>>;
18
18
 
19
19
  protected doesMatch(request: Request): [true, Params] | [false] {
20
- if (request.method != "PATCH") {
20
+ if (request.method !== 'PATCH') {
21
21
  return [false];
22
22
  }
23
23
 
24
- const params = Endpoint.parseParameters(request.url, "/user/@id", { id: String });
24
+ const params = Endpoint.parseParameters(request.url, '/user/@id', { id: String });
25
25
 
26
26
  if (params) {
27
27
  return [true, params as Params];
@@ -31,76 +31,79 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
31
31
 
32
32
  async handle(request: DecodedRequest<Params, Query, Body>) {
33
33
  const organization = await Context.setOptionalOrganizationScope();
34
- const {user, token} = await Context.authenticate({allowWithoutAccount: true})
34
+ const { user, token } = await Context.authenticate({ allowWithoutAccount: true });
35
35
 
36
36
  if (request.body.id !== request.params.id) {
37
37
  throw new SimpleError({
38
- code: "invalid_request",
39
- message: "Invalid request: id mismatch",
40
- statusCode: 400
41
- })
38
+ code: 'invalid_request',
39
+ message: 'Invalid request: id mismatch',
40
+ statusCode: 400,
41
+ });
42
42
  }
43
43
 
44
- const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id)
45
-
44
+ const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id);
45
+
46
46
  if (!editUser || !await Context.auth.canAccessUser(editUser, PermissionLevel.Write) || editUser.isApiUser) {
47
- throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze gebruiker te wijzigen")
47
+ throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om deze gebruiker te wijzigen');
48
48
  }
49
49
 
50
50
  if (await Context.auth.canEditUserName(editUser)) {
51
51
  if (editUser.memberId) {
52
- const member = await Member.getWithRegistrations(editUser.memberId)
52
+ const member = await Member.getWithRegistrations(editUser.memberId);
53
53
  if (member) {
54
- member.details.firstName = request.body.firstName ?? member.details.firstName
55
- member.details.lastName = request.body.lastName ?? member.details.lastName
54
+ member.details.firstName = request.body.firstName ?? member.details.firstName;
55
+ member.details.lastName = request.body.lastName ?? member.details.lastName;
56
56
 
57
- editUser.firstName = member.details.firstName
58
- editUser.lastName = member.details.lastName
59
- await member.save()
57
+ editUser.firstName = member.details.firstName;
58
+ editUser.lastName = member.details.lastName;
59
+ await member.save();
60
60
 
61
61
  // Also propage the name change to other users of the same member if needed
62
- await MemberUserSyncer.onChangeMember(member)
62
+ await MemberUserSyncer.onChangeMember(member);
63
63
  }
64
- } else {
65
- editUser.firstName = request.body.firstName ?? editUser.firstName
66
- editUser.lastName = request.body.lastName ?? editUser.lastName
64
+ }
65
+ else {
66
+ editUser.firstName = request.body.firstName ?? editUser.firstName;
67
+ editUser.lastName = request.body.lastName ?? editUser.lastName;
67
68
  }
68
69
  }
69
70
 
70
71
  if (request.body.permissions !== undefined) {
71
72
  if (!await Context.auth.canAccessUser(editUser, PermissionLevel.Full)) {
72
73
  throw new SimpleError({
73
- code: "permission_denied",
74
- message: "Je hebt geen rechten om de rechten van deze gebruiker te wijzigen"
75
- })
74
+ code: 'permission_denied',
75
+ message: 'Je hebt geen rechten om de rechten van deze gebruiker te wijzigen',
76
+ });
76
77
  }
77
78
 
78
79
  if (request.body.permissions) {
79
80
  if (organization) {
80
- editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id)
81
+ editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id);
81
82
 
82
83
  if (editUser.id === user.id && (!editUser.permissions || !editUser.permissions.forOrganization(organization)?.hasFullAccess()) && STAMHOOFD.environment !== 'development') {
83
84
  throw new SimpleError({
84
- code: "permission_denied",
85
- message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
86
- })
85
+ code: 'permission_denied',
86
+ message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
87
+ });
87
88
  }
88
- } else {
89
+ }
90
+ else {
89
91
  if (editUser.permissions) {
90
- editUser.permissions.patchOrPut(request.body.permissions)
91
- } else {
92
- editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null
92
+ editUser.permissions.patchOrPut(request.body.permissions);
93
+ }
94
+ else {
95
+ editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null;
93
96
  }
94
97
 
95
98
  if (editUser.permissions && editUser.permissions.isEmpty) {
96
- editUser.permissions = null
99
+ editUser.permissions = null;
97
100
  }
98
101
 
99
102
  if (editUser.id === user.id && !editUser.permissions?.platform?.hasFullAccess() && STAMHOOFD.environment !== 'development') {
100
103
  throw new SimpleError({
101
- code: "permission_denied",
102
- message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
103
- })
104
+ code: 'permission_denied',
105
+ message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
106
+ });
104
107
  }
105
108
  }
106
109
  }
@@ -108,9 +111,9 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
108
111
 
109
112
  if (editUser.id == user.id && request.body.password) {
110
113
  // password changes
111
- await editUser.changePassword(request.body.password)
112
- await PasswordToken.clearFor(editUser.id)
113
- await Token.clearFor(editUser.id, token.accessToken)
114
+ await editUser.changePassword(request.body.password);
115
+ await PasswordToken.clearFor(editUser.id);
116
+ await Token.clearFor(editUser.id, token.accessToken);
114
117
  }
115
118
 
116
119
  await editUser.save();
@@ -119,23 +122,23 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
119
122
  if (request.body.email && request.body.email !== editUser.email) {
120
123
  // Create an validation code
121
124
  // We always need the code, to return it. Also on password recovery -> may not be visible to the client whether the user exists or not
122
- const code = await EmailVerificationCode.createFor(editUser, request.body.email)
123
- code.send(editUser, organization, request.i18n, editUser.id === user.id)
125
+ const code = await EmailVerificationCode.createFor(editUser, request.body.email);
126
+ code.send(editUser, organization, request.i18n, editUser.id === user.id).catch(console.error);
124
127
 
125
128
  throw new SimpleError({
126
- code: "verify_email",
127
- message: "Your email address needs verification",
128
- human: editUser.id === user.id ? "Verifieer jouw nieuwe e-mailadres via de link in de e-mail, daarna passen we het automatisch aan." : "Er is een verificatie e-mail verstuurd naar "+request.body.email+" om het e-mailadres te verifiëren. Zodra dat is gebeurd, wordt het e-mailadres gewijzigd.",
129
+ code: 'verify_email',
130
+ message: 'Your email address needs verification',
131
+ human: editUser.id === user.id ? 'Verifieer jouw nieuwe e-mailadres via de link in de e-mail, daarna passen we het automatisch aan.' : 'Er is een verificatie e-mail verstuurd naar ' + request.body.email + ' om het e-mailadres te verifiëren. Zodra dat is gebeurd, wordt het e-mailadres gewijzigd.',
129
132
  meta: SignupResponse.create({
130
133
  token: code.token,
131
134
  }).encode({ version: request.request.getVersion() }),
132
- statusCode: 403
135
+ statusCode: 403,
133
136
  });
134
137
  }
135
138
  }
136
139
 
137
140
  return new Response(
138
- await AuthenticatedStructures.userWithMembers(editUser)
139
- );
141
+ await AuthenticatedStructures.userWithMembers(editUser),
142
+ );
140
143
  }
141
144
  }
@@ -1,7 +1,7 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { EmailVerificationCode } from '@stamhoofd/models';
4
- import { PollEmailVerificationRequest, PollEmailVerificationResponse } from "@stamhoofd/structures";
4
+ import { PollEmailVerificationRequest, PollEmailVerificationResponse } from '@stamhoofd/structures';
5
5
 
6
6
  import { Context } from '../../helpers/Context';
7
7
 
@@ -14,11 +14,11 @@ export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body,
14
14
  bodyDecoder = PollEmailVerificationRequest as Decoder<PollEmailVerificationRequest>;
15
15
 
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
17
- if (request.method != "POST") {
17
+ if (request.method !== 'POST') {
18
18
  return [false];
19
19
  }
20
20
 
21
- const params = Endpoint.parseParameters(request.url, "/verify-email/poll", {});
21
+ const params = Endpoint.parseParameters(request.url, '/verify-email/poll', {});
22
22
 
23
23
  if (params) {
24
24
  return [true, params as Params];
@@ -27,11 +27,11 @@ export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body,
27
27
  }
28
28
 
29
29
  async handle(request: DecodedRequest<Params, Query, Body>) {
30
- const organization = await Context.setOptionalOrganizationScope()
31
- const valid = await EmailVerificationCode.poll(organization?.id ?? null, request.body.token)
32
-
30
+ const organization = await Context.setOptionalOrganizationScope();
31
+ const valid = await EmailVerificationCode.poll(organization?.id ?? null, request.body.token);
32
+
33
33
  return new Response(PollEmailVerificationResponse.create({
34
- valid
34
+ valid,
35
35
  }));
36
36
  }
37
- }
37
+ }
@@ -1,7 +1,7 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { EmailVerificationCode } from '@stamhoofd/models';
4
- import { PollEmailVerificationRequest, PollEmailVerificationResponse } from "@stamhoofd/structures";
4
+ import { PollEmailVerificationRequest, PollEmailVerificationResponse } from '@stamhoofd/structures';
5
5
 
6
6
  import { Context } from '../../helpers/Context';
7
7
 
@@ -14,11 +14,11 @@ export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body,
14
14
  bodyDecoder = PollEmailVerificationRequest as Decoder<PollEmailVerificationRequest>;
15
15
 
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
17
- if (request.method != "POST") {
17
+ if (request.method !== 'POST') {
18
18
  return [false];
19
19
  }
20
20
 
21
- const params = Endpoint.parseParameters(request.url, "/verify-email/retry", {});
21
+ const params = Endpoint.parseParameters(request.url, '/verify-email/retry', {});
22
22
 
23
23
  if (params) {
24
24
  return [true, params as Params];
@@ -27,15 +27,15 @@ export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body,
27
27
  }
28
28
 
29
29
  async handle(request: DecodedRequest<Params, Query, Body>) {
30
- const organization = await Context.setOptionalOrganizationScope()
30
+ const organization = await Context.setOptionalOrganizationScope();
31
31
  const valid = await EmailVerificationCode.poll(organization?.id ?? null, request.body.token);
32
32
 
33
33
  if (valid) {
34
- EmailVerificationCode.resend(organization, request.body.token, request.i18n).catch(console.error)
34
+ EmailVerificationCode.resend(organization, request.body.token, request.i18n).catch(console.error);
35
35
  }
36
36
 
37
37
  return new Response(PollEmailVerificationResponse.create({
38
- valid
38
+ valid,
39
39
  }));
40
40
  }
41
- }
41
+ }
@@ -1,8 +1,8 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { EmailVerificationCode, PasswordToken, sendEmailTemplate, User } from '@stamhoofd/models';
5
- import { EmailTemplateType, NewUser, Recipient, Replacement, SignupResponse } from "@stamhoofd/structures";
5
+ import { EmailTemplateType, NewUser, Recipient, Replacement, SignupResponse } from '@stamhoofd/structures';
6
6
 
7
7
  import { Context } from '../../helpers/Context';
8
8
 
@@ -15,11 +15,11 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
15
15
  bodyDecoder = NewUser as Decoder<NewUser>;
16
16
 
17
17
  protected doesMatch(request: Request): [true, Params] | [false] {
18
- if (request.method != "POST") {
18
+ if (request.method !== 'POST') {
19
19
  return [false];
20
20
  }
21
21
 
22
- const params = Endpoint.parseParameters(request.url, "/sign-up", {});
22
+ const params = Endpoint.parseParameters(request.url, '/sign-up', {});
23
23
 
24
24
  if (params) {
25
25
  return [true, params as Params];
@@ -28,32 +28,34 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
28
28
  }
29
29
 
30
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
- const organization = await Context.setUserOrganizationScope()
31
+ const organization = await Context.setUserOrganizationScope();
32
32
 
33
- const u = await User.getForRegister(organization?.id ?? null, request.body.email)
33
+ const u = await User.getForRegister(organization?.id ?? null, request.body.email);
34
34
 
35
35
  // Don't optimize. Always run two queries atm.
36
- let user = u ? undefined : (await User.register(
37
- organization,
38
- request.body
39
- ));
36
+ let user = u
37
+ ? undefined
38
+ : (await User.register(
39
+ organization,
40
+ request.body,
41
+ ));
40
42
 
41
- let sendCode = true
43
+ let sendCode = true;
42
44
 
43
45
  if (!user) {
44
46
  if (!u) {
45
47
  // Fail silently because user did exist, and we don't want to expose that the user doesn't exists
46
- console.error("Could not register, but user doesn't exist: "+request.body.email)
48
+ console.error("Could not register, but user doesn't exist: " + request.body.email);
47
49
 
48
50
  throw new SimpleError({
49
- code: "unexpected_error",
50
- message: "Something went wrong",
51
- human: "Er ging iets mis",
52
- statusCode: 500
53
- })
51
+ code: 'unexpected_error',
52
+ message: 'Something went wrong',
53
+ human: 'Er ging iets mis',
54
+ statusCode: 500,
55
+ });
54
56
  }
55
57
 
56
- user = u
58
+ user = u;
57
59
 
58
60
  if (u.hasAccount()) {
59
61
  // Don't send the code
@@ -62,7 +64,7 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
62
64
  // We don't await this block to avoid user enumeration attack using request response time
63
65
  (async () => {
64
66
  // Send an e-mail to say you already have an account + follow password forgot flow
65
- const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n)
67
+ const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n);
66
68
 
67
69
  // Create e-mail builder
68
70
  await sendEmailTemplate(organization, {
@@ -74,42 +76,41 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
74
76
  replacements: [
75
77
  Replacement.create({
76
78
  token: 'resetUrl',
77
- value: recoveryUrl
78
- })
79
- ]
80
- })
79
+ value: recoveryUrl,
80
+ }),
81
+ ],
82
+ }),
81
83
  ],
82
84
  template: {
83
85
  type: EmailTemplateType.SignupAlreadyHasAccount,
84
86
  },
85
- type: 'transactional'
86
- })
87
-
87
+ type: 'transactional',
88
+ });
88
89
  })().catch(console.error);
89
-
90
- } else {
90
+ }
91
+ else {
91
92
  // This is safe, because we are the first one. There is no password yet.
92
93
  // If a hacker tries this, he won't be able to sign in, because he needs to
93
94
  // verify the e-mail first (same as if the user didn't exist)
94
95
  // If we didn't set the password, we would allow a different kind of attack:
95
96
  // a hacker could send an e-mail to the user (try to register again, seindgin a new email which would trigger a different password change), right after the user registered (without verifying yet), when he had set a different password
96
- // user clicks on second e-mail -> this sets the hackers password instead
97
- user.verified = false
98
- await user.changePassword(request.body.password)
99
- await PasswordToken.clearFor(user.id)
100
- await user.save()
97
+ // user clicks on second e-mail -> this sets the hackers password instead
98
+ user.verified = false;
99
+ await user.changePassword(request.body.password);
100
+ await PasswordToken.clearFor(user.id);
101
+ await user.save();
101
102
  }
102
103
  }
103
104
 
104
105
  // We always need the code, to return it. Also on password recovery -> may not be visible to the client whether the user exists or not
105
- const code = await EmailVerificationCode.createFor(user, user.email)
106
+ const code = await EmailVerificationCode.createFor(user, user.email);
106
107
 
107
108
  if (sendCode) {
108
- code.send(user, organization, request.i18n).catch(console.error)
109
+ code.send(user, organization, request.i18n).catch(console.error);
109
110
  }
110
111
 
111
112
  return new Response(SignupResponse.create({
112
- token: code.token
113
+ token: code.token,
113
114
  }));
114
115
  }
115
116
  }