@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
@@ -14,11 +14,11 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
14
14
  protected bodyDecoder = CreateTokenStruct;
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, "/oauth/token", {});
21
+ const params = Endpoint.parseParameters(request.url, '/oauth/token', {});
22
22
 
23
23
  if (params) {
24
24
  return [true, params as Params];
@@ -34,167 +34,166 @@ export class CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
34
34
  // - check if not multiple attempts for the same username are started in parallel
35
35
  // - Limit the amount of failed attemps by IP (will only make it a bit harder)
36
36
  // - Detect attacks on random accounts (using email list + most used passwords) and temorary require CAPTCHA on all accounts
37
- const organization = await Context.setOptionalOrganizationScope()
38
-
39
- switch (request.body.grantType) {
40
- case "refresh_token": {
41
- const oldToken = await Token.getByRefreshToken(request.body.refreshToken)
42
- if (!oldToken) {
43
- throw new SimpleError({
44
- code: "invalid_refresh_token",
45
- message: "Invalid refresh token",
46
- statusCode: 400
47
- });
48
- }
37
+ const organization = await Context.setOptionalOrganizationScope();
49
38
 
50
- if (oldToken.user.organizationId !== null && oldToken.user.organizationId !== (organization?.id ?? null)) {
39
+ switch (request.body.grantType) {
40
+ case 'refresh_token': {
41
+ const oldToken = await Token.getByRefreshToken(request.body.refreshToken);
42
+ if (!oldToken) {
43
+ throw new SimpleError({
44
+ code: 'invalid_refresh_token',
45
+ message: 'Invalid refresh token',
46
+ statusCode: 400,
47
+ });
48
+ }
49
+
50
+ if (oldToken.user.organizationId !== null && oldToken.user.organizationId !== (organization?.id ?? null)) {
51
51
  // Invalid scope
52
- throw new SimpleError({
53
- code: "invalid_refresh_token",
54
- message: "Invalid refresh token",
55
- statusCode: 400
56
- });
52
+ throw new SimpleError({
53
+ code: 'invalid_refresh_token',
54
+ message: 'Invalid refresh token',
55
+ statusCode: 400,
56
+ });
57
+ }
58
+
59
+ // Important to create a new token before adjusting the old token
60
+ const token = await Token.createToken(oldToken.user);
61
+
62
+ // In the rare event our response doesn't reach the client anymore, we don't want the client to sign out...
63
+ // So we give them a second chance and create a new token BUT we expire our existing token in an hour (forever!)
64
+ oldToken.refreshTokenValidUntil = new Date(Date.now() + 60 * 60 * 1000);
65
+ oldToken.accessTokenValidUntil = new Date(Date.now() - 60 * 60 * 1000);
66
+
67
+ // Do not delete the old one, only expire it fast so it will get deleted in the future
68
+ await oldToken.save();
69
+
70
+ if (!token) {
71
+ throw new SimpleError({
72
+ code: 'error',
73
+ message: 'Could not generate token',
74
+ human: 'Er ging iets mis bij het aanmelden',
75
+ statusCode: 500,
76
+ });
77
+ }
78
+
79
+ const st = new TokenStruct(token);
80
+ return new Response(st);
57
81
  }
58
82
 
59
- // Important to create a new token before adjusting the old token
60
- const token = await Token.createToken(oldToken.user);
61
-
62
- // In the rare event our response doesn't reach the client anymore, we don't want the client to sign out...
63
- // So we give them a second chance and create a new token BUT we expire our existing token in an hour (forever!)
64
- oldToken.refreshTokenValidUntil = new Date(Date.now() + 60*60*1000)
65
- oldToken.accessTokenValidUntil = new Date(Date.now() - 60 * 60 * 1000)
66
-
67
- // Do not delete the old one, only expire it fast so it will get deleted in the future
68
- await oldToken.save();
69
-
70
- if (!token) {
71
- throw new SimpleError({
72
- code: "error",
73
- message: "Could not generate token",
74
- human: "Er ging iets mis bij het aanmelden",
75
- statusCode: 500
76
- });
77
- }
78
-
79
- const st = new TokenStruct(token);
80
- return new Response(st);
81
- }
82
-
83
- case "password": {
83
+ case 'password': {
84
84
  // Increase timout for legacy
85
- request.request.request?.setTimeout(30 * 1000);
86
- const user = await User.login(organization?.id ?? null, request.body.username, request.body.password)
85
+ request.request.request?.setTimeout(30 * 1000);
86
+ const user = await User.login(organization?.id ?? null, request.body.username, request.body.password);
87
87
 
88
- const errBody = {
89
- code: "invalid_username_or_password",
90
- message: "Invalid username or password",
91
- human: "Foutief wachtwoord of onbekend emailadres",
92
- statusCode: 400
93
- };
88
+ const errBody = {
89
+ code: 'invalid_username_or_password',
90
+ message: 'Invalid username or password',
91
+ human: 'Foutief wachtwoord of onbekend emailadres',
92
+ statusCode: 400,
93
+ };
94
94
 
95
- if (!user) {
95
+ if (!user) {
96
96
  // TODO: increase counter
97
- throw new SimpleError(errBody);
98
- }
99
-
100
- // Yay! Valid password
101
- // Now check if e-mail is already validated
102
- // if not: throw a validation error (e-mail validation is required)
103
- if (!user.verified) {
104
- const code = await EmailVerificationCode.createFor(user, user.email)
105
- code.send(user, organization, request.i18n)
106
-
107
- throw new SimpleError({
108
- code: "verify_email",
109
- message: "Your email address needs verification",
110
- human: "Jouw e-mailadres is nog niet geverifieerd. Verifieer jouw e-mailadres via de link in de e-mail.",
111
- meta: SignupResponse.create({
112
- token: code.token
113
- }).encode({ version: request.request.getVersion() }),
114
- statusCode: 403
115
- });
116
- }
117
-
118
- const token = await Token.createToken(user);
119
-
120
- if (!token) {
121
- throw new SimpleError({
122
- code: "error",
123
- message: "Could not generate token",
124
- human: "Er ging iets mis bij het aanmelden",
125
- statusCode: 500
126
- });
127
- }
128
-
129
- const st = new TokenStruct(token);
130
- return new Response(st);
131
- }
132
-
133
- case "password_token": {
134
- const passwordToken = await PasswordToken.getToken(request.body.token)
135
- if (!passwordToken) {
136
- throw new SimpleError({
137
- code: "invalid_token",
138
- message: "Invalid token",
139
- human: "Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.",
140
- statusCode: 400
141
- });
97
+ throw new SimpleError(errBody);
98
+ }
99
+
100
+ // Yay! Valid password
101
+ // Now check if e-mail is already validated
102
+ // if not: throw a validation error (e-mail validation is required)
103
+ if (!user.verified) {
104
+ const code = await EmailVerificationCode.createFor(user, user.email);
105
+ code.send(user, organization, request.i18n).catch(console.error);
106
+
107
+ throw new SimpleError({
108
+ code: 'verify_email',
109
+ message: 'Your email address needs verification',
110
+ human: 'Jouw e-mailadres is nog niet geverifieerd. Verifieer jouw e-mailadres via de link in de e-mail.',
111
+ meta: SignupResponse.create({
112
+ token: code.token,
113
+ }).encode({ version: request.request.getVersion() }),
114
+ statusCode: 403,
115
+ });
116
+ }
117
+
118
+ const token = await Token.createToken(user);
119
+
120
+ if (!token) {
121
+ throw new SimpleError({
122
+ code: 'error',
123
+ message: 'Could not generate token',
124
+ human: 'Er ging iets mis bij het aanmelden',
125
+ statusCode: 500,
126
+ });
127
+ }
128
+
129
+ const st = new TokenStruct(token);
130
+ return new Response(st);
142
131
  }
143
132
 
144
- // Check scope
145
- if (organization && passwordToken.user.organizationId && passwordToken.user.organizationId != organization.id) {
133
+ case 'password_token': {
134
+ const passwordToken = await PasswordToken.getToken(request.body.token);
135
+ if (!passwordToken) {
136
+ throw new SimpleError({
137
+ code: 'invalid_token',
138
+ message: 'Invalid token',
139
+ human: 'Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.',
140
+ statusCode: 400,
141
+ });
142
+ }
143
+
144
+ // Check scope
145
+ if (organization && passwordToken.user.organizationId && passwordToken.user.organizationId !== organization.id) {
146
146
  // user of a different organization
147
- throw new SimpleError({
148
- code: "invalid_token",
149
- message: "Invalid token",
150
- human: "Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.",
151
- statusCode: 400
152
- });
153
- }
154
-
155
- if (!organization && passwordToken.user.organizationId) {
147
+ throw new SimpleError({
148
+ code: 'invalid_token',
149
+ message: 'Invalid token',
150
+ human: 'Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.',
151
+ statusCode: 400,
152
+ });
153
+ }
154
+
155
+ if (!organization && passwordToken.user.organizationId) {
156
156
  // User is scoped to a single organization, while the request is not
157
- throw new SimpleError({
158
- code: "invalid_token",
159
- message: "Invalid token",
160
- human: "Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.",
161
- statusCode: 400
162
- });
157
+ throw new SimpleError({
158
+ code: 'invalid_token',
159
+ message: 'Invalid token',
160
+ human: 'Deze link is ongeldig of is al vervallen. Je zal nogmaals een e-mail moeten versturen om je wachtwoord te herstellen.',
161
+ statusCode: 400,
162
+ });
163
+ }
164
+
165
+ // Important to create a new token before adjusting the old token
166
+ const token = await Token.createToken(passwordToken.user);
167
+
168
+ // TODO: make token short lived until renewal
169
+
170
+ if (!token) {
171
+ throw new SimpleError({
172
+ code: 'error',
173
+ message: 'Could not generate token',
174
+ human: 'Er ging iets mis bij het inloggen',
175
+ statusCode: 500,
176
+ });
177
+ }
178
+
179
+ // For now we keep the password token because the user might want to reload the page or load it on a different device/browser
180
+ // await passwordToken.delete();
181
+
182
+ // Verify this email address, since the user can't change its email address without being verified
183
+ if (!token.user.verified) {
184
+ token.user.verified = true;
185
+ await token.user.save();
186
+ }
187
+
188
+ const st = new TokenStruct(token);
189
+ return new Response(st);
163
190
  }
164
191
 
165
- // Important to create a new token before adjusting the old token
166
- const token = await Token.createToken(passwordToken.user);
167
-
168
- // TODO: make token short lived until renewal
169
-
170
- if (!token) {
171
- throw new SimpleError({
172
- code: "error",
173
- message: "Could not generate token",
174
- human: "Er ging iets mis bij het inloggen",
175
- statusCode: 500
176
- });
177
- }
178
-
179
- // For now we keep the password token because the user might want to reload the page or load it on a different device/browser
180
- //await passwordToken.delete();
181
-
182
- // Verify this email address, since the user can't change its email address without being verified
183
- if (!token.user.verified) {
184
- token.user.verified = true
185
- await token.user.save()
186
- }
187
-
188
- const st = new TokenStruct(token);
189
- return new Response(st);
190
- }
191
-
192
- default: {
192
+ default: {
193
193
  // t should always be 'never' so we get no compiler error when this compiles
194
194
  // if you get a compiler error here, you missed a possible value for grantType
195
- throw new Error("Grant type " + request.body.grantType + " not supported");
196
- }
195
+ throw new Error('Grant type ' + request.body.grantType + ' not supported');
196
+ }
197
197
  }
198
-
199
198
  }
200
199
  }
@@ -9,11 +9,11 @@ type ResponseBody = undefined;
9
9
 
10
10
  export class DeleteTokenEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
11
11
  protected doesMatch(request: Request): [true, Params] | [false] {
12
- if (request.method != "DELETE") {
12
+ if (request.method !== 'DELETE') {
13
13
  return [false];
14
14
  }
15
15
 
16
- const params = Endpoint.parseParameters(request.url, "/oauth/token", {});
16
+ const params = Endpoint.parseParameters(request.url, '/oauth/token', {});
17
17
 
18
18
  if (params) {
19
19
  return [true, params as Params];
@@ -22,10 +22,10 @@ export class DeleteTokenEndpoint extends Endpoint<Params, Query, Body, ResponseB
22
22
  }
23
23
 
24
24
  async handle(_: DecodedRequest<Params, Query, Body>) {
25
- await Context.setOptionalOrganizationScope()
26
- const {token} = await Context.authenticate({allowWithoutAccount: true})
27
- await token.delete()
28
-
29
- return new Response(undefined)
25
+ await Context.setOptionalOrganizationScope();
26
+ const { token } = await Context.authenticate({ allowWithoutAccount: true });
27
+ await token.delete();
28
+
29
+ return new Response(undefined);
30
30
  }
31
31
  }
@@ -11,11 +11,11 @@ type ResponseBody = undefined;
11
11
 
12
12
  export class DeleteUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
13
  protected doesMatch(request: Request): [true, Params] | [false] {
14
- if (request.method != "DELETE") {
14
+ if (request.method !== 'DELETE') {
15
15
  return [false];
16
16
  }
17
17
 
18
- const params = Endpoint.parseParameters(request.url, "/user", {});
18
+ const params = Endpoint.parseParameters(request.url, '/user', {});
19
19
 
20
20
  if (params) {
21
21
  return [true, params as Params];
@@ -24,35 +24,35 @@ export class DeleteUserEndpoint extends Endpoint<Params, Query, Body, ResponseBo
24
24
  }
25
25
 
26
26
  async handle(_: DecodedRequest<Params, Query, Body>) {
27
- const organization = await Context.setOptionalOrganizationScope()
28
- const {user, token} = await Context.authenticate({allowWithoutAccount: true})
29
-
27
+ const organization = await Context.setOptionalOrganizationScope();
28
+ const { user, token } = await Context.authenticate({ allowWithoutAccount: true });
29
+
30
30
  // Send an e-mail to inform everyone about this action
31
31
 
32
32
  // Delete the account
33
33
 
34
34
  const bcc = (await getDefaultEmailFrom(null, {
35
- template: {}
36
- }))
35
+ template: {},
36
+ }));
37
37
  await sendEmailTemplate(organization, {
38
38
  recipients: [
39
39
  Recipient.create({
40
- email: user.email
41
- })
40
+ email: user.email,
41
+ }),
42
42
  ],
43
43
  singleBcc: bcc.replyTo || bcc.from,
44
44
  template: {
45
45
  type: EmailTemplateType.DeleteAccountConfirmation,
46
46
  },
47
- type: 'transactional'
48
- })
47
+ type: 'transactional',
48
+ });
49
49
 
