@stamhoofd/backend 2.78.2 → 2.78.4

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 (28) hide show
  1. package/.env.ci.json +32 -16
  2. package/index.ts +7 -0
  3. package/jest.config.cjs +17 -0
  4. package/package.json +10 -10
  5. package/src/endpoints/auth/GetUserEndpoint.test.ts +0 -10
  6. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +726 -0
  7. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +31 -18
  8. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
  9. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +1 -1
  10. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +0 -4
  11. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +0 -4
  12. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +288 -8
  13. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +8 -13
  14. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +7 -7
  15. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -217
  16. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
  17. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -3
  18. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -6
  19. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +2 -20
  20. package/src/helpers/AdminPermissionChecker.ts +88 -140
  21. package/src/helpers/GlobalHelper.ts +6 -1
  22. package/src/services/FileSignService.ts +3 -3
  23. package/src/services/MemberRecordStore.ts +155 -0
  24. package/src/services/PlatformMembershipService.ts +17 -8
  25. package/tests/e2e/register.test.ts +49 -21
  26. package/tests/helpers/StripeMocker.ts +7 -2
  27. package/tests/jest.global.setup.ts +6 -1
  28. package/tests/jest.setup.ts +10 -2
@@ -1,7 +1,6 @@
1
- import { AutoEncoderPatchType, PatchableArray } from '@simonbackx/simple-encoding';
2
1
  import { Request } from '@simonbackx/simple-endpoints';
