ecrs-auth-core 1.0.74 → 1.0.76

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.
@@ -33,8 +33,13 @@ let AuthController = class AuthController {
33
33
  const user = await this.authService.validateUser(body.email, body.password, clientIp);
34
34
  console.log(`🔐 User validation result for ${body.email}: ${user ? 'Success' : 'Failed'}`);
35
35
  if (!user) {
36
- // Save failed login attempt
37
- await this.authService.saveLastLogin({ email: body.email }, clientIp, 'failed', 'Invalid credentials or IP not allowed', additionalData).catch(() => { }); // Ignore errors
36
+ // Save failed login attempt to both tables
37
+ await this.authService.saveLastLogin({ email: body.email }, clientIp, 'failed', 'Invalid credentials or IP not allowed', additionalData).catch((err) => {
38
+ console.error('❌ Error saving failed login to tbl_user_last_login:', err.message);
39
+ }); // Log errors for debugging
40
+ await this.authService.saveLoginDetailsJson({ email: body.email }, clientIp, 'failed', 'Invalid credentials or IP not allowed', additionalData).catch((err) => {
41
+ console.error('❌ Error saving failed login to tbl_user_login_details:', err.message);
42
+ }); // Log errors for debugging
38
43
  throw new common_1.UnauthorizedException('Login failed: email or password not matched or IP not allowed');
39
44
  }
40
45
  const requestedModuleId = Number(body.moduleId);
@@ -50,11 +55,21 @@ let AuthController = class AuthController {
50
55
  throw new common_1.UnauthorizedException('You are not authorized to access this module');
51
56
  }
52
57
  const loginResponse = await this.authService.login(user, requestedModuleId);
53
- // Save successful login details with additional client data
58
+ console.log(`✅ User ${body.email} logged in successfully to module ${requestedModuleId}`);
59
+ // Save successful login details with additional client data to both tables
54
60
  await this.authService.saveLastLogin(user, clientIp, 'success', undefined, {
55
61
  ...additionalData,
56
62
  moduleId: requestedModuleId,
57
- }).catch(() => { }); // Ignore errors - don't block login
63
+ }).catch((err) => {
64
+ console.error('❌ Error saving successful login to tbl_user_last_login:', err.message);
65
+ }); // Log errors for debugging
66
+ // Save to JSON-based login details table
67
+ await this.authService.saveLoginDetailsJson(user, clientIp, 'success', undefined, {
68
+ ...additionalData,
69
+ moduleId: requestedModuleId,
70
+ }).catch((err) => {
71
+ console.error('❌ Error saving successful login to tbl_user_login_details:', err.message);
72
+ }); // Log errors for debugging
58
73
  return loginResponse;
59
74
  }
