@unchainedshop/core-users 3.0.0-rc1 → 3.0.0-rc11

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 (61) hide show
  1. package/lib/db/UsersCollection.d.ts +54 -2
  2. package/lib/db/UsersCollection.d.ts.map +1 -1
  3. package/lib/db/UsersCollection.js.map +1 -1
  4. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts +6 -1
  5. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts.map +1 -1
  6. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js.map +1 -1
  7. package/lib/module/addMigrations.d.ts.map +1 -1
  8. package/lib/module/addMigrations.js +32 -41
  9. package/lib/module/addMigrations.js.map +1 -1
  10. package/lib/module/configureUsersModule.d.ts +85 -48
  11. package/lib/module/configureUsersModule.d.ts.map +1 -1
  12. package/lib/module/configureUsersModule.js +49 -20
  13. package/lib/module/configureUsersModule.js.map +1 -1
  14. package/lib/module/configureUsersWebAuthnModule.d.ts +21 -8
  15. package/lib/module/configureUsersWebAuthnModule.d.ts.map +1 -1
  16. package/lib/module/configureUsersWebAuthnModule.js +32 -11
  17. package/lib/module/configureUsersWebAuthnModule.js.map +1 -1
  18. package/lib/module/pbkdf2.js +1 -1
  19. package/lib/module/pbkdf2.js.map +1 -1
  20. package/lib/users-index.d.ts +3 -1
  21. package/lib/users-index.d.ts.map +1 -1
  22. package/lib/users-index.js +3 -1
  23. package/lib/users-index.js.map +1 -1
  24. package/lib/users-settings.d.ts +1 -1
  25. package/lib/users-settings.d.ts.map +1 -1
  26. package/lib/users-settings.js.map +1 -1
  27. package/package.json +20 -9
  28. package/src/db/UsersCollection.ts +59 -2
  29. package/src/db/WebAuthnCredentialsCreationRequestsCollection.ts +7 -1
  30. package/src/module/addMigrations.ts +40 -53
  31. package/src/module/configureUsersModule.ts +83 -105
  32. package/src/module/configureUsersWebAuthnModule.ts +47 -43
  33. package/src/module/pbkdf2.ts +1 -1
  34. package/src/module/removeConfidentialServiceHashes.test.ts +1 -1
  35. package/src/users-index.ts +3 -1
  36. package/src/users-settings.ts +1 -2
  37. package/lib/module/configureUsersModule_BACKUP_57123.d.ts +0 -73
  38. package/lib/module/configureUsersModule_BACKUP_57123.d.ts.map +0 -1
  39. package/lib/module/configureUsersModule_BACKUP_57123.js +0 -567
  40. package/lib/module/configureUsersModule_BACKUP_57123.js.map +0 -1
  41. package/lib/module/configureUsersModule_BASE_57123.d.ts +0 -131
  42. package/lib/module/configureUsersModule_BASE_57123.d.ts.map +0 -1
  43. package/lib/module/configureUsersModule_BASE_57123.js +0 -563
  44. package/lib/module/configureUsersModule_BASE_57123.js.map +0 -1
  45. package/lib/module/configureUsersModule_LOCAL_57123.d.ts +0 -131
  46. package/lib/module/configureUsersModule_LOCAL_57123.d.ts.map +0 -1
  47. package/lib/module/configureUsersModule_LOCAL_57123.js +0 -564
  48. package/lib/module/configureUsersModule_LOCAL_57123.js.map +0 -1
  49. package/lib/module/configureUsersModule_REMOTE_57123.d.ts +0 -74
  50. package/lib/module/configureUsersModule_REMOTE_57123.d.ts.map +0 -1
  51. package/lib/module/configureUsersModule_REMOTE_57123.js +0 -567
  52. package/lib/module/configureUsersModule_REMOTE_57123.js.map +0 -1
  53. package/lib/module/sha256.d.ts +0 -2
  54. package/lib/module/sha256.d.ts.map +0 -1
  55. package/lib/module/sha256.js +0 -6
  56. package/lib/module/sha256.js.map +0 -1
  57. package/lib/types.d.ts +0 -91
  58. package/lib/types.d.ts.map +0 -1
  59. package/lib/types.js +0 -2
  60. package/lib/types.js.map +0 -1
  61. package/src/types.ts +0 -104
