ecrs-auth-core 1.0.74 → 1.0.75

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,9 @@ 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
36
+ // Save failed login attempt to both tables
37
37
  await this.authService.saveLastLogin({ email: body.email }, clientIp, 'failed', 'Invalid credentials or IP not allowed', additionalData).catch(() => { }); // Ignore errors
38
+ await this.authService.saveLoginDetailsJson({ email: body.email }, clientIp, 'failed', 'Invalid credentials or IP not allowed', additionalData).catch(() => { }); // Ignore errors
38
39
  throw new common_1.UnauthorizedException('Login failed: email or password not matched or IP not allowed');
39
40
  }
40
41
  const requestedModuleId = Number(body.moduleId);
@@ -50,11 +51,17 @@ let AuthController = class AuthController {
50
51
  throw new common_1.UnauthorizedException('You are not authorized to access this module');
51
52
  }
52
53
  const loginResponse = await this.authService.login(user, requestedModuleId);
53
- // Save successful login details with additional client data
54
+ console.log(`✅ User ${body.email} logged in successfully to module ${requestedModuleId}`);
55
+ // Save successful login details with additional client data to both tables
54
56
  await this.authService.saveLastLogin(user, clientIp, 'success', undefined, {
55
57
  ...additionalData,
56
58
  moduleId: requestedModuleId,
57
59
  }).catch(() => { }); // Ignore errors - don't block login
60
+ // Save to JSON-based login details table
61
+ await this.authService.saveLoginDetailsJson(user, clientIp, 'success', undefined, {
62
+ ...additionalData,
63
+ moduleId: requestedModuleId,
64
+ }).catch(() => { }); // Ignore errors - don't block login
58
65
  return loginResponse;
59
66
  }