50
50
  // Soft delete until processed manually
51
51
  user.verified = false;
52
52
  user.password = null;
53
- await user.save()
54
- await token.delete()
55
-
56
- return new Response(undefined)
53
+ await user.save();
54
+ await token.delete();
55
+
56
+ return new Response(undefined);
57
57
  }
58
58
  }
@@ -5,7 +5,6 @@ import { EmailTemplateType, ForgotPasswordRequest, Recipient, Replacement } from
5
5
 
6
6
  import { Context } from '../../helpers/Context';
7
7
 
8
- // eslint-disable-next-line @typescript-eslint/ban-types
9
8
  type Params = Record<string, never>;
10
9
  type Query = undefined;
11
10
  type Body = ForgotPasswordRequest;
@@ -15,11 +14,11 @@ export class ForgotPasswordEndpoint extends Endpoint<Params, Query, Body, Respon
15
14
  protected bodyDecoder = ForgotPasswordRequest as Decoder<ForgotPasswordRequest>;
16
15
 
17
16
  protected doesMatch(request: Request): [true, Params] | [false] {
18
- if (request.method != "POST") {
17
+ if (request.method !== 'POST') {
19
18
  return [false];
20
19
  }
21
20
 
22
- const params = Endpoint.parseParameters(request.url, "/forgot-password", {});
21
+ const params = Endpoint.parseParameters(request.url, '/forgot-password', {});
23
22
 
24
23
  if (params) {
25
24
  return [true, params as Params];
@@ -28,27 +27,27 @@ export class ForgotPasswordEndpoint extends Endpoint<Params, Query, Body, Respon
28
27
  }
29
28
 
30
29
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
- const organization = await Context.setOptionalOrganizationScope()
32
- const user = await User.getForAuthentication(organization?.id ?? null, request.body.email, {allowWithoutAccount: true});
33
-
30
+ const organization = await Context.setOptionalOrganizationScope();
31
+ const user = await User.getForAuthentication(organization?.id ?? null, request.body.email, { allowWithoutAccount: true });
32
+
34
33
  if (!user) {
35
34
  // Create e-mail builder
36
35
  await sendEmailTemplate(organization, {
37
36
  recipients: [
38
37
  Recipient.create({
39
- email: request.body.email
40
- })
38
+ email: request.body.email,
39
+ }),
41
40
  ],
42
41
  template: {
43
42
  type: EmailTemplateType.ForgotPasswordButNoAccount,
44
43
  },
45
- type: 'transactional'
46
- })
44
+ type: 'transactional',
45
+ });
47
46
 