60
75
  /**
@@ -27,6 +27,7 @@ export declare class AuthService {
27
27
  private readonly screenPermissionRepo;
28
28
  private readonly ipRestrictionsRepo;
29
29
  private readonly userLastLoginRepo;
30
+ private readonly loginDetailsRepo;
30
31
  private readonly employeeWorkProfileRepo;
31
32
  private uploadPhotoDir;
32
33
  constructor(jwtService: JwtService, options: AuthCoreOptions);
@@ -59,6 +60,30 @@ export declare class AuthService {
59
60
  ipAddressName?: string;
60
61
  metadata?: Record<string, any>;
61
62
  }): Promise<void>;
63
+ /**
64
+ * Save login details to JSON-based table with date-wise update
65
+ * If a record exists for the user on the same date, update it
66
+ * Otherwise, create a new record
67
+ */
68
+ saveLoginDetailsJson(user: User, clientIp: string, loginStatus?: 'success' | 'failed' | 'blocked', failureReason?: string, additionalData?: {
69
+ browser?: string;
70
+ deviceType?: string;
71
+ operatingSystem?: string;
72
+ userAgent?: string;
73
+ location?: string;
74
+ moduleId?: number;
75
+ ipAddressName?: string;
76
+ metadata?: Record<string, any>;
77
+ }): Promise<void>;
78
+ /**
79
+ * Update logout details in JSON-based login details table
80
+ * Finds the latest login entry for today and updates it with logout_time and session_duration
81
+ */
82
+ updateLoginLogoutDetailsJson(userId: number, logoutReason?: string): Promise<void>;
83
+ /**
84
+ * Update last login logout time (for tbl_user_last_login table)
85
+ */
86
+ updateLastLoginLogout(userId: number, logoutReason?: string): Promise<void>;
62
87
  login(user: User, selectedModuleId?: number): Promise<{
63
88
  status: boolean;
64
89
  message: string;
@@ -67,6 +67,7 @@ let AuthService = class AuthService {
67
67
  // Optional repositories
68
68
  this.ipRestrictionsRepo = repositories.ipRestrictionsRepo || null;
69
69
  this.userLastLoginRepo = repositories.userLastLoginRepo || null;
70
+ this.loginDetailsRepo = repositories.loginDetailsRepo || null;
70
71
  this.employeeWorkProfileRepo = repositories.employeeWorkProfileRepo || null;
71
72
  }
72
73
  async validateUser(email, password, clientIp) {
@@ -78,13 +79,13 @@ let AuthService = class AuthService {
78
79
  return null;
79
80
  // console.log(this.ipRestrictionsRepo);
80
81
  // Check IP restrictions if provided and repository is available
81
- // if (clientIp && this.ipRestrictionsRepo) {
82
- // const ipAllowed = await this.validateIpRestriction(user.id, clientIp);
83
- // if (!ipAllowed) {
84
- // // IP restriction exists but doesn't match - return null to block login
85
- // return null;
86
- // }
87
- // }
82
+ if (clientIp && this.ipRestrictionsRepo) {
83
+ const ipAllowed = await this.validateIpRestriction(user.id, clientIp);
84
+ if (!ipAllowed) {
85
+ // IP restriction exists but doesn't match - return null to block login
86
+ return null;
87
+ }
88
+ }
88
89
  return user;
89
90
  }
90
91
  /**
@@ -177,9 +178,22 @@ let AuthService = class AuthService {
177
178
  async saveLastLogin(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
178
179
  if (!this.userLastLoginRepo) {
179
180
  // Last login tracking not configured
181
+ console.warn('⚠️ userLastLoginRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.');
180
182
  return;
181
183
  }
182
184
  try {
185
+ // Map camelCase keys to snake_case database column names
186
+ const mappedAdditionalData = {};
187
+ if (additionalData) {
188
+ mappedAdditionalData.browser = additionalData.browser;
189
+ mappedAdditionalData.device_type = additionalData.deviceType;
190
+ mappedAdditionalData.operating_system = additionalData.operatingSystem;
191
+ mappedAdditionalData.user_agent = additionalData.userAgent;
192
+ mappedAdditionalData.location = additionalData.location;
193
+ mappedAdditionalData.module_id = additionalData.moduleId;
194
+ mappedAdditionalData.ip_address_name = additionalData.ipAddressName;
195
+ mappedAdditionalData.metadata = additionalData.metadata;
196
+ }
183
197
  const lastLoginData = {
184
198
  user_id: user.id,
185
199
  email: user.email,
@@ -189,13 +203,20 @@ let AuthService = class AuthService {
189
203
  login_status: loginStatus,
190
204
  failure_reason: failureReason,
191
205
  login_time: new Date(),
192
- ...additionalData,
206
+ ...mappedAdditionalData,
193
207
  };
194
- // Upsert: Update if exists, insert if not
195
- await this.userLastLoginRepo.upsert(lastLoginData, {
196
- conflictPaths: ['user_id'],
197
- skipUpdateIfNoValuesChanged: true,
208
+ // First check if a record exists for this user
209
+ const existingRecord = await this.userLastLoginRepo.findOne({
210
+ where: { user_id: user.id },
198
211
  });
212
+ if (existingRecord) {
213
+ // Update existing record
214
+ await this.userLastLoginRepo.update({ user_id: user.id }, lastLoginData);
215
+ }
216
+ else {
217
+ // Create new record
218
+ await this.userLastLoginRepo.save(lastLoginData);
219
+ }
199
220
  console.log(`📝 Last login details saved for user ${user.id} (${loginStatus})`);
200
221
  }
201
222
  catch (error) {
@@ -203,6 +224,169 @@ let AuthService = class AuthService {
203
224
  // Don't throw error - this shouldn't block login
204
225
  }
205
226
  }
227
+ /**
228
+ * Save login details to JSON-based table with date-wise update
229
+ * If a record exists for the user on the same date, update it
230
+ * Otherwise, create a new record
231
+ */
232
+ async saveLoginDetailsJson(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
233
+ if (!this.loginDetailsRepo) {
234
+ // Login details tracking not configured
235
+ console.warn('⚠️ loginDetailsRepo not configured. Add loginDetailsRepo to AuthCoreModule repositories.');
236
+ return;
237
+ }
238
+ try {
239
+ const today = new Date();
240
+ today.setHours(0, 0, 0, 0); // Set to start of day
241
+ // Build login detail data
242
+ const loginDetailData = {
243
+ login_time: new Date().toISOString(),
244
+ status: loginStatus,
245
+ ip_address: clientIp,
246
+ browser: additionalData?.browser,
247
+ device_type: additionalData?.deviceType,
248
+ operating_system: additionalData?.operatingSystem,
249
+ location: additionalData?.location,
250
+ module_id: additionalData?.moduleId,
251
+ ip_address_name: additionalData?.ipAddressName,
252
+ failure_reason: failureReason,
253
+ user_agent: additionalData?.userAgent,
254
+ metadata: additionalData?.metadata,
255
+ };
256
+ // Check if record exists for this user and date
257
+ const existingRecord = await this.loginDetailsRepo.findOne({
258
+ where: {
259
+ user_id: user.id,
260
+ date: today,
261
+ },
262
+ });
263
+ let loginData = [];
264
+ let successCount = 0;
265
+ let failedCount = 0;
266
+ if (existingRecord) {
267
+ // Update existing record - append to login_data array
268
+ loginData = existingRecord.login_data || [];
269
+ successCount = existingRecord.successful_logins || 0;
270
+ failedCount = existingRecord.failed_logins || 0;
271
+ // Append new login detail
272
+ loginData.push(loginDetailData);
273
+ // Update counters
274
+ if (loginStatus === 'success') {
275
+ successCount++;
276
+ }
277
+ else if (loginStatus === 'failed' || loginStatus === 'blocked') {
278
+ failedCount++;
279
+ }
280
+ await this.loginDetailsRepo.update({ id: existingRecord.id }, {
281
+ login_data: loginData,
282
+ total_logins: loginData.length,
283
+ successful_logins: successCount,
284
+ failed_logins: failedCount,
285
+ updated_at: new Date(),
286
+ });
287
+ console.log(`📝 Login details updated for user ${user.id} on ${today.toDateString()} (${loginStatus})`);
288
+ }
289
+ else {
290
+ // Create new record
291
+ loginData = [loginDetailData];
292
+ successCount = loginStatus === 'success' ? 1 : 0;
293
+ failedCount = loginStatus === 'failed' || loginStatus === 'blocked' ? 1 : 0;
294
+ const newLoginDetails = this.loginDetailsRepo.create({
295
+ user_id: user.id,
296
+ date: today,
297
+ email: user.email,
298
+ first_name: user.firstName,
299
+ last_name: user.lastName,
300
+ login_data: loginData,
301
+ total_logins: 1,
302
+ successful_logins: successCount,
303
+ failed_logins: failedCount,
304
+ });
305
+ await this.loginDetailsRepo.save(newLoginDetails);
306
+ console.log(`📝 Login details created for user ${user.id} on ${today.toDateString()} (${loginStatus})`);
307
+ }
308
+ }
309
+ catch (error) {
310
+ console.error('Error saving login details JSON:', error);
311
+ // Don't throw error - this shouldn't block login
312
+ }
313
+ }
314
+ /**
315
+ * Update logout details in JSON-based login details table
316
+ * Finds the latest login entry for today and updates it with logout_time and session_duration
317
+ */
318
+ async updateLoginLogoutDetailsJson(userId, logoutReason) {
319
+ if (!this.loginDetailsRepo) {
320
+ return;
321
+ }
322
+ try {
323
+ const today = new Date();
324
+ today.setHours(0, 0, 0, 0);
325
+ const record = await this.loginDetailsRepo.findOne({
326
+ where: {
327
+ user_id: userId,
328
+ date: today,
329
+ },
330
+ });
331
+ if (!record || !record.login_data || record.login_data.length === 0) {
332
+ return;
333
+ }
334
+ // Update the last login entry with logout time
335
+ const loginData = [...record.login_data];
336
+ const lastLoginIndex = loginData.length - 1;
337
+ const lastLogin = loginData[lastLoginIndex];
338
+ // Calculate session duration
339
+ const loginTime = new Date(lastLogin.login_time);
340
+ const logoutTime = new Date();
341
+ const sessionDurationMs = logoutTime.getTime() - loginTime.getTime();
342
+ // Update last login with logout info
343
+ loginData[lastLoginIndex] = {
344
+ ...lastLogin,
345
+ logout_time: logoutTime.toISOString(),
346
+ session_duration_ms: sessionDurationMs,
347
+ metadata: {
348
+ ...(lastLogin.metadata || {}),
349
+ logout_reason: logoutReason,
350
+ },
351
+ };
352
+ await this.loginDetailsRepo.update({ id: record.id }, {
353
+ login_data: loginData,
354
+ updated_at: new Date(),
355
+ });
356
+ console.log(`🚪 Logout details updated for user ${userId} on ${today.toDateString()}`);
357
+ }
358
+ catch (error) {
359
+ console.error('Error updating logout details:', error);
360
+ // Don't throw error - this shouldn't block logout
361
+ }
362
+ }
363
+ /**
364
+ * Update last login logout time (for tbl_user_last_login table)
365
+ */
366
+ async updateLastLoginLogout(userId, logoutReason) {
367
+ if (!this.userLastLoginRepo) {
368
+ return;
369
+ }
370
+ try {
371
+ const record = await this.userLastLoginRepo.findOne({
372
+ where: { user_id: userId },
373
+ });
374
+ if (!record || !record.login_time) {
375
+ return;
376
+ }
377
+ const logoutTime = new Date();
378
+ const sessionDurationMs = logoutTime.getTime() - new Date(record.login_time).getTime();
379
+ await this.userLastLoginRepo.update({ user_id: userId }, {
380
+ logout_time: logoutTime,
381
+ session_duration_ms: sessionDurationMs,
382
+ });
383
+ console.log(`🚪 Last login logout time updated for user ${userId}`);
384
+ }
385
+ catch (error) {
386
+ console.error('Error updating last login logout time:', error);
387
+ // Don't throw error - this shouldn't block logout
388
+ }
389
+ }
206
390
  async login(user, selectedModuleId) {
207
391
  const permissionTree = await this.loadPermissions(user.id);
208
392
  const role = await this.roleRepo.findOne({ where: { id: user.roleId } });
@@ -0,0 +1,34 @@
1
+ export interface LoginDetailData {
2
+ login_time: string;
3
+ logout_time?: string;
4
+ status: 'success' | 'failed' | 'blocked';
5
+ ip_address: string;
6
+ browser?: string;
7
+ device_type?: string;
8
+ operating_system?: string;
9
+ location?: string;
10
+ module_id?: number;
11
+ ip_address_name?: string;
12
+ failure_reason?: string;
13
+ user_agent?: string;
14
+ session_duration_ms?: number;
15
+ metadata?: Record<string, any>;
16
+ }
17
+ export declare class LoginDetailsEntity {
18
+ id: number;
19
+ user_id: number;
20
+ date: Date;
21
+ login_data: LoginDetailData[];
22
+ email: string;
23
+ first_name: string;
24
+ last_name: string;
25
+ total_logins: number;
26
+ successful_logins: number;
27
+ failed_logins: number;
28
+ created_at: Date;
29
+ updated_at: Date;
30
+ deleted_at: Date;
31
+ created_by: number;
32
+ updated_by: number;
33
+ deleted_by: number;
34
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.LoginDetailsEntity = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let LoginDetailsEntity = class LoginDetailsEntity {
15
+ };
16
+ exports.LoginDetailsEntity = LoginDetailsEntity;
17
+ __decorate([
18
+ (0, typeorm_1.PrimaryGeneratedColumn)(),
19
+ __metadata("design:type", Number)
20
+ ], LoginDetailsEntity.prototype, "id", void 0);
21
+ __decorate([
22
+ (0, typeorm_1.Column)({ type: 'int', nullable: false }),
23
+ __metadata("design:type", Number)
24
+ ], LoginDetailsEntity.prototype, "user_id", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)({ type: 'date', nullable: false }),
27
+ __metadata("design:type", Date)
28
+ ], LoginDetailsEntity.prototype, "date", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)({
31
+ name: 'login_data',
32
+ type: 'json',
33
+ nullable: true,
34
+ comment: 'JSON array of login details for the date',
35
+ }),
36
+ __metadata("design:type", Array)
37
+ ], LoginDetailsEntity.prototype, "login_data", void 0);
38
+ __decorate([
39
+ (0, typeorm_1.Column)({ length: 250, nullable: true }),
40
+ __metadata("design:type", String)
41
+ ], LoginDetailsEntity.prototype, "email", void 0);
42
+ __decorate([
43
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
44
+ __metadata("design:type", String)
45
+ ], LoginDetailsEntity.prototype, "first_name", void 0);
46
+ __decorate([
47
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
48
+ __metadata("design:type", String)
49
+ ], LoginDetailsEntity.prototype, "last_name", void 0);
50
+ __decorate([
51
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
52
+ __metadata("design:type", Number)
53
+ ], LoginDetailsEntity.prototype, "total_logins", void 0);
54
+ __decorate([
55
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
56
+ __metadata("design:type", Number)
57
+ ], LoginDetailsEntity.prototype, "successful_logins", void 0);
58
+ __decorate([
59
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
60
+ __metadata("design:type", Number)
61
+ ], LoginDetailsEntity.prototype, "failed_logins", void 0);
62
+ __decorate([
63
+ (0, typeorm_1.CreateDateColumn)({ type: 'timestamp' }),
64
+ __metadata("design:type", Date)
65
+ ], LoginDetailsEntity.prototype, "created_at", void 0);
66
+ __decorate([
67
+ (0, typeorm_1.UpdateDateColumn)({ type: 'timestamp', nullable: true }),
68
+ __metadata("design:type", Date)
69
+ ], LoginDetailsEntity.prototype, "updated_at", void 0);
70
+ __decorate([
71
+ (0, typeorm_1.DeleteDateColumn)({ type: 'timestamp', nullable: true }),
72
+ __metadata("design:type", Date)
73
+ ], LoginDetailsEntity.prototype, "deleted_at", void 0);
74
+ __decorate([
75
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
76
+ __metadata("design:type", Number)
77
+ ], LoginDetailsEntity.prototype, "created_by", void 0);
78
+ __decorate([
79
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
80
+ __metadata("design:type", Number)
81
+ ], LoginDetailsEntity.prototype, "updated_by", void 0);
82
+ __decorate([
83
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
84
+ __metadata("design:type", Number)
85
+ ], LoginDetailsEntity.prototype, "deleted_by", void 0);
86
+ exports.LoginDetailsEntity = LoginDetailsEntity = __decorate([
87
+ (0, typeorm_1.Entity)('tbl_user_login_details'),
88
+ (0, typeorm_1.Index)(['user_id']),
89
+ (0, typeorm_1.Index)(['date']),
90
+ (0, typeorm_1.Index)(['user_id', 'date'])
91
+ ], LoginDetailsEntity);
package/dist/index.d.ts CHANGED
@@ -27,5 +27,6 @@ export * from './entities/user-module-access.entity';
27
27
  export * from './entities/module-screen-permission.entity';
