@unchainedshop/core-users 2.13.0 → 3.0.0-alpha2

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 (51) hide show
  1. package/lib/db/UsersCollection.d.ts.map +1 -1
  2. package/lib/db/UsersCollection.js +0 -8
  3. package/lib/db/UsersCollection.js.map +1 -1
  4. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts +8 -0
  5. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts.map +1 -0
  6. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js +13 -0
  7. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js.map +1 -0
  8. package/lib/module/configureUsersModule.d.ts +88 -2
  9. package/lib/module/configureUsersModule.d.ts.map +1 -1
  10. package/lib/module/configureUsersModule.js +277 -47
  11. package/lib/module/configureUsersModule.js.map +1 -1
  12. package/lib/module/configureUsersWebAuthnModule.d.ts +25 -0
  13. package/lib/module/configureUsersWebAuthnModule.d.ts.map +1 -0
  14. package/lib/module/configureUsersWebAuthnModule.js +148 -0
  15. package/lib/module/configureUsersWebAuthnModule.js.map +1 -0
  16. package/lib/module/pbkdf2.d.ts +4 -0
  17. package/lib/module/pbkdf2.d.ts.map +1 -0
  18. package/lib/module/pbkdf2.js +27 -0
  19. package/lib/module/pbkdf2.js.map +1 -0
  20. package/lib/module/sha256.d.ts +2 -0
  21. package/lib/module/sha256.d.ts.map +1 -0
  22. package/lib/module/sha256.js +6 -0
  23. package/lib/module/sha256.js.map +1 -0
  24. package/lib/services/migrateUserDataService.d.ts +3 -0
  25. package/lib/services/migrateUserDataService.d.ts.map +1 -0
  26. package/lib/services/migrateUserDataService.js +18 -0
  27. package/lib/services/migrateUserDataService.js.map +1 -0
  28. package/lib/services/userServices.d.ts +6 -2
  29. package/lib/services/userServices.d.ts.map +1 -1
  30. package/lib/services/userServices.js +2 -0
  31. package/lib/services/userServices.js.map +1 -1
  32. package/lib/users-index.d.ts +1 -0
  33. package/lib/users-index.d.ts.map +1 -1
  34. package/lib/users-index.js +1 -0
  35. package/lib/users-index.js.map +1 -1
  36. package/lib/users-settings.d.ts +3 -0
  37. package/lib/users-settings.d.ts.map +1 -0
  38. package/lib/users-settings.js +51 -0
  39. package/lib/users-settings.js.map +1 -0
  40. package/package.json +9 -7
  41. package/src/db/UsersCollection.ts +0 -9
  42. package/src/db/WebAuthnCredentialsCreationRequestsCollection.ts +20 -0
  43. package/src/module/configureUsersModule.ts +411 -62
  44. package/src/module/configureUsersWebAuthnModule.ts +201 -0
  45. package/src/module/pbkdf2.ts +39 -0
  46. package/src/module/sha256.ts +5 -0
  47. package/src/services/migrateUserDataService.ts +31 -0
  48. package/src/services/userServices.ts +3 -2
  49. package/src/users-index.ts +1 -0
  50. package/src/users-settings.ts +71 -0
  51. package/tests/mock/user-mock.ts +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"UsersCollection.d.ts","sourceRoot":"","sources":["../../src/db/UsersCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAEpD,eAAO,MAAM,eAAe,OAAc,OAAO,CAAC,EAAE,sCAqGnD,CAAC"}
1
+ {"version":3,"file":"UsersCollection.d.ts","sourceRoot":"","sources":["../../src/db/UsersCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAEpD,eAAO,MAAM,eAAe,OAAc,OAAO,CAAC,EAAE,sCA4FnD,CAAC"}
@@ -58,14 +58,6 @@ export const UsersCollection = async (db) => {
58
58
  sparse: true,
59
59
  },
60
60
  },
