@unchainedshop/core-users 4.0.0-rc.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/lib/db/UsersCollection.d.ts +2 -2
  2. package/lib/db/UsersCollection.d.ts.map +1 -1
  3. package/lib/db/UsersCollection.js +30 -26
  4. package/lib/db/UsersCollection.js.map +1 -1
  5. package/lib/module/addMigrations.d.ts +3 -0
  6. package/lib/module/addMigrations.d.ts.map +1 -0
  7. package/lib/module/addMigrations.js +57 -0
  8. package/lib/module/addMigrations.js.map +1 -0
  9. package/lib/module/configureUsersModule.d.ts +32 -31
  10. package/lib/module/configureUsersModule.d.ts.map +1 -1
  11. package/lib/module/configureUsersModule.js +74 -34
  12. package/lib/module/configureUsersModule.js.map +1 -1
  13. package/lib/module/configureUsersWebAuthnModule.d.ts +5 -5
  14. package/lib/module/configureUsersWebAuthnModule.d.ts.map +1 -1
  15. package/lib/module/configureUsersWebAuthnModule.js +9 -3
  16. package/lib/module/configureUsersWebAuthnModule.js.map +1 -1
  17. package/lib/module/pbkdf2.d.ts.map +1 -1
  18. package/lib/module/pbkdf2.js +9 -2
  19. package/lib/module/pbkdf2.js.map +1 -1
  20. package/lib/services/getUserCountryService.d.ts +3 -0
  21. package/lib/services/getUserCountryService.d.ts.map +1 -0
  22. package/lib/services/getUserCountryService.js +7 -0
  23. package/lib/services/getUserCountryService.js.map +1 -0
  24. package/lib/services/getUserLanguageService.d.ts +3 -0
  25. package/lib/services/getUserLanguageService.d.ts.map +1 -0
  26. package/lib/services/getUserLanguageService.js +7 -0
  27. package/lib/services/getUserLanguageService.js.map +1 -0
  28. package/lib/services/updateUserAvatarAfterUploadService.d.ts +3 -0
  29. package/lib/services/updateUserAvatarAfterUploadService.d.ts.map +1 -0
  30. package/lib/services/updateUserAvatarAfterUploadService.js +24 -0
  31. package/lib/services/updateUserAvatarAfterUploadService.js.map +1 -0
  32. package/lib/services/userServices.d.ts +3 -0
  33. package/lib/services/userServices.d.ts.map +1 -0
  34. package/lib/services/userServices.js +9 -0
  35. package/lib/services/userServices.js.map +1 -0
  36. package/lib/users-settings.d.ts +1 -14
  37. package/lib/users-settings.d.ts.map +1 -1
  38. package/lib/users-settings.js +24 -24
  39. package/lib/users-settings.js.map +1 -1
  40. package/package.json +8 -8
  41. package/src/db/UsersCollection.ts +33 -29
  42. package/src/module/buildFindSelector.test.ts +5 -2
  43. package/src/module/configureUsersModule.ts +114 -78
  44. package/src/module/configureUsersWebAuthnModule.ts +8 -8
  45. package/src/module/pbkdf2.ts +9 -2
  46. package/src/module/removeConfidentialServiceHashes.test.ts +1 -2
  47. package/src/users-settings.ts +31 -45
  48. package/tsconfig.json +2 -1
@@ -4,7 +4,10 @@ import { buildFindSelector } from './configureUsersModule.js';
4
4
 
