@brightchain/node-express-suite 0.27.0 → 0.28.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 (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +6 -4
  3. package/src/index.d.ts +8 -0
  4. package/src/index.d.ts.map +1 -1
  5. package/src/index.js +13 -1
  6. package/src/index.js.map +1 -1
  7. package/src/lib/controllers/user.d.ts +54 -0
  8. package/src/lib/controllers/user.d.ts.map +1 -0
  9. package/src/lib/controllers/user.js +703 -0
  10. package/src/lib/controllers/user.js.map +1 -0
  11. package/src/lib/enumerations/schema-collection.d.ts +12 -0
  12. package/src/lib/enumerations/schema-collection.d.ts.map +1 -0
  13. package/src/lib/enumerations/schema-collection.js +16 -0
  14. package/src/lib/enumerations/schema-collection.js.map +1 -0
  15. package/src/lib/interfaces/auth-credentials.d.ts +6 -0
  16. package/src/lib/interfaces/auth-credentials.d.ts.map +1 -0
  17. package/src/lib/interfaces/auth-credentials.js +3 -0
  18. package/src/lib/interfaces/auth-credentials.js.map +1 -0
  19. package/src/lib/interfaces/auth-token.d.ts +6 -0
  20. package/src/lib/interfaces/auth-token.d.ts.map +1 -0
  21. package/src/lib/interfaces/auth-token.js +3 -0
  22. package/src/lib/interfaces/auth-token.js.map +1 -0
  23. package/src/lib/interfaces/responses/index.d.ts +2 -0
  24. package/src/lib/interfaces/responses/index.d.ts.map +1 -0
  25. package/src/lib/interfaces/responses/index.js +3 -0
  26. package/src/lib/interfaces/responses/index.js.map +1 -0
  27. package/src/lib/interfaces/responses/user-api-responses.d.ts +37 -0
  28. package/src/lib/interfaces/responses/user-api-responses.d.ts.map +1 -0
  29. package/src/lib/interfaces/responses/user-api-responses.js +9 -0
  30. package/src/lib/interfaces/responses/user-api-responses.js.map +1 -0
  31. package/src/lib/interfaces/token-payload.d.ts +9 -0
  32. package/src/lib/interfaces/token-payload.d.ts.map +1 -0
  33. package/src/lib/interfaces/token-payload.js +3 -0
  34. package/src/lib/interfaces/token-payload.js.map +1 -0
  35. package/src/lib/routers/api.d.ts +34 -0
  36. package/src/lib/routers/api.d.ts.map +1 -0
  37. package/src/lib/routers/api.js +42 -0
  38. package/src/lib/routers/api.js.map +1 -0
  39. package/src/lib/services/auth.d.ts +49 -0
  40. package/src/lib/services/auth.d.ts.map +1 -0
  41. package/src/lib/services/auth.js +278 -0
  42. package/src/lib/services/auth.js.map +1 -0
  43. package/src/lib/services/bright-db-authentication-provider.d.ts +13 -1
  44. package/src/lib/services/bright-db-authentication-provider.d.ts.map +1 -1
  45. package/src/lib/services/bright-db-authentication-provider.js +15 -0
  46. package/src/lib/services/bright-db-authentication-provider.js.map +1 -1
  47. package/src/lib/services/dev-store-seeder.js +1 -1
  48. package/src/lib/services/dev-store-seeder.js.map +1 -1
  49. package/src/lib/validation/userValidation.d.ts.map +1 -1
  50. package/src/lib/validation/userValidation.js +21 -0
  51. package/src/lib/validation/userValidation.js.map +1 -1
@@ -0,0 +1,703 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Base UserController for BrightDB-backed applications.
4
+ *
5
+ * Provides core user authentication routes: register, login, verify,
6
+ * request-direct-login, profile, settings, logout, change-password,
7
+ * recover, and refresh-token.
8
+ *
9
+ * Domain-specific extensions (e.g. BrightHub profile creation, backup codes,
10
+ * direct-challenge verification, energy account in profile) are added by
11
+ * subclasses in consuming libraries like brightchain-api-lib.
12
+ *
13
+ * @module controllers/user
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.BrightDbUserController = void 0;
17
+ const tslib_1 = require("tslib");
18
+ const brightchain_lib_1 = require("@brightchain/brightchain-lib");
19
+ const ecies_lib_1 = require("@digitaldefiance/ecies-lib");
20
+ const i18n_lib_1 = require("@digitaldefiance/i18n-lib");
21
+ const node_express_suite_1 = require("@digitaldefiance/node-express-suite");
22
+ const suite_core_lib_1 = require("@digitaldefiance/suite-core-lib");
23
+ const crypto_1 = require("crypto");
24
+ const userValidation_1 = require("../validation/userValidation");
25
+ /** Extract the member ID from req.user, preferring memberId over id. */
26
+ function getUserId(user) {
27
+ const id = user.memberId ?? user.id;
28
+ if (!id)
29
+ throw new Error('No member ID on request user');
30
+ return id;
31
+ }
32
+ let BrightDbUserController = class BrightDbUserController extends node_express_suite_1.DecoratorBaseController {
33
+ constructor(application) {
34
+ super(application);
35
+ }
36
+ async register(req, _res, _next) {
37
+ const validation = (0, userValidation_1.validateRegistration)(req.body);
38
+ if (!validation.valid) {
39
+ return {
40
+ statusCode: 400,
41
+ response: {
42
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_MissingValidatedData),
43
+ errors: validation.errors,
44
+ },
45
+ };
46
+ }
47
+ try {
48
+ const { username, email, password, mnemonic } = req.body;
49
+ const authService = this.application.services.get('auth');
50
+ const result = await authService.register(username, email, new ecies_lib_1.SecureString(password), mnemonic ? new ecies_lib_1.SecureString(mnemonic) : undefined);
51
+ // Hook for subclasses to perform post-registration actions
52
+ // (e.g. creating BrightHub social profiles)
53
+ await this.onPostRegister(result.memberId, username);
54
+ const authResponse = {
55
+ token: result.token,
56
+ memberId: result.memberId,
57
+ energyBalance: result.energyBalance,
58
+ };
59
+ return {
60
+ statusCode: 201,
61
+ response: {
62
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Registration_Success),
63
+ data: authResponse,
64
+ },
65
+ };
66
+ }
67
+ catch (error) {
68
+ const errorMessage = error instanceof Error
69
+ ? error.message
70
+ : (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError);
71
+ return {
72
+ statusCode: 400,
73
+ response: {
74
+ message: errorMessage,
75
+ error: errorMessage,
76
+ },
77
+ };
78
+ }
79
+ }
80
+ /**
81
+ * Hook called after successful registration.
82
+ * Override in subclasses to create social profiles, etc.
83
+ */
84
+ async onPostRegister(_memberId, _username) {
85
+ // No-op in base class
86
+ }
87
+ async login(req, _res, _next) {
88
+ const validation = (0, userValidation_1.validateLogin)(req.body);
89
+ if (!validation.valid) {
90
+ return {
91
+ statusCode: 400,
92
+ response: {
93
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_MissingValidatedData),
94
+ errors: validation.errors,
95
+ },
96
+ };
97
+ }
98
+ try {
99
+ const { username, password } = req.body;
100
+ const authService = this.application.services.get('auth');
101
+ const result = await authService.login({
102
+ username,
103
+ password: new ecies_lib_1.SecureString(password),
104
+ });
105
+ const authResponse = {
106
+ token: result.token,
107
+ memberId: result.memberId,
108
+ energyBalance: result.energyBalance,
109
+ };
110
+ return {
111
+ statusCode: 200,
112
+ response: {
113
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.LoggedIn_Success),
114
+ data: authResponse,
115
+ },
116
+ };
117
+ }
118
+ catch {
119
+ return {
120
+ statusCode: 401,
121
+ response: {
122
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
123
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
124
+ },
125
+ };
126
+ }
127
+ }
128
+ async getProfile(req, _res, _next) {
129
+ const user = req.user;
130
+ if (!user) {
131
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
132
+ }
133
+ try {
134
+ const memberId = getUserId(user);
135
+ const sp = brightchain_lib_1.ServiceProvider.getInstance();
136
+ const typedId = sp.idProvider.idFromString(memberId);
137
+ const idRawBytes = sp.idProvider.toBytes(typedId);
138
+ const memberChecksum = sp.checksumService.calculateChecksum(idRawBytes);
139
+ const energyStore = this.application.services.get('energyStore');
140
+ const energyAccount = await energyStore.getOrCreate(memberChecksum);
141
+ let email = '';
142
+ const memberStore = this.application.services.get('memberStore');
143
+ try {
144
+ if (memberStore) {
145
+ const member = await memberStore.getMember(typedId);
146
+ email = member.email.toString();
147
+ }
148
+ }
149
+ catch {
150
+ // Member lookup failed, continue with empty email
151
+ }
152
+ let memberProfile;
153
+ try {
154
+ if (memberStore) {
155
+ const profile = await memberStore.getMemberProfile(typedId);
156
+ if (profile.publicProfile) {
157
+ memberProfile = {
158
+ status: profile.publicProfile.status,
159
+ storageQuota: profile.publicProfile.storageQuota?.toString(),
160
+ storageUsed: profile.publicProfile.storageUsed?.toString(),
161
+ lastActive: profile.publicProfile.lastActive?.toISOString(),
162
+ dateCreated: profile.publicProfile.dateCreated?.toISOString(),
163
+ };
164
+ }
165
+ }
166
+ }
167
+ catch {
168
+ // MemberStore profile not available
169
+ }
170
+ const userProfile = {
171
+ memberId,
172
+ username: user.username,
173
+ email,
174
+ energyBalance: energyAccount.balance,
175
+ availableBalance: energyAccount.availableBalance,
176
+ earned: energyAccount.earned,
177
+ spent: energyAccount.spent,
178
+ reserved: energyAccount.reserved,
179
+ reputation: energyAccount.reputation,
180
+ createdAt: energyAccount.createdAt.toISOString(),
181
+ lastUpdated: energyAccount.lastUpdated.toISOString(),
182
+ ...(memberProfile && { profile: memberProfile }),
183
+ };
184
+ return {
185
+ statusCode: 200,
186
+ response: {
187
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Settings_RetrievedSuccess),
188
+ data: userProfile,
189
+ },
190
+ };
191
+ }
192
+ catch {
193
+ return {
194
+ statusCode: 500,
195
+ response: {
196
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
197
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
198
+ },
199
+ };
200
+ }
201
+ }
202
+ async updateProfile(req, _res, _next) {
203
+ const user = req.user;
204
+ if (!user) {
205
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
206
+ }
207
+ try {
208
+ const memberId = getUserId(user);
209
+ const updateData = req.body;
210
+ const sp = brightchain_lib_1.ServiceProvider.getInstance();
211
+ const typedId = sp.idProvider.idFromString(memberId);
212
+ const idRawBytes = sp.idProvider.toBytes(typedId);
213
+ const memberChecksum = sp.checksumService.calculateChecksum(idRawBytes);
214
+ const memberStore = this.application.services.get('memberStore');
215
+ if (memberStore && updateData.settings) {
216
+ const completeSettings = {
217
+ autoReplication: updateData.settings.autoReplication ?? true,
218
+ minRedundancy: updateData.settings.minRedundancy ?? 3,
219
+ preferredRegions: updateData.settings.preferredRegions ?? [],
220
+ };
221
+ await memberStore.updateMember(typedId, {
222
+ id: typedId,
223
+ privateChanges: {
224
+ settings: completeSettings,
225
+ },
226
+ });
227
+ }
228
+ const energyStore = this.application.services.get('energyStore');
229
+ const energyAccount = await energyStore.getOrCreate(memberChecksum);
230
+ let email = '';
231
+ try {
232
+ if (memberStore) {
233
+ const member = await memberStore.getMember(typedId);
234
+ email = member.email.toString();
235
+ }
236
+ }
237
+ catch {
238
+ // Member lookup failed
239
+ }
240
+ let memberProfile;
241
+ try {
242
+ if (memberStore) {
243
+ const profile = await memberStore.getMemberProfile(typedId);
244
+ if (profile.publicProfile) {
245
+ memberProfile = {
246
+ status: profile.publicProfile.status,
247
+ storageQuota: profile.publicProfile.storageQuota?.toString(),
248
+ storageUsed: profile.publicProfile.storageUsed?.toString(),
249
+ lastActive: profile.publicProfile.lastActive?.toISOString(),
250
+ dateCreated: profile.publicProfile.dateCreated?.toISOString(),
251
+ };
252
+ }
253
+ }
254
+ }
255
+ catch {
256
+ // MemberStore profile not available
257
+ }
258
+ const userProfile = {
259
+ memberId,
260
+ username: user.username,
261
+ email,
262
+ energyBalance: energyAccount.balance,
263
+ availableBalance: energyAccount.availableBalance,
264
+ earned: energyAccount.earned,
265
+ spent: energyAccount.spent,
266
+ reserved: energyAccount.reserved,
267
+ reputation: energyAccount.reputation,
268
+ createdAt: energyAccount.createdAt.toISOString(),
269
+ lastUpdated: energyAccount.lastUpdated.toISOString(),
270
+ ...(memberProfile && { profile: memberProfile }),
271
+ };
272
+ return {
273
+ statusCode: 200,
274
+ response: {
275
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Settings_SaveSuccess),
276
+ data: userProfile,
277
+ },
278
+ };
279
+ }
280
+ catch {
281
+ return {
282
+ statusCode: 500,
283
+ response: {
284
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
285
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
286
+ },
287
+ };
288
+ }
289
+ }
290
+ async changePassword(req, _res, _next) {
291
+ const validation = (0, userValidation_1.validatePasswordChange)(req.body);
292
+ if (!validation.valid) {
293
+ return {
294
+ statusCode: 400,
295
+ response: {
296
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_MissingValidatedData),
297
+ errors: validation.errors,
298
+ },
299
+ };
300
+ }
301
+ const user = req.user;
302
+ if (!user) {
303
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
304
+ }
305
+ try {
306
+ const memberId = getUserId(user);
307
+ const { currentPassword, newPassword } = req.body;
308
+ const sp = brightchain_lib_1.ServiceProvider.getInstance();
309
+ const typedId = sp.idProvider.idFromString(memberId);
310
+ const authService = this.application.services.get('auth');
311
+ await authService.changePassword(typedId, currentPassword, newPassword);
312
+ return {
313
+ statusCode: 200,
314
+ response: {
315
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.PasswordChange_Success),
316
+ data: {
317
+ memberId,
318
+ success: true,
319
+ },
320
+ },
321
+ };
322
+ }
323
+ catch (error) {
324
+ const errorMessage = error instanceof Error
325
+ ? error.message
326
+ : (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Error_PasswordChange);
327
+ if (errorMessage === 'Invalid credentials') {
328
+ return {
329
+ statusCode: 401,
330
+ response: {
331
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
332
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
333
+ },
334
+ };
335
+ }
336
+ return {
337
+ statusCode: 500,
338
+ response: {
339
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Error_PasswordChange),
340
+ error: errorMessage,
341
+ },
342
+ };
343
+ }
344
+ }
345
+ async recover(req, _res, _next) {
346
+ const validation = (0, userValidation_1.validateRecovery)(req.body);
347
+ if (!validation.valid) {
348
+ return {
349
+ statusCode: 400,
350
+ response: {
351
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_MissingValidatedData),
352
+ errors: validation.errors,
353
+ },
354
+ };
355
+ }
356
+ try {
357
+ const { email, mnemonic, newPassword } = req.body;
358
+ const authService = this.application.services.get('auth');
359
+ const result = await authService.recoverWithMnemonic(email, new ecies_lib_1.SecureString(mnemonic), newPassword);
360
+ return {
361
+ statusCode: 200,
362
+ response: {
363
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.MnemonicRecovery_Success),
364
+ data: result,
365
+ },
366
+ };
367
+ }
368
+ catch (error) {
369
+ const errorMessage = error instanceof Error
370
+ ? error.message
371
+ : (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError);
372
+ if (errorMessage === 'Invalid credentials' ||
373
+ errorMessage === 'Invalid mnemonic') {
374
+ return {
375
+ statusCode: 401,
376
+ response: {
377
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
378
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_InvalidCredentials),
379
+ },
380
+ };
381
+ }
382
+ return {
383
+ statusCode: 500,
384
+ response: {
385
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
386
+ error: errorMessage,
387
+ },
388
+ };
389
+ }
390
+ }
391
+ async logout(req, _res, _next) {
392
+ const user = req.user;
393
+ if (!user) {
394
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
395
+ }
396
+ try {
397
+ const authHeader = String(req.headers
398
+ ?.authorization ?? '');
399
+ if (!authHeader.startsWith('Bearer ')) {
400
+ return {
401
+ statusCode: 401,
402
+ response: {
403
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_TokenMissing),
404
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_TokenMissing),
405
+ },
406
+ };
407
+ }
408
+ const token = authHeader.slice('Bearer '.length);
409
+ const sessionAdapter = this.application.services.get('sessionAdapter');
410
+ const session = await sessionAdapter.validateToken(token);
411
+ if (session) {
412
+ await sessionAdapter.deleteSession(session.sessionId);
413
+ }
414
+ return {
415
+ statusCode: 200,
416
+ response: {
417
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_Success),
418
+ },
419
+ };
420
+ }
421
+ catch {
422
+ return {
423
+ statusCode: 500,
424
+ response: {
425
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
426
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
427
+ },
428
+ };
429
+ }
430
+ }
431
+ async requestDirectLogin(_req, _res, _next) {
432
+ const systemUser = node_express_suite_1.SystemUserService.getSystemUser(
433
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
434
+ this.application.environment, this.application.constants);
435
+ const time = Buffer.alloc(8);
436
+ time.writeBigUInt64BE(BigInt(new Date().getTime()));
437
+ const nonce = (0, crypto_1.randomBytes)(32);
438
+ const signature = systemUser.sign(Buffer.concat([time, nonce]));
439
+ const challenge = Buffer.concat([time, nonce, signature]).toString('hex');
440
+ return {
441
+ statusCode: 200,
442
+ response: {
443
+ challenge,
444
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Login_ChallengeGenerated),
445
+ serverPublicKey: this.application.environment.systemPublicKeyHex ?? '',
446
+ },
447
+ };
448
+ }
449
+ /**
450
+ * GET /verify — returns the authenticated user's DTO.
451
+ * The auth middleware already populates req.user with a full IRequestUserDTO
452
+ * via buildRequestUserDTO, so we just return it.
453
+ */
454
+ async verify(req, _res, _next) {
455
+ if (!req.user) {
456
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
457
+ }
458
+ const user = req.user;
459
+ return {
460
+ statusCode: 200,
461
+ response: {
462
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_TokenValid),
463
+ user: {
464
+ id: user.id,
465
+ email: user.email,
466
+ username: user.username,
467
+ roles: user.roles || [],
468
+ rolePrivileges: user.rolePrivileges,
469
+ timezone: user.timezone,
470
+ currency: user.currency,
471
+ emailVerified: user.emailVerified,
472
+ darkMode: user.darkMode,
473
+ siteLanguage: user.siteLanguage,
474
+ directChallenge: user.directChallenge,
475
+ ...(user.lastLogin && { lastLogin: user.lastLogin }),
476
+ },
477
+ },
478
+ };
479
+ }
480
+ /**
481
+ * GET /settings — returns the authenticated user's settings.
482
+ */
483
+ async getSettings(req, _res, _next) {
484
+ if (!req.user) {
485
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
486
+ }
487
+ const user = req.user;
488
+ return {
489
+ statusCode: 200,
490
+ response: {
491
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Settings_RetrievedSuccess),
492
+ settings: {
493
+ email: user.email || '',
494
+ timezone: user.timezone || '',
495
+ currency: user.currency || '',
496
+ siteLanguage: user.siteLanguage || '',
497
+ darkMode: user.darkMode || false,
498
+ directChallenge: user.directChallenge || false,
499
+ },
500
+ },
501
+ };
502
+ }
503
+ /**
504
+ * POST /settings — updates the authenticated user's settings.
505
+ */
506
+ async updateSettings(req, _res, _next) {
507
+ if (!req.user) {
508
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
509
+ }
510
+ try {
511
+ const user = req.user;
512
+ const memberId = user.id;
513
+ const { email, timezone, siteLanguage, currency, darkMode, directChallenge, } = req.body;
514
+ const sp = brightchain_lib_1.ServiceProvider.getInstance();
515
+ const typedId = sp.idProvider.idFromString(memberId);
516
+ const idHex = sp.idProvider.toString(typedId, 'hex');
517
+ // Update user-facing settings (timezone, darkMode, etc.) in the DB
518
+ // users collection. These are NOT part of the member store's
519
+ // IPrivateMemberData.settings (which tracks replication/storage config).
520
+ try {
521
+ const db = this.application.services.get('db');
522
+ if (db) {
523
+ const usersCol = db.collection('user_settings');
524
+ const updateFields = {};
525
+ if (email !== undefined)
526
+ updateFields['email'] = email;
527
+ if (timezone !== undefined)
528
+ updateFields['timezone'] = timezone;
529
+ if (siteLanguage !== undefined)
530
+ updateFields['siteLanguage'] = siteLanguage;
531
+ if (currency !== undefined)
532
+ updateFields['currency'] = currency;
533
+ if (darkMode !== undefined)
534
+ updateFields['darkMode'] = darkMode;
535
+ if (directChallenge !== undefined)
536
+ updateFields['directChallenge'] = directChallenge;
537
+ if (Object.keys(updateFields).length > 0) {
538
+ await usersCol.updateOne({ _id: idHex }, { $set: updateFields }, { upsert: true });
539
+ }
540
+ }
541
+ }
542
+ catch {
543
+ // DB update is best-effort
544
+ }
545
+ // Build updated user DTO
546
+ const updatedUser = {
547
+ ...user,
548
+ ...(email !== undefined && { email }),
549
+ ...(timezone !== undefined && { timezone }),
550
+ ...(siteLanguage !== undefined && { siteLanguage }),
551
+ ...(currency !== undefined && { currency }),
552
+ ...(darkMode !== undefined && { darkMode }),
553
+ ...(directChallenge !== undefined && { directChallenge }),
554
+ };
555
+ return {
556
+ statusCode: 200,
557
+ response: {
558
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Settings_SaveSuccess),
559
+ user: updatedUser,
560
+ },
561
+ };
562
+ }
563
+ catch {
564
+ return {
565
+ statusCode: 500,
566
+ response: {
567
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
568
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
569
+ },
570
+ };
571
+ }
572
+ }
573
+ /**
574
+ * GET /refresh-token — re-signs the JWT and returns a new token + user DTO.
575
+ */
576
+ async refreshToken(req, _res, _next) {
577
+ if (!req.user) {
578
+ throw new i18n_lib_1.HandleableError(new Error((0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_NoUserOnRequest)), { statusCode: 401 });
579
+ }
580
+ try {
581
+ const user = req.user;
582
+ const authService = this.application.services.get('auth');
583
+ // Verify the current token is still valid
584
+ const authHeader = String(req.headers
585
+ ?.authorization ?? '');
586
+ if (!authHeader.startsWith('Bearer ')) {
587
+ return {
588
+ statusCode: 401,
589
+ response: {
590
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_TokenMissing),
591
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Validation_TokenMissing),
592
+ },
593
+ };
594
+ }
595
+ const oldToken = authHeader.slice('Bearer '.length);
596
+ const decoded = await authService.verifyToken(oldToken);
597
+ // Re-sign with fresh expiry
598
+ const newToken = authService.signToken(decoded.memberId, decoded.username, decoded.type);
599
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
600
+ const env = this.application.environment;
601
+ const serverPublicKey = env.systemPublicKeyHex ?? '';
602
+ return {
603
+ statusCode: 200,
604
+ response: {
605
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_Success),
606
+ user,
607
+ token: newToken,
608
+ serverPublicKey,
609
+ },
610
+ headers: {
611
+ Authorization: `Bearer ${newToken}`,
612
+ },
613
+ };
614
+ }
615
+ catch {
616
+ return {
617
+ statusCode: 500,
618
+ response: {
619
+ message: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
620
+ error: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_UnexpectedError),
621
+ },
622
+ };
623
+ }
624
+ }
625
+ };
626
+ exports.BrightDbUserController = BrightDbUserController;
627
+ tslib_1.__decorate([
628
+ (0, node_express_suite_1.Post)('/register'),
629
+ tslib_1.__metadata("design:type", Function),
630
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
631
+ tslib_1.__metadata("design:returntype", Promise)
632
+ ], BrightDbUserController.prototype, "register", null);
633
+ tslib_1.__decorate([
634
+ (0, node_express_suite_1.Post)('/login'),
635
+ tslib_1.__metadata("design:type", Function),
636
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
637
+ tslib_1.__metadata("design:returntype", Promise)
638
+ ], BrightDbUserController.prototype, "login", null);
639
+ tslib_1.__decorate([
640
+ (0, node_express_suite_1.Get)('/profile', { auth: true }),
641
+ tslib_1.__metadata("design:type", Function),
642
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
643
+ tslib_1.__metadata("design:returntype", Promise)
644
+ ], BrightDbUserController.prototype, "getProfile", null);
645
+ tslib_1.__decorate([
646
+ (0, node_express_suite_1.Put)('/profile', { auth: true }),
647
+ tslib_1.__metadata("design:type", Function),
648
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
649
+ tslib_1.__metadata("design:returntype", Promise)
650
+ ], BrightDbUserController.prototype, "updateProfile", null);
651
+ tslib_1.__decorate([
652
+ (0, node_express_suite_1.Post)('/change-password', { auth: true }),
653
+ tslib_1.__metadata("design:type", Function),
654
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
655
+ tslib_1.__metadata("design:returntype", Promise)
656
+ ], BrightDbUserController.prototype, "changePassword", null);
657
+ tslib_1.__decorate([
658
+ (0, node_express_suite_1.Post)('/recover'),
659
+ tslib_1.__metadata("design:type", Function),
660
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
661
+ tslib_1.__metadata("design:returntype", Promise)
662
+ ], BrightDbUserController.prototype, "recover", null);
663
+ tslib_1.__decorate([
664
+ (0, node_express_suite_1.Post)('/logout', { auth: true }),
665
+ tslib_1.__metadata("design:type", Function),
666
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
667
+ tslib_1.__metadata("design:returntype", Promise)
668
+ ], BrightDbUserController.prototype, "logout", null);
669
+ tslib_1.__decorate([
670
+ (0, node_express_suite_1.Post)('/request-direct-login'),
671
+ tslib_1.__metadata("design:type", Function),
672
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
673
+ tslib_1.__metadata("design:returntype", Promise)
674
+ ], BrightDbUserController.prototype, "requestDirectLogin", null);
675
+ tslib_1.__decorate([
676
+ (0, node_express_suite_1.Get)('/verify', { auth: true }),
677
+ tslib_1.__metadata("design:type", Function),
678
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
679
+ tslib_1.__metadata("design:returntype", Promise)
680
+ ], BrightDbUserController.prototype, "verify", null);
681
+ tslib_1.__decorate([
682
+ (0, node_express_suite_1.Get)('/settings', { auth: true }),
683
+ tslib_1.__metadata("design:type", Function),
684
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
685
+ tslib_1.__metadata("design:returntype", Promise)
686
+ ], BrightDbUserController.prototype, "getSettings", null);
687
+ tslib_1.__decorate([
688
+ (0, node_express_suite_1.Post)('/settings', { auth: true }),
689
+ tslib_1.__metadata("design:type", Function),
690
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
691
+ tslib_1.__metadata("design:returntype", Promise)
692
+ ], BrightDbUserController.prototype, "updateSettings", null);
693
+ tslib_1.__decorate([
694
+ (0, node_express_suite_1.Get)('/refresh-token', { auth: true }),
695
+ tslib_1.__metadata("design:type", Function),
696
+ tslib_1.__metadata("design:paramtypes", [Object, Object, Function]),
697
+ tslib_1.__metadata("design:returntype", Promise)
698
+ ], BrightDbUserController.prototype, "refreshToken", null);
699
+ exports.BrightDbUserController = BrightDbUserController = tslib_1.__decorate([
700
+ (0, node_express_suite_1.Controller)(),
701
+ tslib_1.__metadata("design:paramtypes", [Object])
702
+ ], BrightDbUserController);
703
+ //# sourceMappingURL=user.js.map