@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,1031 @@
|
|
|
1
|
+
import { AutoEncoderPatchType, PatchMap } from "@simonbackx/simple-encoding"
|
|
2
|
+
import { SimpleError } from "@simonbackx/simple-errors"
|
|
3
|
+
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Group, Member, MemberWithRegistrations, Order, Organization, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
4
|
+
import { AccessRight, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from "@stamhoofd/structures"
|
|
5
|
+
import { Formatter } from "@stamhoofd/utility"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One class with all the responsabilities of checking permissions to each resource in the system by a given user, possibly in an organization context.
|
|
9
|
+
* This helps when dependencies of permissions change, such as parent categories for groups
|
|
10
|
+
*/
|
|
11
|
+
export class AdminPermissionChecker {
|
|
12
|
+
organization: Organization|null
|
|
13
|
+
user: User
|
|
14
|
+
platform: PlatformStruct
|
|
15
|
+
|
|
16
|
+
organizationCache: Map<string, Organization|Promise<Organization|undefined>> = new Map()
|
|
17
|
+
organizationGroupsCache: Map<string, Group[]|Promise<Group[]>> = new Map()
|
|
18
|
+
|
|
19
|
+
constructor(user: User, platform: PlatformStruct, organization?: Organization,) {
|
|
20
|
+
this.user = user
|
|
21
|
+
this.platform = platform
|
|
22
|
+
|
|
23
|
+
if (user.organizationId && (!organization || organization.id !== user.organizationId)) {
|
|
24
|
+
throw new SimpleError({
|
|
25
|
+
code: 'invalid_scope',
|
|
26
|
+
message: 'Tried accessing a resource without an organization context, but this user is limited to the organization context',
|
|
27
|
+
statusCode: 403
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.organization = organization ?? null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getOrganization(id: string|Organization): Promise<Organization> {
|
|
35
|
+
if (this.organization && id === this.organization.id) {
|
|
36
|
+
return this.organization
|
|
37
|
+
}
|
|
38
|
+
if (typeof id === 'string') {
|
|
39
|
+
const c = this.organizationCache.get(id);
|
|
40
|
+
if (c) {
|
|
41
|
+
const result = await c;
|
|
42
|
+
if (!result) {
|
|
43
|
+
throw new Error('Unexpected missing organization in AdminPermissionChecker.getOrganization')
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
const promise = Organization.getByID(id)
|
|
48
|
+
this.organizationCache.set(id, promise)
|
|
49
|
+
const result = await promise;
|
|
50
|
+
if (!result) {
|
|
51
|
+
console.error('Unexpected missing organization in AdminPermissionChecker.getOrganization', id)
|
|
52
|
+
this.organizationCache.delete(id)
|
|
53
|
+
throw new Error('Unexpected missing organization in AdminPermissionChecker.getOrganization')
|
|
54
|
+
}
|
|
55
|
+
this.organizationCache.set(id, result)
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getOrganizationGroups(id: string) {
|
|
62
|
+
const c = this.organizationGroupsCache.get(id);
|
|
63
|
+
if (c) {
|
|
64
|
+
return await c;
|
|
65
|
+
}
|
|
66
|
+
const organization = await this.getOrganization(id)
|
|
67
|
+
const promise = Group.getAll(id, organization.periodId, true)
|
|
68
|
+
this.organizationGroupsCache.set(id, promise)
|
|
69
|
+
const result = await promise;
|
|
70
|
+
this.organizationGroupsCache.set(id, result)
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
error(message?: string): SimpleError {
|
|
75
|
+
return new SimpleError({
|
|
76
|
+
code: "permission_denied",
|
|
77
|
+
message: "You do not have permissions for this action",
|
|
78
|
+
human: message ?? 'Je hebt geen toegangsrechten voor deze actie',
|
|
79
|
+
statusCode: 403
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
notFoundOrNoAccess(message?: string): SimpleError {
|
|
84
|
+
return new SimpleError({
|
|
85
|
+
code: "not_found",
|
|
86
|
+
message: "Resource not found or no access",
|
|
87
|
+
human: message ?? 'Niet gevonden of geen toegang tot dit object',
|
|
88
|
+
statusCode: 404
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get platformPermissions() {
|
|
93
|
+
return this.user.permissions?.forPlatform(this.platform)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getOrganizationPermissions(organizationOrId: string|Organization) {
|
|
97
|
+
if (!this.user.permissions) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const organization = await this.getOrganization(organizationOrId)
|
|
101
|
+
|
|
102
|
+
const p = this.user.permissions.forOrganization(
|
|
103
|
+
organization,
|
|
104
|
+
this.platform
|
|
105
|
+
)
|
|
106
|
+
return p
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async canAccessPrivateOrganizationData(organization: Organization) {
|
|
110
|
+
if (!this.checkScope(organization.id)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!await this.hasSomeAccess(organization.id)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
checkScope(organizationId: string|null) {
|
|
121
|
+
if (organizationId) {
|
|
122
|
+
// If request is scoped to a different organization
|
|
123
|
+
if (this.organization && organizationId !== this.organization.id) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If user is limited to scope
|
|
128
|
+
if (this.user.organizationId && organizationId !== this.user.organizationId) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// User is limited to a scope
|
|
133
|
+
if (this.user.organizationId) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async canAccessGroup(group: Group, permissionLevel: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
|
|
142
|
+
// Check permissions aren't scoped to a specific organization, and they mismatch
|
|
143
|
+
if (!this.checkScope(group.organizationId)) {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (group.deletedAt || group.status === GroupStatus.Archived) {
|
|
148
|
+
return await this.canAccessArchivedGroups(group.organizationId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const organizationPermissions = await this.getOrganizationPermissions(group.organizationId)
|
|
152
|
+
|
|
153
|
+
if (!organizationPermissions) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check global level permissions for this user
|
|
158
|
+
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.Groups, group.id, permissionLevel)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check parent categories
|
|
163
|
+
const organization = await this.getOrganization(group.organizationId)
|
|
164
|
+
const parentCategories = group.getParentCategories(organization.meta.categories)
|
|
165
|
+
for (const category of parentCategories) {
|
|
166
|
+
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.GroupCategories, category.id, permissionLevel)) {
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async canAccessArchivedGroups(organizationId: string) {
|
|
175
|
+
return await this.hasFullAccess(organizationId)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Note: only checks admin permissions. Users that 'own' this member can also access it but that does not use the AdminPermissionChecker
|
|
180
|
+
*/
|
|
181
|
+
async canAccessMember(member: MemberWithRegistrations, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
182
|
+
// Check user has permissions
|
|
183
|
+
if (!this.user.permissions) {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (this.hasPlatformFullAccess()) {
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (member.organizationId && await this.hasFullAccess(member.organizationId, permissionLevel)) {
|
|
192
|
+
return true
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const registration of member.registrations) {
|
|
196
|
+
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Only full admins can delete members permanently
|
|
206
|
+
*/
|
|
207
|
+
async canDeleteMember(member: MemberWithRegistrations) {
|
|
208
|
+
if (member.organizationId) {
|
|
209
|
+
return await this.hasFullAccess(member.organizationId)
|
|
210
|
+
}
|
|
211
|
+
return this.hasPlatformFullAccess()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Note: only checks admin permissions. Users that 'own' this member can also access it but that does not use the AdminPermissionChecker
|
|
216
|
+
*/
|
|
217
|
+
async canAccessRegistration(registration: Registration, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
218
|
+
const organizationPermissions = await this.getOrganizationPermissions(registration.organizationId)
|
|
219
|
+
|
|
220
|
+
if (!organizationPermissions) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (organizationPermissions.hasAccess(permissionLevel)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const allGroups = await this.getOrganizationGroups(registration.organizationId)
|
|
229
|
+
const group = allGroups.find(g => g.id === registration.groupId)
|
|
230
|
+
if (!group) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (await this.canAccessGroup(group, permissionLevel)) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async canAccessWebshop(webshop: Webshop, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
242
|
+
const organizationPermissions = await this.getOrganizationPermissions(webshop.organizationId)
|
|
243
|
+
|
|
244
|
+
if (!organizationPermissions) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.Webshops, webshop.id, permissionLevel)) {
|
|
249
|
+
console.warn('has access organizationPermissions.hasResourceAccess')
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (permissionLevel === PermissionLevel.Read && organizationPermissions.hasResourceAccessRight(PermissionsResourceType.Webshops, webshop.id, AccessRight.WebshopScanTickets)) {
|
|
254
|
+
console.warn('has access organizationPermissions.hasResourceAccessRight')
|
|
255
|
+
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async canAccessWebshopTickets(webshop: Webshop, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
263
|
+
if (!this.checkScope(webshop.organizationId)) {
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const organizationPermissions = await this.getOrganizationPermissions(webshop.organizationId)
|
|
268
|
+
|
|
269
|
+
if (!organizationPermissions) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.Webshops, webshop.id, permissionLevel)) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if ((permissionLevel === PermissionLevel.Read || permissionLevel === PermissionLevel.Write) && organizationPermissions.hasResourceAccessRight(PermissionsResourceType.Webshops, webshop.id, AccessRight.WebshopScanTickets)) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async canAccessOrder(webshop: Webshop, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
285
|
+
return await this.canAccessWebshop(webshop, permissionLevel);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async canAccessPayment(payment: Payment, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
289
|
+
return await this.canAccessPayments([payment], permissionLevel)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async canAccessPayments(payments: Payment[], permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
293
|
+
for (const payment of payments) {
|
|
294
|
+
if (!this.checkScope(payment.organizationId)) {
|
|
295
|
+
// Invalid scope
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (payments.length === 0) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const organizationId = payments[0].organizationId
|
|
305
|
+
for (const item of payments) {
|
|
306
|
+
if (item.organizationId !== organizationId) {
|
|
307
|
+
// Cannot merge multiple organizations for now
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// First try without queries
|
|
313
|
+
if (!organizationId) {
|
|
314
|
+
return this.hasPlatformFullAccess()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (await this.canManagePayments(organizationId)) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const {balanceItems} = await Payment.loadBalanceItems(payments)
|
|
322
|
+
return await this.canAccessBalanceItems(balanceItems, permissionLevel)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async canAccessBalanceItems(
|
|
326
|
+
balanceItems: BalanceItem[],
|
|
327
|
+
permissionLevel: PermissionLevel = PermissionLevel.Read,
|
|
328
|
+
data?: {
|
|
329
|
+
registrations: Registration[],
|
|
330
|
+
orders: Order[],
|
|
331
|
+
members: Member[]
|
|
332
|
+
}
|
|
333
|
+
): Promise<boolean> {
|
|
334
|
+
for (const balanceItem of balanceItems) {
|
|
335
|
+
if (!this.checkScope(balanceItem.organizationId)) {
|
|
336
|
+
// Invalid scope
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (balanceItems.length === 0) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const organizationId = balanceItems[0].organizationId
|
|
346
|
+
for (const item of balanceItems) {
|
|
347
|
+
if (item.organizationId !== organizationId) {
|
|
348
|
+
// Cannot merge multiple organizations for now
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// First try without queries
|
|
354
|
+
if (await this.canManagePayments(organizationId)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (permissionLevel === PermissionLevel.Read) {
|
|
359
|
+
for (const balanceItem of balanceItems) {
|
|
360
|
+
if (balanceItem.userId === this.user.id) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Slight optimization possible here
|
|
367
|
+
const {registrations, orders, members} = data ?? (this.user.permissions || permissionLevel === PermissionLevel.Read) ? (await Payment.loadBalanceItemRelations(balanceItems)) : {registrations: [], members: [] as Member[], orders: []}
|
|
368
|
+
|
|
369
|
+
if (this.user.permissions) {
|
|
370
|
+
// We grant permission for a whole payment when the user has at least permission for a part of that payment.
|
|
371
|
+
for (const registration of registrations) {
|
|
372
|
+
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const webshopCache: Map<string, Webshop> = new Map()
|
|
378
|
+
|
|
379
|
+
for (const order of orders) {
|
|
380
|
+
const webshop = webshopCache.get(order.webshopId) ?? await Webshop.getByID(order.webshopId)
|
|
381
|
+
if (webshop) {
|
|
382
|
+
webshopCache.set(order.webshopId, webshop)
|
|
383
|
+
|
|
384
|
+
if (await this.canAccessWebshop(webshop, permissionLevel)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (permissionLevel === PermissionLevel.Read) {
|
|
392
|
+
// Check members
|
|
393
|
+
const userMembers = await Member.getMembersWithRegistrationForUser(this.user)
|
|
394
|
+
for (const member of userMembers) {
|
|
395
|
+
if (members.find(m => m.id === member.id)) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async canAccessDocumentTemplate(documentTemplate: DocumentTemplate, _: PermissionLevel = PermissionLevel.Read) {
|
|
405
|
+
if (!this.checkScope(documentTemplate.organizationId)) {
|
|
406
|
+
return false
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return await this.hasFullAccess(documentTemplate.organizationId)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async canAccessDocument(document: Document, level: PermissionLevel = PermissionLevel.Read) {
|
|
413
|
+
if (!this.checkScope(document.organizationId)) {
|
|
414
|
+
return false
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (await this.hasFullAccess(document.organizationId)) {
|
|
418
|
+
return true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (level === PermissionLevel.Read && document.memberId) {
|
|
422
|
+
const members = await Member.getMembersWithRegistrationForUser(this.user)
|
|
423
|
+
|
|
424
|
+
if (members.find(m => m.id == document.memberId)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async canAccessUser(user: User, level: PermissionLevel = PermissionLevel.Read) {
|
|
433
|
+
// Write = edit email, name
|
|
434
|
+
// full = edit permissions
|
|
435
|
+
if (user.id === this.user.id && (level === PermissionLevel.Read || level === PermissionLevel.Write)) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!this.checkScope(user.organizationId)) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!user.organizationId) {
|
|
444
|
+
return this.hasPlatformFullAccess()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return await this.canManageAdmins(user.organizationId);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async canEditUserName(user: User) {
|
|
451
|
+
if (user.id === this.user.id) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (user.organizationId) {
|
|
456
|
+
// normal behaviour
|
|
457
|
+
return this.canAccessUser(user, PermissionLevel.Write)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// platform user: only allowed to change names if not platform admins
|
|
461
|
+
if (user.permissions?.globalPermissions) {
|
|
462
|
+
return this.hasPlatformFullAccess()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return this.canAccessUser(user, PermissionLevel.Write)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async canEditUserEmail(user: User) {
|
|
469
|
+
return this.canEditUserName(user)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async canAccessEmailTemplate(template: EmailTemplate, level: PermissionLevel = PermissionLevel.Read) {
|
|
473
|
+
if (level === PermissionLevel.Read) {
|
|
474
|
+
if (template.organizationId === null) {
|
|
475
|
+
// Public templates
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return this.canReadEmailTemplates(template.organizationId);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Note: if the template has an organizationId of null, everyone can access it, but only for reading
|
|
482
|
+
// that is why we only check the scope afterwards
|
|
483
|
+
if (!this.checkScope(template.organizationId)) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!template.organizationId) {
|
|
488
|
+
return this.hasPlatformFullAccess()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (await this.hasFullAccess(template.organizationId)) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (template.webshopId) {
|
|
496
|
+
const webshop = await Webshop.getByID(template.webshopId)
|
|
497
|
+
if (!webshop || !(await this.canAccessWebshop(webshop, PermissionLevel.Full))) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (template.groupId) {
|
|
505
|
+
const group = await Group.getByID(template.groupId)
|
|
506
|
+
if (!group || !(await this.canAccessGroup(group, PermissionLevel.Full))) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async canLinkBalanceItemToUser(balanceItem: BalanceItem, linkingUser: User) {
|
|
517
|
+
if (!this.checkScope(linkingUser.organizationId)) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!this.checkScope(balanceItem.organizationId)) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (await this.canManagePayments(balanceItem.organizationId)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async canLinkBalanceItemToMember(member: MemberWithRegistrations) {
|
|
533
|
+
if (!this.checkScope(member.organizationId)) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (member.organizationId) {
|
|
538
|
+
if (await this.canManagePayments(member.organizationId)) {
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
const organizationIds = Formatter.uniqueArray(member.registrations.map(r => r.organizationId))
|
|
543
|
+
for (const organizationId of organizationIds) {
|
|
544
|
+
if (await this.canManagePayments(organizationId)) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (await this.hasFinancialMemberAccess(member, PermissionLevel.Write)) {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async canManageFinances(organizationId: string) {
|
|
558
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
559
|
+
|
|
560
|
+
if (!organizationPermissions) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return organizationPermissions.hasAccessRight(AccessRight.OrganizationFinanceDirector)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Mainly for transfer payment management
|
|
569
|
+
*/
|
|
570
|
+
async canManagePayments(organizationId: string) {
|
|
571
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
572
|
+
|
|
573
|
+
if (!organizationPermissions) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return !!organizationPermissions && (
|
|
578
|
+
organizationPermissions.hasAccessRight(AccessRight.OrganizationManagePayments)
|
|
579
|
+
|| organizationPermissions.hasAccessRight(AccessRight.OrganizationFinanceDirector)
|
|
580
|
+
)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async canCreateWebshops(organizationId: string) {
|
|
584
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
585
|
+
|
|
586
|
+
if (!organizationPermissions) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return !!organizationPermissions && organizationPermissions.hasAccessRight(AccessRight.OrganizationCreateWebshops)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async canManagePaymentAccounts(organizationId: string, level: PermissionLevel = PermissionLevel.Read) {
|
|
594
|
+
if (level === PermissionLevel.Read) {
|
|
595
|
+
return await this.hasSomeAccess(organizationId);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return await this.canManageFinances(organizationId)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async canActivatePackages(organizationId: string) {
|
|
602
|
+
return this.canManageFinances(organizationId)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async canDeactivatePackages(organizationId: string) {
|
|
606
|
+
return this.canManageFinances(organizationId)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async canManageDocuments(organizationId: string, _: PermissionLevel = PermissionLevel.Read) {
|
|
610
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
611
|
+
|
|
612
|
+
if (!organizationPermissions) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return this.hasFullAccess(organizationId)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async canAccessEmailBounces(organizationId: string) {
|
|
620
|
+
return this.hasSomeAccess(organizationId)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
canSendEmails() {
|
|
624
|
+
return !!this.user.permissions
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async canReadEmailTemplates(organizationId: string) {
|
|
628
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
629
|
+
|
|
630
|
+
if (!organizationPermissions) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return !!this.user.permissions
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async canCreateGroupInCategory(organizationId: string, category: GroupCategory) {
|
|
638
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
639
|
+
|
|
640
|
+
if (!organizationPermissions) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (!organizationPermissions.hasResourceAccessRight(PermissionsResourceType.GroupCategories, category.id, AccessRight.OrganizationCreateGroups)) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
canUpload() {
|
|
652
|
+
return !!this.user.permissions
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
canManageOrganizationDomain(organizationId: string) {
|
|
656
|
+
return this.hasFullAccess(organizationId)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
canManageSSOSettings(organizationId: string) {
|
|
660
|
+
return this.hasFullAccess(organizationId)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async canManageOrganizationSettings(organizationId: string) {
|
|
664
|
+
return this.hasFullAccess(organizationId);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Use this as a circuit breaker to avoid queries for non-admin users
|
|
669
|
+
*/
|
|
670
|
+
async hasSomeAccess(organizationId: string): Promise<boolean> {
|
|
671
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
672
|
+
return !!organizationPermissions;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async canManageAdmins(organizationId: string) {
|
|
676
|
+
return !this.user.isApiUser && (await this.hasFullAccess(organizationId))
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async hasFullAccess(organizationId: string, level = PermissionLevel.Full): Promise<boolean> {
|
|
680
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId)
|
|
681
|
+
|
|
682
|
+
if (!organizationPermissions) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return !!organizationPermissions && organizationPermissions.hasAccess(level)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
isUserManager(member: MemberWithRegistrations) {
|
|
690
|
+
return !!member.users.find(u => u.id === this.user.id)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Return a list of RecordSettings the current user can view or edit
|
|
695
|
+
*/
|
|
696
|
+
async getAccessibleRecordCategories(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<RecordCategory[]> {
|
|
697
|
+
const isUserManager = this.isUserManager(member)
|
|
698
|
+
|
|
699
|
+
// First list all organizations this member is part of
|
|
700
|
+
const organizations: Organization[] = [];
|
|
701
|
+
|
|
702
|
+
if (member.organizationId) {
|
|
703
|
+
if (this.checkScope(member.organizationId)) {
|
|
704
|
+
organizations.push(await this.getOrganization(member.organizationId))
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
for (const registration of member.registrations) {
|
|
709
|
+
if (this.checkScope(registration.organizationId)) {
|
|
710
|
+
if (!organizations.find(o => o.id === registration.organizationId)) {
|
|
711
|
+
organizations.push(await this.getOrganization(registration.organizationId))
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Loop all organizations.
|
|
717
|
+
// Check if we have access to their data
|
|
718
|
+
const recordCategories: RecordCategory[] = []
|
|
719
|
+
for (const organization of organizations) {
|
|
720
|
+
if (isUserManager) {
|
|
721
|
+
// If the user is a manager, we can always access all records
|
|
722
|
+
// if we ever add private records, we can exclude them here
|
|
723
|
+
for (const category of organization.meta.recordsConfiguration.recordCategories) {
|
|
724
|
+
recordCategories.push(category)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
for (const [id] of organization.meta.recordsConfiguration.inheritedRecordCategories) {
|
|
728
|
+
if (recordCategories.find(c => c.id === id)) {
|
|
729
|
+
// Already added
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const category = this.platform.config.recordsConfiguration.recordCategories.find(c => c.id === id)
|
|
734
|
+
if (category) {
|
|
735
|
+
recordCategories.push(category)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const permissions = await this.getOrganizationPermissions(organization)
|
|
742
|
+
if (!permissions) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Now add all records of this organization
|
|
747
|
+
for (const category of organization.meta.recordsConfiguration.recordCategories) {
|
|
748
|
+
if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
|
|
749
|
+
recordCategories.push(category)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
for (const [id] of organization.meta.recordsConfiguration.inheritedRecordCategories) {
|
|
754
|
+
if (recordCategories.find(c => c.id === id)) {
|
|
755
|
+
// Already added
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, id, level)) {
|
|
760
|
+
const category = this.platform.config.recordsConfiguration.recordCategories.find(c => c.id === id)
|
|
761
|
+
if (category) {
|
|
762
|
+
recordCategories.push(category)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Platform data
|
|
769
|
+
const platformPermissions = this.platformPermissions
|
|
770
|
+
if (platformPermissions || isUserManager) {
|
|
771
|
+
for (const category of this.platform.config.recordsConfiguration.recordCategories) {
|
|
772
|
+
if (recordCategories.find(c => c.id === category.id)) {
|
|
773
|
+
// Already added
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (isUserManager || platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
|
|
778
|
+
recordCategories.push(category)
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return recordCategories
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Return a list of RecordSettings the current user can view or edit
|
|
788
|
+
*/
|
|
789
|
+
async hasFinancialMemberAccess(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
|
|
790
|
+
const isUserManager = this.isUserManager(member)
|
|
791
|
+
|
|
792
|
+
if (isUserManager) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (!await this.canAccessMember(member, level)) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// First list all organizations this member is part of
|
|
801
|
+
const organizations: Organization[] = [];
|
|
802
|
+
|
|
803
|
+
if (member.organizationId) {
|
|
804
|
+
if (this.checkScope(member.organizationId)) {
|
|
805
|
+
organizations.push(await this.getOrganization(member.organizationId))
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
for (const registration of member.registrations) {
|
|
810
|
+
if (this.checkScope(registration.organizationId)) {
|
|
811
|
+
if (!organizations.find(o => o.id === registration.organizationId)) {
|
|
812
|
+
organizations.push(await this.getOrganization(registration.organizationId))
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Loop all organizations.
|
|
818
|
+
for (const organization of organizations) {
|
|
819
|
+
const permissions = await this.getOrganizationPermissions(organization)
|
|
820
|
+
if (!permissions) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (permissions.hasAccessRight(level === PermissionLevel.Read ? AccessRight.MemberReadFinancialData : AccessRight.MemberWriteFinancialData)) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Platform data
|
|
830
|
+
const platformPermissions = this.platformPermissions
|
|
831
|
+
if (platformPermissions) {
|
|
832
|
+
if (platformPermissions.hasAccessRight(level === PermissionLevel.Read ? AccessRight.MemberReadFinancialData : AccessRight.MemberWriteFinancialData)) {
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return false
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Return a list of RecordSettings the current user can view or edit
|
|
842
|
+
*/
|
|
843
|
+
async getAccessibleRecordSet(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<Set<string>> {
|
|
844
|
+
const categories = await this.getAccessibleRecordCategories(member, level)
|
|
845
|
+
const set = new Set<string>()
|
|
846
|
+
|
|
847
|
+
for (const category of categories) {
|
|
848
|
+
for (const record of category.getAllRecords()) {
|
|
849
|
+
set.add(record.id)
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return set
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Changes data inline
|
|
858
|
+
*/
|
|
859
|
+
async filterMemberData(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob): Promise<MemberWithRegistrationsBlob> {
|
|
860
|
+
const isUserManager = this.isUserManager(member)
|
|
861
|
+
if (isUserManager) {
|
|
862
|
+
// For the user manager, we don't delete data, because when registering a new member, it doesn't have any organizations yet...
|
|
863
|
+
return data;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const records = await this.getAccessibleRecordSet(member, PermissionLevel.Read)
|
|
867
|
+
|
|
868
|
+
const cloned = data.clone()
|
|
869
|
+
|
|
870
|
+
for (const [key, value] of cloned.details.recordAnswers.entries()) {
|
|
871
|
+
if (!records.has(value.settings.id)) {
|
|
872
|
+
cloned.details.recordAnswers.delete(key)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Has financial read access?
|
|
877
|
+
if (!await this.hasFinancialMemberAccess(member, PermissionLevel.Read)) {
|
|
878
|
+
cloned.details.requiresFinancialSupport = null
|
|
879
|
+
cloned.outstandingBalance = 0
|
|
880
|
+
|
|
881
|
+
for (const registration of cloned.registrations) {
|
|
882
|
+
registration.price = 0
|
|
883
|
+
registration.pricePaid = 0
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return cloned;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async filterMemberPatch(member: MemberWithRegistrations, data: AutoEncoderPatchType<MemberWithRegistrationsBlob>): Promise<AutoEncoderPatchType<MemberWithRegistrationsBlob>> {
|
|
891
|
+
if (!data.details) {
|
|
892
|
+
return data;
|
|
893
|
+
}
|
|
894
|
+
if (data.details.isPut()) {
|
|
895
|
+
throw new SimpleError({
|
|
896
|
+
code: 'invalid_request',
|
|
897
|
+
message: 'Cannot PUT a full member details object',
|
|
898
|
+
statusCode: 400
|
|
899
|
+
})
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (data.details.recordAnswers) {
|
|
903
|
+
if (!(data.details.recordAnswers instanceof PatchMap)) {
|
|
904
|
+
throw new SimpleError({
|
|
905
|
+
code: 'invalid_request',
|
|
906
|
+
message: 'Cannot PUT recordAnswers',
|
|
907
|
+
statusCode: 400
|
|
908
|
+
})
|
|
909
|
+
}
|
|
910
|
+
const isUserManager = this.isUserManager(member)
|
|
911
|
+
const records = isUserManager ? new Set() : await this.getAccessibleRecordSet(member, PermissionLevel.Write)
|
|
912
|
+
|
|
913
|
+
for (const [key, value] of data.details.recordAnswers.entries()) {
|
|
914
|
+
let name: string | undefined = undefined
|
|
915
|
+
if (value) {
|
|
916
|
+
if (value.isPatch()) {
|
|
917
|
+
throw new SimpleError({
|
|
918
|
+
code: 'invalid_request',
|
|
919
|
+
message: 'Cannot PATCH a record answer object',
|
|
920
|
+
statusCode: 400
|
|
921
|
+
})
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const id = value.settings.id
|
|
925
|
+
|
|
926
|
+
if (id !== key) {
|
|
927
|
+
throw new SimpleError({
|
|
928
|
+
code: 'invalid_request',
|
|
929
|
+
message: 'Record answer key does not match record id',
|
|
930
|
+
statusCode: 400
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
name = value.settings.name
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (!isUserManager && !records.has(key)) {
|
|
938
|
+
throw new SimpleError({
|
|
939
|
+
code: 'permission_denied',
|
|
940
|
+
message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
941
|
+
statusCode: 400
|
|
942
|
+
})
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Has financial write access?
|
|
948
|
+
if (!await this.hasFinancialMemberAccess(member, PermissionLevel.Write)) {
|
|
949
|
+
if (data.details.requiresFinancialSupport) {
|
|
950
|
+
throw new SimpleError({
|
|
951
|
+
code: 'permission_denied',
|
|
952
|
+
message: 'Je hebt geen toegangsrechten om de financiële status van dit lid aan te passen',
|
|
953
|
+
statusCode: 400
|
|
954
|
+
})
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (data.outstandingBalance) {
|
|
958
|
+
throw new SimpleError({
|
|
959
|
+
code: 'permission_denied',
|
|
960
|
+
message: 'Je hebt geen toegangsrechten om het openstaande saldo van dit lid aan te passen',
|
|
961
|
+
statusCode: 400
|
|
962
|
+
})
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
for (const {put: registration} of data.registrations.getPuts()) {
|
|
966
|
+
if (registration.price) {
|
|
967
|
+
throw new SimpleError({
|
|
968
|
+
code: 'permission_denied',
|
|
969
|
+
message: 'Je hebt geen toegangsrechten om de prijs van een inschrijving te bepalen',
|
|
970
|
+
statusCode: 400
|
|
971
|
+
})
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (registration.pricePaid) {
|
|
975
|
+
throw new SimpleError({
|
|
976
|
+
code: 'permission_denied',
|
|
977
|
+
message: 'Je hebt geen toegangsrechten om het betaalde bedrag van een inschrijving te bepalen',
|
|
978
|
+
statusCode: 400
|
|
979
|
+
})
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return data
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
canAccessAllPlatformMembers(): boolean {
|
|
988
|
+
return !!this.platformPermissions && !!this.platformPermissions.hasAccessRight(AccessRight.PlatformLoginAs)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
hasPlatformFullAccess(): boolean {
|
|
992
|
+
return !!this.platformPermissions && !!this.platformPermissions.hasFullAccess()
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
getPlatformAccessibleOrganizationTags(level: PermissionLevel): string[] | 'all' {
|
|
996
|
+
if (!this.hasSomePlatformAccess()) {
|
|
997
|
+
return [];
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (this.hasPlatformFullAccess()) {
|
|
1001
|
+
return 'all'
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (this.platformPermissions?.hasResourceAccess(PermissionsResourceType.OrganizationTags, '', level)) {
|
|
1005
|
+
return 'all'
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const allTags = this.platform.config.tags
|
|
1009
|
+
const tags: string[] = []
|
|
1010
|
+
|
|
1011
|
+
for (const tag of allTags) {
|
|
1012
|
+
if (this.platformPermissions?.hasResourceAccess(PermissionsResourceType.OrganizationTags, tag.id, level)) {
|
|
1013
|
+
tags.push(tag.id)
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (tags.length === allTags.length) {
|
|
1018
|
+
return 'all'
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return tags
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
hasSomePlatformAccess(): boolean {
|
|
1025
|
+
return !!this.platformPermissions
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
canManagePlatformAdmins() {
|
|
1029
|
+
return this.hasPlatformFullAccess()
|
|
1030
|
+
}
|
|
1031
|
+
}
|