48
- return new Response(undefined)
47
+ return new Response(undefined);
49
48
  }
50
49
 
51
- const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n)
50
+ const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n);
52
51
 
53
52
  // Create e-mail builder
54
53
  await sendEmailTemplate(organization, {
@@ -60,16 +59,16 @@ export class ForgotPasswordEndpoint extends Endpoint<Params, Query, Body, Respon
60
59
  replacements: [
61
60
  Replacement.create({
62
61
  token: 'resetUrl',
63
- value: recoveryUrl
64
- })
65
- ]
66
- })
62
+ value: recoveryUrl,
63
+ }),
64
+ ],
65
+ }),
67
66
  ],
68
67
  template: {
69
68
  type: EmailTemplateType.ForgotPassword,
70
69
  },
71
- type: 'transactional'
72
- })
70
+ type: 'transactional',
71
+ });
73
72
 
74
73
  return new Response(undefined);
75
74
  }
@@ -5,19 +5,18 @@ import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures';
5
5
  import { Context } from '../../helpers/Context';
6
6
  import { User } from '@stamhoofd/models';
7
7
 
8
- type Params = {id: string};
8
+ type Params = { id: string };
9
9
  type Query = undefined;
10
10
  type Body = undefined;
11
11
  type ResponseBody = UserWithMembers;
