@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,196 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
|
+
import { DNSRecord, DNSRecordType, Organization as OrganizationStruct,OrganizationDomains } from "@stamhoofd/structures";
|
|
5
|
+
import NodeRSA from 'node-rsa';
|
|
6
|
+
|
|
7
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
8
|
+
import { Context } from '../../../../helpers/Context';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type Body = OrganizationDomains;
|
|
13
|
+
type ResponseBody = OrganizationStruct
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One endpoint to create, patch and delete groups. Usefull because on organization setup, we need to create multiple groups at once. Also, sometimes we need to link values and update multiple groups at once
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export class SetOrganizationDomainEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
20
|
+
bodyDecoder = OrganizationDomains as Decoder<OrganizationDomains>
|
|
21
|
+
|
|
22
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
23
|
+
if (request.method != "POST") {
|
|
24
|
+
return [false];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const params = Endpoint.parseParameters(request.url, "/organization/domain", {});
|
|
28
|
+
|
|
29
|
+
if (params) {
|
|
30
|
+
return [true, params as Params];
|
|
31
|
+
}
|
|
32
|
+
return [false];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
36
|
+
const organization = await Context.setOrganizationScope();
|
|
37
|
+
await Context.authenticate()
|
|
38
|
+
|
|
39
|
+
if (!await Context.auth.canManageOrganizationDomain(organization.id)) {
|
|
40
|
+
throw Context.auth.error()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const errors = new SimpleErrors()
|
|
44
|
+
|
|
45
|
+
// check if changed
|
|
46
|
+
if (
|
|
47
|
+
(organization.privateMeta.pendingRegisterDomain ?? organization.registerDomain) !== request.body.registerDomain // changed register domain
|
|
48
|
+
||
|
|
49
|
+
(organization.privateMeta.pendingMailDomain ?? organization.privateMeta.mailDomain) !== request.body.mailDomain // changed pending domain
|
|
50
|
+
) {
|
|
51
|
+
console.log("Domains changed")
|
|
52
|
+
|
|
53
|
+
// Validate domains
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
55
|
+
if (request.body.registerDomain !== null && !request.body.registerDomain.match(/^([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-]+\.[a-zA-Z]+$/)) {
|
|
56
|
+
throw new SimpleError({
|
|
57
|
+
code: "invalid_domain",
|
|
58
|
+
message: "registerDomain is invalid",
|
|
59
|
+
human: "De subdomeinnaam voor jouw registratiepagina is ongeldig",
|
|
60
|
+
field: "registerDomain"
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
65
|
+
if (request.body.mailDomain !== null && !request.body.mailDomain.match(/^([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-]+\.[a-zA-Z]+$/)) {
|
|
66
|
+
throw new SimpleError({
|
|
67
|
+
code: "invalid_domain",
|
|
68
|
+
message: "mailDomain is invalid",
|
|
69
|
+
human: "De domeinnaam voor e-mails is ongeldig",
|
|
70
|
+
field: "mailDomain"
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const oldMailDomain = organization.privateMeta.mailDomain
|
|
75
|
+
|
|
76
|
+
organization.privateMeta.pendingRegisterDomain = request.body.registerDomain?.toLowerCase() ?? null
|
|
77
|
+
organization.privateMeta.pendingMailDomain = request.body.mailDomain?.toLowerCase() ?? null
|
|
78
|
+
|
|
79
|
+
// We don't keep the current register domain because we have no way to validate the old DNS-records
|
|
80
|
+
if (organization.privateMeta.pendingRegisterDomain === null || organization.registerDomain !== organization.privateMeta.pendingRegisterDomain) {
|
|
81
|
+
organization.registerDomain = null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// We don't keep the current mail domain because we have no way to validate the old DNS-records
|
|
85
|
+
if (organization.privateMeta.pendingMailDomain === null || organization.privateMeta.mailDomain !== organization.privateMeta.pendingMailDomain) {
|
|
86
|
+
organization.privateMeta.mailDomain = null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Always temporary disable mail domain until validated
|
|
90
|
+
organization.privateMeta.mailDomainActive = false
|
|
91
|
+
|
|
92
|
+
// Reset notification counters
|
|
93
|
+
organization.serverMeta.DNSRecordWarningCount = 0
|
|
94
|
+
organization.serverMeta.firstInvalidDNSRecords = undefined
|
|
95
|
+
organization.serverMeta.didSendDomainSetupMail = false
|
|
96
|
+
|
|
97
|
+
// Generate new DNS-records
|
|
98
|
+
organization.privateMeta.dnsRecords = []
|
|
99
|
+
|
|
100
|
+
if (organization.privateMeta.pendingMailDomain !== null) {
|
|
101
|
+
const defaultFromDomain = "stamhoofd." + organization.privateMeta.pendingMailDomain;
|
|
102
|
+
if (organization.privateMeta.pendingRegisterDomain === null || !organization.privateMeta.pendingRegisterDomain.endsWith('.' + organization.privateMeta.pendingMailDomain)) {
|
|
103
|
+
// We set a custom domainname for webshops already
|
|
104
|
+
// This is not used at this moment
|
|
105
|
+
organization.privateMeta.mailFromDomain = defaultFromDomain;
|
|
106
|
+
} else {
|
|
107
|
+
// CNAME domain: for SPF + MX + A record
|
|
108
|
+
organization.privateMeta.mailFromDomain = organization.privateMeta.pendingRegisterDomain;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (organization.privateMeta.mailFromDomain !== organization.privateMeta.pendingRegisterDomain) {
|
|
112
|
+
|
|
113
|
+
organization.privateMeta.dnsRecords.push(DNSRecord.create({
|
|
114
|
+
type: DNSRecordType.CNAME,
|
|
115
|
+
name: organization.privateMeta.mailFromDomain + ".",
|
|
116
|
+
// Use shops for mail domain, to allow reuse
|
|
117
|
+
value: STAMHOOFD.domains.webshopCname + "."
|
|
118
|
+
}))
|
|
119
|
+
|
|
120
|
+
if (STAMHOOFD.domains.registration && organization.privateMeta.pendingRegisterDomain) {
|
|
121
|
+
organization.privateMeta.dnsRecords.push(DNSRecord.create({
|
|
122
|
+
type: DNSRecordType.CNAME,
|
|
123
|
+
name: organization.privateMeta.pendingRegisterDomain+".",
|
|
124
|
+
// Use registration domain
|
|
125
|
+
value: STAMHOOFD.domains.registrationCname + "."
|
|
126
|
+
}))
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
organization.privateMeta.dnsRecords.push(DNSRecord.create({
|
|
130
|
+
type: DNSRecordType.CNAME,
|
|
131
|
+
name: organization.privateMeta.mailFromDomain+".",
|
|
132
|
+
// Use registration domain
|
|
133
|
+
value: STAMHOOFD.domains.registrationCname + "."
|
|
134
|
+
}))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (request.body.mailDomain !== null) {
|
|
139
|
+
|
|
140
|
+
let priv: string
|
|
141
|
+
let pub: string
|
|
142
|
+
|
|
143
|
+
if (!organization.serverMeta.privateDKIMKey || !organization.serverMeta.publicDKIMKey ) {
|
|
144
|
+
const key = new NodeRSA({ b: request.body.useDkim1024bit ? 1024 : 2048 });
|
|
145
|
+
const privArr = (key.exportKey('private') as string).split("\n")
|
|
146
|
+
priv = privArr.splice(1, privArr.length - 2).join("");
|
|
147
|
+
|
|
148
|
+
const pubArr = (key.exportKey('public') as string).split("\n")
|
|
149
|
+
pub = pubArr.splice(1, pubArr.length - 2).join("");
|
|
150
|
+
|
|
151
|
+
organization.serverMeta.privateDKIMKey = priv
|
|
152
|
+
organization.serverMeta.publicDKIMKey = pub
|
|
153
|
+
} else {
|
|
154
|
+
priv = organization.serverMeta.privateDKIMKey
|
|
155
|
+
pub = organization.serverMeta.publicDKIMKey
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// DKIM records
|
|
159
|
+
organization.privateMeta.dnsRecords.push(DNSRecord.create({
|
|
160
|
+
type: DNSRecordType.TXT,
|
|
161
|
+
name: "stamhoofd._domainkey." + request.body.mailDomain + ".",
|
|
162
|
+
value: "v=DKIM1; k=rsa; p=" + pub + ""
|
|
163
|
+
}))
|
|
164
|
+
} else {
|
|
165
|
+
if (oldMailDomain) {
|
|
166
|
+
organization.deleteAWSMailIdenitity(oldMailDomain).catch(console.error)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (organization.serverMeta.privateDKIMKey && organization.serverMeta.publicDKIMKey ) {
|
|
170
|
+
// Allow creation of new keys for new domains
|
|
171
|
+
console.log("Backup DKIM keys for "+organization.id)
|
|
172
|
+
console.log("Private: "+organization.serverMeta.privateDKIMKey)
|
|
173
|
+
console.log("Public: "+organization.serverMeta.publicDKIMKey)
|
|
174
|
+
|
|
175
|
+
// Delete keys if mail domain is deleted -> to allow new keys
|
|
176
|
+
organization.serverMeta.privateDKIMKey = undefined
|
|
177
|
+
organization.serverMeta.publicDKIMKey = undefined
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await organization.save()
|
|
182
|
+
|
|
183
|
+
} else {
|
|
184
|
+
// Validate DNS-records if not empty
|
|
185
|
+
console.log("Validating domains")
|
|
186
|
+
await organization.updateDNSRecords()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log("Done.")
|
|
190
|
+
|
|
191
|
+
errors.throwIfNotEmpty()
|
|
192
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { OpenIDClientConfiguration } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { Context } from '../../../../helpers/Context';
|
|
6
|
+
import { OpenIDConnectHelper } from '../../../../helpers/OpenIDConnectHelper';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = OpenIDClientConfiguration;
|
|
11
|
+
type ResponseBody = OpenIDClientConfiguration
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One endpoint to create, patch and delete groups. Usefull because on organization setup, we need to create multiple groups at once. Also, sometimes we need to link values and update multiple groups at once
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class SetOrganizationSSOEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
18
|
+
bodyDecoder = OpenIDClientConfiguration as Decoder<OpenIDClientConfiguration>
|
|
19
|
+
|
|
20
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
21
|
+
if (request.method != "POST") {
|
|
22
|
+
return [false];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const params = Endpoint.parseParameters(request.url, "/organization/sso", {});
|
|
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
|
+
const organization = await Context.setOrganizationScope();
|
|
35
|
+
await Context.authenticate()
|
|
36
|
+
|
|
37
|
+
if (!await Context.auth.canManageSSOSettings(organization.id)) {
|
|
38
|
+
throw Context.auth.error()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Validate configuration
|
|
42
|
+
const helper = new OpenIDConnectHelper(organization, request.body)
|
|
43
|
+
await helper.getClient()
|
|
44
|
+
|
|
45
|
+
organization.serverMeta.ssoConfiguration = request.body
|
|
46
|
+
await organization.save()
|
|
47
|
+
|
|
48
|
+
return new Response(request.body);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
3
|
+
import { BalanceItem, Group, Member } from "@stamhoofd/models";
|
|
4
|
+
import { MemberBalanceItem } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { Context } from "../../../../helpers/Context";
|
|
7
|
+
|
|
8
|
+
type Params = { id: string };
|
|
9
|
+
type Query = undefined
|
|
10
|
+
type Body = undefined
|
|
11
|
+
type ResponseBody = MemberBalanceItem[]
|
|
12
|
+
|
|
13
|
+
export class GetMemberBalanceEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
15
|
+
if (request.method != "GET") {
|
|
16
|
+
return [false];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const params = Endpoint.parseParameters(request.url, "/organization/members/@id/balance", { id: String});
|
|
20
|
+
|
|
21
|
+
if (params) {
|
|
22
|
+
return [true, params as Params];
|
|
23
|
+
}
|
|
24
|
+
return [false];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
28
|
+
const organization = await Context.setOrganizationScope();
|
|
29
|
+
await Context.authenticate()
|
|
30
|
+
|
|
31
|
+
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
32
|
+
throw Context.auth.error()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const member = (await Member.getWithRegistrations(request.params.id))
|
|
36
|
+
|
|
37
|
+
if (!member || !await Context.auth.hasFinancialMemberAccess(member)) {
|
|
38
|
+
throw Context.auth.notFoundOrNoAccess("Geen lid gevonden, of je hebt geen toegang tot dit lid")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get all balance items for this member or users
|
|
42
|
+
const balanceItems = await BalanceItem.balanceItemsForUsersAndMembers(organization.id, member.users.map(u => u.id), [member.id])
|
|
43
|
+
|
|
44
|
+
return new Response(
|
|
45
|
+
await BalanceItem.getMemberStructure(balanceItems)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { AutoEncoder, Data, DateDecoder, Decoder, EnumDecoder, field, IntegerDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Organization, Payment } from "@stamhoofd/models";
|
|
4
|
+
import { PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
7
|
+
import { Context } from "../../../../helpers/Context";
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Body = undefined
|
|
11
|
+
type ResponseBody = PaymentGeneral[]
|
|
12
|
+
|
|
13
|
+
export class StringArrayDecoder<T> implements Decoder<T[]> {
|
|
14
|
+
decoder: Decoder<T>;
|
|
15
|
+
|
|
16
|
+
constructor(decoder: Decoder<T>) {
|
|
17
|
+
this.decoder = decoder;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
decode(data: Data): T[] {
|
|
21
|
+
const strValue = data.string;
|
|
22
|
+
|
|
23
|
+
// Split on comma
|
|
24
|
+
const parts = strValue.split(",");
|
|
25
|
+
return parts
|
|
26
|
+
.map((v, index) => {
|
|
27
|
+
return data.clone({
|
|
28
|
+
data: v,
|
|
29
|
+
context: data.context,
|
|
30
|
+
field: data.addToCurrentField(index)
|
|
31
|
+
}).decode(this.decoder)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class StringNullableDecoder<T> implements Decoder<T | null> {
|
|
37
|
+
decoder: Decoder<T>;
|
|
38
|
+
|
|
39
|
+
constructor(decoder: Decoder<T>) {
|
|
40
|
+
this.decoder = decoder;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
decode(data: Data): T | null {
|
|
44
|
+
if (data.value === 'null') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return data.decode(this.decoder);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Query extends AutoEncoder {
|
|
54
|
+
/**
|
|
55
|
+
* Usage in combination with paidSince is special!
|
|
56
|
+
*/
|
|
57
|
+
@field({ decoder: StringDecoder, optional: true })
|
|
58
|
+
afterId?: string
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Return all payments that were paid after (and including) this date.
|
|
62
|
+
* Only returns orders **equal** to this date if afterId is not provided or if the id of those payments is also higher.
|
|
63
|
+
*/
|
|
64
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
65
|
+
paidSince?: Date
|
|
66
|
+
|
|
67
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
68
|
+
paidBefore?: Date
|
|
69
|
+
|
|
70
|
+
@field({ decoder: IntegerDecoder, optional: true })
|
|
71
|
+
limit?: number
|
|
72
|
+
|
|
73
|
+
@field({ decoder: new StringArrayDecoder(new EnumDecoder(PaymentMethod)), optional: true })
|
|
74
|
+
methods?: PaymentMethod[]
|
|
75
|
+
|
|
76
|
+
@field({ decoder: new StringArrayDecoder(new StringNullableDecoder(new EnumDecoder(PaymentProvider))), optional: true })
|
|
77
|
+
providers?: (PaymentProvider|null)[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
81
|
+
protected queryDecoder = Query;
|
|
82
|
+
|
|
83
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
84
|
+
if (request.method != "GET") {
|
|
85
|
+
return [false];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const params = Endpoint.parseParameters(request.url, "/organization/payments", {});
|
|
89
|
+
|
|
90
|
+
if (params) {
|
|
91
|
+
return [true, params as Params];
|
|
92
|
+
}
|
|
93
|
+
return [false];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
97
|
+
const organization = await Context.setOrganizationScope();
|
|
98
|
+
await Context.authenticate()
|
|
99
|
+
|
|
100
|
+
if (!await Context.auth.canManagePayments(organization.id)) {
|
|
101
|
+
throw Context.auth.error()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Response(
|
|
105
|
+
(await this.getPayments(organization, request.query))
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getPayments(organization: Organization, query: Query) {
|
|
110
|
+
const paidSince = query.paidSince ?? new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
111
|
+
paidSince.setMilliseconds(0)
|
|
112
|
+
const payments: Payment[] = []
|
|
113
|
+
|
|
114
|
+
if (query.afterId) {
|
|
115
|
+
// First return all payments with id > afterId and paidAt == paidSince
|
|
116
|
+
payments.push(...await Payment.where({
|
|
117
|
+
organizationId: organization.id,
|
|
118
|
+
paidAt: {
|
|
119
|
+
sign: '=',
|
|
120
|
+
value: paidSince
|
|
121
|
+
},
|
|
122
|
+
id: {
|
|
123
|
+
sign: '>',
|
|
124
|
+
value: query.afterId ?? ""
|
|
125
|
+
},
|
|
126
|
+
method: {
|
|
127
|
+
sign: 'IN',
|
|
128
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
129
|
+
},
|
|
130
|
+
provider: {
|
|
131
|
+
sign: 'IN',
|
|
132
|
+
value: query.providers ?? [null]
|
|
133
|
+
}
|
|
134
|
+
}, {
|
|
135
|
+
limit: query.limit ?? undefined,
|
|
136
|
+
sort: [{
|
|
137
|
+
column: "id",
|
|
138
|
+
direction: "ASC"
|
|
139
|
+
}]
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
payments.push(...await Payment.where({
|
|
144
|
+
organizationId: organization.id,
|
|
145
|
+
paidAt: query.paidBefore ? [{
|
|
146
|
+
sign: query.afterId ? '>' : '>=',
|
|
147
|
+
value: paidSince
|
|
148
|
+
}, {
|
|
149
|
+
sign: '<=',
|
|
150
|
+
value: query.paidBefore
|
|
151
|
+
}] : {
|
|
152
|
+
sign: query.afterId ? '>' : '>=',
|
|
153
|
+
value: paidSince
|
|
154
|
+
},
|
|
155
|
+
method: {
|
|
156
|
+
sign: 'IN',
|
|
157
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
158
|
+
},
|
|
159
|
+
provider: {
|
|
160
|
+
sign: 'IN',
|
|
161
|
+
value: query.providers ?? [null]
|
|
162
|
+
}
|
|
163
|
+
}, {
|
|
164
|
+
limit: query.limit ? (query.limit - payments.length) : undefined,
|
|
165
|
+
sort: [{
|
|
166
|
+
column: "paidAt",
|
|
167
|
+
direction: "ASC"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
column: "id",
|
|
171
|
+
direction: "ASC"
|
|
172
|
+
}]
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if (!query.paidSince && !query.methods && !query.providers) {
|
|
177
|
+
// Default behaviour is to return all not-paid transfer payments that are not yet paid
|
|
178
|
+
|
|
179
|
+
payments.push(...
|
|
180
|
+
await Payment.where({
|
|
181
|
+
organizationId: organization.id,
|
|
182
|
+
paidAt: null,
|
|
183
|
+
method: PaymentMethod.Transfer,
|
|
184
|
+
status: {
|
|
185
|
+
sign: '!=',
|
|
186
|
+
value: PaymentStatus.Failed
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
payments.push(...
|
|
192
|
+
await Payment.where({
|
|
193
|
+
organizationId: organization.id,
|
|
194
|
+
paidAt: null,
|
|
195
|
+
updatedAt: {
|
|
196
|
+
sign: '>',
|
|
197
|
+
value: new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
198
|
+
},
|
|
199
|
+
method: PaymentMethod.Transfer,
|
|
200
|
+
status: PaymentStatus.Failed
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return await AuthenticatedStructures.paymentsGeneral(payments, true)
|
|
206
|
+
}
|
|
207
|
+
}
|