60
67
  /**
@@ -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
  /**
@@ -180,6 +181,18 @@ let AuthService = class AuthService {
180
181
  return;
181
182
  }
182
183
  try {
184
+ // Map camelCase keys to snake_case database column names
185
+ const mappedAdditionalData = {};
186
+ if (additionalData) {
187
+ mappedAdditionalData.browser = additionalData.browser;
188
+ mappedAdditionalData.device_type = additionalData.deviceType;
189
+ mappedAdditionalData.operating_system = additionalData.operatingSystem;
190
+ mappedAdditionalData.user_agent = additionalData.userAgent;
191
+ mappedAdditionalData.location = additionalData.location;
192
+ mappedAdditionalData.module_id = additionalData.moduleId;
193
+ mappedAdditionalData.ip_address_name = additionalData.ipAddressName;
194
+ mappedAdditionalData.metadata = additionalData.metadata;
195
+ }
183
196
  const lastLoginData = {
184
197
  user_id: user.id,
185
198
  email: user.email,
@@ -189,13 +202,20 @@ let AuthService = class AuthService {
189
202
  login_status: loginStatus,
190
203
  failure_reason: failureReason,
191
204
  login_time: new Date(),
192
- ...additionalData,
205
+ ...mappedAdditionalData,
193
206
  };
194
- // Upsert: Update if exists, insert if not
195
- await this.userLastLoginRepo.upsert(lastLoginData, {
196
- conflictPaths: ['user_id'],
197
- skipUpdateIfNoValuesChanged: true,
207
+ // First check if a record exists for this user
208
+ const existingRecord = await this.userLastLoginRepo.findOne({
209
+ where: { user_id: user.id },
198
210
  });
211
+ if (existingRecord) {
212
+ // Update existing record
213
+ await this.userLastLoginRepo.update({ user_id: user.id }, lastLoginData);
214
+ }
215
+ else {
216
+ // Create new record
217
+ await this.userLastLoginRepo.save(lastLoginData);
218
+ }
199
219
  console.log(`📝 Last login details saved for user ${user.id} (${loginStatus})`);
200
220
  }
201
221
  catch (error) {
@@ -203,6 +223,168 @@ let AuthService = class AuthService {
203
223
  // Don't throw error - this shouldn't block login
204
224
  }
205
225
  }
226
+ /**
227
+ * Save login details to JSON-based table with date-wise update
228
+ * If a record exists for the user on the same date, update it
229
+ * Otherwise, create a new record
230
+ */
231
+ async saveLoginDetailsJson(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
232
+ if (!this.loginDetailsRepo) {
233
+ // Login details tracking not configured
234
+ return;
235
+ }
236
+ try {
237
+ const today = new Date();
238
+ today.setHours(0, 0, 0, 0); // Set to start of day
239
+ // Build login detail data
240
+ const loginDetailData = {
241
+ login_time: new Date().toISOString(),
242
+ status: loginStatus,
243
+ ip_address: clientIp,
244
+ browser: additionalData?.browser,
245
+ device_type: additionalData?.deviceType,
246
+ operating_system: additionalData?.operatingSystem,
247
+ location: additionalData?.location,
248
+ module_id: additionalData?.moduleId,
249
+ ip_address_name: additionalData?.ipAddressName,
250
+ failure_reason: failureReason,
251
+ user_agent: additionalData?.userAgent,
252
+ metadata: additionalData?.metadata,
253
+ };
254
+ // Check if record exists for this user and date
255
+ const existingRecord = await this.loginDetailsRepo.findOne({
256
+ where: {
257
+ user_id: user.id,
258
+ date: today,
259
+ },
260
+ });
261
+ let loginData = [];
262
+ let successCount = 0;
263
+ let failedCount = 0;
264
+ if (existingRecord) {
265
+ // Update existing record - append to login_data array
266
+ loginData = existingRecord.login_data || [];
267
+ successCount = existingRecord.successful_logins || 0;
268
+ failedCount = existingRecord.failed_logins || 0;
269
+ // Append new login detail
270
+ loginData.push(loginDetailData);
271
+ // Update counters
272
+ if (loginStatus === 'success') {
273
+ successCount++;
274
+ }
275
+ else if (loginStatus === 'failed' || loginStatus === 'blocked') {
276
+ failedCount++;
277
+ }
278
+ await this.loginDetailsRepo.update({ id: existingRecord.id }, {
279
+ login_data: loginData,
280
+ total_logins: loginData.length,
281
+ successful_logins: successCount,
282
+ failed_logins: failedCount,
283
+ updated_at: new Date(),
284
+ });
285
+ console.log(`📝 Login details updated for user ${user.id} on ${today.toDateString()} (${loginStatus})`);
286
+ }
287
+ else {
288
+ // Create new record
289
+ loginData = [loginDetailData];
290
+ successCount = loginStatus === 'success' ? 1 : 0;
291
+ failedCount = loginStatus === 'failed' || loginStatus === 'blocked' ? 1 : 0;
292
+ const newLoginDetails = this.loginDetailsRepo.create({
293
+ user_id: user.id,
294
+ date: today,
295
+ email: user.email,
296
+ first_name: user.firstName,
297
+ last_name: user.lastName,
298
+ login_data: loginData,
299
+ total_logins: 1,
300
+ successful_logins: successCount,
301
+ failed_logins: failedCount,
302
+ });
303
+ await this.loginDetailsRepo.save(newLoginDetails);
304
+ console.log(`📝 Login details created for user ${user.id} on ${today.toDateString()} (${loginStatus})`);
305
+ }
306
+ }
307
+ catch (error) {
308
+ console.error('Error saving login details JSON:', error);
309
+ // Don't throw error - this shouldn't block login
310
+ }
311
+ }
312
+ /**
313
+ * Update logout details in JSON-based login details table
314
+ * Finds the latest login entry for today and updates it with logout_time and session_duration
315
+ */
316
+ async updateLoginLogoutDetailsJson(userId, logoutReason) {
317
+ if (!this.loginDetailsRepo) {
318
+ return;
319
+ }
320
+ try {
321
+ const today = new Date();
322
+ today.setHours(0, 0, 0, 0);
323
+ const record = await this.loginDetailsRepo.findOne({
324
+ where: {
325
+ user_id: userId,
326
+ date: today,
327
+ },
328
+ });
329
+ if (!record || !record.login_data || record.login_data.length === 0) {
330
+ return;
331
+ }
332
+ // Update the last login entry with logout time
333
+ const loginData = [...record.login_data];
334
+ const lastLoginIndex = loginData.length - 1;
335
+ const lastLogin = loginData[lastLoginIndex];
336
+ // Calculate session duration
337
+ const loginTime = new Date(lastLogin.login_time);
338
+ const logoutTime = new Date();
339
+ const sessionDurationMs = logoutTime.getTime() - loginTime.getTime();
340
+ // Update last login with logout info
341
+ loginData[lastLoginIndex] = {
342
+ ...lastLogin,
343
+ logout_time: logoutTime.toISOString(),
344
+ session_duration_ms: sessionDurationMs,
345
+ metadata: {
346
+ ...(lastLogin.metadata || {}),
347
+ logout_reason: logoutReason,
348
+ },
349
+ };
350
+ await this.loginDetailsRepo.update({ id: record.id }, {
351
+ login_data: loginData,
352
+ updated_at: new Date(),
353
+ });
354
+ console.log(`🚪 Logout details updated for user ${userId} on ${today.toDateString()}`);
355
+ }
356
+ catch (error) {
357
+ console.error('Error updating logout details:', error);
358
+ // Don't throw error - this shouldn't block logout
359
+ }
360
+ }
361
+ /**
362
+ * Update last login logout time (for tbl_user_last_login table)
363
+ */
364
+ async updateLastLoginLogout(userId, logoutReason) {
365
+ if (!this.userLastLoginRepo) {
366
+ return;
367
+ }
368
+ try {
369
+ const record = await this.userLastLoginRepo.findOne({
370
+ where: { user_id: userId },
371
+ });
372
+ if (!record || !record.login_time) {
373
+ return;
374
+ }
375
+ const logoutTime = new Date();
376
+ const sessionDurationMs = logoutTime.getTime() - new Date(record.login_time).getTime();
377
+ await this.userLastLoginRepo.update({ user_id: userId }, {
378
+ logout_time: logoutTime,
379
+ session_duration_ms: sessionDurationMs,
380
+ });
381
+ console.log(`🚪 Last login logout time updated for user ${userId}`);
382
+ }
383
+ catch (error) {
384
+ console.error('Error updating last login logout time:', error);
385
+ // Don't throw error - this shouldn't block logout
386
+ }
387
+ }
206
388
  async login(user, selectedModuleId) {
207
389
  const permissionTree = await this.loadPermissions(user.id);
208
390
  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.75",
4
4
  "description": "Centralized authentication and authorization module for ECRS apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",