@unchainedshop/core-users 4.3.5 → 4.5.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 (45) hide show
  1. package/README.md +124 -2
  2. package/lib/db/UsersCollection.d.ts +8 -5
  3. package/lib/db/UsersCollection.js +0 -1
  4. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts +1 -2
  5. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js +0 -1
  6. package/lib/migrations/20241218092300-convert-locale.d.ts +2 -0
  7. package/lib/migrations/20241218092300-convert-locale.js +36 -0
  8. package/lib/module/configureUsersModule.d.ts +53 -23
  9. package/lib/module/configureUsersModule.js +127 -18
  10. package/lib/module/configureUsersWebAuthnModule.d.ts +78 -20
  11. package/lib/module/configureUsersWebAuthnModule.js +126 -112
  12. package/lib/module/pbkdf2.d.ts +0 -1
  13. package/lib/module/pbkdf2.js +3 -7
  14. package/lib/users-index.d.ts +5 -6
  15. package/lib/users-index.js +5 -6
  16. package/lib/users-settings.d.ts +11 -11
  17. package/lib/users-settings.js +7 -10
  18. package/lib/utils/web3-verification.d.ts +1 -0
  19. package/lib/utils/web3-verification.js +81 -0
  20. package/package.json +26 -16
  21. package/lib/db/UsersCollection.d.ts.map +0 -1
  22. package/lib/db/UsersCollection.js.map +0 -1
  23. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts.map +0 -1
  24. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js.map +0 -1
  25. package/lib/module/configureUsersModule.d.ts.map +0 -1
  26. package/lib/module/configureUsersModule.js.map +0 -1
  27. package/lib/module/configureUsersWebAuthnModule.d.ts.map +0 -1
  28. package/lib/module/configureUsersWebAuthnModule.js.map +0 -1
  29. package/lib/module/pbkdf2.d.ts.map +0 -1
  30. package/lib/module/pbkdf2.js.map +0 -1
  31. package/lib/users-index.d.ts.map +0 -1
  32. package/lib/users-index.js.map +0 -1
  33. package/lib/users-settings.d.ts.map +0 -1
  34. package/lib/users-settings.js.map +0 -1
  35. package/src/db/UsersCollection.ts +0 -174
  36. package/src/db/WebAuthnCredentialsCreationRequestsCollection.ts +0 -26
  37. package/src/module/buildFindSelector.test.ts +0 -34
  38. package/src/module/configureUsersModule.ts +0 -857
  39. package/src/module/configureUsersWebAuthnModule.ts +0 -234
  40. package/src/module/pbkdf2.ts +0 -46
  41. package/src/module/removeConfidentialServiceHashes.test.ts +0 -12
  42. package/src/users-index.ts +0 -5
  43. package/src/users-settings.ts +0 -99
  44. package/tests/mock/user-mock.ts +0 -72
  45. package/tsconfig.json +0 -10
