ecrs-auth-core 1.0.108 → 1.0.110

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.
@@ -1,6 +1,6 @@
1
- import { JwtService } from '@nestjs/jwt';
2
- import { AuthCoreOptions } from './interfaces/auth-core-options.interface';
3
- import { User } from './entities/user.entity';
1
+ import { JwtService } from "@nestjs/jwt";
2
+ import { AuthCoreOptions } from "./interfaces/auth-core-options.interface";
3
+ import { User } from "./entities/user.entity";
4
4
  export type RoutePermissionSet = {
5
5
  view?: boolean;
6
6
  create?: boolean;
@@ -44,7 +44,7 @@ export declare class AuthService {
44
44
  * Save user last login details
45
45
  * Updates the tbl_user_last_login table with latest login info
46
46
  */
47
- saveLastLogin(user: User, clientIp: string, loginStatus?: 'success' | 'failed' | 'blocked', failureReason?: string, additionalData?: {
47
+ saveLastLogin(user: User, clientIp: string, loginStatus?: "success" | "failed" | "blocked", failureReason?: string, additionalData?: {
48
48
  browser?: string;
49
49
  deviceType?: string;
50
50
  operatingSystem?: string;
@@ -59,7 +59,7 @@ export declare class AuthService {
59
59
  * If a record exists for the user on the same date, update it
60
60
  * Otherwise, create a new record
61
61
  */
62
- saveLoginDetailsJson(user: User, clientIp: string, loginStatus?: 'success' | 'failed' | 'blocked', failureReason?: string, additionalData?: {
62
+ saveLoginDetailsJson(user: User, clientIp: string, loginStatus?: "success" | "failed" | "blocked", failureReason?: string, additionalData?: {
63
63
  browser?: string;
64
64
  deviceType?: string;
65
65
  operatingSystem?: string;
@@ -55,7 +55,7 @@ let AuthService = class AuthService {
55
55
  constructor(jwtService, options) {
56
56
  this.jwtService = jwtService;
57
57
  this.options = options;
58
- this.uploadPhotoDir = 'uploads/organization/photos';
58
+ this.uploadPhotoDir = "uploads/organization/photos";
59
59
  const { repositories } = options;
60
60
  this.userRepo = repositories.userRepo;
61
61
  this.roleRepo = repositories.roleRepo;
@@ -75,12 +75,17 @@ let AuthService = class AuthService {
75
75
  // Guard: reject missing or oversized inputs before any DB/bcrypt work.
76
76
  // Oversized passwords cause bcrypt to hang (DoS); oversized emails are never valid.
77
77
  if (!email || !password || password.length > 72 || email.length > 254) {
78
- throw new common_1.UnauthorizedException('Invalid credentials');
78
+ throw new common_1.UnauthorizedException("Invalid credentials");
79
79
  }
80
80
  // Normalize email for consistent comparison, then use ILike so the DB
81
81
  // matches regardless of how the email was originally stored (mixed/upper/lower).
82
82
  const normalizedEmail = email.trim().toLowerCase();
83
- const whereClause = { email: (0, typeorm_1.ILike)(normalizedEmail), deletedBy: null, deletedAt: null, status: 1 };
83
+ const whereClause = {
84
+ email: (0, typeorm_1.ILike)(normalizedEmail),
85
+ deletedBy: null,
86
+ deletedAt: null,
87
+ status: 1,
88
+ };
84
89
  //role-id
85
90
  //1-"Superadmin" 2-"Admin" 3-"Employee" 5-"CUSTOMER_BOOKER" 6-"SECURITY" 7-"SUPPLIER"
86
91
  //module-id
@@ -95,6 +100,7 @@ let AuthService = class AuthService {
95
100
  else if (moduleId !== undefined && modulearray.includes(moduleId)) {
96
101
  whereClause.roleId = (0, typeorm_1.In)([1, 2, 3]);
97
102
  }
103
+ console.log(`🔍 Validating user with email: ${normalizedEmail}, moduleId: ${moduleId}, whereClause:`, whereClause);
98
104
  const user = await this.userRepo.findOne({ where: whereClause });
99
105
  // Always run bcrypt.compare regardless of whether the user was found.
100
106
  // This prevents timing-based user enumeration: both paths take the same time.
@@ -102,15 +108,19 @@ let AuthService = class AuthService {
102
108
  // const DUMMY_HASH = '$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012345';
103
109
  // const hashToCompare = user?.password ?? DUMMY_HASH;
104
110
  // const isValid = await bcrypt.compare(password, hashToCompare);
105
- const isValid = user ? await bcrypt.compare(password, user.password) : false;
111
+ console.log(`🔐 Comparing password for user ${user?.id}`);
112
+ console.log(`🔐 Comparing password for user ${user?.password}`);
113
+ const isValid = user
114
+ ? await bcrypt.compare(password, user.password)
115
+ : false;
106
116
  // Single generic message — never reveal whether the email exists
107
117
  if (!user || !isValid) {
108
- throw new common_1.UnauthorizedException('Invalid credentials');
118
+ throw new common_1.UnauthorizedException("Invalid credentials");
109
119
  }
110
120
  if (clientIp && this.ipRestrictionsRepo) {
111
121
  const ipAllowed = await this.validateIpRestriction(user.id, clientIp);
112
122
  if (!ipAllowed) {
113
- throw new common_1.UnauthorizedException('Login not allowed from this IP address');
123
+ throw new common_1.UnauthorizedException("Login not allowed from this IP address");
114
124
  }
115
125
  }
116
126
  return user;
@@ -128,17 +138,17 @@ let AuthService = class AuthService {
128
138
  normalizeIp(ip) {
129
139
  // Remove descriptive text if present (e.g., "IPv4 Address. . . . . . : 192.167.0.173")
130
140
  let cleanIp = ip.trim();
131
- if (cleanIp.includes(':')) {
132
- const parts = cleanIp.split(':');
141
+ if (cleanIp.includes(":")) {
142
+ const parts = cleanIp.split(":");
133
143
  cleanIp = parts[parts.length - 1].trim();
134
144
  }
135
145
  // Convert IPv6-mapped IPv4 addresses (::ffff:x.x.x.x) to plain IPv4 (x.x.x.x)
136
- if (cleanIp.startsWith('::ffff:')) {
146
+ if (cleanIp.startsWith("::ffff:")) {
137
147
  return cleanIp.substring(7);
138
148
  }
139
149
  // Also handle other IPv6 prefixes
140
- if (cleanIp.startsWith('::1')) {
141
- return '127.0.0.1'; // IPv6 loopback to IPv4 loopback
150
+ if (cleanIp.startsWith("::1")) {
151
+ return "127.0.0.1"; // IPv6 loopback to IPv4 loopback
142
152
  }
143
153
  return cleanIp;
144
154
  }
@@ -151,11 +161,11 @@ let AuthService = class AuthService {
151
161
  // Normalize the incoming IP
152
162
  const normalizedRequestIp = this.normalizeIp(requestIp);
153
163
  // Get all active IP restrictions for this user
154
- const restrictions = await this.ipRestrictionsRepo.find({
164
+ const restrictions = (await this.ipRestrictionsRepo.find({
155
165
  where: {
156
166
  user_id: userId,
157
167
  },
158
- });
168
+ }));
159
169
  // If no restrictions exist, allow login
160
170
  if (!restrictions || restrictions.length === 0) {
161
171
  console.log(`✅ User ${userId}: No IP restrictions configured - Allow login`);
@@ -173,16 +183,18 @@ let AuthService = class AuthService {
173
183
  return true;
174
184
  }
175
185
  // IP doesn't match any allowed IP
176
- const allowedIps = restrictions.map((r) => {
186
+ const allowedIps = restrictions
187
+ .map((r) => {
177
188
  const ip = r.allowed_ip_address || r.ip_address;
178
189
  return this.normalizeIp(ip);
179
- }).join(', ');
190
+ })
191
+ .join(", ");
180
192
  console.log(`❌ User ${userId}: IP ${requestIp} (normalized: ${normalizedRequestIp}) does not match allowed IPs - Deny login`);
181
193
  console.log(` Allowed IPs: ${allowedIps}`);
182
194
  return false;
183
195
  }
184
196
  catch (error) {
185
- console.error('Error validating IP restriction:', error);
197
+ console.error("Error validating IP restriction:", error);
186
198
  // On error, allow login (fail open)
187
199
  return true;
188
200
  }
@@ -199,13 +211,13 @@ let AuthService = class AuthService {
199
211
  AND status = 1
200
212
  AND is_deleted = 0
201
213
  LIMIT 1`, [userId, moduleId]);
202
- console.log('Raw Query Result:', result);
214
+ console.log("Raw Query Result:", result);
203
215
  const allowed = result.length > 0;
204
- console.log(`📊 Module access check result for user ID ${userId} and module ID ${moduleId}: ${allowed ? 'Allowed' : 'Denied'}`);
216
+ console.log(`📊 Module access check result for user ID ${userId} and module ID ${moduleId}: ${allowed ? "Allowed" : "Denied"}`);
205
217
  return allowed;
206
218
  }
207
219
  catch (error) {
208
- console.error('❌ Error checking module access:', error);
220
+ console.error("❌ Error checking module access:", error);
209
221
  return false;
210
222
  }
211
223
  }
@@ -217,10 +229,10 @@ let AuthService = class AuthService {
217
229
  * Save user last login details
218
230
  * Updates the tbl_user_last_login table with latest login info
219
231
  */
220
- async saveLastLogin(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
232
+ async saveLastLogin(user, clientIp, loginStatus = "success", failureReason, additionalData) {
221
233
  if (!this.userLastLoginRepo) {
222
234
  // Last login tracking not configured
223
- console.warn('⚠️ userLastLoginRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.');
235
+ console.warn("⚠️ userLastLoginRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.");
224
236
  return;
225
237
  }
226
238
  try {
@@ -262,13 +274,13 @@ let AuthService = class AuthService {
262
274
  console.log(`📝 Last login details saved for user ${user.id} (${loginStatus})`);
263
275
  }
264
276
  catch (error) {
265
- if (error?.message?.includes('No metadata')) {
266
- console.error('❌ ERROR: UserLastLoginEntity is not registered in your TypeORM configuration.\n' +
267
- 'Please see ENTITY_SETUP_GUIDE.md for setup instructions.\n' +
268
- 'Entity must be added to TypeOrmModule.forRoot(entities: [...]) in your app.');
277
+ if (error?.message?.includes("No metadata")) {
278
+ console.error("❌ ERROR: UserLastLoginEntity is not registered in your TypeORM configuration.\n" +
279
+ "Please see ENTITY_SETUP_GUIDE.md for setup instructions.\n" +
280
+ "Entity must be added to TypeOrmModule.forRoot(entities: [...]) in your app.");
269
281
  }
270
282
  else {
271
- console.error('Error saving last login details:', error);
283
+ console.error("Error saving last login details:", error);
272
284
  }
273
285
  // Don't throw error - this shouldn't block login
274
286
  }
@@ -278,10 +290,10 @@ let AuthService = class AuthService {
278
290
  * If a record exists for the user on the same date, update it
279
291
  * Otherwise, create a new record
280
292
  */
281
- async saveLoginDetailsJson(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
293
+ async saveLoginDetailsJson(user, clientIp, loginStatus = "success", failureReason, additionalData) {
282
294
  if (!this.loginDetailsRepo) {
283
295
  // Login details tracking not configured
284
- console.warn('⚠️ loginDetailsRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.');
296
+ console.warn("⚠️ loginDetailsRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.");
285
297
  return;
286
298
  }
287
299
  try {
@@ -320,10 +332,10 @@ let AuthService = class AuthService {
320
332
  // Append new login detail
321
333
  loginData.push(loginDetailData);
322
334
  // Update counters
323
- if (loginStatus === 'success') {
335
+ if (loginStatus === "success") {
324
336
  successCount++;
325
337
  }
326
- else if (loginStatus === 'failed' || loginStatus === 'blocked') {
338
+ else if (loginStatus === "failed" || loginStatus === "blocked") {
327
339
  failedCount++;
328
340
  }
329
341
  await this.loginDetailsRepo.update({ id: existingRecord.id }, {
@@ -338,8 +350,9 @@ let AuthService = class AuthService {
338
350
  else {
339
351
  // Create new record
340
352
  loginData = [loginDetailData];
341
- successCount = loginStatus === 'success' ? 1 : 0;
342
- failedCount = loginStatus === 'failed' || loginStatus === 'blocked' ? 1 : 0;
353
+ successCount = loginStatus === "success" ? 1 : 0;
354
+ failedCount =
355
+ loginStatus === "failed" || loginStatus === "blocked" ? 1 : 0;
343
356
  const newLoginDetails = this.loginDetailsRepo.create({
344
357
  user_id: user.id,
345
358
  date: today,
@@ -356,13 +369,13 @@ let AuthService = class AuthService {
356
369
  }
357
370
  }
358
371
  catch (error) {
359
- if (error?.message?.includes('No metadata')) {
360
- console.error('❌ ERROR: LoginDetailsEntity is not registered in your TypeORM configuration.\n' +
361
- 'Please see ENTITY_SETUP_GUIDE.md for setup instructions.\n' +
362
- 'Entity must be added to TypeOrmModule.forRoot(entities: [...]) in your app.');
372
+ if (error?.message?.includes("No metadata")) {
373
+ console.error("❌ ERROR: LoginDetailsEntity is not registered in your TypeORM configuration.\n" +
374
+ "Please see ENTITY_SETUP_GUIDE.md for setup instructions.\n" +
375
+ "Entity must be added to TypeOrmModule.forRoot(entities: [...]) in your app.");
363
376
  }
364
377
  else {
365
- console.error('Error saving login details JSON:', error);
378
+ console.error("Error saving login details JSON:", error);
366
379
  }
367
380
  // Don't throw error - this shouldn't block login
368
381
  }
@@ -412,12 +425,12 @@ let AuthService = class AuthService {
412
425
  console.log(`🚪 Logout details updated for user ${userId} on ${today.toDateString()}`);
413
426
  }
414
427
  catch (error) {
415
- if (error?.message?.includes('No metadata')) {
416
- console.error('❌ ERROR: LoginDetailsEntity is not registered in your TypeORM configuration.\n' +
417
- 'Please see ENTITY_SETUP_GUIDE.md for setup instructions.');
428
+ if (error?.message?.includes("No metadata")) {
429
+ console.error("❌ ERROR: LoginDetailsEntity is not registered in your TypeORM configuration.\n" +
430
+ "Please see ENTITY_SETUP_GUIDE.md for setup instructions.");
418
431
  }
419
432
  else {
420
- console.error('Error updating logout details:', error);
433
+ console.error("Error updating logout details:", error);
421
434
  }
422
435
  // Don't throw error - this shouldn't block logout
423
436
  }
@@ -445,12 +458,12 @@ let AuthService = class AuthService {
445
458
  console.log(`🚪 Last login logout time updated for user ${userId}`);
446
459
  }
447
460
  catch (error) {
448
- if (error?.message?.includes('No metadata')) {
449
- console.error('❌ ERROR: UserLastLoginEntity is not registered in your TypeORM configuration.\n' +
450
- 'Please see ENTITY_SETUP_GUIDE.md for setup instructions.');
461
+ if (error?.message?.includes("No metadata")) {
462
+ console.error("❌ ERROR: UserLastLoginEntity is not registered in your TypeORM configuration.\n" +
463
+ "Please see ENTITY_SETUP_GUIDE.md for setup instructions.");
451
464
  }
452
465
  else {
453
- console.error('Error updating last login logout time:', error);
466
+ console.error("Error updating last login logout time:", error);
454
467
  }
455
468
  // Don't throw error - this shouldn't block logout
456
469
  }
@@ -461,7 +474,9 @@ let AuthService = class AuthService {
461
474
  console.log(`📊 Loaded permissions for user ID ${user.id}:`, permissionTree);
462
475
  const role = await this.roleRepo.findOne({ where: { id: user.roleId } });
463
476
  const roleName = role?.roleName || null;
464
- const effectiveModuleId = Number.isFinite(selectedModuleId) ? selectedModuleId : user.moduleId ?? null;
477
+ const effectiveModuleId = Number.isFinite(selectedModuleId)
478
+ ? selectedModuleId
479
+ : (user.moduleId ?? null);
465
480
  // Fetch workprofile/employee details from EmployeeWorkProfileEntity
466
481
  let branchId = null;
467
482
  let dispatchCenterId = null;
@@ -480,7 +495,7 @@ let AuthService = class AuthService {
480
495
  }
481
496
  }
482
497
  catch (error) {
483
- console.error('Error fetching work profile:', error);
498
+ console.error("Error fetching work profile:", error);
484
499
  // Continue with null values if fetch fails
485
500
  }
486
501
  }
@@ -492,14 +507,14 @@ let AuthService = class AuthService {
492
507
  try {
493
508
  const lastLogin = await this.userLastLoginRepo.findOne({
494
509
  where: { user_id: user.id },
495
- order: { login_time: 'DESC' },
510
+ order: { login_time: "DESC" },
496
511
  });
497
512
  if (lastLogin) {
498
513
  lastLoginTime = lastLogin?.login_time || null;
499
514
  }
500
515
  }
501
516
  catch (error) {
502
- console.error('Error fetching last login details:', error);
517
+ console.error("Error fetching last login details:", error);
503
518
  // Continue with null values if fetch fails
504
519
  }
505
520
  }
@@ -535,7 +550,7 @@ let AuthService = class AuthService {
535
550
  };
536
551
  return {
537
552
  status: true,
538
- message: 'Login successful',
553
+ message: "Login successful",
539
554
  data: {
540
555
  user: {
541
556
  id: user.id,
@@ -571,7 +586,7 @@ let AuthService = class AuthService {
571
586
  */
572
587
  extractUserIpv4(clientIp) {
573
588
  if (!clientIp)
574
- return '';
589
+ return "";
575
590
  return this.normalizeIp(clientIp);
576
591
  }
577
592
  async findUserById(id) {
@@ -582,11 +597,11 @@ let AuthService = class AuthService {
582
597
  if (this.userLastLoginRepo) {
583
598
  try {
584
599
  const result = await this.userLastLoginRepo
585
- .createQueryBuilder('login')
586
- .select('login.login_time', 'login_time')
587
- .where('login.user_id = :userId', { userId })
588
- .andWhere('login.logout_time IS NULL')
589
- .orderBy('login.login_time', 'DESC')
600
+ .createQueryBuilder("login")
601
+ .select("login.login_time", "login_time")
602
+ .where("login.user_id = :userId", { userId })
603
+ .andWhere("login.logout_time IS NULL")
604
+ .orderBy("login.login_time", "DESC")
590
605
  .limit(1)
591
606
  .getRawOne();
592
607
  if (result) {
@@ -594,7 +609,7 @@ let AuthService = class AuthService {
594
609
  }
595
610
  }
596
611
  catch (error) {
597
- console.error('Error fetching last login details:', error);
612
+ console.error("Error fetching last login details:", error);
598
613
  // Continue with null values if fetch fails
599
614
  }
600
615
  }
@@ -610,7 +625,7 @@ let AuthService = class AuthService {
610
625
  }
611
626
  }
612
627
  catch (error) {
613
- console.error('Error updating last activity:', error);
628
+ console.error("Error updating last activity:", error);
614
629
  }
615
630
  }
616
631
  // private async loadPermissions(userId: number): Promise<PermissionsTree> {
@@ -54,7 +54,7 @@ __decorate([
54
54
  __metadata("design:type", String)
55
55
  ], UserCustomer.prototype, "userImage", void 0);
56
56
  __decorate([
57
- (0, typeorm_1.Column)(),
57
+ (0, typeorm_1.Column)({ type: "int" }),
58
58
  __metadata("design:type", Number)
59
59
  ], UserCustomer.prototype, "roleId", void 0);
60
60
  __decorate([
@@ -115,15 +115,15 @@ __decorate([
115
115
  __metadata("design:type", Number)
116
116
  ], UserCustomer.prototype, "token_version", void 0);
117
117
  __decorate([
118
- (0, typeorm_1.Column)(),
118
+ (0, typeorm_1.Column)({ type: "int" }),
119
119
  __metadata("design:type", Number)
120
120
  ], UserCustomer.prototype, "createdBy", void 0);
121
121
  __decorate([
122
- (0, typeorm_1.Column)({ nullable: true }),
122
+ (0, typeorm_1.Column)({ type: "int", nullable: true }),
123
123
  __metadata("design:type", Number)
124
124
  ], UserCustomer.prototype, "updatedBy", void 0);
125
125
  __decorate([
126
- (0, typeorm_1.Column)({ nullable: true }),
126
+ (0, typeorm_1.Column)({ type: "int", nullable: true }),
127
127
  __metadata("design:type", Object)
128
128
  ], UserCustomer.prototype, "deletedBy", void 0);
129
129
  __decorate([
package/package.json CHANGED
@@ -1,52 +1,52 @@
1
- {
2
- "name": "ecrs-auth-core",
3
- "version": "1.0.108",
4
- "description": "Centralized authentication and authorization module for ECRS apps",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist"
9
- ],
10
- "scripts": {
11
- "build": "tsc -p tsconfig.json"
12
- },
13
- "keywords": [
14
- "nestjs",
15
- "auth",
16
- "jwt",
17
- "module",
18
- "ecrs"
19
- ],
20
- "author": "Chetan Yadnik",
21
- "license": "MIT",
22
- "peerDependencies": {
23
- "@nestjs/common": "^10.2.7 || ^11.0.0",
24
- "@nestjs/core": "^10.2.7 || ^11.0.0",
25
- "@nestjs/passport": "^10.0.3 || ^11.0.0",
26
- "bcrypt": "^5.1.1",
27
- "passport": "^0.6.0",
28
- "passport-jwt": "^4.0.1",
29
- "typeorm": "^0.3.25"
30
- },
31
- "dependencies": {
32
- "@nestjs/jwt": "^11.0.0",
33
- "@nestjs/swagger": "^7.1.14",
34
- "@nestjs/typeorm": "^11.0.0",
35
- "axios": "^1.16.1",
36
- "class-transformer": "^0.5.1",
37
- "class-validator": "^0.14.3",
38
- "jsonwebtoken": "^9.0.2",
39
- "rxjs": "^7.8.1",
40
- "swagger-ui-express": "^5.0.0"
41
- },
42
- "devDependencies": {
43
- "@nestjs/common": "^10.2.7",
44
- "@nestjs/core": "^10.2.7",
45
- "@nestjs/passport": "^10.0.3",
46
- "@types/bcrypt": "^6.0.0",
47
- "@types/passport-jwt": "^4.0.1",
48
- "reflect-metadata": "^0.1.13",
49
- "typeorm": "^0.3.17",
50
- "typescript": "^5.8.3"
51
- }
52
- }
1
+ {
2
+ "name": "ecrs-auth-core",
3
+ "version": "1.0.110",
4
+ "description": "Centralized authentication and authorization module for ECRS apps",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json"
12
+ },
13
+ "keywords": [
14
+ "nestjs",
15
+ "auth",
16
+ "jwt",
17
+ "module",
18
+ "ecrs"
19
+ ],
20
+ "author": "Chetan Yadnik",
21
+ "license": "MIT",
22
+ "peerDependencies": {
23
+ "@nestjs/common": "^10.2.7 || ^11.0.0",
24
+ "@nestjs/core": "^10.2.7 || ^11.0.0",
25
+ "@nestjs/passport": "^10.0.3 || ^11.0.0",
26
+ "bcrypt": "^5.1.1",
27
+ "passport": "^0.6.0",
28
+ "passport-jwt": "^4.0.1",
29
+ "typeorm": "^0.3.25"
30
+ },
31
+ "dependencies": {
32
+ "@nestjs/jwt": "^11.0.0",
33
+ "@nestjs/swagger": "^7.1.14",
34
+ "@nestjs/typeorm": "^11.0.0",
35
+ "axios": "^1.16.1",
36
+ "class-transformer": "^0.5.1",
37
+ "class-validator": "^0.14.3",
38
+ "jsonwebtoken": "^9.0.2",
39
+ "rxjs": "^7.8.1",
40
+ "swagger-ui-express": "^5.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@nestjs/common": "^10.2.7",
44
+ "@nestjs/core": "^10.2.7",
45
+ "@nestjs/passport": "^10.0.3",
46
+ "@types/bcrypt": "^6.0.0",
47
+ "@types/passport-jwt": "^4.0.1",
48
+ "reflect-metadata": "^0.1.13",
49
+ "typeorm": "^0.3.17",
50
+ "typescript": "^5.8.3"
51
+ }
52
+ }