@stamhoofd/backend 2.78.2 → 2.78.3
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.ci.json +15 -10
- package/jest.config.cjs +17 -0
- package/package.json +10 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +0 -10
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +1 -1
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +0 -4
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +0 -4
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +288 -8
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +7 -7
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -217
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -3
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -6
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +2 -20
- package/src/helpers/AdminPermissionChecker.ts +72 -142
- package/src/helpers/GlobalHelper.ts +6 -1
- package/src/services/FileSignService.ts +2 -2
- package/src/services/MemberRecordStore.ts +155 -0
- package/src/services/PlatformMembershipService.ts +17 -8
- package/tests/e2e/register.test.ts +49 -21
- package/tests/helpers/StripeMocker.ts +7 -2
- package/tests/jest.global.setup.ts +6 -1
- 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 {
|
|
4
|
-
import {
|
|
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
|
});
|
|
@@ -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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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.
|
|
@@ -853,80 +854,6 @@ export class AdminPermissionChecker {
|
|
|
853
854
|
return !!member.users.find(u => u.id === this.user.id);
|
|
854
855
|
}
|
|
855
856
|
|
|
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
857
|
/**
|
|
931
858
|
* Return a list of RecordSettings the current user can view or edit
|
|
932
859
|
*/
|
|
@@ -1053,56 +980,73 @@ export class AdminPermissionChecker {
|
|
|
1053
980
|
return false;
|
|
1054
981
|
}
|
|
1055
982
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
983
|
+
async checkRecordAccess(member: MemberWithRegistrations, recordId: string, level: PermissionLevel = PermissionLevel.Read): Promise<{ canAccess: false; record: RecordSettings | null } | { canAccess: true; record: RecordSettings }> {
|
|
984
|
+
const record = await MemberRecordStore.getRecord(recordId);
|
|
985
|
+
if (!record) {
|
|
986
|
+
return {
|
|
987
|
+
canAccess: false,
|
|
988
|
+
record: null,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
1062
991
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
992
|
+
if (!this.checkScope(record.organizationId)) {
|
|
993
|
+
return {
|
|
994
|
+
canAccess: false,
|
|
995
|
+
record: record.record,
|
|
996
|
+
};
|
|
1067
997
|
}
|
|
1068
998
|
|
|
1069
999
|
const isUserManager = this.isUserManager(member);
|
|
1070
|
-
|
|
1071
|
-
// Also include those we can access as user manager
|
|
1072
1000
|
if (isUserManager) {
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (member.organizationId) {
|
|
1079
|
-
if (this.checkScope(member.organizationId)) {
|
|
1080
|
-
organizations.push(await this.getOrganization(member.organizationId));
|
|
1081
|
-
}
|
|
1001
|
+
if (record.record.checkPermissionForUserManager(level)) {
|
|
1002
|
+
return {
|
|
1003
|
+
canAccess: true,
|
|
1004
|
+
record: record.record,
|
|
1005
|
+
};
|
|
1082
1006
|
}
|
|
1007
|
+
}
|
|
1083
1008
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1009
|
+
if (!this.user.permissions) {
|
|
1010
|
+
return {
|
|
1011
|
+
canAccess: false,
|
|
1012
|
+
record: record.record,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1091
1015
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1016
|
+
if (record.organizationId) {
|
|
1017
|
+
const organizationPermissions = await this.getOrganizationPermissions(record.organizationId);
|
|
1018
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1019
|
+
return {
|
|
1020
|
+
canAccess: true,
|
|
1021
|
+
record: record.record,
|
|
1022
|
+
};
|
|
1094
1023
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
// 1. Check all organizations (they can give permissions)
|
|
1027
|
+
for (const organizationId of this.user.permissions.organizationPermissions.keys()) {
|
|
1028
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId);
|
|
1029
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1030
|
+
return {
|
|
1031
|
+
canAccess: true,
|
|
1032
|
+
record: record.record,
|
|
1033
|
+
};
|
|
1101
1034
|
}
|
|
1102
1035
|
}
|
|
1103
1036
|
}
|
|
1104
1037
|
|
|
1105
|
-
|
|
1038
|
+
// 2. Check platform permissions
|
|
1039
|
+
if (this.platformPermissions?.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1040
|
+
return {
|
|
1041
|
+
canAccess: true,
|
|
1042
|
+
record: record.record,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return {
|
|
1047
|
+
canAccess: false,
|
|
1048
|
+
record: record.record,
|
|
1049
|
+
};
|
|
1106
1050
|
}
|
|
1107
1051
|
|
|
1108
1052
|
async getAccessibleGroups(organizationId: string, level: PermissionLevel = PermissionLevel.Read): Promise<string[] | 'all'> {
|
|
@@ -1125,14 +1069,19 @@ export class AdminPermissionChecker {
|
|
|
1125
1069
|
* Changes data inline
|
|
1126
1070
|
*/
|
|
1127
1071
|
async filterMemberData(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob): Promise<MemberWithRegistrationsBlob> {
|
|
1128
|
-
const records = await this.getAccessibleRecordSet(member, PermissionLevel.Read);
|
|
1129
|
-
|
|
1130
1072
|
const cloned = data.clone();
|
|
1131
1073
|
|
|
1132
1074
|
for (const [key, value] of cloned.details.recordAnswers.entries()) {
|
|
1133
|
-
|
|
1075
|
+
const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Read);
|
|
1076
|
+
if (!canAccess) {
|
|
1134
1077
|
cloned.details.recordAnswers.delete(key);
|
|
1135
1078
|
}
|
|
1079
|
+
else {
|
|
1080
|
+
if (value) {
|
|
1081
|
+
// Force update
|
|
1082
|
+
value.settings = record;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1136
1085
|
}
|
|
1137
1086
|
|
|
1138
1087
|
const isUserManager = this.isUserManager(member);
|
|
@@ -1227,39 +1176,20 @@ export class AdminPermissionChecker {
|
|
|
1227
1176
|
});
|
|
1228
1177
|
}
|
|
1229
1178
|
|
|
1230
|
-
const records = await this.getAccessibleRecordSet(member, PermissionLevel.Write);
|
|
1231
|
-
|
|
1232
1179
|
for (const [key, value] of data.details.recordAnswers.entries()) {
|
|
1233
|
-
|
|
1234
|
-
if (
|
|
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)) {
|
|
1180
|
+
const { canAccess, record } = await this.checkRecordAccess(member, key, PermissionLevel.Write);
|
|
1181
|
+
if (!canAccess) {
|
|
1257
1182
|
throw new SimpleError({
|
|
1258
1183
|
code: 'permission_denied',
|
|
1259
|
-
message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
1184
|
+
message: `Je hebt geen toegangsrechten om het antwoord op ${record?.name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
1260
1185
|
statusCode: 400,
|
|
1261
1186
|
});
|
|
1262
1187
|
}
|
|
1188
|
+
|
|
1189
|
+
// Force set the value settings
|
|
1190
|
+
if (value) {
|
|
1191
|
+
value.settings = record;
|
|
1192
|
+
}
|
|
1263
1193
|
}
|
|
1264
1194
|
}
|
|
1265
1195
|
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
2
2
|
import { FileSignService } from '../services/FileSignService';
|
|
3
|
-
import { Context } from './Context';
|
|
3
|
+
import { Context, ContextInstance } from './Context';
|
|
4
|
+
import { Address, Country } from '@stamhoofd/structures';
|
|
5
|
+
import { MemberRecordStore } from '../services/MemberRecordStore';
|
|
4
6
|
|
|
5
7
|
export class GlobalHelper {
|
|
6
8
|
static async load() {
|
|
7
9
|
await I18n.load();
|
|
8
10
|
this.loadGlobalTranslateFunction();
|
|
9
11
|
await FileSignService.load();
|
|
12
|
+
MemberRecordStore.init();
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
private static loadGlobalTranslateFunction() {
|
|
13
16
|
(global as any).$t = (key: string, replace?: Record<string, string>) => Context.i18n.$t(key, replace);
|
|
17
|
+
(global as any).$getLanguage = () => ContextInstance.optional?.i18n.language ?? 'nl';
|
|
18
|
+
(global as any).$getCountry = () => ContextInstance.optional?.i18n.country ?? STAMHOOFD.fixedCountry ?? Country.Belgium;
|
|
14
19
|
}
|
|
15
20
|
}
|
|
@@ -70,7 +70,7 @@ export class FileSignService {
|
|
|
70
70
|
if (e instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
71
71
|
return false;
|
|
72
72
|
}
|
|
73
|
-
console.error('Failed to verify file:', e);
|
|
73
|
+
console.error('Failed to verify file signature:', e);
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
};
|
|
@@ -115,7 +115,7 @@ export class FileSignService {
|
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
catch (e) {
|
|
118
|
-
console.error('Failed to
|
|
118
|
+
console.error('Failed to generate signedUrl for file:', e);
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
}
|