@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.
- package/eslint.config.mjs +5 -0
- package/index.ts +81 -74
- package/jest.config.cjs +10 -0
- package/migrations.ts +16 -14
- package/package.json +11 -11
- package/src/crons/clear-excel-cache.test.ts +48 -50
- package/src/crons/clear-excel-cache.ts +18 -18
- package/src/crons/setup-steps.ts +2 -2
- package/src/crons.ts +325 -306
- package/src/decoders/StringArrayDecoder.ts +7 -7
- package/src/decoders/StringNullableDecoder.ts +1 -2
- package/src/email-recipient-loaders/members.ts +22 -22
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
- package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
- package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
- package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
- package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
- package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
- package/src/endpoints/auth/SignupEndpoint.ts +37 -36
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
- package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
- package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
- package/src/endpoints/global/files/GetFileCache.ts +13 -13
- package/src/endpoints/global/files/UploadFile.ts +51 -54
- package/src/endpoints/global/files/UploadImage.ts +53 -53
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
- package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
- package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
- package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
- package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
- package/src/excel-loaders/members.ts +102 -103
- package/src/excel-loaders/payments.ts +155 -156
- package/src/helpers/AddressValidator.test.ts +32 -32
- package/src/helpers/AddressValidator.ts +128 -122
- package/src/helpers/AdminPermissionChecker.ts +339 -236
- package/src/helpers/AuthenticatedStructures.ts +233 -134
- package/src/helpers/BuckarooHelper.ts +134 -134
- package/src/helpers/CheckSettlements.ts +94 -88
- package/src/helpers/Context.ts +87 -86
- package/src/helpers/CookieHelper.ts +23 -22
- package/src/helpers/EmailResumer.ts +10 -10
- package/src/helpers/FileCache.ts +62 -62
- package/src/helpers/ForwardHandler.test.ts +122 -124
- package/src/helpers/ForwardHandler.ts +76 -70
- package/src/helpers/MemberUserSyncer.ts +101 -96
- package/src/helpers/MembershipCharger.ts +69 -69
- package/src/helpers/MembershipHelper.ts +11 -12
- package/src/helpers/OpenIDConnectHelper.ts +85 -82
- package/src/helpers/PeriodHelper.ts +65 -70
- package/src/helpers/StripeHelper.ts +146 -137
- package/src/helpers/StripePayoutChecker.ts +51 -52
- package/src/helpers/ViesHelper.ts +46 -44
- package/src/helpers/fetchToAsyncIterator.ts +14 -14
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
- package/src/middleware/ContextMiddleware.ts +5 -5
- package/src/migrations/1646578856-validate-addresses.ts +6 -9
- package/src/seeds/0000000000-example.ts +3 -5
- package/src/seeds/1715028563-user-permissions.ts +16 -18
- package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
- package/src/seeds/1722344162-sync-member-users.ts +14 -15
- package/src/seeds/1722344162-update-membership.ts +6 -6
- package/src/seeds/1726055544-balance-item-paid.ts +4 -4
- package/src/seeds/1726055545-balance-item-pending.ts +4 -4
- package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
- package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
- package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
- package/src/seeds/1726847064-setup-steps.ts +16 -0
- package/src/sql-filters/balance-item-payments.ts +7 -7
- package/src/sql-filters/events.ts +14 -14
- package/src/sql-filters/members.ts +96 -96
- package/src/sql-filters/organizations.ts +139 -75
- package/src/sql-filters/payments.ts +28 -28
- package/src/sql-filters/registrations.ts +14 -14
- package/src/sql-sorters/events.ts +25 -25
- package/src/sql-sorters/members.ts +26 -26
- package/src/sql-sorters/organizations.ts +36 -36
- package/src/sql-sorters/payments.ts +26 -26
- package/tests/e2e/stock.test.ts +616 -621
- package/tests/e2e/tickets.test.ts +255 -260
- package/tests/helpers/StripeMocker.ts +177 -179
- package/tests/helpers/TestServer.ts +9 -9
- package/tests/jest.global.setup.ts +14 -13
- package/tests/jest.setup.ts +33 -32
- package/.eslintrc.js +0 -61
- package/jest.config.js +0 -11
- package/src/helpers/SetupStepsUpdater.ts +0 -359
- 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
|
|
17
|
+
if (request.method !== 'POST') {
|
|
18
18
|
return [false];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const params = Endpoint.parseParameters(request.url,
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
+
if (!user) {
|
|
96
96
|
// TODO: increase counter
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
12
|
+
if (request.method !== 'DELETE') {
|
|
13
13
|
return [false];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const params = Endpoint.parseParameters(request.url,
|
|
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
|
|
14
|
+
if (request.method !== 'DELETE') {
|
|
15
15
|
return [false];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const params = Endpoint.parseParameters(request.url,
|
|
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
|
|
17
|
+
if (request.method !== 'POST') {
|
|
19
18
|
return [false];
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
const params = Endpoint.parseParameters(request.url,
|
|
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
|
|
15
|
+
if (request.method !== 'GET') {
|
|
17
16
|
return [false];
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const params = Endpoint.parseParameters(request.url,
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
19
|
-
r.headers.authorization =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
47
|
-
r.headers.authorization =
|
|
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(
|
|
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(
|
|
58
|
-
r.headers.authorization =
|
|
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
|
});
|