@stamhoofd/backend 2.78.3 → 2.79.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 (33) hide show
  1. package/.env.ci.json +19 -8
  2. package/index.ts +8 -1
  3. package/package.json +13 -12
  4. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
  6. package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
  7. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  8. package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
  9. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +2686 -0
  10. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +206 -20
  11. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
  12. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
  13. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  14. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +15 -14
  15. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
  16. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
  17. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
  18. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
  19. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
  20. package/src/helpers/AdminPermissionChecker.ts +41 -2
  21. package/src/helpers/AuthenticatedStructures.ts +77 -20
  22. package/src/helpers/ForwardHandler.test.ts +16 -5
  23. package/src/helpers/ForwardHandler.ts +21 -9
  24. package/src/helpers/MemberUserSyncer.test.ts +822 -0
  25. package/src/helpers/MemberUserSyncer.ts +137 -108
  26. package/src/helpers/TagHelper.ts +3 -3
  27. package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
  28. package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
  29. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
  30. package/src/services/FileSignService.ts +1 -1
  31. package/src/services/PlatformMembershipService.ts +7 -2
  32. package/src/services/SSOService.ts +1 -1
  33. package/tests/e2e/register.test.ts +2 -2
@@ -1,11 +1,11 @@
1
- import { CachedBalance, Member, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Platform, User } from '@stamhoofd/models';
1
+ import { CachedBalance, Member, MemberResponsibilityRecord, MemberWithUsers, Organization, Platform, User } from '@stamhoofd/models';
2
+ import { QueueHandler } from '@stamhoofd/queues';
2
3
  import { SQL } from '@stamhoofd/sql';
3
4
  import { AuditLogSource, MemberDetails, PermissionRole, Permissions, UserPermissions } from '@stamhoofd/structures';
4
- import crypto from 'crypto';
5
+ import { Formatter } from '@stamhoofd/utility';
5
6
  import basex from 'base-x';
7
+ import crypto from 'crypto';
6
8
  import { AuditLogService } from '../services/AuditLogService';
7
- import { Formatter } from '@stamhoofd/utility';
8
- import { QueueHandler } from '@stamhoofd/queues';
9
9
 
10
10
  const ALPHABET = '123456789ABCDEFGHJKMNPQRSTUVWXYZ'; // Note: we removed 0, O, I and l to make it easier for humans
11
11
  const customBase = basex(ALPHABET);