5
5
  describe('buildFindSelector', () => {
6
6
  it('Return the correct filter when no parameter is passed', () => {
7
- assert.deepStrictEqual(buildFindSelector({}), { deleted: null, guest: { $in: [false, null] } });
7
+ assert.deepStrictEqual(buildFindSelector({}), {
8
+ deleted: null,
9
+ guest: { $ne: true },
10
+ });
8
11
  });
9
12
  it('Return the correct filter when no parameter is passed queryString and includeGuest: true', () => {
10
13
  assert.deepStrictEqual(buildFindSelector({ queryString: 'Hello world', includeGuests: true }), {
@@ -23,7 +26,7 @@ describe('buildFindSelector', () => {
23
26
  {
24
27
  'profile.displayName': 'mikael',
25
28
  deleted: null,
26
- guest: { $in: [false, null] },
29
+ guest: { $ne: true },
27
30
  $text: { $search: 'Hello world' },
28
31
  },
29
32
  );
@@ -1,4 +1,4 @@
1
- import * as bcrypt from '@node-rs/bcrypt';
1
+ import * as bcrypt from 'bcryptjs';
2
2
  import {
3
3
  ModuleInput,
4
4
  Address,
@@ -47,7 +47,7 @@ const USER_EVENTS = [
47
47
  'USER_REMOVE',
48
48
  ];
49
49
  export const removeConfidentialServiceHashes = (rawUser: User): User => {
50
- const user = rawUser;
50
+ const user = { ...rawUser };
51
51
  delete user?.services;
52
52
  return user;
53
53
  };
@@ -58,14 +58,18 @@ export const buildFindSelector = ({
58
58
  queryString,
59
59
  emailVerified,
60
60
  lastLogin,
61
+ tags,
61
62
  ...rest
62
63
  }: UserQuery) => {
63
64
  const selector: mongodb.Filter<User> = { ...rest };
64
- if (!includeDeleted) selector.deleted = null;
65
- if (!includeGuests) selector.guest = { $in: [false, null] };
65
+ if (!includeDeleted) selector.deleted = null as any;
66
+ if (!includeGuests) selector.guest = { $ne: true };
66
67
  if (emailVerified === true) {
67
68
  selector['emails.verified'] = true;
68
69
  }
70
+ if (Array.isArray(tags) && tags?.length) {
71
+ selector.tags = { $in: tags };
72
+ }
69
73
  if (emailVerified === false) {
70
74
  // We need to use $ne here else we'd also find users with many emails where one is
71
75
  // unverified
@@ -87,12 +91,13 @@ export const buildFindSelector = ({
87
91
  return selector;
88
92
  };
89
93
 
90
- export const configureUsersModule = async ({ db, options }: ModuleInput<UserSettingsOptions>) => {
94
+ export const configureUsersModule = async (moduleInput: ModuleInput<UserSettingsOptions>) => {
95
+ const { db, options } = moduleInput;
91
96
  userSettings.configureSettings(options || {}, db);
92
97
  registerEvents(USER_EVENTS);
93
98
  const Users = await UsersCollection(db);
94
99
 
95
- const webAuthn = await configureUsersWebAuthnModule({ db, options });
100
+ const webAuthn = await configureUsersWebAuthnModule(moduleInput);
96
101
 
97
102
  return {
98
103
  // Queries
@@ -102,17 +107,17 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
102
107
  return userCount;
103
108
  },
104
109
 
105
- async findUserById(userId: string): Promise<User> {
110
+ async findUserById(userId: string) {
106
111
  if (!userId) return null;
107
112
  return Users.findOne(generateDbFilterById(userId), {});
108
113
  },
109
114
 
110
- async findUserByUsername(username: string): Promise<User> {
115
+ async findUserByUsername(username: string) {
111
116
  if (!username) return null;
112
117
  return Users.findOne({ username: insensitiveTrimmedRegexOperator(username) }, {});
113
118
  },
114
119
 
115
- async findUserByEmail(email: string): Promise<User> {
120
+ async findUserByEmail(email: string) {
116
121
  if (!email) return null;
117
122
  return Users.findOne({ 'emails.address': insensitiveTrimmedRegexOperator(email) }, {});
118
123
  },
@@ -121,7 +126,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
121
126
  userId: string;
122
127
  address: string;
123
128
  when: Date;
124
- }> {
129
+ } | null> {
125
130
  if (!plainToken) return null;
126
131
  const token = await sha256(plainToken);
127
132
  const user = await Users.findOne(
@@ -174,7 +179,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
174
179
  userId: string;
175
180
  address: string;
176
181
  when: Date;
177
- }> {
182
+ } | null> {
178
183
  const token = await sha256(plainToken);
179
184
  const user = await Users.findOne(
180
185
  {
@@ -195,7 +200,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
195
200
  };
196
201
  },
197
202
 
198
- async findUserByToken(plainToken: string): Promise<User> {
203
+ async findUserByToken(plainToken: string) {
199
204
  const token = await sha256(plainToken);
200
205
 
201
206
  if (token) {
@@ -207,10 +212,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
207
212
  return null;
208
213
  },
209
214
 
210
- async findUser(
211
- query: UserQuery & { sort?: SortOption[] },
212
- findOptions?: mongodb.FindOptions,
213
- ): Promise<User> {
215
+ async findUser(query: UserQuery & { sort?: SortOption[] }, findOptions?: mongodb.FindOptions) {
214
216
  const selector = buildFindSelector(query);
215
217
  return Users.findOne(selector, findOptions);
216
218
  },
@@ -244,7 +246,10 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
244
246
  },
245
247
 
246
248
  async userExists({ userId }: { userId: string }): Promise<boolean> {
247
- const userCount = await Users.countDocuments({ _id: userId, deleted: null }, { limit: 1 });
249
+ const userCount = await Users.countDocuments(
250
+ { _id: userId, deleted: { $exists: false } },
251
+ { limit: 1 },
252
+ );
248
253
  return userCount === 1;
249
254
  },
250
255
 
@@ -255,7 +260,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
255
260
  )?.[0];
256
261
  },
257
262
 
258
- userLocale(user: User): Intl.Locale {
263
+ userLocale(user: User | null): Intl.Locale {
259
264
  if (!user?.lastLogin?.locale) return systemLocale;
260
265
  try {
261
266
  return new Intl.Locale(user.lastLogin.locale);
@@ -344,7 +349,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
344
349
  /* */
345
350
  }
346
351
 
347
- const user = await Users.findOne({ _id: userId }, {});
352
+ const user = (await Users.findOne({ _id: userId }, {})) as User;
348
353
  await emit('USER_CREATE', {
349
354
  user: removeConfidentialServiceHashes(user),
350
355
  });
@@ -453,39 +458,46 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
453
458
  });
454
459
  },
455
460
 
456
- addRoles: async (userId: string, roles: string[]): Promise<number> => {
461
+ addRoles: async (userId: string, roles: string[]) => {
457
462
  const selector = generateDbFilterById(userId);
458
- const updateResult = await Users.updateOne(selector, {
459
- $addToSet: { roles: { $each: roles } },
460
- });
463
+ const user = await Users.findOneAndUpdate(
464
+ selector,
465
+ {
466
+ $addToSet: { roles: { $each: roles } },
467
+ },
468
+ { returnDocument: 'after' },
469
+ );
470
+
471
+ if (!user) return null;
461
472
 
462
- const user = await Users.findOne(selector, {});
463
473
  await emit('USER_ADD_ROLES', {
464
474
  user: removeConfidentialServiceHashes(user),
465
475
  });
466
476
 
467
- return updateResult.modifiedCount;
477
+ return user;
468
478
  },
469
479
 
470
480
  async setUsername(userId: string, username: string) {
471
481
  if (!(await userSettings.validateUsername(username))) {
472
482
  throw new Error(`Username ${username} is invalid`, { cause: 'USERNAME_INVALID' });
473
483
  }
474
- await Users.updateOne(
484
+ const user = await Users.findOneAndUpdate(
475
485
  { _id: userId },
476
486
  {
477
487
  $set: {
478
488
  username: username.trim(),
479
489
  },
480
490
  },
491
+ { returnDocument: 'after' },
481
492
  );
482
- const user = await Users.findOne({ _id: userId }, {});
493
+ if (!user) return null;
483
494
  await emit('USER_UPDATE_USERNAME', {
484
495
  user: removeConfidentialServiceHashes(user),
485
496
  });
497
+ return user;
486
498
  },
487
499
 
488
- async setPassword(userId: string, plainPassword: string): Promise<User> {
500
+ async setPassword(userId: string, plainPassword: string) {
489
501
  if (!(await userSettings.validatePassword(plainPassword))) {
490
502
  throw new Error(`Password ***** is invalid`, { cause: 'PASSWORD_INVALID' });
491
503
  }
@@ -502,13 +514,14 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
502
514
  },
503
515
  { returnDocument: 'after' },
504
516
  );
517
+ if (!user) return null;
505
518
  await emit('USER_UPDATE_PASSWORD', {
506
519
  user: removeConfidentialServiceHashes(user),
507
520
  });
508
521
  return user;
509
522
  },
510
523
 
511
- async resetPassword(token: string, newPassword: string): Promise<User> {
524
+ async resetPassword(token: string, newPassword: string) {
512
525
  const resetToken = await this.findResetToken(token);
513
526
  if (!resetToken) return null;
514
527
  const updatedUser = await this.setPassword(resetToken.userId, newPassword);
@@ -536,7 +549,7 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
536
549
  return updatedUser;
537
550
  },
538
551
 
539
- updateAvatar: async (_id: string, fileId: string): Promise<User> => {
552
+ updateAvatar: async (_id: string, fileId: string) => {
540
553
  const userFilter = generateDbFilterById(_id);
541
554
  const modifier = {
542
555
  $set: {
@@ -549,44 +562,54 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
549
562
  returnDocument: 'after',
550
563
  });
551
564
 
565
+ if (!user) return null;
552
566
  await emit('USER_UPDATE_AVATAR', {
553
567
  user: removeConfidentialServiceHashes(user),
554
568
  });
555
569
  return user;
556
570
  },
557
571
 
558
- updateGuest: async (user: User, guest: boolean): Promise<void> => {
559
- const modifier = { $set: { guest } };
560
- await Users.updateOne(generateDbFilterById(user._id), modifier);
572
+ updateGuest: async (user: User, guest: boolean) => {
573
+ const updatedUser = await Users.findOneAndUpdate(
574
+ generateDbFilterById(user._id),
575
+ {
576
+ $set: { guest },
577
+ },
578
+ { returnDocument: 'after' },
579
+ );
580
+ if (!updatedUser) return null;
561
581
  await emit('USER_UPDATE_GUEST', {
562
582
  user: removeConfidentialServiceHashes({
563
- ...user,
583
+ ...updatedUser,
564
584
  guest,
565
585
  }),
566
586
  });
587
+ return updatedUser;
567
588
  },
568
589
 
569
- updateHeartbeat: async (userId: string, lastLogin: UserLastLogin): Promise<User> => {
570
- const modifier = {
571
- $set: {
572
- lastLogin: {
573
- timestamp: new Date(),
574
- ...lastLogin,
590
+ updateHeartbeat: async (userId: string, lastLogin: UserLastLogin) => {
591
+ const user = await Users.findOneAndUpdate(
592
+ generateDbFilterById(userId),
593
+ {
594
+ $set: {
595
+ lastLogin: {
596
+ timestamp: new Date(),
597
+ ...lastLogin,
598
+ },
575
599
  },
576
600
  },
577
- };
578
-
579
- const user = await Users.findOneAndUpdate(generateDbFilterById(userId), modifier, {
580
- returnDocument: 'after',
581
- });
582
-
601
+ {
602
+ returnDocument: 'after',
603
+ },
604
+ );
605
+ if (!user) return null;
583
606
  await emit('USER_UPDATE_HEARTBEAT', {
584
607
  user: removeConfidentialServiceHashes(user),
585
608
  });
586
609
  return user;
587
610
  },
588
611
 
589
- markDeleted: async (userId: string): Promise<User> => {
612
+ markDeleted: async (userId: string) => {
590
613
  await db.collection('sessions').deleteMany({
591
614
  session: insensitiveTrimmedRegexOperator(`"user":"${userId}"`),
592
615
  });
@@ -598,18 +621,21 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
598
621
  deleted: new Date(),
599
622
  emails: [],
600
623
  roles: [],
601
- profile: null,
602
- lastBillingAddress: null,
603
624
  services: {},
604
625
  pushSubscriptions: [],
605
- avatarId: null,
606
626
  initialPassword: false,
607
- lastContact: null,
608
- lastLogin: null,
627
+ },
628
+ $unset: {
629
+ profile: 1,
630
+ lastBillingAddress: 1,
631
+ lastContact: 1,
632
+ lastLogin: 1,
633
+ avatarId: 1,
609
634
  },
610
635
  },
611
636
  { returnDocument: 'after' },
612
637
  );
638
+ if (!user) return null;
613
639
 
614
640
  await emit('USER_REMOVE', {
615
641
  user,
@@ -617,14 +643,11 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
617
643
  return user;
618
644
  },
619
645
 
620
- deletePermanently: async ({ userId }: { userId: string }): Promise<User> => {
646
+ deletePermanently: async ({ userId }: { userId: string }) => {
621
647
  return Users.findOneAndDelete({ _id: userId });
622
648
  },
623
649
 
624
- updateProfile: async (
625
- userId: string,
626
- updatedData: { profile?: UserProfile; meta?: any },
627
- ): Promise<User> => {
650
+ updateProfile: async (userId: string, updatedData: { profile?: UserProfile; meta?: any }) => {
628
651
  const userFilter = generateDbFilterById(userId);
629
652
  const { meta, profile } = updatedData;
630
653
 
@@ -653,16 +676,17 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
653
676
  returnDocument: 'after',
654
677
  });
655
678
 
679
+ if (!user) return null;
656
680
  await emit('USER_UPDATE_PROFILE', {
657
681
  user: removeConfidentialServiceHashes(user),
658
682
  });
659
683
  return user;
660
684
  },
661
685
 
662
- updateLastBillingAddress: async (_id: string, lastBillingAddress: Address): Promise<User> => {
686
+ updateLastBillingAddress: async (_id: string, lastBillingAddress: Address) => {
663
687
  const userFilter = generateDbFilterById(_id);
664
688
  const user = await Users.findOne(userFilter, {});
665
-
689
+ if (!user) return null;
666
690
  if (!lastBillingAddress) return user;
667
691
 
668
692
  const modifier = {
@@ -687,18 +711,17 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
687
711
  returnDocument: 'after',
688
712
  });
689
713
 
690
- if (updatedUser) {
691
- await emit('USER_UPDATE_BILLING_ADDRESS', {
692
- user: removeConfidentialServiceHashes(updatedUser),
693
- });
694
- }
695
-
714
+ if (!updatedUser) return null;
715
+ await emit('USER_UPDATE_BILLING_ADDRESS', {
716
+ user: removeConfidentialServiceHashes(updatedUser),
717
+ });
696
718
  return updatedUser;
697
719
  },
698
720
 
699
- updateLastContact: async (_id: string, lastContact: Contact): Promise<User> => {
721
+ updateLastContact: async (_id: string, lastContact: Contact) => {
700
722
  const userFilter = generateDbFilterById(_id);
701
723
  const user = await Users.findOne(userFilter, {});
724
+ if (!user) return null;
702
725
  const profile = user.profile || {};
703
726
  const isGuest = !!user.guest;
704
727
 
@@ -718,13 +741,14 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
718
741
  returnDocument: 'after',
719
742
  });
720
743
 
744
+ if (!updatedUser) return null;
721
745
  await emit('USER_UPDATE_LAST_CONTACT', {
722
746
  user: removeConfidentialServiceHashes(user),
723
747
  });
724
748
  return updatedUser;
725
749
  },
726
750
 
727
- updateRoles: async (_id: string, roles: string[]): Promise<User> => {
751
+ updateRoles: async (_id: string, roles: string[]) => {
728
752
  const modifier = {
729
753
  $set: {
730
754
  updated: new Date(),
@@ -735,23 +759,27 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
735
759
  const user = await Users.findOneAndUpdate(generateDbFilterById(_id), modifier, {
736
760
  returnDocument: 'after',
737
761
  });
738
-
762
+ if (!user) return null;
739
763
  await emit('USER_UPDATE_ROLE', {
740
764
  user: removeConfidentialServiceHashes(user),
741
765
  });
742
766
  return user;
743
767
  },
744
768
 
745
- updateTags: async (_id: string, tags: string[]): Promise<User> => {
746
- const modifier = {
747
- $set: {
748
- updated: new Date(),
749
- tags,
769
+ updateTags: async (_id: string, tags: string[]) => {
770
+ const user = await Users.findOneAndUpdate(
771
+ generateDbFilterById(_id),
772
+ {
773
+ $set: {
774
+ updated: new Date(),
775
+ tags,
776
+ },
750
777
  },
751
- };
752
- const user = await Users.findOneAndUpdate(generateDbFilterById(_id), modifier, {
753
- returnDocument: 'after',
754
- });
778
+ {
779
+ returnDocument: 'after',
780
+ },
781
+ );
782
+ if (!user) return null;
755
783
  await emit('USER_UPDATE_TAGS', {
756
784
  user: removeConfidentialServiceHashes(user),
757
785
  });
@@ -762,11 +790,12 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
762
790
  selector: mongodb.Filter<User>,
763
791
  modifier: mongodb.UpdateFilter<User>,
764
792
  updateOptions?: mongodb.FindOneAndUpdateOptions,
765
- ): Promise<User> => {
793
+ ) => {
766
794
  const user = await Users.findOneAndUpdate(selector, modifier, {
767
795
  ...updateOptions,
768
796
  returnDocument: 'after',
769
797
  });
798
+ if (!user) return null;
770
799
  await emit('USER_UPDATE', {
771
800
  user: removeConfidentialServiceHashes(user),
772
801
  });
@@ -816,6 +845,13 @@ export const configureUsersModule = async ({ db, options }: ModuleInput<UserSett
816
845
  {},
817
846
  );
818
847
  },
848
+ existingTags: async (): Promise<string[]> => {
849
+ const tags = (await Users.distinct('tags', {
850
+ tags: { $exists: true },
851
+ deleted: { $exists: false },
852
+ })) as string[];
853
+ return tags.filter(Boolean).toSorted();
854
+ },
819
855
  };
820
856
  };
821
857
 
@@ -6,7 +6,7 @@ import type { PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOpti
6
6
 
7
7
  const logger = createLogger('unchained:core-users');
8
8
 
9
- const { ROOT_URL, EMAIL_WEBSITE_NAME } = process.env;
9
+ const { ROOT_URL = 'http://localhost:4010', EMAIL_WEBSITE_NAME = 'Unchained' } = process.env;
10
10
 
11
11
  type SerializedOptions<T> = Omit<T, 'challenge' | 'requestId'> & {
12
12
  challenge: string;
@@ -86,7 +86,8 @@ export const configureUsersWebAuthnModule = async ({ db }: ModuleInput<any>) =>
86
86
  if (!f2l) return null;
87
87
 
88
88
  const registrationOptions = await f2l.attestationOptions(extensionOptions);
89
- const challenge = Buffer.from(registrationOptions.challenge).toString('base64');
89
+ // Convert challenge to base64 without using Buffer
90
+ const challenge = btoa(String.fromCharCode(...new Uint8Array(registrationOptions.challenge)));
90
91
  const { insertedId } = await WebAuthnCredentialsCreationRequests.insertOne({
91
92
  _id: new Date().getTime(),
92
93
  challenge,
@@ -102,15 +103,12 @@ export const configureUsersWebAuthnModule = async ({ db }: ModuleInput<any>) =>
102
103
  } as SerializedOptions<PublicKeyCredentialCreationOptions>;
103
104
  },
104
105
 
105
- createCredentialRequestOptions: async (
106
- origin: string,
107
- username?: string,
108
- extensionOptions?: any,
109
- ) => {
106
+ createCredentialRequestOptions: async (origin: string, username: string, extensionOptions?: any) => {
110
107
  if (!f2l) return null;
111
108
 
112
109
  const loginOptions = await f2l.assertionOptions(extensionOptions);
113
- const challenge = Buffer.from(loginOptions.challenge).toString('base64');
110
+ // Convert challenge to base64 without using Buffer
111
+ const challenge = btoa(String.fromCharCode(...new Uint8Array(loginOptions.challenge)));
114
112
  const { insertedId } = await WebAuthnCredentialsCreationRequests.insertOne({
115
113
  _id: new Date().getTime(),
116
114
  challenge,
@@ -135,6 +133,7 @@ export const configureUsersWebAuthnModule = async ({ db }: ModuleInput<any>) =>
135
133
  },
136
134
  { sort: { _id: -1 } },
137
135
  );
136
+ if (!request) return null;
138
137
 
139
138
  const attestationExpectations = {
140
139
  challenge: request.challenge,
@@ -184,6 +183,7 @@ export const configureUsersWebAuthnModule = async ({ db }: ModuleInput<any>) =>
184
183
  },
185
184
  { sort: { _id: -1 } },
186
185
  );
186
+ if (!request) return null;
187
187
 
188
188
  const id = Buffer.from(credentials.id, 'base64');
189
189
  const authenticatorData = Buffer.from(credentials.response.authenticatorData, 'base64');
@@ -5,7 +5,11 @@ const PBKDF2_SALT_LENGTH = 16; // Bytes
5
5
 
6
6
  export function generateSalt(saltLength = PBKDF2_SALT_LENGTH) {
7
7
  const array = new Uint8Array(saltLength);
8
- return Buffer.from(crypto.getRandomValues(array)).toString('hex');
8
+ crypto.getRandomValues(array);
9
+ // Convert to hex string without using Buffer
10
+ return Array.from(array)
11
+ .map((b) => b.toString(16).padStart(2, '0'))
12
+ .join('');
9
13
  }
10
14
 
11
15
  export async function getDerivedKey(
@@ -30,7 +34,10 @@ export async function getDerivedKey(
30
34
  importedKey,
31
35
  keyLength,
32
36
  );
33
- return Buffer.from(bits).toString('hex');
37
+ // Convert ArrayBuffer to hex string without using Buffer
38
+ return Array.from(new Uint8Array(bits))
39
+ .map((b) => b.toString(16).padStart(2, '0'))
40
+ .join('');
34
41
  }
35
42
 
36
43
  export async function compare(password: string, hash: string, salt: string) {
@@ -7,7 +7,6 @@ import { User } from '../db/UsersCollection.js';
7
7
  describe('removeConfidentialServiceHashes', () => {
8
8
  it('Should remove sensitive user credentials ', () => {
9
9
  assert.notStrictEqual(user.services, undefined);
10
- removeConfidentialServiceHashes(user as unknown as User);
11
- assert.strictEqual(user.services, undefined);
10
+ assert.strictEqual(removeConfidentialServiceHashes(user as unknown as User)?.services, undefined);
12
11
  });
13
12
  });
@@ -1,6 +1,5 @@
1
1
  import { insensitiveTrimmedRegexOperator, mongodb } from '@unchainedshop/mongodb';
2
2
  import { User } from './db/UsersCollection.js';
3
-
4
3
  export interface UserRegistrationData extends Partial<User> {
5
4
  email?: string;
6
5
  password: string | null;
@@ -14,22 +13,6 @@ export enum UserAccountAction {
14
13
  PASSWORD_RESETTED = 'password-resetted',
15
14
  EMAIL_VERIFIED = 'email-verified',
16
15
  }
17
- export interface UserSettingsOptions {
18
- mergeUserCartsOnLogin?: boolean;
19
- autoMessagingAfterUserCreation?: boolean;
20
- /**
21
- * Function to calculate the earliest valid token date. Defaults to 1h if not set.
22
- * @param type The type of user account action.
23
- * @returns A date. All tokens created after that date are considered valid.
24
- */
25
- earliestValidTokenDate?: (
26
- type: UserAccountAction.VERIFY_EMAIL | UserAccountAction.RESET_PASSWORD,
27
- ) => Date;
28
- validateEmail?: (email: string) => Promise<boolean>;
29
- validateUsername?: (username: string) => Promise<boolean>;
30
- validateNewUser?: (user: UserRegistrationData) => Promise<UserRegistrationData>;
31
- validatePassword?: (password: string) => Promise<boolean>;
32
- }
33
16
  export interface UserSettings {
34
17
  mergeUserCartsOnLogin: boolean;
35
18
  autoMessagingAfterUserCreation: boolean;
@@ -43,14 +26,37 @@ export interface UserSettings {
43
26
  configureSettings: (options: UserSettingsOptions, db: mongodb.Db) => void;
44
27
  }
45
28
 
29
+ export type UserSettingsOptions = Omit<Partial<UserSettings>, 'configureSettings'>;
30
+
31
+ const defaultAutoMessagingAfterUserCreation = true;
32
+ const defaultMergeUserCartsOnLogin = true;
33
+
34
+ const defaultEarliestValidTokenDate = () => {
35
+ // 1 hour ago
36
+ return new Date(new Date().getTime() - 1000 * 60 * 60);
37
+ };
38
+
39
+ const defaultValidateNewUser = async (user: UserRegistrationData) => {
40
+ return {
41
+ ...user,
42
+ username: user.username?.trim().toLowerCase(),
43
+ email: user.email?.trim().toLowerCase(),
44
+ password: user.password ?? null,
45
+ };
46
+ };
47
+
48
+ const defaultValidatePassword = async (password: string) => {
49
+ return password?.length >= 8;
50
+ };
51
+
46
52
  export const userSettings: UserSettings = {
47
- autoMessagingAfterUserCreation: null,
48
- mergeUserCartsOnLogin: null,
49
- earliestValidTokenDate: null,
50
- validateEmail: null,
51
- validateUsername: null,
52
- validateNewUser: null,
53
- validatePassword: null,
53
+ autoMessagingAfterUserCreation: defaultAutoMessagingAfterUserCreation,
54
+ mergeUserCartsOnLogin: defaultMergeUserCartsOnLogin,
55
+ earliestValidTokenDate: defaultEarliestValidTokenDate,
56
+ validateNewUser: defaultValidateNewUser,
57
+ validateEmail: () => Promise.resolve(true),
58
+ validateUsername: () => Promise.resolve(true),
59
+ validatePassword: () => Promise.resolve(true),
54
60
 
55
61
  configureSettings: (
56
62
  {
@@ -61,17 +67,9 @@ export const userSettings: UserSettings = {
61
67
  validateUsername,
62
68
  validateNewUser,
63
69
  validatePassword,
64
- }: UserSettingsOptions,
70
+ },
65
71
  db: mongodb.Db,
66
72
  ) => {
67
- const defaultAutoMessagingAfterUserCreation = true;
68
- const defaultMergeUserCartsOnLogin = true;
69
-
70
- const defaultEarliestValidTokenDate = () => {
71
- // 1 hour ago
72
- return new Date(new Date().getTime() - 1000 * 60 * 60);
73
- };
74
-
75
73
  const defaultValidateEmail = async (rawEmail: string) => {
76
74
  if (!rawEmail?.includes?.('@')) return false;
77
75
  const emailAlreadyExists = await db
@@ -88,18 +86,6 @@ export const userSettings: UserSettings = {
88
86
  if (usernameAlreadyExists) return false;
89
87
  return true;
90
88
  };
91
- const defaultValidateNewUser = async (user: UserRegistrationData) => {
92
- return {
93
- ...user,
94
- username: user.username?.trim().toLowerCase(),
95
- email: user.email?.trim().toLowerCase(),
96
- password: user.password ?? null,
97
- };
98
- };
99
-
100
- const defaultValidatePassword = async (password: string) => {
101
- return password?.length >= 8;
102
- };
103
89
 
104
90
  userSettings.mergeUserCartsOnLogin = mergeUserCartsOnLogin ?? defaultMergeUserCartsOnLogin;
105
91
  userSettings.autoMessagingAfterUserCreation =