3
- import { GroupFactory, OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
4
- import { Group, GroupGenderType, GroupPatch, GroupPrivateSettings, GroupSettings, GroupSettingsPatch, Organization, PermissionLevel, PermissionRole, PermissionRoleDetailed, Permissions, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
2
+ import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
+ import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
5
4
 
6
5
  import { testServer } from '../../../../../tests/helpers/TestServer';
7
6
  import { PatchOrganizationEndpoint } from './PatchOrganizationEndpoint';
@@ -60,218 +59,4 @@ describe('Endpoint.PatchOrganization', () => {
60
59
 
61
60
  await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
62
61
  });
63
-
64
- test('Change the name of a group with access', async () => {
65
- const organization = await new OrganizationFactory({}).create();
66
- const role = PermissionRoleDetailed.create({
67
- name: 'Role',
68
- });
69
- organization.privateMeta.roles.push(
70
- role,
71
- );
72
- const groups = await new GroupFactory({ organization }).createMultiple(2);
73
-
74
- role.resources.set(PermissionsResourceType.Groups, new Map());
75
- role.resources.get(PermissionsResourceType.Groups)!.set(groups[0].id, ResourcePermissions.create({
76
- level: PermissionLevel.Full,
77
- }));
78
-
79
- await organization.save();
80
-
81
- const validPermissions = [
82
- Permissions.create({
83
- level: PermissionLevel.None,
84
- roles: [PermissionRole.create(role)],
85
- }),
86
- Permissions.create({
87
- level: PermissionLevel.Full,
88
- }),
89
- ];
90
-
91
- for (const permission of validPermissions) {
92
- const user = await new UserFactory({ organization,
93
- permissions: permission,
94
- }).create();
95
- const token = await Token.createToken(user);
96
-
97
- const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
98
- changes.addPatch(GroupPatch.create({
99
- id: groups[0].id,
100
- settings: GroupSettingsPatch.create({
101
- name: 'My crazy group name',
102
- }),
103
- }));
104
-
105
- const r = Request.buildJson('PATCH', '/organization', organization.getApiHost(), {
106
- id: organization.id,
107
- groups: changes.encode({ version: 2 }),
108
- });
109
- r.headers.authorization = 'Bearer ' + token.accessToken;
110
-
111
- const response = await testServer.test(endpoint, r);
112
- expect(response.body).toBeDefined();
113
-
114
- if (!(response.body instanceof Organization)) {
115
- throw new Error('Expected Organization');
116
- }
117
-
118
- expect(response.body.id).toEqual(organization.id);
119
- expect(response.body.groups.find(g => g.id == groups[0].id)!.settings.name).toEqual('My crazy group name');
120
- }
121
- });
122
-
123
- test("Can't change name of group without access", async () => {
124
- const organization = await new OrganizationFactory({}).create();
125
- const role = PermissionRoleDetailed.create({
126
- name: 'Role',
127
- });
128
- const role2 = PermissionRoleDetailed.create({
129
- name: 'Role2',
130
- });
131
- organization.privateMeta.roles.push(
132
- role,
133
- role2,
134
- );
135
- await organization.save();
136
- const groups = await new GroupFactory({ organization }).createMultiple(2);
137
-
138
- groups[0].privateSettings.permissions.write.push(PermissionRole.create(role));
139
- await groups[0].save();
140
-
141
- groups[0].privateSettings.permissions.read.push(PermissionRole.create(role2));
142
- await groups[0].save();
143
-
144
- const invalidPermissions = [
145
- Permissions.create({
146
- level: PermissionLevel.Read,
147
- roles: [PermissionRole.create(role)],
148
- }),
149
- Permissions.create({
150
- level: PermissionLevel.None,
151
- roles: [PermissionRole.create(role2)],
152
- }),
153
- Permissions.create({
154
- level: PermissionLevel.Write,
155
- roles: [PermissionRole.create(role2), PermissionRole.create(role)],
156
- }),
157
- Permissions.create({
158
- level: PermissionLevel.Write,
159
- }),
160
- Permissions.create({
161
- level: PermissionLevel.Read,
162
- }),
163
- null,
164
- ];
165
-
166
- for (const permission of invalidPermissions) {
167
- const user = await new UserFactory({
168
- organization,
169
- permissions: permission,
170
- }).create();
171
- const token = await Token.createToken(user);
172
-
173
- const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
174
- changes.addPatch(GroupPatch.create({
175
- id: groups[0].id,
176
- settings: GroupSettingsPatch.create({
177
- name: 'My crazy group name',
178
- }),
179
- }));
180
- const r = Request.buildJson('PATCH', '/organization', organization.getApiHost(), {
181
- id: organization.id,
182
- groups: changes.encode({ version: 2 }),
183
- });
184
- r.headers.authorization = 'Bearer ' + token.accessToken;
185
- await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
186
- }
187
- });
188
-
189
- test('Create a group with access', async () => {
190
- const organization = await new OrganizationFactory({}).create();
191
- const groups = await new GroupFactory({ organization }).createMultiple(2);
192
-
193
- const validPermissions = [
194
- Permissions.create({
195
- level: PermissionLevel.Full,
196
- }),
197
- ];
198
-
199
- const invalidPermissions = [
200
- Permissions.create({
201
- level: PermissionLevel.Write,
202
- }),
203
- ];
204
-
205
- for (const permission of validPermissions) {
206
- const user = await new UserFactory({
207
- organization,
208
- permissions: permission,
209
- }).create();
210
- const token = await Token.createToken(user);
211
-
212
- const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
213
- const put = Group.create({
214
- cycle: 0,
215
- organizationId: organization.id,
216
- periodId: organization.periodId,
217
- settings: GroupSettings.create({
218
- name: 'My crazy group name',
219
- startDate: new Date(),
220
- endDate: new Date(),
221
- registrationStartDate: new Date(),
222
- registrationEndDate: new Date(),
223
- genderType: GroupGenderType.Mixed,
224
- }),
225
- privateSettings: GroupPrivateSettings.create({}),
226
- });
227
- changes.addPut(put);
228
-
229
- const r = Request.buildJson('PATCH', '/v140/organization', organization.getApiHost(), {
230
- id: organization.id,
231
- groups: changes.encode({ version: 140 }),
232
- });
233
- r.headers.authorization = 'Bearer ' + token.accessToken;
234
-
235
- const response = await testServer.test(endpoint, r);
236
- expect(response.body).toBeDefined();
237
-
238
- if (!(response.body instanceof Organization)) {
239
- throw new Error('Expected Organization');
240
- }
241
-
242
- expect(response.body.id).toEqual(organization.id);
243
- expect(response.body.groups.map(g => g.id)).toContainEqual(put.id);
244
- }
245
-
246
- for (const permission of invalidPermissions) {
247
- const user = await new UserFactory({
248
- organization,
249
- permissions: permission,
250
- }).create();
251
- const token = await Token.createToken(user);
252
-
253
- const changes = new PatchableArray<string, Group, AutoEncoderPatchType<Group>>();
254
- const put = Group.create({
255
- cycle: 0,
256
- organizationId: organization.id,
257
- periodId: organization.periodId,
258
- settings: GroupSettings.create({
259
- name: 'My crazy group name',
260
- startDate: new Date(),
261
- endDate: new Date(),
262
- registrationStartDate: new Date(),
263
- registrationEndDate: new Date(),
264
- genderType: GroupGenderType.Mixed,
265
- }),
266
- });
267
- changes.addPut(put);
268
-
269
- const r = Request.buildJson('PATCH', '/v2/organization', organization.getApiHost(), {
270
- id: organization.id,
271
- groups: changes.encode({ version: 2 }),
272
- });
273
- r.headers.authorization = 'Bearer ' + token.accessToken;
274
- await expect(testServer.test(endpoint, r)).rejects.toThrow(/permissions/i);
275
- }
276
- });
277
62
  });
