@stamhoofd/backend 2.83.0 → 2.83.2
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/package.json +10 -10
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +1218 -3
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +127 -3
- package/src/helpers/AdminPermissionChecker.ts +22 -1
- package/src/services/PlatformMembershipService.ts +42 -38
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, isPatchMap, ObjectData, PatchableArray, PatchableArrayAutoEncoder, PatchMap, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
5
|
-
import { BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel,
|
|
5
|
+
import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
9
|
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
11
|
+
import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer';
|
|
11
12
|
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater';
|
|
12
13
|
import { TagHelper } from '../../../../helpers/TagHelper';
|
|
13
14
|
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
14
|
-
import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer';
|
|
15
15
|
|
|
16
16
|
type Params = Record<string, never>;
|
|
17
17
|
type Query = undefined;
|
|
@@ -376,6 +376,130 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
376
376
|
statusCode: 403,
|
|
377
377
|
});
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
// Give users without full access permission to alter responsibilities in order to give other users permissions to resources they also have full permissions to
|
|
381
|
+
if (request.body.privateMeta && request.body.privateMeta.isPatch()) {
|
|
382
|
+
if (request.body.privateMeta.inheritedResponsibilityRoles) {
|
|
383
|
+
const patchableArray: PatchableArrayAutoEncoder<PermissionRoleForResponsibility> = new PatchableArray();
|
|
384
|
+
|
|
385
|
+
for (const patch of request.body.privateMeta.inheritedResponsibilityRoles.getPatches()) {
|
|
386
|
+
const resources: Map<PermissionsResourceType, Map<string, ResourcePermissions>> = patch.resources.applyTo(new Map<PermissionsResourceType, Map<string, ResourcePermissions>>());
|
|
387
|
+
|
|
388
|
+
if (!await Context.auth.hasFullAccessForOrganizationResources(request.body.id, resources)) {
|
|
389
|
+
throw new SimpleError({
|
|
390
|
+
code: 'permission_denied',
|
|
391
|
+
message: 'You do not have permissions to edit inherited responsibility roles',
|
|
392
|
+
statusCode: 403,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
patchableArray.addPatch(PermissionRoleForResponsibility.patch({
|
|
397
|
+
id: patch.id,
|
|
398
|
+
resources: patch.resources,
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (const { put, afterId } of request.body.privateMeta.inheritedResponsibilityRoles.getPuts()) {
|
|
403
|
+
const resources: Map<PermissionsResourceType, Map<string, ResourcePermissions>> = put.resources;
|
|
404
|
+
|
|
405
|
+
if (!await Context.auth.hasFullAccessForOrganizationResources(request.body.id, resources)) {
|
|
406
|
+
throw new SimpleError({
|
|
407
|
+
code: 'permission_denied',
|
|
408
|
+
message: 'You do not have permissions to add inherited responsibility roles',
|
|
409
|
+
statusCode: 403,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const limitedPut = PermissionRoleForResponsibility.create({
|
|
414
|
+
id: put.id,
|
|
415
|
+
name: put.name,
|
|
416
|
+
responsibilityId: put.responsibilityId,
|
|
417
|
+
responsibilityGroupId: put.responsibilityGroupId,
|
|
418
|
+
resources: put.resources,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
patchableArray.addPut(limitedPut, afterId);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
organization.privateMeta = organization.privateMeta.patch({
|
|
425
|
+
inheritedResponsibilityRoles: patchableArray,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (request.body.privateMeta.responsibilities) {
|
|
430
|
+
const patchableArray: PatchableArrayAutoEncoder<MemberResponsibility> = new PatchableArray();
|
|
431
|
+
|
|
432
|
+
for (const patch of request.body.privateMeta.responsibilities.getPatches()) {
|
|
433
|
+
if (!patch.permissions) {
|
|
434
|
+
continue;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const resources: Map<PermissionsResourceType, Map<string, ResourcePermissions>> = isPatchMap(patch.permissions.resources) ? patch.permissions.resources.applyTo(new Map<PermissionsResourceType, Map<string, ResourcePermissions>>()) : patch.permissions.resources;
|
|
438
|
+
|
|
439
|
+
if (!await Context.auth.hasFullAccessForOrganizationResources(request.body.id, resources)) {
|
|
440
|
+
throw new SimpleError({
|
|
441
|
+
code: 'permission_denied',
|
|
442
|
+
message: 'You do not have permissions to edit responsibilities',
|
|
443
|
+
statusCode: 403,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
patchableArray.addPatch(MemberResponsibility.patch({
|
|
448
|
+
id: patch.id,
|
|
449
|
+
permissions: PermissionRoleForResponsibility.patch({
|
|
450
|
+
resources: isPatchMap(patch.permissions.resources) ? patch.permissions.resources : new PatchMap(patch.permissions.resources),
|
|
451
|
+
}),
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (request.body.privateMeta.responsibilities.getPuts().length > 0) {
|
|
456
|
+
throw new SimpleError({
|
|
457
|
+
code: 'permission_denied',
|
|
458
|
+
message: 'You do not have permissions to add responsibilities',
|
|
459
|
+
statusCode: 403,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
organization.privateMeta = organization.privateMeta.patch({
|
|
464
|
+
responsibilities: patchableArray,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (request.body.privateMeta.roles) {
|
|
469
|
+
const patchableArray: PatchableArrayAutoEncoder<PermissionRoleDetailed> = new PatchableArray();
|
|
470
|
+
|
|
471
|
+
for (const patch of request.body.privateMeta.roles.getPatches()) {
|
|
472
|
+
const resources: Map<PermissionsResourceType, Map<string, ResourcePermissions>> = patch.resources.applyTo(new Map<PermissionsResourceType, Map<string, ResourcePermissions>>());
|
|
473
|
+
|
|
474
|
+
if (!await Context.auth.hasFullAccessForOrganizationResources(request.body.id, resources)) {
|
|
475
|
+
throw new SimpleError({
|
|
476
|
+
code: 'permission_denied',
|
|
477
|
+
message: 'You do not have permissions to edit roles',
|
|
478
|
+
statusCode: 403,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
patchableArray.addPatch(PermissionRoleDetailed.patch({
|
|
483
|
+
id: patch.id,
|
|
484
|
+
resources: patch.resources,
|
|
485
|
+
}));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (request.body.privateMeta.roles.getPuts().length > 0) {
|
|
489
|
+
throw new SimpleError({
|
|
490
|
+
code: 'permission_denied',
|
|
491
|
+
message: 'You do not have permissions to add roles',
|
|
492
|
+
statusCode: 403,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
organization.privateMeta = organization.privateMeta.patch({
|
|
497
|
+
roles: patchableArray,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
await organization.save();
|
|
502
|
+
}
|
|
379
503
|
}
|
|
380
504
|
|
|
381
505
|
// Only needed for permissions atm, so no put or delete here
|
|
@@ -1,7 +1,7 @@
|
|
|
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, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordSettings } from '@stamhoofd/structures';
|
|
4
|
+
import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordSettings, ResourcePermissions } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { MemberRecordStore } from '../services/MemberRecordStore';
|
|
7
7
|
import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
|
|
@@ -373,6 +373,27 @@ export class AdminPermissionChecker {
|
|
|
373
373
|
return false;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
/**
|
|
377
|
+
Returns true if the user has full access to all resource ids in the provided resources map. The resource permissions in the map are ignored for now.
|
|
378
|
+
*/
|
|
379
|
+
async hasFullAccessForOrganizationResources(organizationId: string, resources: Map<PermissionsResourceType, Map<string, ResourcePermissions>>): Promise<boolean> {
|
|
380
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId);
|
|
381
|
+
|
|
382
|
+
if (!organizationPermissions) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const [resourceType, mapForType] of resources.entries()) {
|
|
387
|
+
for (const resourceId of mapForType.keys()) {
|
|
388
|
+
if (!organizationPermissions.hasResourceAccess(resourceType, resourceId, PermissionLevel.Full)) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
376
397
|
async canAccessWebshop(webshop: { id: string; organizationId: string }, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
377
398
|
const organizationPermissions = await this.getOrganizationPermissions(webshop.organizationId);
|
|
378
399
|
|
|
@@ -214,7 +214,6 @@ export class PlatformMembershipService {
|
|
|
214
214
|
membershipTypeId: { sign: 'IN', value: types },
|
|
215
215
|
deletedAt: null,
|
|
216
216
|
});
|
|
217
|
-
const activeMembershipsUndeletable = activeMemberships.filter(m => !m.canDelete() || !m.generated);
|
|
218
217
|
|
|
219
218
|
if (defaultMemberships.length === 0) {
|
|
220
219
|
// Stop all active memberships that were added automatically
|
|
@@ -233,23 +232,6 @@ export class PlatformMembershipService {
|
|
|
233
232
|
continue;
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
if (activeMembershipsUndeletable.length) {
|
|
237
|
-
// Skip automatic additions
|
|
238
|
-
for (const m of activeMembershipsUndeletable) {
|
|
239
|
-
try {
|
|
240
|
-
await m.calculatePrice(me);
|
|
241
|
-
}
|
|
242
|
-
catch (e) {
|
|
243
|
-
// Ignore error: membership might not be available anymore
|
|
244
|
-
if (!silent) {
|
|
245
|
-
console.error('Failed to calculate price for undeletable membership', m.id, e);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
await m.save();
|
|
249
|
-
}
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
235
|
// Add the cheapest available membership
|
|
254
236
|
const organizations = await Organization.getByIDs(...Formatter.uniqueArray(defaultMemberships.map(m => m.registration.organizationId)));
|
|
255
237
|
|
|
@@ -286,28 +268,58 @@ export class PlatformMembershipService {
|
|
|
286
268
|
})[0];
|
|
287
269
|
|
|
288
270
|
if (!cheapestMembership) {
|
|
271
|
+
// Technically not possible, but for type checking
|
|
289
272
|
console.error('No membership found');
|
|
290
273
|
continue;
|
|
291
274
|
}
|
|
292
275
|
|
|
293
276
|
// Check if already have the same membership
|
|
294
277
|
// if that is the case, we'll keep that one and update the price + dates if the organization matches the cheapest/earliest membership
|
|
295
|
-
let didFind =
|
|
278
|
+
let didFind: MemberPlatformMembership | null = null;
|
|
296
279
|
for (const m of activeMemberships) {
|
|
297
280
|
if (m.membershipTypeId === cheapestMembership.membership.id && m.organizationId === cheapestMembership.registration.organizationId) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
281
|
+
if (!m.locked) {
|
|
282
|
+
// Update the price and dates of this active membership (could have changed)
|
|
283
|
+
try {
|
|
284
|
+
await m.calculatePrice(me, cheapestMembership.registration);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
// Ignore error: membership might not be available anymore
|
|
288
|
+
if (!silent) {
|
|
289
|
+
console.error('Failed to calculate price for active membership', m.id, e);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
await m.save();
|
|
301
293
|
}
|
|
302
|
-
|
|
303
|
-
|
|
294
|
+
didFind = m;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Delete all other generated memberships that are not the cheapest one
|
|
300
|
+
for (const m of activeMemberships) {
|
|
301
|
+
if (m.id !== didFind?.id) {
|
|
302
|
+
if (!m.locked && (m.generated || m.membershipTypeId === cheapestMembership.membership.id)) {
|
|
304
303
|
if (!silent) {
|
|
305
|
-
console.
|
|
304
|
+
console.log('Removing membership because cheaper membership found or duplicate, for: ' + me.id + ' - membership ' + m.id);
|
|
305
|
+
}
|
|
306
|
+
await m.doDelete();
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// Update price
|
|
310
|
+
if (!m.locked) {
|
|
311
|
+
try {
|
|
312
|
+
await m.calculatePrice(me);
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
// Ignore error: membership might not be available anymore
|
|
316
|
+
if (!silent) {
|
|
317
|
+
console.error('Failed to calculate price for undeletable membership', m.id, e);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
await m.save();
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
|
-
await m.save();
|
|
309
|
-
didFind = true;
|
|
310
|
-
break;
|
|
311
323
|
}
|
|
312
324
|
}
|
|
313
325
|
|
|
@@ -315,6 +327,8 @@ export class PlatformMembershipService {
|
|
|
315
327
|
continue;
|
|
316
328
|
}
|
|
317
329
|
|
|
330
|
+
// Otherwise make sure we create a new membership
|
|
331
|
+
|
|
318
332
|
const periodConfig = cheapestMembership.membership.periods.get(period.id);
|
|
319
333
|
if (!periodConfig) {
|
|
320
334
|
console.error('Missing membership prices for membership type ' + cheapestMembership.membership.id + ' and period ' + period.id);
|
|
@@ -351,16 +365,6 @@ export class PlatformMembershipService {
|
|
|
351
365
|
|
|
352
366
|
await membership.calculatePrice(me, cheapestMembership.registration);
|
|
353
367
|
await membership.save();
|
|
354
|
-
|
|
355
|
-
// This reasoning allows us to replace an existing membership with a cheaper one (not date based ones, but type based ones)
|
|
356
|
-
for (const toDelete of activeMemberships) {
|
|
357
|
-
if (toDelete.canDelete() && toDelete.generated) {
|
|
358
|
-
if (!silent) {
|
|
359
|
-
console.log('Removing membership because cheaper membership found for: ' + me.id + ' - membership ' + toDelete.id);
|
|
360
|
-
}
|
|
361
|
-
await toDelete.doDelete();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
368
|
}
|
|
365
369
|
});
|
|
366
370
|
});
|