28
28
  export * from './entities/api-key.entity';
29
29
  export * from './entities/user-last-login.entity';
30
+ export * from './entities/login-details.entity';
30
31
  export * from './entities/ip-access.entity';
31
32
  export * from './entities/work-profile.entity';
package/dist/index.js CHANGED
@@ -50,5 +50,6 @@ __exportStar(require("./entities/user-module-access.entity"), exports);
50
50
  __exportStar(require("./entities/module-screen-permission.entity"), exports);
51
51
  __exportStar(require("./entities/api-key.entity"), exports);
52
52
  __exportStar(require("./entities/user-last-login.entity"), exports);
53
+ __exportStar(require("./entities/login-details.entity"), exports);
53
54
  __exportStar(require("./entities/ip-access.entity"), exports);
54
55
  __exportStar(require("./entities/work-profile.entity"), exports);
@@ -9,6 +9,7 @@ import { UserModuleAccess } from '../entities/user-module-access.entity';
9
9
  import { ModuleScreenPermission } from '../entities/module-screen-permission.entity';
10
10
  import { ApiKeyEntity } from '../entities/api-key.entity';
11
11
  import { UserLastLoginEntity } from '../entities/user-last-login.entity';
12
+ import { LoginDetailsEntity } from '../entities/login-details.entity';
12
13
  import { EmployeeIPAccessEntity } from '../entities/ip-access.entity';
13
14
  import { EmployeeWorkProfileEntity } from '../entities/work-profile.entity';
14
15
  export interface Repositories {
@@ -23,6 +24,7 @@ export interface Repositories {
23
24
  apiKeyRepo?: Repository<ApiKeyEntity>;
24
25
  ipRestrictionsRepo?: Repository<EmployeeIPAccessEntity>;
25
26
  userLastLoginRepo?: Repository<UserLastLoginEntity>;
27
+ loginDetailsRepo?: Repository<LoginDetailsEntity>;
26
28
  employeeWorkProfileRepo?: Repository<EmployeeWorkProfileEntity>;
27
29
  }
28
30
  export interface AuthModuleConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecrs-auth-core",
3
- "version": "1.0.74",
3
+ "version": "1.0.76",
4
4
  "description": "Centralized authentication and authorization module for ECRS apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",