@@ -28,89 +28,101 @@ export class MemberUserSyncerStatic {
28
28
  * - responsibilities have changed
29
29
  * - email addresses have changed
30
30
  */
31
- async onChangeMember(member: MemberWithRegistrations, unlinkUsers: boolean = false) {
32
- await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
33
- const { userEmails, parentAndUnverifiedEmails } = this.getMemberAccessEmails(member.details);
31
+ async onChangeMember(member: Member) {
32
+ await QueueHandler.schedule('change-member/' + member.id, async () => {
33
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
34
+ // Note: Refresh member data is not really required because we'll just schedule a new task if it changes again
35
+ // Reload member users
36
+ await Member.users.load(member);
37
+ if (!Member.users.isLoaded(member)) {
38
+ throw new Error('Failed to load users for member ' + member.id);
39
+ }
34
40
 
35
- // Make sure all these users have access to the member
36
- for (const email of userEmails) {
37
- // Link users that are found with these email addresses.
38
- await this.linkUser(email, member, false, true);
39
- }
41
+ const { userEmails, parentEmails, unverifiedEmails, allEmails } = this.getMemberAccessEmails(member.details);
40
42
 
41
- for (const email of parentAndUnverifiedEmails) {
42
- if (userEmails.includes(email)) {
43
- continue;
43
+ // Make sure all these users have access to the member
44
+ for (const email of userEmails) {
45
+ // Link users that are found with these email addresses.
46
+ await this.linkUser(email, member, false, {
47
+ firstName: member.details.firstName,
48
+ lastName: member.details.lastName,
49
+ });
44
50
  }
45
51
 
46
- // Link parents and unverified emails
47
- // Now we add the responsibility permissions to the parent if there are no userEmails
48
- const asParent = userEmails.length > 0 || !member.details.unverifiedEmails.includes(email) || member.details.defaultAge < 16;
49
- await this.linkUser(email, member, asParent, true);
50
- }
52
+ for (const parent of member.details.parents) {
53
+ for (const email of parent.getEmails()) {
54
+ if (userEmails.includes(email.toLocaleLowerCase())) {
55
+ continue;
56
+ }
51
57
 
52
- if (unlinkUsers && !member.details.parentsHaveAccess) {
53
- // Remove access of users that are not in this list
54
- // NOTE: we should only do this once a year (preferably on the birthday of the member)
55
- // only once because otherwise users loose the access to a member during the creation of the member, or when they have changed their email address
56
- // users can regain access to a member after they have lost control by using the normal verification flow when detecting duplicate members
58
+ // Link parents and unverified emails
59
+ await this.linkUser(email, member, true, {
60
+ firstName: parent.firstName,
61
+ lastName: parent.lastName,
62
+ });
63
+ }
64
+ }
57
65
 
58
- for (const user of member.users) {
59
- if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
60
- await this.unlinkUser(user, member);
66
+ // Make sure all these users have access to the member
67
+ for (const email of unverifiedEmails) {
68
+ if (userEmails.includes(email)) {
69
+ continue;
70
+ }
71
+ if (parentEmails.includes(email)) {
72
+ continue;
61
73
  }
74
+
75
+ // Do not update name + do not link as member (no permissions inheritance here)
76
+ await this.linkUser(email, member, true);
62
77
  }
63
- }
64
- else {
65
- // Only auto unlink users that do not have an account
66
- for (const user of member.users) {
67
- if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
78
+
79
+ // Only auto unlink users that do not have an account
80
+ // note: we create a copy of the users array here because it is modified in the loop
81
+ for (const user of [...member.users]) {
82
+ if (!allEmails.includes(user.email.toLocaleLowerCase())) {
68
83
  if (!user.hasAccount()) {
69
84
  await this.unlinkUser(user, member);
70
85
  }
71
86
  else {
72
- // Make sure only linked as a parent, not as user self
73
- // This makes sure we don't inherit permissions and aren't counted as 'being' the member
87
+ // Make sure only linked as a parent, not as user self
88
+ // This makes sure we don't inherit permissions and aren't counted as 'being' the member
74
89
  await this.linkUser(user.email, member, true);
75
90
  }
76
91
  }
77
92
  }
78
- }
79
93
 
80
- if (member.details.securityCode === null) {
81
- console.log('Generating security code for member ' + member.id);
94
+ if (member.details.securityCode === null) {
95
+ console.log('Generating security code for member ' + member.id);
82
96
 
83
- const length = 16;
84
- const code = customBase.encode(await randomBytes(100)).toUpperCase().substring(0, length);
85
- member.details.securityCode = code;
86
- await member.save();
87
- }
97
+ const length = 16;
98
+ const code = customBase.encode(await randomBytes(100)).toUpperCase().substring(0, length);
99
+ member.details.securityCode = code;
100
+ await member.save();
101
+ }
102
+ });
88
103
  });
89
104
  }
90
105
 
91
106
  getMemberAccessEmails(details: MemberDetails) {
92
- const userEmails = [...details.alternativeEmails];
93
-
94
- if (details.email) {
95
- userEmails.push(details.email);
96
- }
97
-
98
- const unverifiedEmails: string[] = details.unverifiedEmails;
99
- const parentAndUnverifiedEmails = details.parentsHaveAccess ? details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(unverifiedEmails) : details.unverifiedEmails;
107
+ const userEmails = details.getMemberEmails().map(e => e.toLocaleLowerCase());
108
+ const parentEmails = details.getParentEmails().map(e => e.toLocaleLowerCase());
109
+ const unverifiedEmails = details.unverifiedEmails.map(e => e.toLocaleLowerCase());
110
+ const allEmails = [...userEmails, ...parentEmails, ...unverifiedEmails];
100
111
 
101
112
  return {
113
+ allEmails,
102
114
  userEmails,
103
- parentAndUnverifiedEmails,
104
- emails: userEmails.concat(parentAndUnverifiedEmails),
115
+ parentEmails,
116
+ unverifiedEmails,
105
117
  };
106
118
  }
107
119
 
108
120
  doesEmailHaveAccess(details: MemberDetails, email: string) {
109
- const { emails } = this.getMemberAccessEmails(details);
110
- return emails.includes(email);
121
+ const { allEmails } = this.getMemberAccessEmails(details);
122
+ return allEmails.includes(email.toLocaleLowerCase());
111
123
  }
112
124
 
113
- async onDeleteMember(member: MemberWithRegistrations) {
125
+ async onDeleteMember(member: MemberWithUsers) {
114
126
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
115
127
  for (const u of member.users) {
116
128
  console.log('Unlinking user ' + u.email + ' from deleted member ' + member.id);
@@ -245,7 +257,7 @@ export class MemberUserSyncerStatic {
245
257
  await user.save();
246
258
  }
247
259
 
248
- async unlinkUser(user: User, member: MemberWithRegistrations) {
260
+ async unlinkUser(user: User, member: MemberWithUsers) {
249
261
  console.log('Removing access for ' + user.id + ' to member ' + member.id);
250
262
  await Member.users.reverse('members').unlink(user, member);
251
263
 
@@ -254,6 +266,11 @@ export class MemberUserSyncerStatic {
254
266
 
255
267
  if (user.memberId === member.id) {
256
268
  user.memberId = null;
269
+
270
+ if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
271
+ user.firstName = null;
272
+ user.lastName = null;
273
+ }
257
274
  }
258
275
 
259
276
  // Update model relation to correct response
@@ -265,64 +282,84 @@ export class MemberUserSyncerStatic {
265
282
  await this.updateInheritedPermissions(user);
266
283
  }
267
284
 
268
- async linkUser(email: string, member: MemberWithRegistrations, asParent: boolean, updateNameIfEqual = true) {
285
+ private async resolveUserWithMultipleMembers(currentMemberId: string, otherMember: Member): Promise<'current' | 'other'> {
286
+ const otherMemberId = otherMember.id;
287
+ const responsibilities = await this.getResponsibilitiesForMembers([otherMemberId, currentMemberId]);
288
+ const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMemberId && r.getBaseStructure().isActive);
289
+ const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === currentMemberId && r.getBaseStructure().isActive);
290
+
291
+ if (responsibilitiesOther.length > 0 && responsibilitiesCurrent.length === 0) {
292
+ return 'other';
293
+ }
294
+
295
+ if (responsibilitiesCurrent.length > 0 && responsibilitiesOther.length === 0) {
296
+ return 'current';
297
+ }
298
+ const currentMember = await Member.getByID(currentMemberId);
299
+ if (!currentMember) {
300
+ return 'other';
301
+ }
302
+
303
+ if (responsibilitiesOther.length > 0 && responsibilitiesCurrent.length > 0) {
304
+ // Resolve to oldest created member (this is always the most secure option)
305
+ if (currentMember.createdAt <= otherMember.createdAt) {
306
+ return 'current';
307
+ }
308
+
309
+ return 'other';
310
+ }
311
+
312
+ // Resolve to newest member
313
+ if (currentMember.createdAt > otherMember.createdAt) {
314
+ return 'current';
315
+ }
316
+
317
+ return 'other';
318
+ }
319
+
320
+ async linkUser(email: string, member: MemberWithUsers, asParent: boolean, name: { firstName: string; lastName: string } | null = null) {
269
321
  // This needs to happen in the queue to prevent creating duplicate users in the database
270
322
  await QueueHandler.schedule('link-user/' + email, async () => {
271
- console.log('Linking user', email, 'to member', member.id, 'as parent', asParent, 'update name if equal', updateNameIfEqual);
272
-
273
323
  let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, { allowWithoutAccount: true });
274
324
 
275
325
  if (user) {
276
- // console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
277
326
  if (!asParent) {
327
+ let setMemberId = true;
278
328
  if (user.memberId && user.memberId !== member.id) {
279
- console.error('Found conflicting user with multiple members', user.id, 'members', user.memberId, 'to', member.id);
280
-
281
- const otherMember = await Member.getWithRegistrations(user.memberId);
282
-
283
- if (otherMember) {
284
- if (otherMember.registrations.length > 0 && member.registrations.length === 0) {
285
- // Choose the other member
286
- // don't make changes
287
- console.error('Resolved to current member - no changes made');
288
- return;
289
- }
290
-
291
- const responsibilities = await this.getResponsibilitiesForMembers([otherMember.id, member.id]);
292
- const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMember.id);
293
- const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === member.id);
294
-
295
- if (responsibilitiesOther.length >= responsibilitiesCurrent.length) {
296
- console.error('Resolved to current member because of more responsibilities - no changes made');
297
- return;
298
- }
329
+ const resolveTo = await this.resolveUserWithMultipleMembers(user.memberId, member);
330
+
331
+ if (resolveTo === 'current') {
332
+ // Do not change memberId
333
+ setMemberId = false;
299
334
  }
300
335
  }
301
336
 
302
- if (updateNameIfEqual) {
303
- user.firstName = member.details.firstName;
304
- user.lastName = member.details.lastName;
337
+ if (setMemberId) {
338
+ if (name) {
339
+ user.firstName = name.firstName;
340
+ user.lastName = name.lastName;
341
+ }
342
+ user.memberId = member.id;
305
343
  }
306
- user.memberId = member.id;
307
344
  await this.updateInheritedPermissions(user);
308
345
  }
309
346
  else {
310
347
  let shouldSave = false;
311
348
 
312
- if (!user.firstName && !user.lastName) {
313
- const parents = member.details.parents.filter(p => p.email === email);
314
- if (parents.length === 1) {
315
- if (updateNameIfEqual) {
316
- user.firstName = parents[0].firstName;
317
- user.lastName = parents[0].lastName;
318
- }
349
+ // Clear clearly wrong name
350
+ if (user.firstName?.toLocaleLowerCase() === member.details.firstName?.toLocaleLowerCase() && user.lastName?.toLocaleLowerCase() === member.details.lastName?.toLocaleLowerCase()) {
351
+ if (!name || (name.firstName !== user.firstName && name.lastName !== user.lastName)) {
352
+ user.firstName = null;
353
+ user.lastName = null;
319
354
  shouldSave = true;
320
355
  }
321
356
  }
322
357
 
323
- if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
324
- user.firstName = null;
325
- user.lastName = null;
358
+ if (!user.firstName && !user.lastName) {
359
+ if (name) {
360
+ user.firstName = name.firstName;
361
+ user.lastName = name.lastName;
362
+ }
326
363
  shouldSave = true;
327
364
  }
328
365
 
@@ -341,25 +378,17 @@ export class MemberUserSyncerStatic {
341
378
  user = new User();
342
379
  user.organizationId = member.organizationId;
343
380
  user.email = email;
381
+ if (name) {
382
+ user.firstName = name.firstName;
383
+ user.lastName = name.lastName;
384
+ }
344
385
 
345
386
  if (!asParent) {
346
- if (updateNameIfEqual) {
347
- user.firstName = member.details.firstName;
348
- user.lastName = member.details.lastName;
349
- }
350
387
  user.memberId = member.id;
351
388
  await this.updateInheritedPermissions(user);
352
389
  }
353
390
  else {
354
- const parents = member.details.parents.filter(p => p.email === email);
355
- if (parents.length === 1) {
356
- if (updateNameIfEqual) {
357
- user.firstName = parents[0].firstName;
358
- user.lastName = parents[0].lastName;
359
- }
360
- }
361
-
362
- if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
391
+ if (!name && user.firstName?.toLocaleLowerCase() === member.details.firstName?.toLocaleLowerCase() && user.lastName?.toLocaleLowerCase() === member.details.lastName?.toLocaleLowerCase()) {
363
392
  user.firstName = null;
364
393
  user.lastName = null;
365
394
  }
@@ -10,12 +10,12 @@ export class TagHelper extends SharedTagHelper {
10
10
 
11
11
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
12
12
  await QueueHandler.schedule(queueId, async () => {
13
- let platform = await Platform.getShared();
13
+ const sharedPlatform = await Platform.getShared();
14
14
 
15
15
  const tagCounts = new Map<string, number>();
16
16
 
17
17
  for await (const organization of Organization.select().all()) {
18
- organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
18
+ organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, sharedPlatform.config.tags);
19
19
 
20
20
  for (const tag of organization.meta.tags) {
21
21
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
@@ -24,7 +24,7 @@ export class TagHelper extends SharedTagHelper {
24
24
  }
25
25
 
26
26
  // Reload platform to avoid race conditions
27
- platform = await Platform.getShared();
27
+ const platform = await Platform.getForEditing();
28
28
  for (const [tag, count] of tagCounts.entries()) {
29
29
  const tagObject = platform.config.tags.find(t => t.id === tag);
30
30
  if (tagObject) {
@@ -44,7 +44,7 @@ export default new Migration(async () => {
44
44
  console.log('Updated ' + c + ' registration periods');
45
45
 
46
46
  // Now update platform
47
- const platform = await Platform.getShared();
47
+ const platform = await Platform.getForEditing();
48
48
  await platform.setPreviousPeriodId();
49
49
  await platform.save();
50
50
 
@@ -0,0 +1,50 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { AuditLog } from '@stamhoofd/models';
3
+ import { scalarToSQLExpression, SQL, SQLWhereLike } from '@stamhoofd/sql';
4
+
5
+ export default new Migration(async () => {
6
+ if (STAMHOOFD.environment === 'test') {
7
+ console.log('skipped in tests');
8
+ return;
9
+ }
10
+
11
+ if (STAMHOOFD.platformName !== 'keeo') {
12
+ console.log('skipped for platform (only run for keeo): ' + STAMHOOFD.platformName);
13
+ return;
14
+ }
15
+
16
+ console.log('Start fixing audit log descriptions.');
17
+
18
+ const batchSize = 100;
19
+ let count = 0;
20
+
21
+ for await (const log of AuditLog.select()
22
+ .where(new SQLWhereLike(
23
+ SQL.column('audit_logs', 'description'),
24
+ scalarToSQLExpression(`%werd verplaatst van % naar %, door%`),
25
+ )).limit(batchSize).all()) {
26
+ if (log.meta.get('fixed') === 1) {
27
+ continue;
28
+ }
29
+ log.description = fixDescription(log.description);
30
+ log.meta.set('fixed', 1);
31
+ await log.save();
32
+ count += 1;
33
+ }
34
+
35
+ console.log('Finished saving ' + count + ' audit logs.');
36
+ });
37
+
38
+ function fixDescription(description: string): string {
39
+ const regex = /werd verplaatst van (.+) naar (.*), door/;
40
+ const result = description.match(regex);
41
+ if (!result) {
42
+ throw new Error(`Could not fix audit log description, no regex match found: ${description}`);
43
+ }
44
+
45
+ const to = result[1];
46
+ const from = result[2];
47
+
48
+ const fixed = description.replace(regex, `werd verplaatst van ${from} naar ${to}, door`);
49
+ return fixed;
50
+ }
@@ -0,0 +1,88 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { AuditLog } from '@stamhoofd/models';
3
+ import { scalarToSQLExpression, SQL, SQLWhereLike } from '@stamhoofd/sql';
4
+
5
+ const replacements: [from: string, to: string][] = [
6
+ ['u00eb', 'ë'],
7
+ ['u00E9', 'é'],
8
+ ['U00EF', 'ï'],
9
+ ['U00E6', 'æ'],
10
+ ['U00F6', 'ö'],
11
+ ['U00E0', 'à'],
12
+ ['U00E1', 'á'],
13
+ ['U00E7', 'ç'],
14
+ ['U00E4', 'ä'],
15
+ ['U00E5', 'å'],
16
+ ['U00E8', 'è'],
17
+ ['U00F3', 'ó'],
18
+ ['U00ED', 'í'],
19
+ ['U00C4', 'Ä'],
20
+ ['U00C1', 'Á'],
21
+ ['U00FC', 'ü'],
22
+ ['U00F4', 'ö'],
23
+ ['U00D6', 'Ö'],
24
+ ['U00C9', 'É'],
25
+ ['U00F2', 'ò'],
26
+ ['U00E2', 'â'],
27
+ ['U00F2', 'ò'],
28
+ ['U00F2', 'ò'],
29
+ ['U00E2', 'â'],
30
+ ['U00EC', 'ì'],
31
+ ['U00C5', 'Å'],
32
+ ['U00EE', 'î'],
33
+ ['U00A9', '©'],
34
+ ['U00C3', 'Ã'],
35
+ ['U00C7', 'Ç'],
36
+ ['U00F9', 'ù'],
37
+ ['U00EA', 'ê'],
38
+ ['U00FF', 'ÿ'],
39
+ ['U00CA', 'Ê'],
40
+ ['U00CD', 'Í'],
41
+ ['U00A8', '¨'],
42
+ ['U00AF', '¯'],
43
+ ['U00AB', '«'],
44
+ ['U00CE', 'Î'],
45
+ ];
46
+
47
+ export default new Migration(async () => {
48
+ if (STAMHOOFD.environment === 'test') {
49
+ console.log('skipped in tests');
50
+ return;
51
+ }
52
+
53
+ if (STAMHOOFD.platformName !== 'keeo') {
54
+ console.log('skipped for platform (only run for keeo): ' + STAMHOOFD.platformName);
55
+ return;
56
+ }
57
+
58
+ console.log('Start fixing audit log uft16 descriptions.');
59
+
60
+ const batchSize = 100;
61
+ let count = 0;
62
+
63
+ for await (const log of AuditLog.select()
64
+ .where(new SQLWhereLike(
65
+ SQL.column('audit_logs', 'description'),
66
+ scalarToSQLExpression(`%u00%`),
67
+ ))
68
+ .limit(batchSize).all()) {
69
+ if (log.meta.get('fixed-uft16') === 1) {
70
+ continue;
71
+ }
72
+ log.description = fixDescription(log.description);
73
+ log.meta.set('fixed-uft16', 1);
74
+ await log.save();
75
+ count += 1;
76
+ }
77
+
78
+ console.log('Finished saving ' + count + ' audit logs uft16.');
79
+ });
80
+
81
+ function fixDescription(description: string): string {
82
+ let result = description;
83
+ replacements.forEach(([from, to]) => {
84
+ result = result.replace(new RegExp(from.toLowerCase(), 'g'), to);
85
+ });
86
+
87
+ return result;
88
+ }
@@ -22,7 +22,7 @@ export class FileSignService {
22
22
  const alg = STAMHOOFD.FILE_SIGNING_ALG || 'ES256';
23
23
 
24
24
  if (!STAMHOOFD.FILE_SIGNING_PUBLIC_KEY || !STAMHOOFD.FILE_SIGNING_PRIVATE_KEY) {
25
- if (STAMHOOFD.environment !== 'development') {
25
+ if (STAMHOOFD.environment !== 'development' && STAMHOOFD.environment !== 'test') {
26
26
  throw new Error('Missing environment variables for file signing. Please make sure FILE_SIGNING_PUBLIC_KEY, FILE_SIGNING_PRIVATE_KEY and FILE_SIGNING_ALG are set.');
27
27
  }
28
28
 
@@ -169,6 +169,13 @@ export class PlatformMembershipService {
169
169
  console.warn('No periods found for member ' + me.id);
170
170
  }
171
171
  }
172
+ const types = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
173
+ if (types.length === 0) {
174
+ if (!silent) {
175
+ console.warn('No membership types found for memberships');
176
+ }
177
+ return;
178
+ }
172
179
 
173
180
  // Every (not-locked) period can have a generated membership
174
181
  for (const period of periods) {
@@ -200,8 +207,6 @@ export class PlatformMembershipService {
200
207
  }];
201
208
  });
202
209
 
203
- const types = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
204
-
205
210
  // Get active memberships for this member that
206
211
  const activeMemberships = await MemberPlatformMembership.where({
207
212
  memberId: me.id,
@@ -130,7 +130,7 @@ export class SSOService {
130
130
 
131
131
  static async fromContext(provider: LoginProviderType) {
132
132
  const organization = Context.organization;
133
- const platform = await Platform.getShared();
133
+ const platform = await Platform.getForEditing();
134
134
 
135
135
  const service = new SSOService({ provider, platform, organization });
136
136
  service.validateConfiguration();
@@ -1035,7 +1035,7 @@ describe('E2E.Register', () => {
1035
1035
  ]),
1036
1036
  });
1037
1037
 
1038
- const platform = await Platform.getShared();
1038
+ const platform = await Platform.getForEditing();
1039
1039
 
1040
1040
  platform.config.membershipTypes = [
1041
1041
  platformMembershipType,
@@ -1135,7 +1135,7 @@ describe('E2E.Register', () => {
1135
1135
  ]),
1136
1136
  });
1137
1137
 
1138
- const platform = await Platform.getShared();
1138
+ const platform = await Platform.getForEditing();
1139
1139
 
1140
1140
  platform.config.membershipTypes = [
1141
1141
  platformMembershipType,