@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.
Files changed (150) hide show
  1. package/.env.template.json +63 -0
  2. package/.eslintrc.js +61 -0
  3. package/README.md +40 -0
  4. package/index.ts +172 -0
  5. package/jest.config.js +11 -0
  6. package/migrations.ts +33 -0
  7. package/package.json +48 -0
  8. package/src/crons.ts +845 -0
  9. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
  10. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
  11. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
  12. package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
  13. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
  14. package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
  15. package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
  16. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
  17. package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
  18. package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
  19. package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
  20. package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
  21. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
  22. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
  23. package/src/endpoints/auth/SignupEndpoint.ts +107 -0
  24. package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
  25. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
  26. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
  27. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
  28. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
  29. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
  30. package/src/endpoints/global/files/UploadFile.ts +147 -0
  31. package/src/endpoints/global/files/UploadImage.ts +119 -0
  32. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
  33. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
  34. package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
  35. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
  36. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
  37. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
  38. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
  39. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
  40. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
  41. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
  42. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
  43. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
  44. package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
  45. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
  46. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
  47. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
  48. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
  49. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
  50. package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
  51. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
  52. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
  53. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
  54. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
  55. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
  56. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
  57. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
  58. package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
  59. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
  60. package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
  61. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
  62. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
  63. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
  64. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
  65. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
  66. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
  67. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
  68. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
  69. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
  70. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
  71. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
  72. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
  73. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
  74. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
  75. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
  76. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
  77. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
  78. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
  79. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
  80. package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
  81. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
  82. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
  83. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
  84. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
  85. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
  86. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
  87. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
  88. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
  89. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
  90. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
  91. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
  92. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
  93. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
  94. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
  95. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
  96. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
  97. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
  98. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
  99. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
  100. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
  101. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
  102. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
  103. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
  104. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
  105. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
  106. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
  107. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
  108. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
  109. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
  110. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
  111. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
  112. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
  113. package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
  114. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
  115. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
  116. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
  117. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
  118. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
  119. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
  120. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
  121. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
  122. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
  123. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
  124. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
  125. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
  126. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
  127. package/src/helpers/AddressValidator.test.ts +40 -0
  128. package/src/helpers/AddressValidator.ts +256 -0
  129. package/src/helpers/AdminPermissionChecker.ts +1031 -0
  130. package/src/helpers/AuthenticatedStructures.ts +158 -0
  131. package/src/helpers/BuckarooHelper.ts +279 -0
  132. package/src/helpers/CheckSettlements.ts +215 -0
  133. package/src/helpers/Context.ts +202 -0
  134. package/src/helpers/CookieHelper.ts +45 -0
  135. package/src/helpers/ForwardHandler.test.ts +216 -0
  136. package/src/helpers/ForwardHandler.ts +140 -0
  137. package/src/helpers/OpenIDConnectHelper.ts +284 -0
  138. package/src/helpers/StripeHelper.ts +293 -0
  139. package/src/helpers/StripePayoutChecker.ts +188 -0
  140. package/src/middleware/ContextMiddleware.ts +16 -0
  141. package/src/migrations/1646578856-validate-addresses.ts +60 -0
  142. package/src/seeds/0000000000-example.ts +13 -0
  143. package/src/seeds/1715028563-user-permissions.ts +52 -0
  144. package/tests/e2e/stock.test.ts +2120 -0
  145. package/tests/e2e/tickets.test.ts +926 -0
  146. package/tests/helpers/StripeMocker.ts +362 -0
  147. package/tests/helpers/TestServer.ts +21 -0
  148. package/tests/jest.global.setup.ts +29 -0
  149. package/tests/jest.setup.ts +59 -0
  150. 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
+ }