package/README.md CHANGED
@@ -1,3 +1,125 @@
1
- # Users (Unchained Engine)
1
+ [![npm version](https://img.shields.io/npm/v/@unchainedshop/core-users.svg)](https://npmjs.com/package/@unchainedshop/core-users)
2
+ [![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://opensource.org/licenses/EUPL-1.2)
2
3
 
3
- This package contains business logic and database abstraction for users in Unchained.
4
+ # @unchainedshop/core-users
5
+
6
+ User management module for the Unchained Engine. Handles user accounts, authentication, profiles, and WebAuthn support.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @unchainedshop/core-users
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { configureUsersModule } from '@unchainedshop/core-users';
18
+
19
+ const usersModule = await configureUsersModule({ db });
20
+
21
+ // Find users
22
+ const users = await usersModule.findUsers({
23
+ includeGuests: false,
24
+ limit: 50,
25
+ });
26
+
27
+ // Create a user
28
+ const userId = await usersModule.createUser({
29
+ email: 'user@example.com',
30
+ password: 'securePassword',
31
+ });
32
+
33
+ // Update profile
34
+ await usersModule.updateProfile(userId, {
35
+ displayName: 'John Doe',
36
+ });
37
+ ```
38
+
39
+ ## API Overview
40
+
41
+ ### Module Configuration
42
+
43
+ | Export | Description |
44
+ |--------|-------------|
45
+ | `configureUsersModule` | Configure and return the users module |
46
+ | `configureUsersWebAuthnModule` | Configure WebAuthn submodule |
47
+
48
+ ### Queries
49
+
50
+ | Method | Description |
51
+ |--------|-------------|
52
+ | `findUser` | Find user by ID, email, or username |
53
+ | `findUsers` | Find users with filtering, sorting, and pagination |
54
+ | `count` | Count users matching query |
55
+ | `userExists` | Check if a user exists |
56
+
57
+ ### Mutations
58
+
59
+ | Method | Description |
60
+ |--------|-------------|
61
+ | `createUser` | Create a new user account |
62
+ | `updateUser` | Update user data |
63
+ | `updateProfile` | Update user profile |
64
+ | `updateRoles` | Update user roles |
65
+ | `updateTags` | Update user tags |
66
+ | `updateAvatar` | Set user avatar |
67
+ | `updateBillingAddress` | Update billing address |
68
+ | `updatePassword` | Change user password |
69
+ | `updateUsername` | Change username |
70
+ | `delete` | Soft delete a user |
71
+ | `replaceUserId` | Migrate data between users |
72
+
73
+ ### Authentication
74
+
75
+ | Method | Description |
76
+ |--------|-------------|
77
+ | `verifyPassword` | Verify password against stored hash |
78
+ | `hashPassword` | Hash a password for storage |
79
+ | `addEmail` | Add email address to user |
80
+ | `removeEmail` | Remove email address |
81
+ | `verifyEmail` | Mark email as verified |
82
+ | `updateHeartbeat` | Update last activity timestamp |
83
+ | `updateLastLogin` | Record login event |
84
+
85
+ ### WebAuthn Submodule
86
+
87
+ | Method | Description |
88
+ |--------|-------------|
89
+ | `webAuthn.findCredentials` | Find WebAuthn credentials for user |
90
+ | `webAuthn.createCredential` | Register new WebAuthn credential |
91
+ | `webAuthn.removeCredential` | Remove WebAuthn credential |
92
+
93
+ ### Settings
94
+
95
+ | Export | Description |
96
+ |--------|-------------|
97
+ | `userSettings` | Access user module settings |
98
+ | `UserAccountAction` | Account action types enum |
99
+
100
+ ### Types
101
+
102
+ | Export | Description |
103
+ |--------|-------------|
104
+ | `User` | User document type |
105
+ | `UserQuery` | Query parameters type |
106
+ | `UserProfile` | User profile type |
107
+ | `Email` | Email address type |
108
+ | `UsersModule` | Module interface type |
109
+ | `UserSettingsOptions` | Configuration options type |
110
+
111
+ ## Events
112
+
113
+ | Event | Description |
114
+ |-------|-------------|
115
+ | `USER_CREATE` | User created |
116
+ | `USER_UPDATE` | User updated |
117
+ | `USER_REMOVE` | User deleted |
118
+ | `USER_UPDATE_PROFILE` | Profile updated |
119
+ | `USER_UPDATE_PASSWORD` | Password changed |
120
+ | `USER_UPDATE_ROLES` | Roles changed |
121
+ | `USER_ACCOUNT_ACTION` | Account action triggered |
122
+
123
+ ## License
124
+
125
+ EUPL-1.2
@@ -1,5 +1,5 @@
1
- import { TimestampFields, Address, Contact, mongodb } from '@unchainedshop/mongodb';
2
- import { DateFilterInput } from '@unchainedshop/utils';
1
+ import { type TimestampFields, type Address, type Contact, type mongodb } from '@unchainedshop/mongodb';
2
+ import type { DateFilterInput } from '@unchainedshop/utils';
3
3
  export interface UserProfile {
4
4
  displayName?: string;
5
5
  birthday?: Date;
@@ -46,12 +46,15 @@ export type User = {
46
46
  username?: string;
47
47
  meta?: any;
48
48
  } & TimestampFields;
49
- export type UserQuery = mongodb.Filter<User> & {
49
+ export interface UserQuery {
50
50
  includeGuests?: boolean;
51
51
  includeDeleted?: boolean;
52
52
  queryString?: string;
53
53
  emailVerified?: boolean;
54
54
  lastLogin?: DateFilterInput;
55
- };
55
+ tags?: string[];
56
+ userIds?: string[];
57
+ username?: string;
58
+ web3Verified?: boolean;
59
+ }
56
60
  export declare const UsersCollection: (db: mongodb.Db) => Promise<mongodb.Collection<User>>;
57
- //# sourceMappingURL=UsersCollection.d.ts.map
@@ -99,4 +99,3 @@ export const UsersCollection = async (db) => {
99
99
  ]);
100
100
  return Users;
101
101
  };
102
- //# sourceMappingURL=UsersCollection.js.map
@@ -1,4 +1,4 @@
1
- import { mongodb } from '@unchainedshop/mongodb';
1
+ import { type mongodb } from '@unchainedshop/mongodb';
2
2
  export interface WebAuthnCredentialsCreationRequest {
3
3
  challenge: string;
4
4
  username: string;
@@ -10,4 +10,3 @@ type Collection = WebAuthnCredentialsCreationRequest & {
10
10
  };
11
11
  export declare const WebAuthnCredentialsCreationRequestsCollection: (db: mongodb.Db) => Promise<mongodb.Collection<Collection>>;
12
12
  export {};
13
- //# sourceMappingURL=WebAuthnCredentialsCreationRequestsCollection.d.ts.map
@@ -10,4 +10,3 @@ export const WebAuthnCredentialsCreationRequestsCollection = async (db) => {
10
10
  ]);
11
11
  return WebAuthnCredentialsCreationRequests;
12
12
  };
13
- //# sourceMappingURL=WebAuthnCredentialsCreationRequestsCollection.js.map
@@ -0,0 +1,2 @@
1
+ import type { MigrationRepository } from '@unchainedshop/mongodb';
2
+ export default function convertUserLocale(repository: MigrationRepository): void;
@@ -0,0 +1,36 @@
1
+ import { UsersCollection } from "../db/UsersCollection.js";
2
+ import { systemLocale } from '@unchainedshop/utils';
3
+ export default function convertUserLocale(repository) {
4
+ repository?.register({
5
+ id: 20241218092300,
6
+ name: 'Convert user.lastLogin.locale',
7
+ up: async () => {
8
+ const Users = await UsersCollection(repository.db);
9
+ const users = await Users.find({
10
+ 'lastLogin.locale': { $exists: true },
11
+ }, { projection: { _id: true, lastLogin: true } }).toArray();
12
+ for (const user of users) {
13
+ let newLocale;
14
+ const currentLocale = user.lastLogin?.locale;
15
+ if (!currentLocale)
16
+ continue;
17
+ try {
18
+ newLocale = new Intl.Locale(currentLocale).baseName;
19
+ }
20
+ catch {
21
+ try {
22
+ newLocale = new Intl.Locale(currentLocale.split('_').join('-')).baseName;
23
+ }
24
+ catch {
25
+ newLocale = systemLocale.baseName;
26
+ }
27
+ }
28
+ await Users.updateOne({
29
+ _id: user._id,
30
+ }, {
31
+ $set: { 'lastLogin.locale': newLocale },
32
+ });
33
+ }
34
+ },
35
+ });
36
+ }
@@ -1,29 +1,42 @@
1
- import { ModuleInput, Address, Contact, mongodb } from '@unchainedshop/mongodb';
2
- import { User, UserQuery, Email, UserLastLogin, UserProfile } from '../db/UsersCollection.js';
3
- import { SortOption } from '@unchainedshop/utils';
4
- import { UserRegistrationData, UserSettingsOptions } from '../users-settings.js';
1
+ import { type ModuleInput, type Address, type Contact, type mongodb } from '@unchainedshop/mongodb';
2
+ import { type User, type UserQuery, type Email, type UserLastLogin, type UserProfile } from '../db/UsersCollection.ts';
3
+ import { type SortOption } from '@unchainedshop/utils';
4
+ import { type UserRegistrationData, type UserSettingsOptions } from '../users-settings.ts';
5
5
  export declare const removeConfidentialServiceHashes: (rawUser: User) => User;
6
- export declare const buildFindSelector: ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, ...rest }: UserQuery) => mongodb.Filter<User>;
6
+ export declare const buildFindSelector: ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, userIds, username, web3Verified, }: UserQuery) => mongodb.Filter<User>;
7
7
  export declare const configureUsersModule: (moduleInput: ModuleInput<UserSettingsOptions>) => Promise<{
8
8
  webAuthn: {
9
- findMDSMetadataForAAGUID: (aaguid: string) => Promise<any>;
10
- createCredentialCreationOptions: (origin: string, username: string, extensionOptions?: any) => Promise<(Omit<import("fido2-lib").PublicKeyCredentialCreationOptions, "challenge" | "requestId"> & {
11
- challenge: string;
12
- requestId: number;
13
- }) | null>;
14
- createCredentialRequestOptions: (origin: string, username: string, extensionOptions?: any) => Promise<(Omit<import("fido2-lib").PublicKeyCredentialRequestOptions, "challenge" | "requestId"> & {
15
- challenge: string;
16
- requestId: number;
17
- }) | null>;
18
- verifyCredentialCreation: (username: string, credentials: any) => Promise<{
19
- publicKey: any;
20
- counter: any;
21
- id: any;
22
- aaguid: string;
23
- created: Date;
9
+ findMDSMetadataForAAGUID: (aaguid: string) => Promise<{
10
+ [key: string]: unknown;
11
+ description?: string;
12
+ icon?: string;
13
+ authenticatorGetInfo?: {
14
+ versions?: string[];
15
+ extensions?: string[];
16
+ options?: Record<string, boolean>;
17
+ };
24
18
  } | null>;
25
- verifyCredentialRequest: (userPublicKeys: any[], username: string, credentials: any) => Promise<{
26
- userHandle: any;
19
+ createCredentialCreationOptions: (origin: string, username: string, extensionOptions?: {
20
+ timeout?: number;
21
+ authenticatorSelection?: import("./configureUsersWebAuthnModule.ts").WebAuthnCredentialCreationOptions["authenticatorSelection"];
22
+ }) => Promise<import("./configureUsersWebAuthnModule.ts").WebAuthnCredentialCreationOptions>;
23
+ createCredentialRequestOptions: (origin: string, username: string, extensionOptions?: {
24
+ timeout?: number;
25
+ userVerification?: "required" | "preferred" | "discouraged";
26
+ allowCredentials?: import("./configureUsersWebAuthnModule.ts").WebAuthnCredentialRequestOptions["allowCredentials"];
27
+ }) => Promise<import("./configureUsersWebAuthnModule.ts").WebAuthnCredentialRequestOptions>;
28
+ verifyCredentialCreation: (username: string, credentials: import("@passwordless-id/webauthn/dist/esm/types.js").RegistrationJSON) => Promise<import("./configureUsersWebAuthnModule.ts").WebAuthnCredential | null>;
29
+ verifyCredentialRequest: (userPublicKeys: {
30
+ id: string;
31
+ publicKey: string;
32
+ algorithm?: import("@passwordless-id/webauthn/dist/esm/types.js").NamedAlgo;
33
+ counter?: number;
34
+ transports?: import("@passwordless-id/webauthn/dist/esm/types.js").ExtendedAuthenticatorTransport[];
35
+ }[], username: string, credentials: import("@passwordless-id/webauthn/dist/esm/types.js").AuthenticationJSON & {
36
+ requestId: number;
37
+ }) => Promise<{
38
+ userHandle: string;
39
+ counter: number;
27
40
  } | null>;
28
41
  deleteUserWebAuthnCredentials: (username: string) => Promise<number>;
29
42
  };
@@ -69,6 +82,24 @@ export declare const configureUsersModule: (moduleInput: ModuleInput<UserSetting
69
82
  }, plainPassword: string): Promise<boolean>;
70
83
  addEmail(userId: string, address: string): Promise<void>;
71
84
  removeEmail(userId: string, address: string): Promise<void>;
85
+ addWeb3Address(userId: string, address: string): Promise<User | null>;
86
+ removeWeb3Address(userId: string, address: string): Promise<User | null>;
87
+ findWeb3Address(user: User, address: string): {
88
+ address: string;
89
+ nonce?: string;
90
+ verified?: boolean;
91
+ } | null;
92
+ addWebAuthnCredential(userId: string, webAuthnService: {
93
+ id: string;
94
+ publicKey: string;
95
+ created: Date;
96
+ }): Promise<User | null>;
97
+ removeWebAuthnCredential(userId: string, credentialsId: string): Promise<User | null>;
98
+ setAccessToken(username: string, plainSecret: string): Promise<User | null>;
99
+ verifyWeb3SignatureAndUpdate(user: User, credentials: {
100
+ address: string;
101
+ nonce: string;
102
+ }, signature: `0x${string}`): Promise<User | null>;
72
103
  sendResetPasswordEmail(userId: string, email: string, isEnrollment?: boolean): Promise<void>;
73
104
  sendVerificationEmail(userId: string, email: string): Promise<void>;
74
105
  addRoles: (userId: string, roles: string[]) => Promise<mongodb.WithId<User> | null>;
@@ -99,4 +130,3 @@ export declare const configureUsersModule: (moduleInput: ModuleInput<UserSetting
99
130
  existingTags: () => Promise<string[]>;
100
131
  }>;
101
132
  export type UsersModule = Awaited<ReturnType<typeof configureUsersModule>>;
102
- //# sourceMappingURL=configureUsersModule.d.ts.map
@@ -1,11 +1,13 @@
1
1
  import * as bcrypt from 'bcryptjs';
2
2
  import { generateDbFilterById, buildSortOptions, generateDbObjectId, insensitiveTrimmedRegexOperator, assertDocumentDBCompatMode, } from '@unchainedshop/mongodb';
3
- import { UsersCollection, } from '../db/UsersCollection.js';
3
+ import { UsersCollection, } from "../db/UsersCollection.js";
4
4
  import { emit, registerEvents } from '@unchainedshop/events';
5
5
  import { systemLocale, SortDirection, sha256 } from '@unchainedshop/utils';
6
- import { UserAccountAction, userSettings, } from '../users-settings.js';
7
- import { configureUsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
8
- import * as pbkdf2 from './pbkdf2.js';
6
+ import { UserAccountAction, userSettings, } from "../users-settings.js";
7
+ import { configureUsersWebAuthnModule } from "./configureUsersWebAuthnModule.js";
8
+ import * as pbkdf2 from "./pbkdf2.js";
9
+ import { verifyWeb3Signature } from "../utils/web3-verification.js";
10
+ import convertUserLocale from "../migrations/20241218092300-convert-locale.js";
9
11
  const USER_EVENTS = [
10
12
  'USER_ACCOUNT_ACTION',
11
13
  'USER_CREATE',
@@ -21,6 +23,7 @@ const USER_EVENTS = [
21
23
  'USER_UPDATE_HEARTBEAT',
22
24
  'USER_UPDATE_BILLING_ADDRESS',
23
25
  'USER_UPDATE_LAST_CONTACT',
26
+ 'USER_UPDATE_WEB3_ADDRESS',
24
27
  'USER_REMOVE',
25
28
  ];
26
29
  export const removeConfidentialServiceHashes = (rawUser) => {
@@ -28,12 +31,18 @@ export const removeConfidentialServiceHashes = (rawUser) => {
28
31
  delete user?.services;
29
32
  return user;
30
33
  };
31
- export const buildFindSelector = ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, ...rest }) => {
32
- const selector = { ...rest };
34
+ export const buildFindSelector = ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, userIds, username, web3Verified, }) => {
35
+ const selector = {};
33
36
  if (!includeDeleted)
34
37
  selector.deleted = null;
35
38
  if (!includeGuests)
36
39
  selector.guest = { $ne: true };
40
+ if (userIds) {
41
+ selector._id = { $in: userIds };
42
+ }
43
+ if (username) {
44
+ selector.username = insensitiveTrimmedRegexOperator(username);
45
+ }
37
46
  if (emailVerified === true) {
38
47
  selector['emails.verified'] = true;
39
48
  }
@@ -41,10 +50,11 @@ export const buildFindSelector = ({ includeGuests, includeDeleted, queryString,
41
50
  selector.tags = { $in: tags };
42
51
  }
43
52
  if (emailVerified === false) {
44
- // We need to use $ne here else we'd also find users with many emails where one is
45
- // unverified
46
53
  selector['emails.verified'] = { $ne: true };
47
54
  }
55
+ if (web3Verified === true) {
56
+ selector['services.web3.verified'] = true;
57
+ }
48
58
  if (lastLogin?.start) {
49
59
  selector['lastLogin.timestamp'] = { $exists: true };
50
60
  }
@@ -61,13 +71,13 @@ export const buildFindSelector = ({ includeGuests, includeDeleted, queryString,
61
71
  return selector;
62
72
  };
63
73
  export const configureUsersModule = async (moduleInput) => {
64
- const { db, options } = moduleInput;
74
+ const { db, options, migrationRepository } = moduleInput;
75
+ convertUserLocale(migrationRepository);
65
76
  userSettings.configureSettings(options || {}, db);
66
77
  registerEvents(USER_EVENTS);
67
78
  const Users = await UsersCollection(db);
68
79
  const webAuthn = await configureUsersWebAuthnModule(moduleInput);
69
80
  return {
70
- // Queries
71
81
  webAuthn,
72
82
  async count(query) {
73
83
  const userCount = await Users.countDocuments(buildFindSelector(query));
@@ -182,7 +192,6 @@ export const configureUsersModule = async (moduleInput) => {
182
192
  const userCount = await Users.countDocuments({ _id: userId, deleted: { $exists: false } }, { limit: 1 });
183
193
  return userCount === 1;
184
194
  },
185
- // Transformations
186
195
  primaryEmail(user) {
187
196
  return (user.emails || []).toSorted((left, right) => Number(right.verified) - Number(left.verified))?.[0];
188
197
  },
@@ -196,11 +205,13 @@ export const configureUsersModule = async (moduleInput) => {
196
205
  return systemLocale;
197
206
  }
198
207
  },
199
- // Mutations
200
208
  async createUser(rawUserData, { skipMessaging, skipPasswordEnrollment, } = {}) {
201
209
  const { password, email, username, initialPassword, roles, webAuthnPublicKeyCredentials, ...userData } = await userSettings.validateNewUser(rawUserData);
202
210
  const webAuthnService = webAuthnPublicKeyCredentials &&
203
211
  (await this.webAuthn.verifyCredentialCreation(username, webAuthnPublicKeyCredentials));
212
+ if (webAuthnPublicKeyCredentials && !webAuthnService) {
213
+ throw new Error('WebAuthn credential verification failed', { cause: 'WEBAUTHN_INVALID' });
214
+ }
204
215
  const services = {};
205
216
  if (email) {
206
217
  if (!(await userSettings.validateEmail(email))) {
@@ -250,7 +261,6 @@ export const configureUsersModule = async (moduleInput) => {
250
261
  }
251
262
  }
252
263
  catch {
253
- /* */
254
264
  }
255
265
  const user = (await Users.findOne({ _id: userId }, {}));
256
266
  await emit('USER_CREATE', {
@@ -294,6 +304,110 @@ export const configureUsersModule = async (moduleInput) => {
294
304
  },
295
305
  });
296
306
  },
307
+ async addWeb3Address(userId, address) {
308
+ const user = await Users.findOne(generateDbFilterById(userId), {});
309
+ if (!user)
310
+ return null;
311
+ const existingEntry = user.services?.web3?.find((service) => service.address.toLowerCase() === address.toLowerCase());
312
+ if (existingEntry)
313
+ return user;
314
+ const nonce = Math.floor(Math.random() * 1000000).toString();
315
+ const updatedUser = await Users.findOneAndUpdate(generateDbFilterById(userId), {
316
+ $push: {
317
+ 'services.web3': {
318
+ address,
319
+ nonce,
320
+ },
321
+ },
322
+ }, { returnDocument: 'after' });
323
+ if (!updatedUser)
324
+ return null;
325
+ await emit('USER_UPDATE_WEB3_ADDRESS', {
326
+ action: 'add',
327
+ address,
328
+ user: removeConfidentialServiceHashes(updatedUser),
329
+ });
330
+ return updatedUser;
331
+ },
332
+ async removeWeb3Address(userId, address) {
333
+ const user = await Users.findOne(generateDbFilterById(userId), {});
334
+ if (!user)
335
+ return null;
336
+ const existingEntry = user.services?.web3?.find((service) => service.address.toLowerCase() === address.toLowerCase());
337
+ if (!existingEntry)
338
+ return null;
339
+ const updatedUser = await Users.findOneAndUpdate(generateDbFilterById(userId), {
340
+ $pull: {
341
+ 'services.web3': { address: existingEntry.address },
342
+ },
343
+ }, { returnDocument: 'after' });
344
+ if (!updatedUser)
345
+ return null;
346
+ await emit('USER_UPDATE_WEB3_ADDRESS', {
347
+ action: 'remove',
348
+ address: existingEntry.address,
349
+ user: removeConfidentialServiceHashes(updatedUser),
350
+ });
351
+ return updatedUser;
352
+ },
353
+ findWeb3Address(user, address) {
354
+ return (user.services?.web3?.find((service) => service.address.toLowerCase() === address.toLowerCase()) || null);
355
+ },
356
+ async addWebAuthnCredential(userId, webAuthnService) {
357
+ const updatedUser = await Users.findOneAndUpdate(generateDbFilterById(userId), {
358
+ $push: {
359
+ 'services.webAuthn': webAuthnService,
360
+ },
361
+ }, { returnDocument: 'after' });
362
+ return updatedUser;
363
+ },
364
+ async removeWebAuthnCredential(userId, credentialsId) {
365
+ const updatedUser = await Users.findOneAndUpdate(generateDbFilterById(userId), {
366
+ $pull: {
367
+ 'services.webAuthn': { id: credentialsId },
368
+ },
369
+ }, { returnDocument: 'after' });
370
+ return updatedUser;
371
+ },
372
+ async setAccessToken(username, plainSecret) {
373
+ const secret = await sha256(`${username}:${plainSecret}`);
374
+ const updatedUser = await Users.findOneAndUpdate({ username: insensitiveTrimmedRegexOperator(username) }, {
375
+ $set: {
376
+ 'services.token': { secret },
377
+ },
378
+ }, { returnDocument: 'after' });
379
+ return updatedUser;
380
+ },
381
+ async verifyWeb3SignatureAndUpdate(user, credentials, signature) {
382
+ const isValid = await verifyWeb3Signature(credentials.nonce, signature, credentials.address);
383
+ if (!isValid)
384
+ return null;
385
+ const web3Services = user.services?.web3?.map((service) => {
386
+ if (service.address.toLowerCase() === credentials.address.toLowerCase()) {
387
+ return {
388
+ ...service,
389
+ nonce: undefined,
390
+ verified: true,
391
+ };
392
+ }
393
+ return service;
394
+ });
395
+ if (!web3Services)
396
+ return null;
397
+ const updatedUser = await Users.findOneAndUpdate(generateDbFilterById(user._id), {
398
+ $set: {
399
+ 'services.web3': web3Services,
400
+ },
401
+ }, { returnDocument: 'after' });
402
+ if (!updatedUser)
403
+ return null;
404
+ await emit('USER_UPDATE_WEB3_ADDRESS', {
405
+ action: 'verify',
406
+ address: credentials.address,
407
+ user: removeConfidentialServiceHashes(updatedUser),
408
+ });
409
+ return updatedUser;
410
+ },
297
411
  async sendResetPasswordEmail(userId, email, isEnrollment) {
298
412
  const plainToken = crypto.randomUUID();
299
413
  const resetToken = {
@@ -384,7 +498,6 @@ export const configureUsersModule = async (moduleInput) => {
384
498
  return null;
385
499
  const updatedUser = await this.setPassword(resetToken.userId, newPassword);
386
500
  if (updatedUser) {
387
- // Now invalidate the reset token
388
501
  await Users.updateOne({
389
502
  _id: resetToken.userId,
390
503
  }, {
@@ -500,8 +613,6 @@ export const configureUsersModule = async (moduleInput) => {
500
613
  }, {});
501
614
  }
502
615
  if (meta) {
503
- // eslint-disable-next-line
504
- // @ts-ignore
505
616
  modifier.$set.meta = meta;
506
617
  }
507
618
  const user = await Users.findOneAndUpdate(userFilter, modifier, {
@@ -561,7 +672,6 @@ export const configureUsersModule = async (moduleInput) => {
561
672
  },
562
673
  };
563
674
  if ((!profile.phoneMobile || isGuest) && lastContact.telNumber) {
564
- // Backport the contact phone number to the user profile
565
675
  modifier.$set['profile.phoneMobile'] = lastContact.telNumber;
566
676
  }
567
677
  const updatedUser = await Users.findOneAndUpdate(userFilter, modifier, {
@@ -652,4 +762,3 @@ export const configureUsersModule = async (moduleInput) => {
652
762
  },
653
763
  };
654
764
  };
655
- //# sourceMappingURL=configureUsersModule.js.map
@@ -1,26 +1,84 @@
1
- import { ModuleInput } from '@unchainedshop/mongodb';
2
- import type { PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions } from 'fido2-lib';
3
- type SerializedOptions<T> = Omit<T, 'challenge' | 'requestId'> & {
1
+ import type { ModuleInput } from '@unchainedshop/mongodb';
2
+ import type { RegistrationJSON, AuthenticationJSON, CredentialInfo, NamedAlgo, ExtendedAuthenticatorTransport } from '@passwordless-id/webauthn/dist/esm/types.js';
3
+ export type { RegistrationJSON, AuthenticationJSON, CredentialInfo, NamedAlgo, ExtendedAuthenticatorTransport, };
4
+ export declare function toArrayBuffer(buffer: Buffer): ArrayBuffer;
5
+ export declare function buf2hex(buffer: ArrayBuffer): string;
6
+ export interface WebAuthnCredentialCreationOptions {
4
7
  challenge: string;
5
8
  requestId: number;
6
- };
7
- export declare function toArrayBuffer(buffer: any): any;
8
- export declare function buf2hex(buffer: any): string;
9
- export declare const configureUsersWebAuthnModule: ({ db }: ModuleInput<any>) => Promise<{
10
- findMDSMetadataForAAGUID: (aaguid: string) => Promise<any>;
11
- createCredentialCreationOptions: (origin: string, username: string, extensionOptions?: any) => Promise<SerializedOptions<PublicKeyCredentialCreationOptions> | null>;
12
- createCredentialRequestOptions: (origin: string, username: string, extensionOptions?: any) => Promise<SerializedOptions<PublicKeyCredentialRequestOptions> | null>;
13
- verifyCredentialCreation: (username: string, credentials: any) => Promise<{
14
- publicKey: any;
15
- counter: any;
16
- id: any;
17
- aaguid: string;
18
- created: Date;
9
+ rp: {
10
+ id: string;
11
+ name: string;
12
+ };
13
+ user: {
14
+ id: string;
15
+ name: string;
16
+ displayName: string;
17
+ };
18
+ pubKeyCredParams: {
19
+ type: 'public-key';
20
+ alg: number;
21
+ }[];
22
+ timeout: number;
23
+ attestation: 'none' | 'indirect' | 'direct';
24
+ authenticatorSelection?: {
25
+ authenticatorAttachment?: 'platform' | 'cross-platform';
26
+ requireResidentKey?: boolean;
27
+ userVerification?: 'required' | 'preferred' | 'discouraged';
28
+ };
29
+ }
30
+ export interface WebAuthnCredentialRequestOptions {
31
+ challenge: string;
32
+ requestId: number;
33
+ rpId: string;
34
+ timeout: number;
35
+ userVerification?: 'required' | 'preferred' | 'discouraged';
36
+ allowCredentials?: {
37
+ id: string;
38
+ type: 'public-key';
39
+ transports?: ('usb' | 'nfc' | 'ble' | 'internal')[];
40
+ }[];
41
+ }
42
+ export interface WebAuthnCredential {
43
+ id: string;
44
+ publicKey: string;
45
+ algorithm: NamedAlgo;
46
+ aaguid: string;
47
+ counter: number;
48
+ created: Date;
49
+ }
50
+ export declare const configureUsersWebAuthnModule: ({ db }: ModuleInput<Record<string, any>>) => Promise<{
51
+ findMDSMetadataForAAGUID: (aaguid: string) => Promise<{
52
+ [key: string]: unknown;
53
+ description?: string;
54
+ icon?: string;
55
+ authenticatorGetInfo?: {
56
+ versions?: string[];
57
+ extensions?: string[];
58
+ options?: Record<string, boolean>;
59
+ };
19
60
  } | null>;
20
- verifyCredentialRequest: (userPublicKeys: any[], username: string, credentials: any) => Promise<{
21
- userHandle: any;
61
+ createCredentialCreationOptions: (origin: string, username: string, extensionOptions?: {
62
+ timeout?: number;
63
+ authenticatorSelection?: WebAuthnCredentialCreationOptions["authenticatorSelection"];
64
+ }) => Promise<WebAuthnCredentialCreationOptions>;
65
+ createCredentialRequestOptions: (origin: string, username: string, extensionOptions?: {
66
+ timeout?: number;
67
+ userVerification?: "required" | "preferred" | "discouraged";
68
+ allowCredentials?: WebAuthnCredentialRequestOptions["allowCredentials"];
69
+ }) => Promise<WebAuthnCredentialRequestOptions>;
70
+ verifyCredentialCreation: (username: string, credentials: RegistrationJSON) => Promise<WebAuthnCredential | null>;
71
+ verifyCredentialRequest: (userPublicKeys: {
72
+ id: string;
73
+ publicKey: string;
74
+ algorithm?: NamedAlgo;
75
+ counter?: number;
76
+ transports?: ExtendedAuthenticatorTransport[];
77
+ }[], username: string, credentials: AuthenticationJSON & {
78
+ requestId: number;
79
+ }) => Promise<{
80
+ userHandle: string;
81
+ counter: number;
22
82
  } | null>;
23
83
  deleteUserWebAuthnCredentials: (username: string) => Promise<number>;
24
84
  }>;
25
- export {};
26
- //# sourceMappingURL=configureUsersWebAuthnModule.d.ts.map