@unchainedshop/core-users 5.0.0-alpha.1 → 5.0.0-alpha.11

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 (44) hide show
  1. package/README.md +9 -130
  2. package/lib/db/UsersCollection.js +51 -29
  3. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.d.ts +1 -0
  4. package/lib/db/WebAuthnCredentialsCreationRequestsCollection.js +2 -5
  5. package/lib/migrations/20260625120200-normalize-profile-phone.d.ts +2 -0
  6. package/lib/migrations/20260625120200-normalize-profile-phone.js +52 -0
  7. package/lib/module/configureUsersModule.d.ts +17 -0
  8. package/lib/module/configureUsersModule.js +119 -45
  9. package/lib/module/configureUsersWebAuthnModule.js +3 -1
  10. package/lib/users-settings.d.ts +1 -0
  11. package/lib/users-settings.js +4 -1
  12. package/package.json +10 -8
  13. package/lib/domain/model/Email.d.ts +0 -17
  14. package/lib/domain/model/Email.js +0 -21
  15. package/lib/domain/model/PasswordHash.d.ts +0 -16
  16. package/lib/domain/model/PasswordHash.js +0 -31
  17. package/lib/domain/model/PushSubscription.d.ts +0 -23
  18. package/lib/domain/model/PushSubscription.js +0 -25
  19. package/lib/domain/model/User.d.ts +0 -116
  20. package/lib/domain/model/User.js +0 -386
  21. package/lib/domain/model/UserProfile.d.ts +0 -14
  22. package/lib/domain/model/UserProfile.js +0 -29
  23. package/lib/domain/model/Web3Address.d.ts +0 -16
  24. package/lib/domain/model/Web3Address.js +0 -32
  25. package/lib/domain/model/WebAuthnCredential.d.ts +0 -22
  26. package/lib/domain/model/WebAuthnCredential.js +0 -28
  27. package/lib/domain/model/index.d.ts +0 -14
  28. package/lib/domain/model/index.js +0 -7
  29. package/lib/domain/repository/UserRepository.d.ts +0 -48
  30. package/lib/domain/repository/UserRepository.js +0 -1
  31. package/lib/domain/services/PasswordService.d.ts +0 -5
  32. package/lib/domain/services/PasswordService.js +0 -1
  33. package/lib/domain/services/TokenService.d.ts +0 -8
  34. package/lib/domain/services/TokenService.js +0 -1
  35. package/lib/domain/types.d.ts +0 -28
  36. package/lib/domain/types.js +0 -1
  37. package/lib/infrastructure/persistence/MongoUserRepository.d.ts +0 -4
  38. package/lib/infrastructure/persistence/MongoUserRepository.js +0 -159
  39. package/lib/infrastructure/persistence/buildUserSelector.d.ts +0 -4
  40. package/lib/infrastructure/persistence/buildUserSelector.js +0 -54
  41. package/lib/infrastructure/services/CryptoTokenService.d.ts +0 -2
  42. package/lib/infrastructure/services/CryptoTokenService.js +0 -16
  43. package/lib/infrastructure/services/Pbkdf2PasswordService.d.ts +0 -2
  44. package/lib/infrastructure/services/Pbkdf2PasswordService.js +0 -23
