@stamhoofd/backend 1.0.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/.env.template.json +63 -0
- package/.eslintrc.js +61 -0
- package/README.md +40 -0
- package/index.ts +172 -0
- package/jest.config.js +11 -0
- package/migrations.ts +33 -0
- package/package.json +48 -0
- package/src/crons.ts +845 -0
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
- package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
- package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
- package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
- package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
- package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
- package/src/endpoints/auth/SignupEndpoint.ts +107 -0
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
- package/src/endpoints/global/files/UploadFile.ts +147 -0
- package/src/endpoints/global/files/UploadImage.ts +119 -0
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
- package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
- package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
- package/src/helpers/AddressValidator.test.ts +40 -0
- package/src/helpers/AddressValidator.ts +256 -0
- package/src/helpers/AdminPermissionChecker.ts +1031 -0
- package/src/helpers/AuthenticatedStructures.ts +158 -0
- package/src/helpers/BuckarooHelper.ts +279 -0
- package/src/helpers/CheckSettlements.ts +215 -0
- package/src/helpers/Context.ts +202 -0
- package/src/helpers/CookieHelper.ts +45 -0
- package/src/helpers/ForwardHandler.test.ts +216 -0
- package/src/helpers/ForwardHandler.ts +140 -0
- package/src/helpers/OpenIDConnectHelper.ts +284 -0
- package/src/helpers/StripeHelper.ts +293 -0
- package/src/helpers/StripePayoutChecker.ts +188 -0
- package/src/middleware/ContextMiddleware.ts +16 -0
- package/src/migrations/1646578856-validate-addresses.ts +60 -0
- package/src/seeds/0000000000-example.ts +13 -0
- package/src/seeds/1715028563-user-permissions.ts +52 -0
- package/tests/e2e/stock.test.ts +2120 -0
- package/tests/e2e/tickets.test.ts +926 -0
- package/tests/helpers/StripeMocker.ts +362 -0
- package/tests/helpers/TestServer.ts +21 -0
- package/tests/jest.global.setup.ts +29 -0
- package/tests/jest.setup.ts +59 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,122 @@
|
|
|
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";
|
|
4
|
+
import { EmailVerificationCode, PasswordToken, Token, User } from '@stamhoofd/models';
|
|
5
|
+
import { NewUser, PermissionLevel, SignupResponse, User as UserStruct,UserPermissions } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from '../../helpers/Context';
|
|
8
|
+
|
|
9
|
+
type Params = { id: string };
|
|
10
|
+
type Query = undefined;
|
|
11
|
+
type Body = AutoEncoderPatchType<NewUser>
|
|
12
|
+
type ResponseBody = UserStruct
|
|
13
|
+
|
|
14
|
+
export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
bodyDecoder = NewUser.patchType() as Decoder<AutoEncoderPatchType<NewUser>>
|
|
16
|
+
|
|
17
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
18
|
+
if (request.method != "PATCH") {
|
|
19
|
+
return [false];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const params = Endpoint.parseParameters(request.url, "/user/@id", { id: String });
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
32
|
+
const {user, token} = await Context.authenticate({allowWithoutAccount: true})
|
|
33
|
+
|
|
34
|
+
if (request.body.id !== request.params.id) {
|
|
35
|
+
throw new SimpleError({
|
|
36
|
+
code: "invalid_request",
|
|
37
|
+
message: "Invalid request: id mismatch",
|
|
38
|
+
statusCode: 400
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id)
|
|
43
|
+
|
|
44
|
+
if (!editUser || !await Context.auth.canAccessUser(editUser, PermissionLevel.Write) || editUser.isApiUser) {
|
|
45
|
+
throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze gebruiker te wijzigen")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (await Context.auth.canEditUserName(editUser)) {
|
|
49
|
+
editUser.firstName = request.body.firstName ?? editUser.firstName
|
|
50
|
+
editUser.lastName = request.body.lastName ?? editUser.lastName
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (request.body.permissions !== undefined) {
|
|
54
|
+
if (!await Context.auth.canAccessUser(editUser, PermissionLevel.Full)) {
|
|
55
|
+
throw new SimpleError({
|
|
56
|
+
code: "permission_denied",
|
|
57
|
+
message: "Je hebt geen rechten om de rechten van deze gebruiker te wijzigen"
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (request.body.permissions) {
|
|
62
|
+
if (organization) {
|
|
63
|
+
editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id)
|
|
64
|
+
|
|
65
|
+
if (editUser.id === user.id && (!editUser.permissions || !editUser.permissions.forOrganization(organization)?.hasFullAccess())) {
|
|
66
|
+
throw new SimpleError({
|
|
67
|
+
code: "permission_denied",
|
|
68
|
+
message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
if (editUser.permissions) {
|
|
73
|
+
editUser.permissions.patchOrPut(request.body.permissions)
|
|
74
|
+
} else {
|
|
75
|
+
editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (editUser.permissions && editUser.permissions.isEmpty) {
|
|
79
|
+
editUser.permissions = null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (editUser.id === user.id && !editUser.permissions?.platform?.hasFullAccess()) {
|
|
83
|
+
throw new SimpleError({
|
|
84
|
+
code: "permission_denied",
|
|
85
|
+
message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (editUser.id == user.id && request.body.password) {
|
|
93
|
+
// password changes
|
|
94
|
+
await editUser.changePassword(request.body.password)
|
|
95
|
+
await PasswordToken.clearFor(editUser.id)
|
|
96
|
+
await Token.clearFor(editUser.id, token.accessToken)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await editUser.save();
|
|
100
|
+
|
|
101
|
+
if (await Context.auth.canEditUserEmail(editUser)) {
|
|
102
|
+
if (request.body.email && request.body.email !== editUser.email) {
|
|
103
|
+
// Create an validation code
|
|
104
|
+
// 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(editUser, request.body.email)
|
|
106
|
+
code.send(editUser, organization, request.i18n, editUser.id === user.id)
|
|
107
|
+
|
|
108
|
+
throw new SimpleError({
|
|
109
|
+
code: "verify_email",
|
|
110
|
+
message: "Your email address needs verification",
|
|
111
|
+
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.",
|
|
112
|
+
meta: SignupResponse.create({
|
|
113
|
+
token: code.token,
|
|
114
|
+
}).encode({ version: request.request.getVersion() }),
|
|
115
|
+
statusCode: 403
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return new Response(UserStruct.create({...editUser, hasAccount: editUser.hasAccount()}));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { EmailVerificationCode } from '@stamhoofd/models';
|
|
4
|
+
import { PollEmailVerificationRequest, PollEmailVerificationResponse } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { Context } from '../../helpers/Context';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = PollEmailVerificationRequest;
|
|
11
|
+
type ResponseBody = PollEmailVerificationResponse;
|
|
12
|
+
|
|
13
|
+
export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
bodyDecoder = PollEmailVerificationRequest as Decoder<PollEmailVerificationRequest>;
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "POST") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/verify-email/poll", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
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
|
+
|
|
33
|
+
return new Response(PollEmailVerificationResponse.create({
|
|
34
|
+
valid
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { EmailVerificationCode } from '@stamhoofd/models';
|
|
4
|
+
import { PollEmailVerificationRequest, PollEmailVerificationResponse } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { Context } from '../../helpers/Context';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = PollEmailVerificationRequest;
|
|
11
|
+
type ResponseBody = PollEmailVerificationResponse;
|
|
12
|
+
|
|
13
|
+
export class PollEmailVerificationEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
bodyDecoder = PollEmailVerificationRequest as Decoder<PollEmailVerificationRequest>;
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "POST") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/verify-email/retry", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
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
|
+
|
|
33
|
+
if (valid) {
|
|
34
|
+
EmailVerificationCode.resend(organization, request.body.token, request.i18n).catch(console.error)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new Response(PollEmailVerificationResponse.create({
|
|
38
|
+
valid
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Email } from '@stamhoofd/email';
|
|
5
|
+
import { EmailVerificationCode, PasswordToken, User } from '@stamhoofd/models';
|
|
6
|
+
import { NewUser, SignupResponse } from "@stamhoofd/structures";
|
|
7
|
+
|
|
8
|
+
import { Context } from '../../helpers/Context';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type Body = NewUser;
|
|
13
|
+
type ResponseBody = SignupResponse;
|
|
14
|
+
|
|
15
|
+
export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
16
|
+
bodyDecoder = NewUser as Decoder<NewUser>;
|
|
17
|
+
|
|
18
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
19
|
+
if (request.method != "POST") {
|
|
20
|
+
return [false];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const params = Endpoint.parseParameters(request.url, "/sign-up", {});
|
|
24
|
+
|
|
25
|
+
if (params) {
|
|
26
|
+
return [true, params as Params];
|
|
27
|
+
}
|
|
28
|
+
return [false];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
32
|
+
const organization = await Context.setUserOrganizationScope()
|
|
33
|
+
|
|
34
|
+
const u = await User.getForRegister(organization?.id ?? null, request.body.email)
|
|
35
|
+
|
|
36
|
+
// Don't optimize. Always run two queries atm.
|
|
37
|
+
let user = await User.register(
|
|
38
|
+
organization,
|
|
39
|
+
request.body
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
let sendCode = true
|
|
43
|
+
|
|
44
|
+
if (!user) {
|
|
45
|
+
if (!u) {
|
|
46
|
+
// Fail silently because user did exist, and we don't want to expose that the user doesn't exists
|
|
47
|
+
console.error("Could not register, but user doesn't exist: "+request.body.email)
|
|
48
|
+
|
|
49
|
+
throw new SimpleError({
|
|
50
|
+
code: "unexpected_error",
|
|
51
|
+
message: "Something went wrong",
|
|
52
|
+
human: "Er ging iets mis",
|
|
53
|
+
statusCode: 500
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
user = u
|
|
58
|
+
|
|
59
|
+
if (u.hasAccount()) {
|
|
60
|
+
// Send an e-mail to say you already have an account + follow password forgot flow
|
|
61
|
+
const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n)
|
|
62
|
+
const { from, replyTo } = {
|
|
63
|
+
from: (user.permissions || !organization ? Email.getInternalEmailFor(request.i18n) : organization.getStrongEmail(request.i18n)),
|
|
64
|
+
replyTo: undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const footer = (!user.permissions && organization ? "\n\n—\n\nOnze ledenadministratie werkt via het Stamhoofd platform, op maat van verenigingen. Probeer het ook via https://"+request.i18n.$t("shared.domains.marketing")+"/ledenadministratie\n\n" : '')
|
|
68
|
+
|
|
69
|
+
const name = organization ? organization.name : 'Stamhoofd'
|
|
70
|
+
// Send email
|
|
71
|
+
Email.send({
|
|
72
|
+
from,
|
|
73
|
+
replyTo,
|
|
74
|
+
to: user.email,
|
|
75
|
+
subject: `[${name}] Je hebt al een account`,
|
|
76
|
+
type: "transactional",
|
|
77
|
+
text: (user.firstName ? "Hey "+user.firstName : "Hey") + ", \n\nJe probeerde een account aan te maken, maar je hebt eigenlijk al een account met e-mailadres "+user.email+". Als je jouw wachtwoord niet meer weet, kan je een nieuw wachtwoord instellen door op de volgende link te klikken of door deze te kopiëren in de adresbalk van jouw browser:\n"+recoveryUrl+"\n\nWachtwoord al teruggevonden of heb je helemaal niet proberen te registreren? Dan mag je deze e-mail veilig negeren.\n\nMet vriendelijke groeten,\n"+(user.permissions ? "Stamhoofd" : name)+footer
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Don't send the code
|
|
81
|
+
sendCode = false
|
|
82
|
+
} else {
|
|
83
|
+
// This is safe, because we are the first one. There is no password yet.
|
|
84
|
+
// If a hacker tries this, he won't be able to sign in, because he needs to
|
|
85
|
+
// verify the e-mail first (same as if the user didn't exist)
|
|
86
|
+
// If we didn't set the password, we would allow a different kind of attack:
|
|
87
|
+
// 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
|
|
88
|
+
// user clicks on second e-mail -> this sets the hackers password instead
|
|
89
|
+
user.verified = false
|
|
90
|
+
await user.changePassword(request.body.password)
|
|
91
|
+
await PasswordToken.clearFor(user.id)
|
|
92
|
+
await user.save()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 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
|
|
97
|
+
const code = await EmailVerificationCode.createFor(user, user.email)
|
|
98
|
+
|
|
99
|
+
if (sendCode) {
|
|
100
|
+
code.send(user, organization, request.i18n)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new Response(SignupResponse.create({
|
|
104
|
+
token: code.token
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { EmailVerificationCode, Token, User } from '@stamhoofd/models';
|
|
5
|
+
import { Token as TokenStruct, VerifyEmailRequest } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from '../../helpers/Context';
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Query = undefined;
|
|
11
|
+
type Body = VerifyEmailRequest;
|
|
12
|
+
type ResponseBody = TokenStruct;
|
|
13
|
+
|
|
14
|
+
export class VerifyEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
bodyDecoder = VerifyEmailRequest as Decoder<VerifyEmailRequest>;
|
|
16
|
+
|
|
17
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
18
|
+
if (request.method != "POST") {
|
|
19
|
+
return [false];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const params = Endpoint.parseParameters(request.url, "/verify-email", {});
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
32
|
+
|
|
33
|
+
const code = await EmailVerificationCode.verify(organization?.id ?? null, request.body.token, request.body.code)
|
|
34
|
+
|
|
35
|
+
if (!code) {
|
|
36
|
+
throw new SimpleError({
|
|
37
|
+
code: "invalid_code",
|
|
38
|
+
message: "This code is invalid",
|
|
39
|
+
human: "Deze code is ongeldig of vervallen.",
|
|
40
|
+
statusCode: 400
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const user = await User.getByID(code.userId)
|
|
45
|
+
|
|
46
|
+
if (!user || (user.organizationId !== null && user.organizationId !== (organization?.id ?? null))) {
|
|
47
|
+
throw new SimpleError({
|
|
48
|
+
code: "invalid_code",
|
|
49
|
+
message: "This code is invalid",
|
|
50
|
+
human: "Deze code is ongeldig of vervallen.",
|
|
51
|
+
statusCode: 400
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (user.email != code.email) {
|
|
56
|
+
const other = await User.getForAuthentication(user.organizationId, code.email, {allowWithoutAccount: true})
|
|
57
|
+
|
|
58
|
+
if (other) {
|
|
59
|
+
// Delete the other user, but merge data
|
|
60
|
+
await user.merge(other);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// change user email
|
|
64
|
+
user.email = code.email
|
|
65
|
+
|
|
66
|
+
// If already in use: will fail, so will verification
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
user.verified = true
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await user.save()
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Duplicate key probably
|
|
75
|
+
if (e.code && e.code == "ER_DUP_ENTRY") {
|
|
76
|
+
throw new SimpleError({
|
|
77
|
+
code: "email_in_use",
|
|
78
|
+
message: "This e-mail is already in use, we cannot set it",
|
|
79
|
+
human: "We kunnen het e-mailadres van deze gebruiker niet instellen naar "+code.email+", omdat die al in gebruik is. Waarschijnlijk heb je meerdere accounts. Probeer met dat e-mailadres in te loggen of contacteer ons ("+request.$t("shared.emails.general")+") als we de gebruikers moeten combineren tot één gebruiker."
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const token = await Token.createToken(user);
|
|
86
|
+
const st = new TokenStruct(token);
|
|
87
|
+
return new Response(st);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { City } from '@stamhoofd/models';
|
|
4
|
+
import { Province } from '@stamhoofd/models';
|
|
5
|
+
import { City as CityStruct, Country, Province as ProvinceStruct,SearchRegions } from "@stamhoofd/structures";
|
|
6
|
+
import { StringCompare } from '@stamhoofd/utility';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
class Query extends AutoEncoder {
|
|
10
|
+
@field({ decoder: StringDecoder })
|
|
11
|
+
query: string
|
|
12
|
+
}
|
|
13
|
+
type Body = undefined;
|
|
14
|
+
type ResponseBody = SearchRegions;
|
|
15
|
+
|
|
16
|
+
export class SearchRegionsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
17
|
+
queryDecoder = Query as Decoder<Query>
|
|
18
|
+
|
|
19
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
20
|
+
if (request.method != "GET") {
|
|
21
|
+
return [false];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const params = Endpoint.parseParameters(request.url, "/address/search", {});
|
|
25
|
+
|
|
26
|
+
if (params) {
|
|
27
|
+
return [true, params as Params];
|
|
28
|
+
}
|
|
29
|
+
return [false];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
33
|
+
// Escape query
|
|
34
|
+
const query = request.query.query.replace(/([-+><()~*"@\s]+)/g, " ").replace(/[^\w\d]+$/, "")
|
|
35
|
+
if (query.length == 0) {
|
|
36
|
+
// Do not try searching...
|
|
37
|
+
return new Response(SearchRegions.create({
|
|
38
|
+
cities: [],
|
|
39
|
+
provinces: [],
|
|
40
|
+
countries: [],
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const match = {
|
|
45
|
+
sign: "MATCH",
|
|
46
|
+
value: query + "*", // We replace special operators in boolean mode with spaces since special characters aren't indexed anyway
|
|
47
|
+
mode: "BOOLEAN"
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// We had to add an order by in the query to fix the limit. MySQL doesn't want to limit the results correctly if we don't explicitly sort the results on their relevance
|
|
51
|
+
const cities = await City.where({ name: match }, {
|
|
52
|
+
limit: 5,
|
|
53
|
+
sort: [
|
|
54
|
+
{
|
|
55
|
+
column: { name: match },
|
|
56
|
+
direction: "DESC"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
const loadedCities: (City & { province: Province })[] = []
|
|
61
|
+
|
|
62
|
+
// We had to add an order by in the query to fix the limit. MySQL doesn't want to limit the results correctly if we don't explicitly sort the results on their relevance
|
|
63
|
+
const allProvinces = await Province.all();
|
|
64
|
+
|
|
65
|
+
for (const city of cities) {
|
|
66
|
+
const province = allProvinces.find(p => p.id == city.provinceId);
|
|
67
|
+
if (!province) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
loadedCities.push(city.setRelation(City.province, province))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const provinces: Province[] = []
|
|
74
|
+
for (const province of allProvinces) {
|
|
75
|
+
if (StringCompare.typoCount(request.query.query, province.name) < 3) {
|
|
76
|
+
provinces.push(province)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const countries: Country[] = []
|
|
81
|
+
if (StringCompare.typoCount(request.query.query, "België") < 3) {
|
|
82
|
+
countries.push(Country.Belgium)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (StringCompare.typoCount(request.query.query, "Nederland") < 3) {
|
|
86
|
+
countries.push(Country.Netherlands)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return new Response(SearchRegions.create({
|
|
90
|
+
cities: loadedCities.map(c => CityStruct.create(Object.assign({...c}, { province: ProvinceStruct.create(c.province) }))),
|
|
91
|
+
provinces: provinces.map(c => ProvinceStruct.create(c)),
|
|
92
|
+
countries: countries
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Address, ValidatedAddress } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { AddressValidator } from '../../../helpers/AddressValidator';
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = Address
|
|
10
|
+
type ResponseBody = ValidatedAddress;
|
|
11
|
+
|
|
12
|
+
export class ValidateAddressEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
bodyDecoder = Address as Decoder<Address>
|
|
14
|
+
|
|
15
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
16
|
+
if (request.method != "POST") {
|
|
17
|
+
return [false];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const params = Endpoint.parseParameters(request.url, "/address/validate", {});
|
|
21
|
+
|
|
22
|
+
if (params) {
|
|
23
|
+
return [true, params as Params];
|
|
24
|
+
}
|
|
25
|
+
return [false];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
29
|
+
return new Response(await AddressValidator.validate(request.body));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AutoEncoder, Decoder, field, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Organization, Webshop } from '@stamhoofd/models';
|
|
5
|
+
type Params = Record<string, never>;
|
|
6
|
+
|
|
7
|
+
class Query extends AutoEncoder {
|
|
8
|
+
@field({ decoder: StringDecoder })
|
|
9
|
+
domain: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Body = undefined
|
|
13
|
+
type ResponseBody = undefined;
|
|
14
|
+
|
|
15
|
+
export class CheckDomainCertEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
16
|
+
queryDecoder = Query as Decoder<Query>;
|
|
17
|
+
|
|
18
|
+
registrationDomains = [... new Set(Object.values(STAMHOOFD.domains.registration ?? {}))]
|
|
19
|
+
|
|
20
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
21
|
+
if (request.method != "GET") {
|
|
22
|
+
return [false];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const params = Endpoint.parseParameters(request.url, "/check-domain-cert", {});
|
|
26
|
+
|
|
27
|
+
if (params) {
|
|
28
|
+
return [true, params as Params];
|
|
29
|
+
}
|
|
30
|
+
return [false];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
34
|
+
// check if the domain ends on one of our localized registration domains
|
|
35
|
+
for (const domain of this.registrationDomains) {
|
|
36
|
+
if (request.query.domain.endsWith("." + domain)) {
|
|
37
|
+
const strippped = request.query.domain.substr(0, request.query.domain.length - ("." + domain).length )
|
|
38
|
+
if (strippped.includes(".")) {
|
|
39
|
+
throw new SimpleError({
|
|
40
|
+
code: "invalid_domain",
|
|
41
|
+
message: "This domain format is not supported",
|
|
42
|
+
statusCode: 404
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Search for the URI
|
|
47
|
+
const organization = await Organization.getByURI(strippped)
|
|
48
|
+
|
|
49
|
+
if (!organization) {
|
|
50
|
+
throw new SimpleError({
|
|
51
|
+
code: "unknown_domain",
|
|
52
|
+
message: "Not known",
|
|
53
|
+
statusCode: 404
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
return new Response(undefined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (STAMHOOFD.domains.legacyWebshop && request.query.domain.endsWith("." + STAMHOOFD.domains.legacyWebshop)) {
|
|
61
|
+
const strippped = request.query.domain.substr(0, request.query.domain.length - ("." + STAMHOOFD.domains.legacyWebshop).length )
|
|
62
|
+
if (strippped.includes(".")) {
|
|
63
|
+
throw new SimpleError({
|
|
64
|
+
code: "invalid_domain",
|
|
65
|
+
message: "This domain format is not supported",
|
|
66
|
+
statusCode: 404
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Search for the URI
|
|
71
|
+
const organization = await Organization.getByURI(strippped)
|
|
72
|
+
|
|
73
|
+
if (!organization) {
|
|
74
|
+
throw new SimpleError({
|
|
75
|
+
code: "unknown_domain",
|
|
76
|
+
message: "Not known",
|
|
77
|
+
statusCode: 404
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
return new Response(undefined);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if we have an organization with a custom domain name
|
|
84
|
+
const organization = await Organization.getByRegisterDomain(request.query.domain)
|
|
85
|
+
|
|
86
|
+
if (organization) {
|
|
87
|
+
return new Response(undefined);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const webshops = await Webshop.getByDomainOnly(request.query.domain)
|
|
91
|
+
if (webshops.length > 0) {
|
|
92
|
+
return new Response(undefined);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new SimpleError({
|
|
96
|
+
code: "unknown_domain",
|
|
97
|
+
message: "Not known",
|
|
98
|
+
statusCode: 404
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { EmailAddress } from '@stamhoofd/email';
|
|
5
|
+
import { Organization } from '@stamhoofd/models';
|
|
6
|
+
import { EmailAddressSettings, OrganizationSimple } from '@stamhoofd/structures';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Body = undefined;
|
|
10
|
+
|
|
11
|
+
class Query extends AutoEncoder {
|
|
12
|
+
@field({ decoder: StringDecoder })
|
|
13
|
+
id: string
|
|
14
|
+
|
|
15
|
+
@field({ decoder: StringDecoder })
|
|
16
|
+
token: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ResponseBody = EmailAddressSettings;
|
|
20
|
+
|
|
21
|
+
export class ManageEmailAddressEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
22
|
+
queryDecoder = Query as Decoder<Query>;
|
|
23
|
+
|
|
24
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
25
|
+
if (request.method != "GET") {
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const params = Endpoint.parseParameters(request.url, "/email/manage", {});
|
|
30
|
+
|
|
31
|
+
if (params) {
|
|
32
|
+
return [true, params as Params];
|
|
33
|
+
}
|
|
34
|
+
return [false];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
38
|
+
const email = await EmailAddress.getByID(request.query.id)
|
|
39
|
+
if (!email || email.token !== request.query.token || request.query.token.length < 10 || request.query.id.length < 10) {
|
|
40
|
+
throw new SimpleError({
|
|
41
|
+
code: "invalid_fields",
|
|
42
|
+
message: "Invalid token or id",
|
|
43
|
+
human: "Deze link is vervallen. Probeer het opnieuw in een recentere e-mail"
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const organization = email.organizationId ? (await Organization.getByID(email.organizationId)) : undefined
|
|
48
|
+
return new Response(EmailAddressSettings.create({
|
|
49
|
+
...email,
|
|
50
|
+
organization: organization ? OrganizationSimple.create(organization) : null
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|