@@ -1,97 +1,39 @@
1
- import bcrypt from '@node-rs/bcrypt';
2
- import { ModuleInput, Address, Contact } from '@unchainedshop/mongodb';
3
- import { User, UserQuery, Email, UserLastLogin, UserProfile, UserData } from '../types.js';
4
- import { emit, registerEvents } from '@unchainedshop/events';
1
+ import * as bcrypt from '@node-rs/bcrypt';
5
2
  import {
3
+ ModuleInput,
4
+ Address,
5
+ Contact,
6
6
  generateDbFilterById,
7
7
  buildSortOptions,
8
8
  mongodb,
9
9
  generateDbObjectId,
10
10
  } from '@unchainedshop/mongodb';
11
+ import {
12
+ User,
13
+ UserQuery,
14
+ Email,
15
+ UserLastLogin,
16
+ UserProfile,
17
+ UsersCollection,
18
+ } from '../db/UsersCollection.js';
19
+ import { emit, registerEvents } from '@unchainedshop/events';
11
20
  import { systemLocale, SortDirection, SortOption, sha256 } from '@unchainedshop/utils';
12
- import { UsersCollection } from '../db/UsersCollection.js';
13
21
  import addMigrations from './addMigrations.js';
14
22
  import { userSettings, UserSettingsOptions } from '../users-settings.js';