12
12
 
13
13
  export class GetUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
-
15
14
  protected doesMatch(request: Request): [true, Params] | [false] {
16
- if (request.method != "GET") {
15
+ if (request.method !== 'GET') {
17
16
  return [false];
18
17
  }
19
18
 
20
- const params = Endpoint.parseParameters(request.url, "/user/@id", {id: String});
19
+ const params = Endpoint.parseParameters(request.url, '/user/@id', { id: String });
21
20
 
22
21
  if (params) {
23
22
  return [true, params as Params];
@@ -26,16 +25,16 @@ export class GetUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
26
25
  }
27
26
 
28
27
  async handle(request: DecodedRequest<Params, Query, Body>) {
29
- await Context.setOptionalOrganizationScope()
30
- await Context.authenticate()
28
+ await Context.setOptionalOrganizationScope();
29
+ await Context.authenticate();
31
30
 
32
- const user = await User.getByID(request.params.id)
33
- if (!user || !(await Context.auth.canAccessUser(user)) ){
34
- throw Context.auth.error()
31
+ const user = await User.getByID(request.params.id);
32
+ if (!user || !(await Context.auth.canAccessUser(user))) {
33
+ throw Context.auth.error();
35
34
  }
36
35
 
37
36
  return new Response(
38
- await AuthenticatedStructures.userWithMembers(user)
37
+ await AuthenticatedStructures.userWithMembers(user),
39
38
  );
40
39
  }
41
40
  }
@@ -1,64 +1,61 @@
1
- import { Request } from "@simonbackx/simple-endpoints";
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
3
  import { NewUser } from '@stamhoofd/structures';
4
4
 
5
- import { testServer } from "../../../tests/helpers/TestServer";
5
+ import { testServer } from '../../../tests/helpers/TestServer';
6
6
  import { GetUserEndpoint } from './GetUserEndpoint';
7
7
 
8
-
9
- describe("Endpoint.GetUser", () => {
8
+ describe('Endpoint.GetUser', () => {
10
9
  // Test endpoint
11
10
  const endpoint = new GetUserEndpoint();
12
11
 
13
- test("Request user details when signed in", async () => {
14
- const organization = await new OrganizationFactory({}).create()
15
- const user = await new UserFactory({ organization }).create()
16
- const token = await Token.createToken(user)
12
+ test('Request user details when signed in', async () => {
13
+ const organization = await new OrganizationFactory({}).create();
14
+ const user = await new UserFactory({ organization }).create();
15
+ const token = await Token.createToken(user);
17
16
 
18
- const r = Request.buildJson("GET", "/v1/user", organization.getApiHost());
19
- r.headers.authorization = "Bearer "+token.accessToken
17
+ const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
18
+ r.headers.authorization = 'Bearer ' + token.accessToken;
20
19
 
21
20
  const response = await testServer.test(endpoint, r);
22
21
  expect(response.body).toBeDefined();
23
22
 
24
23
  if (!(response.body instanceof NewUser)) {
25
- throw new Error("Expected NewUser")
26
- }
24
+ throw new Error('Expected NewUser');
25
+ }
27
26
 
28
- expect(response.body.id).toEqual(user.id)
27
+ expect(response.body.id).toEqual(user.id);
29
28
  });
30
29
 
31
- test("Request user details when not signed in is not working", async () => {
32
- const organization = await new OrganizationFactory({}).create()
33
- const user = await new UserFactory({ organization }).create()
34
- const token = await Token.createToken(user)
30
+ test('Request user details when not signed in is not working', async () => {
31
+ const organization = await new OrganizationFactory({}).create();
32
+ const user = await new UserFactory({ organization }).create();
33
+ const token = await Token.createToken(user);
35
34
 
36
- const r = Request.buildJson("GET", "/v1/user", organization.getApiHost());
35
+ const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
37
36
 
38
- await expect(testServer.test(endpoint, r)).rejects.toThrow(/missing/i)
37
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(/missing/i);
39
38
  });
40
39
 
41
- test("Request user details with invalid token is not working", async () => {
42
- const organization = await new OrganizationFactory({}).create()
43
- const user = await new UserFactory({ organization }).create()
44
- const token = await Token.createToken(user)
40
+ test('Request user details with invalid token is not working', async () => {
41
+ const organization = await new OrganizationFactory({}).create();
42
+ const user = await new UserFactory({ organization }).create();
43
+ const token = await Token.createToken(user);
45
44
 
46
- const r = Request.buildJson("GET", "/v1/user", organization.getApiHost());
47
- r.headers.authorization = "Bearer " + token.accessToken+"d"
45
+ const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
46
+ r.headers.authorization = 'Bearer ' + token.accessToken + 'd';
48
47
 
49
- await expect(testServer.test(endpoint, r)).rejects.toThrow(/invalid/i)
48
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(/invalid/i);
50
49
  });
51
50
 
52
- test("Request user details with expired token is not working", async () => {
53
- const organization = await new OrganizationFactory({}).create()
54
- const user = await new UserFactory({ organization }).create()
55
- const token = await Token.createExpiredToken(user)
51
+ test('Request user details with expired token is not working', async () => {
52
+ const organization = await new OrganizationFactory({}).create();
53
+ const user = await new UserFactory({ organization }).create();
54
+ const token = await Token.createExpiredToken(user);
56
55
 
57
- const r = Request.buildJson("GET", "/v1/user", organization.getApiHost());
58
- r.headers.authorization = "Bearer " + token.accessToken
56
+ const r = Request.buildJson('GET', '/v1/user', organization.getApiHost());
57
+ r.headers.authorization = 'Bearer ' + token.accessToken;
59
58
 
60
- await expect(testServer.test(endpoint, r)).rejects.toThrow(/expired/i)
59
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(/expired/i);
61
60
  });
62
-
63
-
64
61
  });