61
- {
62
- index: {
63
- 'services.resume.loginTokens.hashedToken': 1,
64
- },
65
- options: {
66
- sparse: true,
67
- },
68
- },
69
61
  {
70
62
  index: {
71
63
  _id: 'text',
@@ -1 +1 @@
1
- {"version":3,"file":"UsersCollection.js","sourceRoot":"","sources":["../../src/db/UsersCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,EAAc,EAAE,EAAE;IACtD,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,CAAO,OAAO,CAAC,CAAC;IAE3C,MAAM,cAAc,CAAO,KAAK,EAAE;QAChC;YACE,KAAK,EAAE;gBACL,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,CAAC;gBACV,KAAK,EAAE,CAAC;aACT;YACD,OAAO,EAAE,EAAE;SACZ;QACD;YACE,KAAK,EAAE;gBACL,KAAK,EAAE,CAAC;aACT;YACD,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QACD;YACE,KAAK,EAAE;gBACL,OAAO,EAAE,CAAC;aACX;YACD,OAAO,EAAE,EAAE;SACZ;QACD;YACE,KAAK,EAAE;gBACL,QAAQ,EAAE,CAAC;aACZ;YACD,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb;SACF;QACD;YACE,KAAK,EAAE;gBACL,gBAAgB,EAAE,CAAC;aACb;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,yCAAyC,EAAE,CAAC;aACtC;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,+BAA+B,EAAE,CAAC;aAC5B;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,yCAAyC,EAAE,CAAC;aACtC;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,GAAG,EAAE,MAAM;gBACX,QAAQ,EAAE,MAAM;gBAChB,gBAAgB,EAAE,MAAM;gBACxB,qBAAqB,EAAE,MAAM;gBAC7B,8BAA8B,EAAE,MAAM;gBACtC,6BAA6B,EAAE,MAAM;gBACrC,4BAA4B,EAAE,MAAM;gBACpC,gCAAgC,EAAE,MAAM;gBACxC,iCAAiC,EAAE,MAAM;aACnC;YACR,OAAO,EAAE;gBACP,OAAO,EAAE;oBACP,GAAG,EAAE,CAAC;oBACN,gBAAgB,EAAE,CAAC;oBACnB,qBAAqB,EAAE,CAAC;oBACxB,8BAA8B,EAAE,CAAC;oBACjC,6BAA6B,EAAE,CAAC;oBAChC,4BAA4B,EAAE,CAAC;oBAC/B,gCAAgC,EAAE,CAAC;oBACnC,iCAAiC,EAAE,CAAC;iBACrC;gBACD,IAAI,EAAE,sBAAsB;aAC7B;SACF;KACF,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"UsersCollection.js","sourceRoot":"","sources":["../../src/db/UsersCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,EAAc,EAAE,EAAE;IACtD,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,CAAO,OAAO,CAAC,CAAC;IAE3C,MAAM,cAAc,CAAO,KAAK,EAAE;QAChC;YACE,KAAK,EAAE;gBACL,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,CAAC;gBACV,KAAK,EAAE,CAAC;aACT;YACD,OAAO,EAAE,EAAE;SACZ;QACD;YACE,KAAK,EAAE;gBACL,KAAK,EAAE,CAAC;aACT;YACD,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QACD;YACE,KAAK,EAAE;gBACL,OAAO,EAAE,CAAC;aACX;YACD,OAAO,EAAE,EAAE;SACZ;QACD;YACE,KAAK,EAAE;gBACL,QAAQ,EAAE,CAAC;aACZ;YACD,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb;SACF;QACD;YACE,KAAK,EAAE;gBACL,gBAAgB,EAAE,CAAC;aACb;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,yCAAyC,EAAE,CAAC;aACtC;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,+BAA+B,EAAE,CAAC;aAC5B;YACR,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI;aACb;SACF;QAED;YACE,KAAK,EAAE;gBACL,GAAG,EAAE,MAAM;gBACX,QAAQ,EAAE,MAAM;gBAChB,gBAAgB,EAAE,MAAM;gBACxB,qBAAqB,EAAE,MAAM;gBAC7B,8BAA8B,EAAE,MAAM;gBACtC,6BAA6B,EAAE,MAAM;gBACrC,4BAA4B,EAAE,MAAM;gBACpC,gCAAgC,EAAE,MAAM;gBACxC,iCAAiC,EAAE,MAAM;aACnC;YACR,OAAO,EAAE;gBACP,OAAO,EAAE;oBACP,GAAG,EAAE,CAAC;oBACN,gBAAgB,EAAE,CAAC;oBACnB,qBAAqB,EAAE,CAAC;oBACxB,8BAA8B,EAAE,CAAC;oBACjC,6BAA6B,EAAE,CAAC;oBAChC,4BAA4B,EAAE,CAAC;oBAC/B,gCAAgC,EAAE,CAAC;oBACnC,iCAAiC,EAAE,CAAC;iBACrC;gBACD,IAAI,EAAE,sBAAsB;aAC7B;SACF;KACF,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { WebAuthnCredentialsCreationRequest } from '@unchainedshop/types/user.js';
2
+ import { mongodb } from '@unchainedshop/mongodb';
3
+ type Collection = WebAuthnCredentialsCreationRequest & {
4
+ _id: number;
5
+ };
6
+ export declare const WebAuthnCredentialsCreationRequestsCollection: (db: mongodb.Db) => Promise<mongodb.Collection<Collection>>;
7
+ export {};
8
+ //# sourceMappingURL=WebAuthnCredentialsCreationRequestsCollection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebAuthnCredentialsCreationRequestsCollection.d.ts","sourceRoot":"","sources":["../../src/db/WebAuthnCredentialsCreationRequestsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kCAAkC,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,EAAE,OAAO,EAAkB,MAAM,wBAAwB,CAAC;AAEjE,KAAK,UAAU,GAAG,kCAAkC,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,eAAO,MAAM,6CAA6C,OAAc,OAAO,CAAC,EAAE,4CAcjF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { buildDbIndexes } from '@unchainedshop/mongodb';
2
+ export const WebAuthnCredentialsCreationRequestsCollection = async (db) => {
3
+ const WebAuthnCredentialsCreationRequests = db.collection('accounts_webauthn_credentials_creation_requests');
4
+ await buildDbIndexes(WebAuthnCredentialsCreationRequests, [
5
+ {
6
+ index: {
7
+ username: 1,
8
+ },
9
+ },
10
+ ]);
11
+ return WebAuthnCredentialsCreationRequests;
12
+ };
13
+ //# sourceMappingURL=WebAuthnCredentialsCreationRequestsCollection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebAuthnCredentialsCreationRequestsCollection.js","sourceRoot":"","sources":["../../src/db/WebAuthnCredentialsCreationRequestsCollection.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAIjE,MAAM,CAAC,MAAM,6CAA6C,GAAG,KAAK,EAAE,EAAc,EAAE,EAAE;IACpF,MAAM,mCAAmC,GAAG,EAAE,CAAC,UAAU,CACvD,iDAAiD,CAClD,CAAC;IAEF,MAAM,cAAc,CAAa,mCAAmC,EAAE;QACpE;YACE,KAAK,EAAE;gBACL,QAAQ,EAAE,CAAC;aACZ;SACF;KACF,CAAC,CAAC;IAEH,OAAO,mCAAmC,CAAC;AAC7C,CAAC,CAAC"}
@@ -1,7 +1,93 @@
1
+ import localePkg from 'locale';
2
+ import { Address, Contact } from '@unchainedshop/types/common.js';
1
3
  import { ModuleInput } from '@unchainedshop/types/core.js';
2
- import { User, UserQuery, UsersModule } from '@unchainedshop/types/user.js';
4
+ import { User, UserQuery, Email, UserLastLogin, UserProfile, UserSettingsOptions, UserData } from '@unchainedshop/types/user.js';
3
5
  import { mongodb } from '@unchainedshop/mongodb';
6
+ import { SortOption } from '@unchainedshop/types/api.js';
4
7
  export declare const removeConfidentialServiceHashes: (rawUser: User) => User;
5
8
  export declare const buildFindSelector: ({ includeGuests, queryString, ...rest }: UserQuery) => mongodb.Filter<User>;
6
- export declare const configureUsersModule: ({ db, migrationRepository, }: ModuleInput<Record<string, never>>) => Promise<UsersModule>;
9
+ export declare const configureUsersModule: ({ db, options, migrationRepository, }: ModuleInput<UserSettingsOptions>) => Promise<{
10
+ webAuthn: {
11
+ findMDSMetadataForAAGUID: (aaguid: any) => Promise<any>;
12
+ createCredentialCreationOptions: (origin: any, username: any, extensionOptions: any) => Promise<Omit<import("fido2-lib").PublicKeyCredentialCreationOptions, "challenge" | "requestId"> & {
13
+ challenge: string;
14
+ requestId: number;
15
+ }>;
16
+ createCredentialRequestOptions: (origin: any, username: any, extensionOptions: any) => Promise<Omit<import("fido2-lib").PublicKeyCredentialRequestOptions, "challenge" | "requestId"> & {
17
+ challenge: string;
18
+ requestId: number;
19
+ }>;
20
+ verifyCredentialCreation: (username: any, credentials: any) => Promise<{
21
+ publicKey: any;
22
+ counter: any;
23
+ id: any;
24
+ aaguid: string;
25
+ created: Date;
26
+ }>;
27
+ verifyCredentialRequest: (userPublicKeys: any, username: any, credentials: any) => Promise<{
28
+ userHandle: any;
29
+ }>;
30
+ };
31
+ count(query: UserQuery): Promise<number>;
32
+ findUserById(userId: string): Promise<User>;
33
+ findUserByUsername(username: string): Promise<User>;
34
+ findUserByEmail(email: string): Promise<User>;
35
+ findUnverifiedEmailToken(plainToken: string): Promise<{
36
+ userId: string;
37
+ address: string;
38
+ when: Date;
39
+ }>;
40
+ verifyEmail(userId: string, address: string): Promise<void>;
41
+ findUserByResetToken(plainToken: string): Promise<User>;
42
+ findUserByToken(plainToken?: string): Promise<User>;
43
+ findUser(query: UserQuery & {
44
+ sort?: Array<SortOption>;
45
+ }, findOptions?: mongodb.FindOptions): Promise<User>;
46
+ findUsers({ limit, offset, ...query }: UserQuery & {
47
+ sort?: Array<SortOption>;
48
+ limit?: number;
49
+ offset?: number;
50
+ }): Promise<Array<User>>;
51
+ userExists({ userId }: {
52
+ userId: string;
53
+ }): Promise<boolean>;
54
+ primaryEmail(user: User): Email;
55
+ userLocale(user: User): localePkg.Locale;
56
+ createUser({ password, email, username, initialPassword, roles, webAuthnPublicKeyCredentials, ...userData }: UserData, { skipMessaging, skipPasswordEnrollment, }?: {
57
+ skipMessaging?: boolean;
58
+ skipPasswordEnrollment?: boolean;
59
+ }): Promise<string>;
60
+ hashPassword(password: any): Promise<{
61
+ pbkdf2: string;
62
+ }>;
63
+ verifyPassword({ bcrypt: bcryptHash, pbkdf2: pbkdf2SaltAndHash }: {
64
+ bcrypt?: string;
65
+ pbkdf2?: string;
66
+ }, plainPassword: string): Promise<boolean>;
67
+ addEmail(userId: string, address: string): Promise<void>;
68
+ removeEmail(userId: string, address: string): Promise<void>;
69
+ sendResetPasswordEmail(userId: string, email: string, isEnrollment?: boolean): Promise<void>;
70
+ sendVerificationEmail(userId: string, email: string): Promise<void>;
71
+ addRoles: (userId: string, roles: Array<string>) => Promise<number>;
72
+ setUsername(userId: string, username: string): Promise<void>;
73
+ setPassword(userId: string, plainPassword: string): Promise<void>;
74
+ updateAvatar: (_id: string, fileId: string) => Promise<User>;
75
+ updateGuest: (user: User, guest: boolean) => Promise<void>;
76
+ updateHeartbeat: (userId: string, lastLogin: UserLastLogin) => Promise<User>;
77
+ delete: (userId: string) => Promise<User>;
78
+ updateProfile: (userId: string, updatedData: {
79
+ profile?: UserProfile;
80
+ meta?: any;
81
+ }) => Promise<User>;
82
+ updateLastBillingAddress: (_id: string, lastBillingAddress: Address) => Promise<User>;
83
+ updateLastContact: (_id: string, lastContact: Contact) => Promise<User>;
84
+ updateRoles: (_id: string, roles: Array<string>) => Promise<User>;
85
+ updateTags: (_id: string, tags: Array<string>) => Promise<User>;
86
+ updateUser: (selector: mongodb.Filter<User>, modifier: mongodb.UpdateFilter<User>, updateOptions?: mongodb.FindOneAndUpdateOptions) => Promise<User>;
87
+ addPushSubscription: (userId: string, subscription: any, subscriptionOptions?: {
88
+ userAgent: string;
89
+ unsubscribeFromOtherUsers: boolean;
90
+ }) => Promise<void>;
91
+ removePushSubscription: (userId: string, p256dh: string) => Promise<void>;
92
+ }>;
7
93
  //# sourceMappingURL=configureUsersModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"configureUsersModule.d.ts","sourceRoot":"","sources":["../../src/module/configureUsersModule.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG5E,OAAO,EAA0C,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAsBzF,eAAO,MAAM,+BAA+B,YAAa,IAAI,KAAG,IAI/D,CAAC;AAEF,eAAO,MAAM,iBAAiB,4CAA6C,SAAS,yBAOnF,CAAC;AAOF,eAAO,MAAM,oBAAoB,iCAG9B,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,KAAG,OAAO,CAAC,WAAW,CAsW1D,CAAC"}
1
+ {"version":3,"file":"configureUsersModule.d.ts","sourceRoot":"","sources":["../../src/module/configureUsersModule.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,29 @@
1
1
  import localePkg from 'locale';
2
- import { log, LogLevel } from '@unchainedshop/logger';
2
+ import bcrypt from 'bcryptjs';
3
3
  import { emit, registerEvents } from '@unchainedshop/events';
4
- import { generateDbFilterById, buildSortOptions } from '@unchainedshop/mongodb';
4
+ import { generateDbFilterById, buildSortOptions, generateDbObjectId, } from '@unchainedshop/mongodb';
5
5
  import { systemLocale } from '@unchainedshop/utils';
6
6
  import { FileDirector } from '@unchainedshop/file-upload';
7
7
  import { SortDirection } from '@unchainedshop/types/api.js';
8
- import { v4 as uuidv4 } from 'uuid';
9
8
  import { UsersCollection } from '../db/UsersCollection.js';
10
9
  import addMigrations from './addMigrations.js';
10
+ import { userSettings } from '../users-settings.js';
11
+ import { configureUsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
12
+ import * as pbkdf2 from './pbkdf2.js';
13
+ import * as sha256 from './sha256.js';
11
14
  const { Locale } = localePkg;
12
15
  const USER_EVENTS = [
16
+ 'USER_ACCOUNT_ACTION',
17
+ 'USER_CREATE',
18
+ 'USER_ADD_ROLES',
13
19
  'USER_UPDATE',
14
20
  'USER_UPDATE_PROFILE',
15
- 'USER_ADD_ROLES',
16
21
  'USER_UPDATE_ROLE',
17
22
  'USER_UPDATE_TAGS',
18
23
  'USER_UPDATE_AVATAR',
24
+ 'USER_UPDATE_GUEST',
25
+ 'USER_UPDATE_USERNAME',
26
+ 'USER_UPDATE_PASSWORD',
19
27
  'USER_UPDATE_HEARTBEAT',
20
28
  'USER_UPDATE_BILLING_ADDRESS',
21
29
  'USER_UPDATE_LAST_CONTACT',
@@ -39,14 +47,17 @@ FileDirector.registerFileUploadCallback('user-avatars', async (file, context) =>
39
47
  const { services } = context;
40
48
  return services.users.updateUserAvatarAfterUpload({ file }, context);
41
49
  });
42
- export const configureUsersModule = async ({ db, migrationRepository, }) => {
50
+ export const configureUsersModule = async ({ db, options, migrationRepository, }) => {
51
+ userSettings.configureSettings(options || {}, db);
43
52
  registerEvents(USER_EVENTS);
44
53
  const Users = await UsersCollection(db);
45
54
  // Migration
46
55
  addMigrations(migrationRepository);
56
+ const webAuthn = await configureUsersWebAuthnModule({ db, options });
47
57
  return {
48
58
  // Queries
49
- count: async (query) => {
59
+ webAuthn,
60
+ async count(query) {
50
61
  const userCount = await Users.countDocuments(buildFindSelector(query));
51
62
  return userCount;
52
63
  },
@@ -55,33 +66,81 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
55
66
  return null;
56
67
  return Users.findOne(generateDbFilterById(userId), {});
57
68
  },
58
- async findUserByToken({ resetToken, hashedToken, verifyEmailToken }) {
59
- if (hashedToken) {
60
- return Users.findOne({
61
- 'services.resume.loginTokens.hashedToken': hashedToken,
62
- });
63
- }
64
- if (resetToken) {
65
- return Users.findOne({
66
- 'services.password.reset.token': resetToken,
67
- });
68
- }
69
- if (verifyEmailToken) {
70
- return Users.findOne({
69
+ async findUserByUsername(username) {
70
+ if (!username)
71
+ return null;
72
+ return Users.findOne({ username }, {});
73
+ },
74
+ async findUserByEmail(email) {
75
+ if (!email)
76
+ return null;
77
+ return Users.findOne({ 'emails.address': { $regex: email, $options: 'i' } }, {});
78
+ },
79
+ async findUnverifiedEmailToken(plainToken) {
80
+ if (!plainToken)
81
+ return null;
82
+ const token = await sha256.hash(plainToken);
83
+ const user = await Users.findOne({
84
+ 'services.email.verificationTokens': {
85
+ $elemMatch: {
86
+ token,
87
+ when: { $gt: new Date().getTime() },
88
+ },
89
+ },
90
+ }, {});
91
+ if (!user)
92
+ return null;
93
+ const verificationToken = user.services.email.verificationTokens.find((v) => v.token === token);
94
+ return {
95
+ userId: user._id,
96
+ ...verificationToken,
97
+ };
98
+ },
99
+ async verifyEmail(userId, address) {
100
+ await Users.updateOne({ _id: userId }, {
101
+ $set: {
102
+ 'emails.$[email].verified': true,
103
+ },
104
+ $pull: {
71
105
  'services.email.verificationTokens': {
72
- $elemMatch: {
73
- token: verifyEmailToken,
74
- },
106
+ address,
75
107
  },
108
+ },
109
+ }, {
110
+ arrayFilters: [{ 'email.address': address }],
111
+ });
112
+ await emit('USER_ACCOUNT_ACTION', {
113
+ action: 'email-verified',
114
+ address,
115
+ userId,
116
+ });
117
+ },
118
+ async findUserByResetToken(plainToken) {
119
+ const token = await sha256.hash(plainToken);
120
+ const user = await Users.findOne({
121
+ 'services.password.reset': {
122
+ $elemMatch: {
123
+ token,
124
+ when: { $gt: new Date().getTime() },
125
+ },
126
+ },
127
+ }, {});
128
+ return user;
129
+ },
130
+ async findUserByToken(plainToken) {
131
+ const token = await sha256.hash(plainToken);
132
+ if (token) {
133
+ return Users.findOne({
134
+ 'services.token.secret': token,
76
135
  });
77
136
  }
78
137
  return null;
79
138
  },
80
- findUser: async (query, options) => {
139
+ async findUser(query, findOptions) {
81
140
  const selector = buildFindSelector(query);
82
- return Users.findOne(selector, options);
141
+ return Users.findOne(selector, findOptions);
83
142
  },
84
- findUsers: async ({ limit, offset, ...query }) => {
143
+ async findUsers({ limit, offset, ...query }) {
85
144
  const defaultSort = [{ key: 'created', value: SortDirection.ASC }];
86
145
  const selector = buildFindSelector(query);
87
146
  if (query.queryString) {
@@ -98,24 +157,197 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
98
157
  sort: buildSortOptions(query.sort || defaultSort),
99
158
  }).toArray();
100
159
  },
101
- userExists: async ({ userId }) => {
160
+ async userExists({ userId }) {
102
161
  const selector = generateDbFilterById(userId);
103
162
  selector.deleted = null; // skip deleted users when checked for existance!
104
163
  const userCount = await Users.countDocuments(selector, { limit: 1 });
105
- return !!userCount;
164
+ return userCount === 1;
106
165
  },
107
166
  // Transformations
108
- primaryEmail: (user) => {
167
+ primaryEmail(user) {
109
168
  return (user.emails || []).sort((left, right) => Number(right.verified) - Number(left.verified))?.[0];
110
169
  },
111
- userLocale: (user) => {
170
+ userLocale(user) {
112
171
  if (!user?.lastLogin?.locale)
113
172
  return systemLocale;
114
173
  return new Locale(user.lastLogin.locale);
115
174
  },
175
+ // Mutations
176
+ async createUser({ password, email, username, initialPassword, roles, webAuthnPublicKeyCredentials, ...userData }, { skipMessaging, skipPasswordEnrollment, } = {}) {
177
+ const normalizedUserData = await userSettings.validateNewUser(userData);
178
+ const webAuthnService = webAuthnPublicKeyCredentials &&
179
+ (await this.webAuthn.verifyCredentialCreation(username, webAuthnPublicKeyCredentials));
180
+ const services = {};
181
+ if (email) {
182
+ if (!(await userSettings.validateEmail(email))) {
183
+ throw new Error(`E-Mail address ${email} is invalid`, { cause: 'EMAIL_INVALID' });
184
+ }
185
+ }
186
+ if (password) {
187
+ if (!(await userSettings.validatePassword(password))) {
188
+ throw new Error(`Provided password is invalid`, { cause: 'PASSWORD_INVALID' });
189
+ }
190
+ services.password = await this.hashPassword(password);
191
+ }
192
+ if (webAuthnService) {
193
+ services.webAuthn = [webAuthnService];
194
+ }
195
+ const doc = {
196
+ ...normalizedUserData,
197
+ _id: normalizedUserData._id || generateDbObjectId(),
198
+ roles: roles || [],
199
+ initialPassword: Boolean(initialPassword),
200
+ emails: email ? [{ address: email, verified: false }] : [],
201
+ created: new Date(),
202
+ };
203
+ if (username) {
204
+ if (!(await userSettings.validateUsername(username))) {
205
+ throw new Error(`Username ${username} is invalid`, { cause: 'USERNAME_INVALID' });
206
+ }
207
+ doc.username = username;
208
+ }
209
+ const { insertedId: userId } = await Users.insertOne(doc);
210
+ try {
211
+ const autoMessagingEnabled = skipMessaging
212
+ ? false
213
+ : userSettings.autoMessagingAfterUserCreation && !!email && !!userId;
214
+ if (autoMessagingEnabled) {
215
+ if (password === undefined) {
216
+ if (!skipPasswordEnrollment && !webAuthnService) {
217
+ await this.sendResetPasswordEmail(userId, email, true);
218
+ }
219
+ }
220
+ else {
221
+ await this.sendVerificationEmail(userId, email);
222
+ }
223
+ }
224
+ }
225
+ catch (e) {
226
+ /* */
227
+ }
228
+ const user = await Users.findOne({ _id: userId }, {});
229
+ await emit('USER_CREATE', {
230
+ user: removeConfidentialServiceHashes(user),
231
+ });
232
+ return userId;
233
+ },
234
+ async hashPassword(password) {
235
+ const salt = pbkdf2.generateSalt();
236
+ const hashedPassword = await pbkdf2.getDerivedKey(salt, password);
237
+ return { pbkdf2: `${salt}:${hashedPassword}` };
238
+ },
239
+ async verifyPassword({ bcrypt: bcryptHash, pbkdf2: pbkdf2SaltAndHash }, plainPassword) {
240
+ if (bcryptHash) {
241
+ const password = await sha256.hash(plainPassword);
242
+ return bcrypt.compare(password, bcryptHash);
243
+ }
244
+ if (pbkdf2SaltAndHash) {
245
+ const [pbkdf2Salt, pbkdf2Hash] = pbkdf2SaltAndHash.split(':');
246
+ return pbkdf2.compare(plainPassword, pbkdf2Hash, pbkdf2Salt);
247
+ }
248
+ return false;
249
+ },
250
+ async addEmail(userId, address) {
251
+ if (!(await userSettings.validateEmail(address))) {
252
+ throw new Error(`E-Mail address ${address} is invalid`, { cause: 'EMAIL_INVALID' });
253
+ }
254
+ await this.updateUser({ _id: userId, 'emails.address': { $not: { $regex: address, $options: 'i' } } }, {
255
+ $push: {
256
+ emails: {
257
+ address: address.trim(),
258
+ verified: false,
259
+ },
260
+ },
261
+ });
262
+ },
263
+ async removeEmail(userId, address) {
264
+ await this.updateUser({ _id: userId, 'emails.address': { $regex: address, $options: 'i' } }, {
265
+ $pull: {
266
+ emails: { address: { $regex: address, $options: 'i' } },
267
+ },
268
+ });
269
+ },
270
+ async sendResetPasswordEmail(userId, email, isEnrollment) {
271
+ const plainToken = crypto.randomUUID();
272
+ const resetToken = {
273
+ token: await sha256.hash(plainToken),
274
+ address: email,
275
+ when: new Date().getTime() + 1000 * 60 * 60, // 1 hour
276
+ };
277
+ await Users.updateOne({ _id: userId }, {
278
+ $push: {
279
+ 'services.password.reset': resetToken,
280
+ },
281
+ });
282
+ await emit('USER_ACCOUNT_ACTION', {
283
+ action: isEnrollment ? 'enroll-account' : 'reset-password',
284
+ userId,
285
+ ...resetToken,
286
+ token: plainToken,
287
+ });
288
+ },
289
+ async sendVerificationEmail(userId, email) {
290
+ const plainToken = crypto.randomUUID();
291
+ const verificationToken = {
292
+ token: await sha256.hash(plainToken),
293
+ address: email,
294
+ when: new Date().getTime() + 1000 * 60 * 60, // 1 hour
295
+ };
296
+ await Users.updateOne({ _id: userId }, {
297
+ $push: {
298
+ 'services.email.verificationTokens': verificationToken,
299
+ },
300
+ });
301
+ await emit('USER_ACCOUNT_ACTION', {
302
+ action: 'verify-email',
303
+ userId,
304
+ ...verificationToken,
305
+ token: plainToken,
306
+ });
307
+ },
308
+ addRoles: async (userId, roles) => {
309
+ const selector = generateDbFilterById(userId);
310
+ const updateResult = await Users.updateOne(selector, {
311
+ $addToSet: { roles: { $each: roles } },
312
+ });
313
+ const user = await Users.findOne(selector, {});
314
+ await emit('USER_ADD_ROLES', {
315
+ user: removeConfidentialServiceHashes(user),
316
+ });
317
+ return updateResult.modifiedCount;
318
+ },
319
+ async setUsername(userId, username) {
320
+ if (!(await userSettings.validateUsername(username))) {
321
+ throw new Error(`Username ${username} is invalid`, { cause: 'USERNAME_INVALID' });
322
+ }
323
+ await Users.updateOne({ _id: userId }, {
324
+ $set: {
325
+ username: username.trim(),
326
+ },
327
+ });
328
+ const user = await Users.findOne({ _id: userId }, {});
329
+ await emit('USER_UPDATE_USERNAME', {
330
+ user: removeConfidentialServiceHashes(user),
331
+ });
332
+ },
333
+ async setPassword(userId, plainPassword) {
334
+ if (!(await userSettings.validatePassword(plainPassword))) {
335
+ throw new Error(`Password ***** is invalid`, { cause: 'PASSWORD_INVALID' });
336
+ }
337
+ const password = plainPassword || crypto.randomUUID().split('-').pop();
338
+ await Users.updateOne({ _id: userId }, {
339
+ $set: {
340
+ initialPassword: false,
341
+ 'services.password': await this.hashPassword(password || crypto.randomUUID().split('-').pop()),
342
+ },
343
+ });
344
+ const user = await Users.findOne({ _id: userId }, {});
345
+ await emit('USER_UPDATE_PASSWORD', {
346
+ user: removeConfidentialServiceHashes(user),
347
+ });
348
+ },
116
349
  updateAvatar: async (_id, fileId) => {
117
350
  const userFilter = generateDbFilterById(_id);
118
- log('Update Avatar', { userId: _id });
119
351
  const modifier = {
120
352
  $set: {
121
353
  avatarId: fileId,
@@ -131,9 +363,14 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
131
363
  return user;
132
364
  },
133
365
  updateGuest: async (user, guest) => {
134
- log('Update guest', { userId: user._id });
135
366
  const modifier = { $set: { guest } };
136
367
  await Users.updateOne(generateDbFilterById(user._id), modifier);
368
+ await emit('USER_UPDATE_GUEST', {
369
+ user: removeConfidentialServiceHashes({
370
+ ...user,
371
+ guest,
372
+ }),
373
+ });
137
374
  },
138
375
  updateHeartbeat: async (userId, lastLogin) => {
139
376
  const modifier = {
@@ -152,14 +389,6 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
152
389
  });
153
390
  return user;
154
391
  },
155
- updateInitialPassword: async (user, initialPassword) => {
156
- log(`Update initial password flag to ${initialPassword}`, {
157
- userId: user._id,
158
- level: LogLevel.Verbose,
159
- });
160
- const modifier = { $set: { initialPassword } };
161
- await Users.updateOne(generateDbFilterById(user._id), modifier);
162
- },
163
392
  delete: async (userId) => {
164
393
  const userFilter = generateDbFilterById(userId);
165
394
  const existingUser = await Users.findOne(userFilter, {
@@ -167,17 +396,18 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
167
396
  });
168
397
  if (!existingUser)
169
398
  return null;
399
+ const uuid = crypto.randomUUID();
170
400
  const obfuscatedEmails = existingUser.emails?.flatMap(({ address, verified }) => {
171
401
  if (!verified)
172
402
  return [];
173
403
  return [
174
404
  {
175
- address: `${address}@${uuidv4()}.unchained.local`,
405
+ address: `${address}@${uuid}.unchained.local`,
176
406
  verified: true,
177
407
  },
178
408
  ];
179
409
  });
180
- const obfuscatedUsername = existingUser.username ? `${existingUser.username}-${uuidv4()}` : null;
410
+ const obfuscatedUsername = existingUser.username ? `${existingUser.username}-${uuid}` : null;
181
411
  Users.updateOne(userFilter, {
182
412
  $set: {
183
413
  emails: obfuscatedEmails,
@@ -299,9 +529,9 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
299
529
  });
300
530
  return user;
301
531
  },
302
- updateUser: async (query, modifier, options) => {
303
- const user = await Users.findOneAndUpdate(query, modifier, {
304
- ...options,
532
+ updateUser: async (selector, modifier, updateOptions) => {
533
+ const user = await Users.findOneAndUpdate(selector, modifier, {
534
+ ...updateOptions,
305
535
  returnDocument: 'after',
306
536
  });
307
537
  await emit('USER_UPDATE', {
@@ -309,16 +539,16 @@ export const configureUsersModule = async ({ db, migrationRepository, }) => {
309
539
  });
310
540
  return user;
311
541
  },
312
- addPushSubscription: async (userId, subscription, { userAgent, unsubscribeFromOtherUsers }) => {
542
+ addPushSubscription: async (userId, subscription, subscriptionOptions) => {
313
543
  const updateResult = await Users.updateOne({ _id: userId, 'pushSubscriptions.keys.p256dh': { $ne: subscription?.keys?.p256dh } }, {
314
544
  $push: {
315
545
  pushSubscriptions: {
316
- userAgent,
546
+ userAgent: subscriptionOptions?.userAgent,
317
547
  ...subscription,
318
548
  },
319
549
  },
320
550
  }, {});
321
- if (updateResult.modifiedCount === 1 && unsubscribeFromOtherUsers) {
551
+ if (updateResult.modifiedCount === 1 && subscriptionOptions?.unsubscribeFromOtherUsers) {
322
552
  await Users.updateMany({ _id: { $ne: userId }, 'pushSubscriptions.keys.p256dh': subscription?.keys?.p256dh }, {
323
553
  $pull: {
324
554
  pushSubscriptions: { 'keys.p256dh': subscription?.keys?.p256dh },