15
- import { configureUsersWebAuthnModule, UsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
23
+ import { configureUsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
16
24
  import * as pbkdf2 from './pbkdf2.js';
17
25
 
18
- export type UsersModule = {
19
- // Submodules
20
- webAuthn: UsersWebAuthnModule;
21
-
22
- // Queries
23
- count: (query: UserQuery) => Promise<number>;
24
- findUserById: (userId: string) => Promise<User>;
25
- findUserByToken: (hashedToken?: string) => Promise<User>;
26
- findUserByResetToken: (token: string) => Promise<User>;
27
- findUnverifiedEmailToken: (token: string) => Promise<{
28
- userId: string;
29
- address: string;
30
- when: Date;
31
- }>;
32
- findUserByEmail(email: string): Promise<User>;
33
- findUserByUsername(username: string): Promise<User>;
34
- findUser: (
35
- selector: UserQuery & { sort?: Array<SortOption> },
36
- options?: mongodb.FindOptions,
37
- ) => Promise<User>;
38
- findUsers: (
39
- query: UserQuery & {
40
- sort?: Array<SortOption>;
41
- limit?: number;
42
- offset?: number;
43
- },
44
- ) => Promise<Array<User>>;
45
- userExists: (query: { userId: string }) => Promise<boolean>;
46
- // Transformations
47
- primaryEmail: (user: User) => Email;
48
- userLocale: (user: User) => Intl.Locale;
49
-
50
- // Mutations
51
- createUser: (
52
- userData: UserData,
53
- options: { skipMessaging?: boolean; skipPasswordEnrollment?: boolean },
54
- ) => Promise<string>;
55
- addEmail: (userId: string, email: string) => Promise<void>;
56
- removeEmail: (userId: string, email: string) => Promise<void>;
57
- sendVerificationEmail: (userId: string, email: string) => Promise<void>;
58
- sendResetPasswordEmail: (userId: string, email: string, isEnrollment?: boolean) => Promise<void>;
59
- verifyEmail: (userId: string, email: string) => Promise<void>;
60
- setUsername: (userId: string, newUsername: string) => Promise<void>;
61
- setPassword: (userId: string, newPassword?: string) => Promise<void>;
62
- verifyPassword: (hashObject: Record<string, string>, password: string) => Promise<boolean>;
63
- addRoles: (userId: string, roles: Array<string>) => Promise<number>;
64
- updateAvatar: (_id: string, fileId: string) => Promise<User>;
65
- updateGuest: (user: User, guest: boolean) => Promise<void>;
66
- updateHeartbeat: (userId: string, doc: UserLastLogin) => Promise<User>;
67
- updateLastBillingAddress: (_id: string, lastAddress: Address) => Promise<User>;
68
- updateLastContact: (_id: string, lastContact: Contact) => Promise<User>;
69
- updateProfile: (
70
- _id: string,
71
- { profile, meta }: { profile?: UserProfile; meta?: any },
72
- ) => Promise<User>;
73
- markDeleted: (userId: string) => Promise<User>;
74
- deletePermanently: (params: { userId: string }) => Promise<User>;
75
- updateRoles: (_id: string, roles: Array<string>) => Promise<User>;
76
- updateTags: (_id: string, tags: Array<string>) => Promise<User>;
77
- updateUser: (
78
- selector: mongodb.Filter<User>,
79
- modifier: mongodb.UpdateFilter<User>,
80
- options: mongodb.FindOneAndUpdateOptions,
81
- ) => Promise<User>;
82
- addPushSubscription: (
83
- userId: string,
84
- subscription: any,
85
- options?: {
86
- userAgent: string;
87
- unsubscribeFromOtherUsers: boolean;
88
- },
89
- ) => Promise<void>;
90
- removePushSubscription: (userId: string, p256dh: string) => Promise<void>;
91
- hashPassword(password: string): Promise<{
92
- pbkdf2: string;
93
- }>;
94
- };
26
+ export interface UserData {
27
+ email?: string;
28
+ guest?: boolean;
29
+ initialPassword?: boolean;
30
+ lastBillingAddress?: User['lastBillingAddress'];
31
+ password: string | null;
32
+ webAuthnPublicKeyCredentials?: any;
33
+ profile?: UserProfile;
34
+ roles?: Array<string>;
35
+ username?: string;
36
+ }
95
37
 
96
38
  const USER_EVENTS = [
97
39
  'USER_ACCOUNT_ACTION',
@@ -152,7 +94,7 @@ export const configureUsersModule = async ({
152
94
  db,
153
95
  options,
154
96
  migrationRepository,
155
- }: ModuleInput<UserSettingsOptions>): Promise<UsersModule> => {
97
+ }: ModuleInput<UserSettingsOptions>) => {
156
98
  userSettings.configureSettings(options || {}, db);
157
99
  registerEvents(USER_EVENTS);
158
100
  const Users = await UsersCollection(db);
@@ -212,31 +154,37 @@ export const configureUsersModule = async ({
212
154
  },
213
155
 
214
156
  async verifyEmail(userId: string, address: string): Promise<void> {
215
- await Users.updateOne(
216
- { _id: userId },
157
+ const updated = await Users.updateOne(
158
+ {
159
+ _id: userId,
160
+ emails: { $elemMatch: { address: { $regex: address, $options: 'i' }, verified: false } },
161
+ },
217
162
  {
218
163
  $set: {
219
- 'emails.$[email].verified': true,
164
+ 'emails.$.verified': true,
220
165
  },
221
166
  $pull: {
222
167
  'services.email.verificationTokens': {
223
- address,
168
+ address: { $regex: address, $options: 'i' },
224
169
  },
225
170
  },
226
171
  },
227
- {
228
- arrayFilters: [{ 'email.address': address }],
229
- },
230
172
  );
231
173
 
232
- await emit('USER_ACCOUNT_ACTION', {
233
- action: 'email-verified',
234
- address,
235
- userId,
236
- });
174
+ if (updated.modifiedCount > 0) {
175
+ await emit('USER_ACCOUNT_ACTION', {
176
+ action: 'email-verified',
177
+ address,
178
+ userId,
179
+ });
180
+ }
237
181
  },
238
182
 
239
- async findUserByResetToken(plainToken: string): Promise<User> {
183
+ async findResetToken(plainToken: string): Promise<{
184
+ userId: string;
185
+ address: string;
186
+ when: Date;
187
+ }> {
240
188
  const token = await sha256(plainToken);
241
189
  const user = await Users.findOne(
242
190
  {
@@ -249,10 +197,15 @@ export const configureUsersModule = async ({
249
197
  },
250
198
  {},
251
199
  );
252
- return user;
200
+ if (!user) return null;
201
+ const resetToken = user.services.password.reset.find((v) => v.token === token);
202
+ return {
203
+ userId: user._id,
204
+ ...resetToken,
205
+ };
253
206
  },
254
207
 
255
- async findUserByToken(plainToken?: string): Promise<User> {
208
+ async findUserByToken(plainToken: string): Promise<User> {
256
209
  const token = await sha256(plainToken);
257
210
 
258
211
  if (token) {
@@ -307,14 +260,18 @@ export const configureUsersModule = async ({
307
260
 
308
261
  // Transformations
309
262
  primaryEmail(user: User): Email {
310
- return (user.emails || []).sort(
263
+ return (user.emails || []).toSorted(
311
264
  (left, right) => Number(right.verified) - Number(left.verified),
312
265
  )?.[0];
313
266
  },
314
267
 
315
268
  userLocale(user: User): Intl.Locale {
316
269
  if (!user?.lastLogin?.locale) return systemLocale;
317
- return new Intl.Locale(user.lastLogin.locale);
270
+ try {
271
+ return new Intl.Locale(user.lastLogin.locale);
272
+ } catch {
273
+ return systemLocale;
274
+ }
318
275
  },
319
276
 
320
277
  // Mutations
@@ -403,7 +360,9 @@ export const configureUsersModule = async ({
403
360
  return userId;
404
361
  },
405
362
 
406
- async hashPassword(password) {
363
+ async hashPassword(password: string): Promise<{
364
+ pbkdf2: string;
365
+ }> {
407
366
  const salt = pbkdf2.generateSalt();
408
367
  const hashedPassword = await pbkdf2.getDerivedKey(salt, password);
409
368
  return { pbkdf2: `${salt}:${hashedPassword}` };
@@ -534,12 +493,12 @@ export const configureUsersModule = async ({
534
493
  });
535
494
  },
536
495
 
537
- async setPassword(userId: string, plainPassword: string) {
496
+ async setPassword(userId: string, plainPassword: string): Promise<User> {
538
497
  if (!(await userSettings.validatePassword(plainPassword))) {
539
498
  throw new Error(`Password ***** is invalid`, { cause: 'PASSWORD_INVALID' });
540
499
  }
541
500
  const password = plainPassword || crypto.randomUUID().split('-').pop();
542
- await Users.updateOne(
501
+ const user = await Users.findOneAndUpdate(
543
502
  { _id: userId },
544
503
  {
545
504
  $set: {
@@ -549,11 +508,28 @@ export const configureUsersModule = async ({
549
508
  ),
550
509
  },
551
510
  },
511
+ { returnDocument: 'after' },
552
512
  );
553
- const user = await Users.findOne({ _id: userId }, {});
554
513
  await emit('USER_UPDATE_PASSWORD', {
555
514
  user: removeConfidentialServiceHashes(user),
556
515
  });
516
+ return user;
517
+ },
518
+
519
+ async resetPassword(token: string, newPassword: string): Promise<User> {
520
+ const resetToken = await this.findResetToken(token);
521
+ if (!resetToken) return null;
522
+ const updatedUser = await this.setPassword(resetToken.userId, newPassword);
523
+ if (updatedUser) {
524
+ // Now invalidate the reset token
525
+ await emit('USER_ACCOUNT_ACTION', {
526
+ action: 'password-resetted',
527
+ userId: updatedUser._id,
528
+ });
529
+
530
+ await this.verifyEmail(updatedUser._id, resetToken.address);
531
+ }
532
+ return updatedUser;
557
533
  },
558
534
 
559
535
  updateAvatar: async (_id: string, fileId: string): Promise<User> => {
@@ -835,3 +811,5 @@ export const configureUsersModule = async ({
835
811
  },
836
812
  };
837
813
  };
814
+
815
+ export type UsersModule = Awaited<ReturnType<typeof configureUsersModule>>;
@@ -1,29 +1,8 @@
1
1
  import { ModuleInput } from '@unchainedshop/mongodb';
2
- import {
3
- Fido2Lib,
4
- PublicKeyCredentialCreationOptions,
5
- PublicKeyCredentialRequestOptions,
6
- } from 'fido2-lib';
7
2
  import { createLogger } from '@unchainedshop/logger';
8
3
  import { WebAuthnCredentialsCreationRequestsCollection } from '../db/WebAuthnCredentialsCreationRequestsCollection.js';
9
4
 
10
- export interface UsersWebAuthnModule {
11
- findMDSMetadataForAAGUID: (aaguid: string) => Promise<any>;
12
-
13
- createCredentialCreationOptions: (
14
- origin: string,
15
- username: string,
16
- extensionOptions?: any,
17
- ) => Promise<any>;
18
- verifyCredentialCreation: (username: string, credentials: any) => Promise<any>;
19
-
20
- createCredentialRequestOptions: (
21
- origin: string,
22
- username?: string,
23
- extensionOptions?: any,
24
- ) => Promise<any>;
25
- verifyCredentialRequest: (userPublicKeys: any[], username: string, credentials: any) => Promise<any>;
26
- }
5
+ import type { PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions } from 'fido2-lib';
27
6
 
28
7
  const logger = createLogger('unchained:core-users');
29
8
 
@@ -39,6 +18,9 @@ const setupMDSCollection = async () => {
39
18
  try {
40
19
  const tocResult = await fetch('https://mds.fidoalliance.org');
41
20
  const tocBase64 = await tocResult.text();
21
+ // eslint-disable-next-line
22
+ // @ts-ignore
23
+ const { Fido2Lib } = await import('fido2-lib');
42
24
  const mc = (Fido2Lib as any).createMdsCollection('FIDO MDS v3'); // createMdsCollection exists but not typed in official package!
43
25
  const tocObj = await mc.addToc(tocBase64);
44
26
  return tocObj.entries;
@@ -65,24 +47,30 @@ export function buf2hex(buffer) {
65
47
  .join('');
66
48
  }
67
49
 
68
- export const configureUsersWebAuthnModule = async ({
69
- db,
70
- }: ModuleInput<any>): Promise<UsersWebAuthnModule> => {
71
- const WebAuthnCredentialsCreationRequests = await WebAuthnCredentialsCreationRequestsCollection(db);
72
-
73
- const thisDomain = new URL(ROOT_URL).hostname;
74
-
75
- const f2l = new Fido2Lib({
76
- rpId: thisDomain,
77
- rpName: EMAIL_WEBSITE_NAME,
78
- rpIcon:
79
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARCSURBVHgB7Ve9UxNbFL8fm7yA4ktegHnlMk98dMbOMnR2D6pnGTqtCH+BWFoBpZXQaaelFbGzEztHHF26DKizzijDx+Zef+fu3vXuJgsxztjonQnZOZw993d+5zOM/epHslFO1a/KS/UbuvTnETsKQ/YDh3+XNi72yuVlzVgbL1YhCaOTk2ssDAI24hHDKsrpfxZkufwCj6t0OUAEBMkrlR6wHzjDMVCd872yemeete7gczeKoh2wQTICsyi0DrTWVc55eHqwu8OGPEMCMNSby5hS89H7Nx0Se9NXVvF1J69O7MDwJsKzgfCcmSP9SQhvxcV6S5cnummCHYVH4kJ9DE9NfHx1+HGLxKo0sSOkvIXHSnyz7mjOySufdLmUN3Vp4slZiSoyXk7NrhHVQui1fGzhzTq+QsZ5U07NtowQ3oH2FavDhXjS2389w7W+lrDgI2+2yfaZAEpT/y5LopjztnEkf1F6GXsc38TvWKO9g91NfO3EBGgjpxzooTosCISvXQjAm7zc1Fyvm7IChRHQM+uVcxGBAbstI84bVcqyUE3lAMyVWkrky0UsuGUYRqeni4zQk1cAk15ELxMYy07OqElKqo5ieVWWymsDAbhKGa9Qatag8P5omcQiUEot9nmb1c9cFkm5FJPJWqWp2UYegKkCMf7XHjRaeGwgs+9T1iPTA8ibkM/h5ev4XwVJthEd7D7kF+ozANRI9B9RlpO+lUO/IcZqz0jGPr8PUUFU7k185mwFZUIwBAuGUqWUob8nxCqLQ0H6qbeOnNmQGftOBVHO9QEw+ozZRKLE8y0wxDxFLISIE6n7KsDfjUS8kBoludZbCYBvl8XNaCMPLAOASsde5vYAlFPbeqWV+q/Pq5xR6HRS6zkWzPzIlXdmGKWXFaM/V06zwAGQaVppSJ3yzk7DAqpcb7UQD/q8cuRoxb75dtlxm5Zb3mzALEj7O7I/zWRUBRurHcO7G9Sw5MXJT+rLh+dmRlSqe6iOm1aOC/5nBELr2yTDOw3YO4Z+x3icq7iB01BMXm4j4dbIO+rtVi6nr7xLBg0tIjN20mGGbBPdVs+8J07mvcjzkbnbGf14tNNeYcb4wJVMH358jpo2zSf1lgCM1V4m6CsFXiUG9Iref9txe4kQXgVT9jqXejNZaEKAvFu4D5ikStBjus3bJcPxdjALNE8OduetHep+yIsXGQcZf9wTxyusGwSFS2mCvmaMcn6Lj9d9OV77BGN7iOuCYQFeqcMPT43+peln2IraFH/EdsvuALDTTTthvE0tIRnvsc/x/8/diIq2HnsSFoKEhXUAWM6zYA5VwoDtaLiV7O85XypFy+hVnbRlhCXoo9zZHWkpGWY3/L613D25PZFzeVVxvZqs6ywSYKZ7/ro+2g8TOu6eSBXAGfWIio2z2n871GY8OgN06BcSfitQuZr1CxuQ3Zh/6qkk0/P3GeV8BeBCYUHc/WHIAAAAAElFTkSuQmCC',
80
- challengeSize: 128,
81
- attestation: 'none',
82
- });
50
+ const initF2L = async () => {
51
+ try {
52
+ const thisDomain = new URL(ROOT_URL).hostname;
53
+ const { Fido2Lib } = await import('fido2-lib');
54
+ const f2l = new Fido2Lib({
55
+ rpId: thisDomain,
56
+ rpName: EMAIL_WEBSITE_NAME,
57
+ rpIcon:
58
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARCSURBVHgB7Ve9UxNbFL8fm7yA4ktegHnlMk98dMbOMnR2D6pnGTqtCH+BWFoBpZXQaaelFbGzEztHHF26DKizzijDx+Zef+fu3vXuJgsxztjonQnZOZw993d+5zOM/epHslFO1a/KS/UbuvTnETsKQ/YDh3+XNi72yuVlzVgbL1YhCaOTk2ssDAI24hHDKsrpfxZkufwCj6t0OUAEBMkrlR6wHzjDMVCd872yemeete7gczeKoh2wQTICsyi0DrTWVc55eHqwu8OGPEMCMNSby5hS89H7Nx0Se9NXVvF1J69O7MDwJsKzgfCcmSP9SQhvxcV6S5cnummCHYVH4kJ9DE9NfHx1+HGLxKo0sSOkvIXHSnyz7mjOySufdLmUN3Vp4slZiSoyXk7NrhHVQui1fGzhzTq+QsZ5U07NtowQ3oH2FavDhXjS2389w7W+lrDgI2+2yfaZAEpT/y5LopjztnEkf1F6GXsc38TvWKO9g91NfO3EBGgjpxzooTosCISvXQjAm7zc1Fyvm7IChRHQM+uVcxGBAbstI84bVcqyUE3lAMyVWkrky0UsuGUYRqeni4zQk1cAk15ELxMYy07OqElKqo5ieVWWymsDAbhKGa9Qatag8P5omcQiUEot9nmb1c9cFkm5FJPJWqWp2UYegKkCMf7XHjRaeGwgs+9T1iPTA8ibkM/h5ev4XwVJthEd7D7kF+ozANRI9B9RlpO+lUO/IcZqz0jGPr8PUUFU7k185mwFZUIwBAuGUqWUob8nxCqLQ0H6qbeOnNmQGftOBVHO9QEw+ozZRKLE8y0wxDxFLISIE6n7KsDfjUS8kBoludZbCYBvl8XNaCMPLAOASsde5vYAlFPbeqWV+q/Pq5xR6HRS6zkWzPzIlXdmGKWXFaM/V06zwAGQaVppSJ3yzk7DAqpcb7UQD/q8cuRoxb75dtlxm5Zb3mzALEj7O7I/zWRUBRurHcO7G9Sw5MXJT+rLh+dmRlSqe6iOm1aOC/5nBELr2yTDOw3YO4Z+x3icq7iB01BMXm4j4dbIO+rtVi6nr7xLBg0tIjN20mGGbBPdVs+8J07mvcjzkbnbGf14tNNeYcb4wJVMH358jpo2zSf1lgCM1V4m6CsFXiUG9Iref9txe4kQXgVT9jqXejNZaEKAvFu4D5ikStBjus3bJcPxdjALNE8OduetHep+yIsXGQcZf9wTxyusGwSFS2mCvmaMcn6Lj9d9OV77BGN7iOuCYQFeqcMPT43+peln2IraFH/EdsvuALDTTTthvE0tIRnvsc/x/8/diIq2HnsSFoKEhXUAWM6zYA5VwoDtaLiV7O85XypFy+hVnbRlhCXoo9zZHWkpGWY3/L613D25PZFzeVVxvZqs6ywSYKZ7/ro+2g8TOu6eSBXAGfWIio2z2n871GY8OgN06BcSfitQuZr1CxuQ3Zh/6qkk0/P3GeV8BeBCYUHc/WHIAAAAAElFTkSuQmCC',
59
+ challengeSize: 128,
60
+ attestation: 'none',
61
+ });
62
+ return f2l;
63
+ } catch {
64
+ logger.warn(`optional peer npm package 'fido2-lib' not installed, WebAuthn will not work`);
65
+ return null;
66
+ }
67
+ };
83
68
 
69
+ export const configureUsersWebAuthnModule = async ({ db }: ModuleInput<any>) => {
70
+ const WebAuthnCredentialsCreationRequests = await WebAuthnCredentialsCreationRequestsCollection(db);
71
+ const f2l = await initF2L();
84
72
  return {
85
- findMDSMetadataForAAGUID: async (aaguid) => {
73
+ findMDSMetadataForAAGUID: async (aaguid: string) => {
86
74
  const mdsCollection = await fetchMDS();
87
75
  const foundEntry = mdsCollection.find((entry) => {
88
76
  return entry.aaguid === aaguid;
@@ -90,7 +78,13 @@ export const configureUsersWebAuthnModule = async ({
90
78
  return foundEntry?.metadataStatement;
91
79
  },
92
80
 
93
- createCredentialCreationOptions: async (origin, username, extensionOptions) => {
81
+ createCredentialCreationOptions: async (
82
+ origin: string,
83
+ username: string,
84
+ extensionOptions?: any,
85
+ ) => {
86
+ if (!f2l) return null;
87
+
94
88
  const registrationOptions = await f2l.attestationOptions(extensionOptions);
95
89
  const challenge = Buffer.from(registrationOptions.challenge).toString('base64');
96
90
  const { insertedId } = await WebAuthnCredentialsCreationRequests.insertOne({
@@ -108,7 +102,13 @@ export const configureUsersWebAuthnModule = async ({
108
102
  } as SerializedOptions<PublicKeyCredentialCreationOptions>;
109
103
  },
110
104
 
111
- createCredentialRequestOptions: async (origin, username, extensionOptions) => {
105
+ createCredentialRequestOptions: async (
106
+ origin: string,
107
+ username?: string,
108
+ extensionOptions?: any,
109
+ ) => {
110
+ if (!f2l) return null;
111
+
112
112
  const loginOptions = await f2l.assertionOptions(extensionOptions);
113
113
  const challenge = Buffer.from(loginOptions.challenge).toString('base64');
114
114
  const { insertedId } = await WebAuthnCredentialsCreationRequests.insertOne({
@@ -126,7 +126,9 @@ export const configureUsersWebAuthnModule = async ({
126
126
  } as SerializedOptions<PublicKeyCredentialRequestOptions>;
127
127
  },
128
128
 
129
- verifyCredentialCreation: async (username, credentials) => {
129
+ verifyCredentialCreation: async (username: string, credentials: any) => {
130
+ if (!f2l) return null;
131
+
130
132
  const request = await WebAuthnCredentialsCreationRequests.findOne(
131
133
  {
132
134
  username,
@@ -173,7 +175,9 @@ export const configureUsersWebAuthnModule = async ({
173
175
  return { publicKey, counter, id: credentials.id, aaguid, created: new Date() };
174
176
  },
175
177
 
176
- verifyCredentialRequest: async (userPublicKeys, username, credentials) => {
178
+ verifyCredentialRequest: async (userPublicKeys: any[], username: string, credentials: any) => {
179
+ if (!f2l) return null;
180
+
177
181
  const request = await WebAuthnCredentialsCreationRequests.findOne(
178
182
  {
179
183
  _id: credentials.requestId,
@@ -192,7 +196,7 @@ export const configureUsersWebAuthnModule = async ({
192
196
  return credentials.id === publicCredentials.id;
193
197
  }) || {};
194
198
 
195
- if (!publicKey) throw new Error('WebAuthn not setup');
199
+ if (!publicKey) return null;
196
200
 
197
201
  const assertionExpectations = {
198
202
  challenge: request.challenge,
@@ -4,7 +4,7 @@ const PBKDF2_KEY_LENGTH = 256; // Bytes
4
4
  const PBKDF2_SALT_LENGTH = 16; // Bytes
5
5
 
6
6
  export function generateSalt(saltLength = PBKDF2_SALT_LENGTH) {
7
- const array = new Uint32Array(saltLength);
7
+ const array = new Uint8Array(saltLength);
8
8
  return Buffer.from(crypto.getRandomValues(array)).toString('hex');
9
9
  }
10
10
 
@@ -1,6 +1,6 @@
1
- import { User } from '../types.js';
2
1
  import { removeConfidentialServiceHashes } from './configureUsersModule.js';
3
2
  import user from '../../tests/mock/user-mock.js';
3
+ import { User } from '../db/UsersCollection.js';
4
4
 
5
5
  describe('removeConfidentialServiceHashes', () => {
6
6
  it('Should remove sensitive user credentials ', () => {
@@ -1,3 +1,5 @@
1
- export * from './types.js';
1
+ export * from './db/UsersCollection.js';
2
+ export * from './db/WebAuthnCredentialsCreationRequestsCollection.js';
2
3
  export * from './module/configureUsersModule.js';
4
+ export * from './module/configureUsersWebAuthnModule.js';
3
5
  export * from './users-settings.js';
@@ -1,6 +1,5 @@
1
- import { User } from './types.js';
2
1
  import { mongodb } from '@unchainedshop/mongodb';
3
-
2
+ import { User } from './db/UsersCollection.js';
4
3
  export interface UserSettingsOptions {
5
4
  mergeUserCartsOnLogin?: boolean;
6
5
  autoMessagingAfterUserCreation?: boolean;
@@ -1,73 +0,0 @@
1
- import { ModuleInput, Address, Contact } from '@unchainedshop/mongodb';
2
- import { User, UserQuery, Email, UserLastLogin, UserProfile, UserData } from '../types.js';
3
- import { mongodb } from '@unchainedshop/mongodb';
4
- import { SortOption } from '@unchainedshop/utils';
5
- import { UserSettingsOptions } from '../users-settings.js';
6
- import { UsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
7
- export type UsersModule = {
8
- webAuthn: UsersWebAuthnModule;
9
- count: (query: UserQuery) => Promise<number>;
10
- findUserById: (userId: string) => Promise<User>;
11
- findUserByToken: (hashedToken?: string) => Promise<User>;
12
- findUserByResetToken: (token: string) => Promise<User>;
13
- findUnverifiedEmailToken: (token: string) => Promise<{
14
- userId: string;
15
- address: string;
16
- when: Date;
17
- }>;
18
- findUserByEmail(email: string): Promise<User>;
19
- findUserByUsername(username: string): Promise<User>;
20
- findUser: (selector: UserQuery & {
21
- sort?: Array<SortOption>;
22
- }, options?: mongodb.FindOptions) => Promise<User>;
23
- findUsers: (query: UserQuery & {
24
- sort?: Array<SortOption>;
25
- limit?: number;
26
- offset?: number;
27
- }) => Promise<Array<User>>;
28
- userExists: (query: {
29
- userId: string;
30
- }) => Promise<boolean>;
31
- primaryEmail: (user: User) => Email;
32
- userLocale: (user: User) => Intl.Locale;
33
- createUser: (userData: UserData, options: {
34
- skipMessaging?: boolean;
35
- skipPasswordEnrollment?: boolean;
36
- }) => Promise<string>;
37
- addEmail: (userId: string, email: string) => Promise<void>;
38
- removeEmail: (userId: string, email: string) => Promise<void>;
39
- sendVerificationEmail: (userId: string, email: string) => Promise<void>;
40
- sendResetPasswordEmail: (userId: string, email: string, isEnrollment?: boolean) => Promise<void>;
41
- verifyEmail: (userId: string, email: string) => Promise<void>;
42
- setUsername: (userId: string, newUsername: string) => Promise<void>;
43
- setPassword: (userId: string, newPassword?: string) => Promise<void>;
44
- verifyPassword: (hashObject: Record<string, string>, password: string) => Promise<boolean>;
45
- addRoles: (userId: string, roles: Array<string>) => Promise<number>;
46
- updateAvatar: (_id: string, fileId: string) => Promise<User>;
47
- updateGuest: (user: User, guest: boolean) => Promise<void>;
48
- updateHeartbeat: (userId: string, doc: UserLastLogin) => Promise<User>;
49
- updateLastBillingAddress: (_id: string, lastAddress: Address) => Promise<User>;
50
- updateLastContact: (_id: string, lastContact: Contact) => Promise<User>;
51
- updateProfile: (_id: string, { profile, meta }: {
52
- profile?: UserProfile;
53
- meta?: any;
54
- }) => Promise<User>;
55
- delete: (params: {
56
- userId: string;
57
- }, context: UnchainedCore) => Promise<User>;
58
- updateRoles: (_id: string, roles: Array<string>) => Promise<User>;
59
- updateTags: (_id: string, tags: Array<string>) => Promise<User>;
60
- updateUser: (selector: mongodb.Filter<User>, modifier: mongodb.UpdateFilter<User>, options: mongodb.FindOneAndUpdateOptions) => Promise<User>;
61
- addPushSubscription: (userId: string, subscription: any, options?: {
62
- userAgent: string;
63
- unsubscribeFromOtherUsers: boolean;
64
- }) => Promise<void>;
65
- removePushSubscription: (userId: string, p256dh: string) => Promise<void>;
66
- hashPassword(password: string): Promise<{
67
- pbkdf2: string;
68
- }>;
69
- };
70
- export declare const removeConfidentialServiceHashes: (rawUser: User) => User;
71
- export declare const buildFindSelector: ({ includeGuests, queryString, ...rest }: UserQuery) => mongodb.Filter<User>;
72
- export declare const configureUsersModule: ({ db, options, migrationRepository, }: ModuleInput<UserSettingsOptions>) => Promise<UsersModule>;
73
- //# sourceMappingURL=configureUsersModule_BACKUP_57123.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"configureUsersModule_BACKUP_57123.d.ts","sourceRoot":"","sources":["../../src/module/configureUsersModule_BACKUP_57123.ts"],"names":[],"mappings":"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"}