@@ -250,7 +250,7 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
250
250
  if (!member || !(await Context.auth.canLinkBalanceItemToMember(member))) {
251
251
  throw new SimpleError({
252
252
  code: 'permission_denied',
253
- message: 'No permission to link balanace items to this member',
253
+ message: 'No permission to link balance items to this member',
254
254
  human: 'Je hebt geen toegang om aanrekeningen te maken verbonden met dit lid',
255
255
  field: 'memberId',
256
256
  });
@@ -264,7 +264,7 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
264
264
  if (!user || !await Context.auth.canLinkBalanceItemToUser(balanceItem, user)) {
265
265
  throw new SimpleError({
266
266
  code: 'permission_denied',
267
- message: 'No permission to link balanace items to this user',
267
+ message: 'No permission to link balance items to this user',
268
268
  human: 'Je hebt geen toegang om aanrekeningen te maken verbonden met deze gebruiker',
269
269
  field: 'userId',
270
270
  });
@@ -137,7 +137,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
137
137
  await order.updateStock(null, true);
138
138
  const totalPrice = order.data.totalPrice;
139
139
 
140
- if (totalPrice == 0) {
140
+ if (totalPrice === 0) {
141
141
  // Force unknown payment method
142
142
  order.data.paymentMethod = PaymentMethod.Unknown;
143
143
 
@@ -161,6 +161,9 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
161
161
  order.paymentId = payment.id;
162
162
  order.setRelation(Order.payment, payment);
163
163
 
164
+ // Save order because we need the id
165
+ await order.save();
166
+
164
167
  // Create balance item
165
168
  const balanceItem = new BalanceItem();
166
169
  balanceItem.orderId = order.id;
@@ -189,12 +192,12 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
189
192
  balanceItemPayment.price = balanceItem.price;
190
193
  await balanceItemPayment.save();
191
194
 
192
- if (payment.method == PaymentMethod.Transfer) {
195
+ if (payment.method === PaymentMethod.Transfer) {
193
196
  await order.markValid(payment, []);
194
197
  await payment.save();
195
198
  await order.save();
196
199
  }
197
- else if (payment.method == PaymentMethod.PointOfSale) {
200
+ else if (payment.method === PaymentMethod.PointOfSale) {
198
201
  // Not really paid, but needed to create the tickets if needed
199
202
  await order.markPaid(payment, organization, webshop);
200
203
  await payment.save();
@@ -1,5 +1,5 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
- import { GroupFactory, OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
2
+ import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
3
  import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
4
4
 
5
5
  import { testServer } from '../../../../../tests/helpers/TestServer';
@@ -12,7 +12,6 @@ describe('Endpoint.GetOrganization', () => {
12
12
  test('Get organization as signed in user', async () => {
13
13
  const organization = await new OrganizationFactory({}).create();
14
14
  const user = await new UserFactory({ organization }).create();
15
- const groups = await new GroupFactory({ organization }).createMultiple(2);
16
15
  const token = await Token.createToken(user);
17
16
 
18
17
  const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
@@ -26,7 +25,6 @@ describe('Endpoint.GetOrganization', () => {
26
25
  }
27
26
 
28
27
  expect(response.body.id).toEqual(organization.id);
29
- expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
30
28
  expect(response.body.privateMeta).toEqual(null);
31
29
  });
32
30
 
@@ -39,7 +37,6 @@ describe('Endpoint.GetOrganization', () => {
39
37
  }),
40
38
  }).create();
41
39
 
42
- const groups = await new GroupFactory({ organization }).createMultiple(2);
43
40
  const token = await Token.createToken(user);
44
41
 
45
42
  const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
@@ -53,7 +50,6 @@ describe('Endpoint.GetOrganization', () => {
53
50
  }
54
51
 
55
52
  expect(response.body.id).toEqual(organization.id);
56
- expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
57
53
  expect(response.body.privateMeta).not.toEqual(null);
58
54
  });
59
55
 
@@ -72,6 +68,8 @@ describe('Endpoint.GetOrganization', () => {
72
68
  const r = Request.buildJson('GET', '/v3/organization', organization.getApiHost());
73
69
  r.headers.authorization = 'Bearer ' + token.accessToken;
74
70
 
75
- await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
71
+ const response = await testServer.test(endpoint, r);
72
+ expect(response.body).toBeDefined();
73
+ expect(response.body.privateMeta).toEqual(null);
76
74
  });
77
75
  });
@@ -105,25 +105,7 @@ describe('Endpoint.GetWebshop', () => {
105
105
  const r = Request.buildJson('GET', '/v244/webshop/' + webshop.id, organization.getApiHost());
106
106
  r.headers.authorization = 'Bearer ' + token.accessToken;
107
107
 
108
- await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
109
- });
110
-
111
- test('If organization scope is missing in v243, access is still checked correctly', async () => {
112
- const organization = await new OrganizationFactory({}).create();
113
- const organization2 = await new OrganizationFactory({}).create();
114
- const user = await new UserFactory({
115
- organization: organization2,
116
- permissions: Permissions.create({
117
- level: PermissionLevel.Full,
118
- }),
119
- }).create();
120
-
121
- const token = await Token.createToken(user);
122
- const webshop = await new WebshopFactory({ organizationId: organization.id }).create();
123
-
124
- const r = Request.buildJson('GET', '/v243/webshop/' + webshop.id);
125
- r.headers.authorization = 'Bearer ' + token.accessToken;
126
-
127
- await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
108
+ const response = await testServer.test(endpoint, r);
109
+ expect((response.body as any).privateMeta).toBeUndefined();
128
110
  });
129
111
  });
@@ -1,9 +1,10 @@
1
1
  import { AutoEncoderPatchType, PatchMap } from '@simonbackx/simple-encoding';
2
2
  import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
3
3
  import { BalanceItem, CachedBalance, Document, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
4
- import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from '@stamhoofd/structures';
4
+ import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory, RecordSettings } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
7
+ import { MemberRecordStore } from '../services/MemberRecordStore';
7
8
 
8
9
  /**
9
10
  * One class with all the responsabilities of checking permissions to each resource in the system by a given user, possibly in an organization context.
@@ -90,6 +91,10 @@ export class AdminPermissionChecker {
90
91
  });
91
92
  }
92
93
 
94
+ memberNotFoundOrNoAccess(): SimpleError {
95
+ return this.notFoundOrNoAccess($t('Je hebt geen toegang tot dit lid of het bestaat niet'));
96
+ }
97
+
93
98
  notFoundOrNoAccess(message?: string): SimpleError {
94
99
  return new SimpleError({
95
100
  code: 'not_found',
@@ -853,80 +858,6 @@ export class AdminPermissionChecker {
853
858
  return !!member.users.find(u => u.id === this.user.id);
854
859
  }
855
860
 
856
- /**
857
- * Return a list of RecordSettings the current user can view or edit
858
- */
859
- async getAccessibleRecordCategories(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<RecordCategory[]> {
860
- // First list all organizations this member is part of
861
- const organizations: Organization[] = [];
862
-
863
- if (member.organizationId) {
864
- if (this.checkScope(member.organizationId)) {
865
- organizations.push(await this.getOrganization(member.organizationId));
866
- }
867
- }
868
-
869
- for (const registration of member.registrations) {
870
- if (this.checkScope(registration.organizationId)) {
871
- if (!organizations.find(o => o.id === registration.organizationId)) {
872
- organizations.push(await this.getOrganization(registration.organizationId));
873
- }
874
- }
875
- }
876
-
877
- // Loop all organizations.
878
- // Check if we have access to their data
879
- const recordCategories: RecordCategory[] = [];
880
- for (const organization of organizations) {
881
- const permissions = await this.getOrganizationPermissions(organization);
882
-
883
- if (!permissions) {
884
- continue;
885
- }
886
-
887
- // Now add all records of this organization
888
- for (const category of organization.meta.recordsConfiguration.recordCategories) {
889
- if (recordCategories.find(c => c.id === category.id)) {
890
- // Already added
891
- continue;
892
- }
893
-
894
- if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
895
- recordCategories.push(category);
896
- }
897
- }
898
-
899
- // Platform ones where we have been given permissions for in this organization
900
- for (const category of this.platform.config.recordsConfiguration.recordCategories) {
901
- if (recordCategories.find(c => c.id === category.id)) {
902
- // Already added
903
- continue;
904
- }
905
-
906
- if (permissions.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
907
- recordCategories.push(category);
908
- }
909
- }
910
- }
911
-
912
- // Platform data
913
- const platformPermissions = this.platformPermissions;
914
- if (platformPermissions) {
915
- for (const category of this.platform.config.recordsConfiguration.recordCategories) {
916
- if (recordCategories.find(c => c.id === category.id)) {
917
- // Already added
918
- continue;
919
- }
920
-
921
- if (platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, category.id, level)) {
922
- recordCategories.push(category);
923
- }
924
- }
925
- }
926
-
927
- return recordCategories;
928
- }
929
-
930
861
  /**
931
862
  * Return a list of RecordSettings the current user can view or edit
932
863
  */
@@ -1053,56 +984,87 @@ export class AdminPermissionChecker {
1053
984
  return false;
1054
985
  }
1055
986
 
1056
- /**
1057
- * Return a list of RecordSettings the current user can view or edit
1058
- */
1059
- async getAccessibleRecordSet(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<Set<string>> {
1060
- const categories = await this.getAccessibleRecordCategories(member, level);
1061
- const set = new Set<string>();
987
+ async checkRecordAccess(member: MemberWithRegistrations, recordId: string, level: PermissionLevel = PermissionLevel.Read): Promise<{ canAccess: false; record: RecordSettings | null } | { canAccess: true; record: RecordSettings }> {
988
+ const record = await MemberRecordStore.getRecord(recordId);
989
+ if (!record) {
990
+ return {
991
+ canAccess: false,
992
+ record: null,
993
+ };
994
+ }
1062
995
 
1063
- for (const category of categories) {
1064
- for (const record of category.getAllRecords()) {
1065
- set.add(record.id);
1066
- }
996
+ if (!this.checkScope(record.organizationId)) {
997
+ return {
998
+ canAccess: false,
999
+ record: record.record,
1000
+ };
1067
1001
  }
1068
1002
 
1069
1003
  const isUserManager = this.isUserManager(member);
1070
-
1071
- // Also include those we can access as user manager
1072
1004
  if (isUserManager) {
1073
- const allCategories = this.platform.config.recordsConfiguration.recordCategories.slice();
1005
+ if (record.record.checkPermissionForUserManager(level)) {
1006
+ return {
1007
+ canAccess: true,
1008
+ record: record.record,
1009
+ };
1010
+ }
1011
+ }
1074
1012
 
1075
- // First list all organizations this member is part of
1076
- const organizations: Organization[] = [];
1013
+ if (!this.user.permissions) {
1014
+ return {
1015
+ canAccess: false,
1016
+ record: record.record,
1017
+ };
1018
+ }
1077
1019
 
1078
- if (member.organizationId) {
1079
- if (this.checkScope(member.organizationId)) {
1080
- organizations.push(await this.getOrganization(member.organizationId));
1081
- }
1020
+ if (record.organizationId) {
1021
+ const organizationPermissions = await this.getOrganizationPermissions(record.organizationId);
1022
+ if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
1023
+ return {
1024
+ canAccess: true,
1025
+ record: record.record,
1026
+ };
1082
1027
  }
1083
-
1084
- for (const registration of member.registrations) {
1085
- if (this.checkScope(registration.organizationId)) {
1086
- if (!organizations.find(o => o.id === registration.organizationId)) {
1087
- organizations.push(await this.getOrganization(registration.organizationId));
1088
- }
1028
+ }
1029
+ else {
1030
+ // 1. Check all organizations (they can give permissions)
1031
+ for (const organizationId of this.user.permissions.organizationPermissions.keys()) {
1032
+ const organizationPermissions = await this.getOrganizationPermissions(organizationId);
1033
+ if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
1034
+ return {
1035
+ canAccess: true,
1036
+ record: record.record,
1037
+ };
1089
1038
  }
1090
1039
  }
1040
+ }
1091
1041
 
1092
- for (const organization of organizations) {
1093
- allCategories.push(...organization.meta.recordsConfiguration.recordCategories);
1094
- }
1042
+ // 2. Check platform permissions
1043
+ if (this.platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
1044
+ return {
1045
+ canAccess: true,
1046
+ record: record.record,
1047
+ };
1048
+ }
1095
1049
 
1096
- for (const category of allCategories) {
1097
- for (const record of category.getAllRecords()) {
1098
- if (record.checkPermissionForUserManager(level)) {
1099
- set.add(record.id);
1100
- }
1050
+ // It is possible that this is a platform admin, and inherits automatic permissions for tags. So'll need to loop all the organizations where this member has an active registration for
1051
+ if (!record.organizationId && this.platformPermissions) {
1052
+ const organizations = Formatter.uniqueArray(member.registrations.map(r => r.organizationId));
1053
+ for (const organizationId of organizations) {
1054
+ const organizationPermissions = await this.getOrganizationPermissions(organizationId);
1055
+ if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
1056
+ return {
1057
+ canAccess: true,
1058
+ record: record.record,
1059
+ };
1101
1060
  }
1102
1061
  }
1103
1062
  }
1104
1063
 
1105
- return set;
1064
+ return {
1065
+ canAccess: false,
1066
+ record: record.record,
1067
+ };
1106
1068
  }
1107
1069
 
1108
1070
  async getAccessibleGroups(organizationId: string, level: PermissionLevel = PermissionLevel.Read): Promise<string[] | 'all'> {
@@ -1125,22 +1087,27 @@ export class AdminPermissionChecker {
1125
1087
  * Changes data inline
1126
1088
  */
1127
1089
  async filterMemberData(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob): Promise<MemberWithRegistrationsBlob> {
1128
- const records = await this.getAccessibleRecordSet(member, PermissionLevel.Read);
1129
-
1130
1090
  const cloned = data.clone();
1131
1091
 
1132
1092
  for (const [key, value] of cloned.details.recordAnswers.entries()) {
1133
- if (!records.has(value.settings.id)) {
1093
+ const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Read);
1094
+ if (!canAccess) {
1134
1095
  cloned.details.recordAnswers.delete(key);
1135
1096
  }
1097
+ else {
1098
+ if (value) {
1099
+ // Force update
1100
+ value.settings = record;
1101
+ }
1102
+ }
1136
1103
  }
1137
1104
 
1138
1105
  const isUserManager = this.isUserManager(member);
1139
1106
  if (isUserManager) {
1140
1107
  // For a user manager without an organization, we don't delete data, because when registering a new member, it doesn't have any organizations yet...
1141
1108
  if (!(await this.canAccessMember(member, PermissionLevel.Full))) {
1142
- cloned.details.securityCode = null;
1143
1109
  cloned.details.notes = null;
1110
+ // a user manager can see the security codes
1144
1111
  }
1145
1112
 
1146
1113
  return cloned;
@@ -1227,39 +1194,20 @@ export class AdminPermissionChecker {
1227
1194
  });
1228
1195
  }
1229
1196
 
1230
- const records = await this.getAccessibleRecordSet(member, PermissionLevel.Write);
1231
-
1232
1197
  for (const [key, value] of data.details.recordAnswers.entries()) {
1233
- let name: string | undefined = undefined;
1234
- if (value) {
1235
- if (value.isPatch()) {
1236
- throw new SimpleError({
1237
- code: 'invalid_request',
1238
- message: 'Cannot PATCH a record answer object',
1239
- statusCode: 400,
1240
- });
1241
- }
1242
-
1243
- const id = value.settings.id;
1244
-
1245
- if (id !== key) {
1246
- throw new SimpleError({
1247
- code: 'invalid_request',
1248
- message: 'Record answer key does not match record id',
1249
- statusCode: 400,
1250
- });
1251
- }
1252
-
1253
- name = value.settings.name;
1254
- }
1255
-
1256
- if (!records.has(key)) {
1198
+ const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Write);
1199
+ if (!canAccess) {
1257
1200
  throw new SimpleError({
1258
1201
  code: 'permission_denied',
1259
- message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
1202
+ message: `Je hebt geen toegangsrechten om het antwoord op ${record?.name ?? 'deze vraag'} aan te passen voor dit lid`,
1260
1203
  statusCode: 400,
1261
1204
  });
1262
1205
  }
1206
+
1207
+ // Force set the value settings
1208
+ if (value) {
1209
+ value.settings = record;
1210
+ }
1263
1211
  }
1264
1212
  }
1265
1213