@@ -1,159 +0,0 @@
1
- import { generateDbFilterById, buildSortOptions, insensitiveTrimmedRegexOperator, } from '@unchainedshop/mongodb';
2
- import { SortDirection } from '@unchainedshop/utils';
3
- import { reconstituteUser } from "../../domain/model/User.js";
4
- import { buildUserSelector } from "./buildUserSelector.js";
5
- export const createMongoUserRepository = (collection, db) => {
6
- const buildFindOptions = (options) => {
7
- const findOptions = {};
8
- if (options?.offset) {
9
- findOptions.skip = options.offset;
10
- }
11
- if (options?.limit) {
12
- findOptions.limit = options.limit;
13
- }
14
- if (options?.sort) {
15
- findOptions.sort = buildSortOptions(options.sort);
16
- }
17
- return findOptions;
18
- };
19
- return {
20
- async findById(id) {
21
- if (!id)
22
- return null;
23
- const data = await collection.findOne(generateDbFilterById(id));
24
- return data ? reconstituteUser(data) : null;
25
- },
26
- async findByUsername(username) {
27
- if (!username)
28
- return null;
29
- const data = await collection.findOne({
30
- username: insensitiveTrimmedRegexOperator(username),
31
- });
32
- return data ? reconstituteUser(data) : null;
33
- },
34
- async findByEmail(email) {
35
- if (!email)
36
- return null;
37
- const data = await collection.findOne({
38
- 'emails.address': insensitiveTrimmedRegexOperator(email),
39
- });
40
- return data ? reconstituteUser(data) : null;
41
- },
42
- async findByToken(hashedToken) {
43
- if (!hashedToken)
44
- return null;
45
- const data = await collection.findOne({
46
- 'services.token.secret': hashedToken,
47
- });
48
- return data ? reconstituteUser(data) : null;
49
- },
50
- async findOne(query, options) {
51
- const selector = buildUserSelector(query);
52
- const findOptions = buildFindOptions(options);
53
- const data = await collection.findOne(selector, findOptions);
54
- return data ? reconstituteUser(data) : null;
55
- },
56
- async findMany(query, options) {
57
- const selector = buildUserSelector(query);
58
- const defaultSort = [{ key: 'created', value: SortDirection.ASC }];
59
- const findOptions = buildFindOptions(options);
60
- if (query.queryString) {
61
- const docs = await collection
62
- .find(selector, {
63
- ...findOptions,
64
- projection: { score: { $meta: 'textScore' } },
65
- sort: { score: { $meta: 'textScore' } },
66
- })
67
- .toArray();
68
- return docs.map(reconstituteUser);
69
- }
70
- if (!findOptions.sort) {
71
- findOptions.sort = buildSortOptions(defaultSort);
72
- }
73
- const docs = await collection.find(selector, findOptions).toArray();
74
- return docs.map(reconstituteUser);
75
- },
76
- async count(query) {
77
- const selector = buildUserSelector(query);
78
- return collection.countDocuments(selector);
79
- },
80
- async exists(userId) {
81
- const count = await collection.countDocuments({ _id: userId, deleted: null }, { limit: 1 });
82
- return count === 1;
83
- },
84
- async findByVerificationToken(hashedToken, validAfter) {
85
- if (!hashedToken)
86
- return null;
87
- const data = await collection.findOne({
88
- 'services.email.verificationTokens': {
89
- $elemMatch: {
90
- token: hashedToken,
91
- when: { $gt: validAfter },
92
- },
93
- },
94
- });
95
- if (!data)
96
- return null;
97
- const verificationToken = data.services?.email?.verificationTokens?.find((v) => v.token === hashedToken);
98
- if (!verificationToken)
99
- return null;
100
- return {
101
- userId: data._id,
102
- address: verificationToken.address,
103
- when: verificationToken.when,
104
- token: verificationToken.token,
105
- };
106
- },
107
- async findByResetToken(hashedToken, validAfter) {
108
- if (!hashedToken)
109
- return null;
110
- const data = await collection.findOne({
111
- 'services.password.reset': {
112
- $elemMatch: {
113
- token: hashedToken,
114
- when: { $gt: validAfter },
115
- },
116
- },
117
- });
118
- if (!data)
119
- return null;
120
- const passwordService = data.services?.password;
121
- const resetToken = passwordService?.reset?.find((v) => v.token === hashedToken);
122
- if (!resetToken)
123
- return null;
124
- return {
125
- userId: data._id,
126
- address: resetToken.address,
127
- when: resetToken.when,
128
- token: resetToken.token,
129
- };
130
- },
131
- async save(user) {
132
- const data = user.toData();
133
- await collection.replaceOne({ _id: data._id }, data, { upsert: true });
134
- user.clearChanges();
135
- },
136
- async insert(user) {
137
- const data = user.toData();
138
- await collection.insertOne(data);
139
- user.clearChanges();
140
- return data._id;
141
- },
142
- async delete(userId) {
143
- const result = await collection.deleteOne({ _id: userId });
144
- return result.deletedCount > 0;
145
- },
146
- async deleteUserSessions(userId) {
147
- await db.collection('sessions').deleteMany({
148
- session: insensitiveTrimmedRegexOperator(`"user":"${userId}"`),
149
- });
150
- },
151
- async distinctTags() {
152
- const tags = (await collection.distinct('tags', {
153
- tags: { $exists: true },
154
- deleted: null,
155
- }));
156
- return tags.filter(Boolean).sort();
157
- },
158
- };
159
- };
@@ -1,4 +0,0 @@
1
- import { type mongodb } from '@unchainedshop/mongodb';
2
- import type { UserQuery } from '../../domain/repository/UserRepository.ts';
3
- import type { UserData } from '../../domain/model/User.ts';
4
- export declare const buildUserSelector: ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, userIds, username, usernames, emails, web3Verified, }: UserQuery) => mongodb.Filter<UserData>;
@@ -1,54 +0,0 @@
1
- import { insensitiveTrimmedRegexOperator, assertDocumentDBCompatMode, } from '@unchainedshop/mongodb';
2
- export const buildUserSelector = ({ includeGuests, includeDeleted, queryString, emailVerified, lastLogin, tags, userIds, username, usernames, emails, web3Verified, }) => {
3
- const selector = {};
4
- if (!includeDeleted) {
5
- selector.deleted = null;
6
- }
7
- if (!includeGuests) {
8
- selector.guest = { $ne: true };
9
- }
10
- if (userIds) {
11
- selector._id = { $in: userIds };
12
- }
13
- if (username) {
14
- selector.username = insensitiveTrimmedRegexOperator(username);
15
- }
16
- if (usernames?.length) {
17
- selector.username = {
18
- $in: usernames.map((u) => insensitiveTrimmedRegexOperator(u)),
19
- };
20
- }
21
- if (emails?.length) {
22
- selector['emails.address'] = {
23
- $in: emails.map((e) => insensitiveTrimmedRegexOperator(e)),
24
- };
25
- }
26
- if (emailVerified === true) {
27
- selector['emails.verified'] = true;
28
- }
29
- if (emailVerified === false) {
30
- selector['emails.verified'] = { $ne: true };
31
- }
32
- if (Array.isArray(tags) && tags.length) {
33
- selector.tags = { $in: tags };
34
- }
35
- if (web3Verified === true) {
36
- selector['services.web3.verified'] = true;
37
- }
38
- if (lastLogin?.start || lastLogin?.end) {
39
- const timestampFilter = { $exists: true };
40
- if (lastLogin.end) {
41
- timestampFilter.$lte = typeof lastLogin.end === 'string' ? new Date(lastLogin.end) : lastLogin.end;
42
- }
43
- if (lastLogin.start) {
44
- timestampFilter.$gte =
45
- typeof lastLogin.start === 'string' ? new Date(lastLogin.start) : lastLogin.start;
46
- }
47
- selector['lastLogin.timestamp'] = timestampFilter;
48
- }
49
- if (queryString) {
50
- assertDocumentDBCompatMode();
51
- selector.$text = { $search: queryString };
52
- }
53
- return selector;
54
- };
@@ -1,2 +0,0 @@
1
- import type { TokenService } from '../../domain/services/TokenService.ts';
2
- export declare const createCryptoTokenService: () => TokenService;
@@ -1,16 +0,0 @@
1
- import { sha256 } from '@unchainedshop/utils';
2
- export const createCryptoTokenService = () => {
3
- return {
4
- generateToken() {
5
- return crypto.randomUUID();
6
- },
7
- async hashToken(plainToken) {
8
- return sha256(plainToken);
9
- },
10
- async generateHashedToken() {
11
- const plain = crypto.randomUUID();
12
- const hashed = await sha256(plain);
13
- return { plain, hashed };
14
- },
15
- };
16
- };
@@ -1,2 +0,0 @@
1
- import type { PasswordService } from '../../domain/services/PasswordService.ts';
2
- export declare const createPbkdf2PasswordService: () => PasswordService;
@@ -1,23 +0,0 @@
1
- import * as bcrypt from 'bcryptjs';
2
- import { sha256 } from '@unchainedshop/utils';
3
- import * as pbkdf2 from "../../module/pbkdf2.js";
4
- export const createPbkdf2PasswordService = () => {
5
- return {
6
- async hash(plainPassword) {
7
- const salt = pbkdf2.generateSalt();
8
- const hashedPassword = await pbkdf2.getDerivedKey(salt, plainPassword);
9
- return { pbkdf2: `${salt}:${hashedPassword}` };
10
- },
11
- async verify(hash, plainPassword) {
12
- if (hash.pbkdf2) {
13
- const [pbkdf2Salt, pbkdf2Hash] = hash.pbkdf2.split(':');
14
- return pbkdf2.compare(plainPassword, pbkdf2Hash, pbkdf2Salt);
15
- }
16
- if (hash.bcrypt) {
17
- const password = await sha256(plainPassword);
18
- return bcrypt.compare(password, hash.bcrypt);
19
- }
20
- return false;
21
- },
22
- };
23
- };