ecrs-auth-core 1.0.64 → 1.0.66

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.
@@ -54,6 +54,7 @@ let AuthService = class AuthService {
54
54
  constructor(jwtService, options) {
55
55
  this.jwtService = jwtService;
56
56
  this.options = options;
57
+ this.uploadPhotoDir = './uploads/organization/photos';
57
58
  const { repositories } = options;
58
59
  this.userRepo = repositories.userRepo;
59
60
  this.roleRepo = repositories.roleRepo;
@@ -63,13 +64,99 @@ let AuthService = class AuthService {
63
64
  this.featureAccessRepo = repositories.featureAccessRepo;
64
65
  this.moduleAccessRepo = repositories.moduleAccessRepo;
65
66
  this.screenPermissionRepo = repositories.screenPermissionRepo;
67
+ // Optional repositories
68
+ this.ipRestrictionsRepo = repositories.ipRestrictionsRepo || null;
69
+ this.userLastLoginRepo = repositories.userLastLoginRepo || null;
70
+ this.employeeWorkProfileRepo = repositories.employeeWorkProfileRepo || null;
66
71
  }
67
- async validateUser(email, password) {
72
+ async validateUser(email, password, clientIp) {
68
73
  const user = await this.userRepo.findOne({ where: { email } });
69
74
  if (!user)
70
75
  return null;
71
76
  const isValid = await bcrypt.compare(password, user.password);
72
- return isValid ? user : null;
77
+ if (!isValid)
78
+ return null;
79
+ // Check IP restrictions if provided and repository is available
80
+ if (clientIp && this.ipRestrictionsRepo) {
81
+ const ipAllowed = await this.validateIpRestriction(user.id, clientIp);
82
+ if (!ipAllowed) {
83
+ // IP restriction exists but doesn't match - return null to block login
84
+ return null;
85
+ }
86
+ }
87
+ return user;
88
+ }
89
+ /**
90
+ * Validate IP restriction for a user
91
+ *
92
+ * Logic:
93
+ * 1. Check if user has any IP restrictions (is_active = 1)
94
+ * 2. If NO restrictions exist → Allow login (return true)
95
+ * 3. If restrictions exist → Check if requestIp matches any allowed IP
96
+ * 4. If match found → Allow login (return true)
97
+ * 5. If NO match → Deny login (return false)
98
+ */
99
+ normalizeIp(ip) {
100
+ // Remove descriptive text if present (e.g., "IPv4 Address. . . . . . : 192.167.0.173")
101
+ let cleanIp = ip.trim();
102
+ if (cleanIp.includes(':')) {
103
+ const parts = cleanIp.split(':');
104
+ cleanIp = parts[parts.length - 1].trim();
105
+ }
106
+ // Convert IPv6-mapped IPv4 addresses (::ffff:x.x.x.x) to plain IPv4 (x.x.x.x)
107
+ if (cleanIp.startsWith('::ffff:')) {
108
+ return cleanIp.substring(7);
109
+ }
110
+ // Also handle other IPv6 prefixes
111
+ if (cleanIp.startsWith('::1')) {
112
+ return '127.0.0.1'; // IPv6 loopback to IPv4 loopback
113
+ }
114
+ return cleanIp;
115
+ }
116
+ async validateIpRestriction(userId, requestIp) {
117
+ if (!this.ipRestrictionsRepo) {
118
+ // No IP restrictions repository configured - allow login
119
+ return true;
120
+ }
121
+ try {
122
+ // Normalize the incoming IP
123
+ const normalizedRequestIp = this.normalizeIp(requestIp);
124
+ // Get all active IP restrictions for this user
125
+ const restrictions = await this.ipRestrictionsRepo.find({
126
+ where: {
127
+ user_id: userId,
128
+ },
129
+ });
130
+ // If no restrictions exist, allow login
131
+ if (!restrictions || restrictions.length === 0) {
132
+ console.log(`✅ User ${userId}: No IP restrictions configured - Allow login`);
133
+ return true;
134
+ }
135
+ // Check if request IP matches any allowed IP
136
+ const ipMatches = restrictions.some((restriction) => {
137
+ // Handle both property name variations
138
+ const allowedIp = restriction.allowed_ip_address || restriction.ip_address;
139
+ const normalizedAllowedIp = this.normalizeIp(allowedIp);
140
+ return normalizedAllowedIp === normalizedRequestIp;
141
+ });
142
+ if (ipMatches) {
143
+ console.log(`✅ User ${userId}: IP ${requestIp} (normalized: ${normalizedRequestIp}) matches allowed IP - Allow login`);
144
+ return true;
145
+ }
146
+ // IP doesn't match any allowed IP
147
+ const allowedIps = restrictions.map((r) => {
148
+ const ip = r.allowed_ip_address || r.ip_address;
149
+ return this.normalizeIp(ip);
150
+ }).join(', ');
151
+ console.log(`❌ User ${userId}: IP ${requestIp} (normalized: ${normalizedRequestIp}) does not match allowed IPs - Deny login`);
152
+ console.log(` Allowed IPs: ${allowedIps}`);
153
+ return false;
154
+ }
155
+ catch (error) {
156
+ console.error('Error validating IP restriction:', error);
157
+ // On error, allow login (fail open)
158
+ return true;
159
+ }
73
160
  }
74
161
  async hasModuleAccess(userId, moduleId) {
75
162
  if (!Number.isFinite(moduleId))
@@ -82,11 +169,66 @@ let AuthService = class AuthService {
82
169
  async getPermissions(userId) {
83
170
  return this.loadPermissions(userId);
84
171
  }
172
+ /**
173
+ * Save user last login details
174
+ * Updates the tbl_user_last_login table with latest login info
175
+ */
176
+ async saveLastLogin(user, clientIp, loginStatus = 'success', failureReason, additionalData) {
177
+ if (!this.userLastLoginRepo) {
178
+ // Last login tracking not configured
179
+ return;
180
+ }
181
+ try {
182
+ const lastLoginData = {
183
+ user_id: user.id,
184
+ email: user.email,
185
+ first_name: user.firstName,
186
+ last_name: user.lastName,
187
+ ip_address: clientIp,
188
+ login_status: loginStatus,
189
+ failure_reason: failureReason,
190
+ login_time: new Date(),
191
+ ...additionalData,
192
+ };
193
+ // Upsert: Update if exists, insert if not
194
+ await this.userLastLoginRepo.upsert(lastLoginData, {
195
+ conflictPaths: ['user_id'],
196
+ skipUpdateIfNoValuesChanged: true,
197
+ });
198
+ console.log(`📝 Last login details saved for user ${user.id} (${loginStatus})`);
199
+ }
200
+ catch (error) {
201
+ console.error('Error saving last login details:', error);
202
+ // Don't throw error - this shouldn't block login
203
+ }
204
+ }
85
205
  async login(user, selectedModuleId) {
86
206
  const permissionTree = await this.loadPermissions(user.id);
87
207
  const role = await this.roleRepo.findOne({ where: { id: user.roleId } });
88
208
  const roleName = role?.roleName || null;
89
209
  const effectiveModuleId = Number.isFinite(selectedModuleId) ? selectedModuleId : user.moduleId ?? null;
210
+ // Fetch workprofile/employee details from EmployeeWorkProfileEntity
211
+ let branchId = null;
212
+ let dispatchCenterId = null;
213
+ let departmentId = null;
214
+ let designationId = null;
215
+ if (this.employeeWorkProfileRepo) {
216
+ try {
217
+ const workProfile = await this.employeeWorkProfileRepo.findOne({
218
+ where: { employee_id: user.referenceId },
219
+ });
220
+ if (workProfile) {
221
+ branchId = workProfile?.branch_id || null;
222
+ dispatchCenterId = workProfile?.dispatch_id || null;
223
+ departmentId = workProfile?.department_id || null;
224
+ designationId = workProfile?.designation_id || null;
225
+ }
226
+ }
227
+ catch (error) {
228
+ console.error('Error fetching work profile:', error);
229
+ // Continue with null values if fetch fails
230
+ }
231
+ }
90
232
  const payload = {
91
233
  id: user.id,
92
234
  email: user.email,
@@ -102,6 +244,10 @@ let AuthService = class AuthService {
102
244
  permissions: permissionTree,
103
245
  parentId: user.parentId,
104
246
  referenceId: user.referenceId,
247
+ branchId,
248
+ dispatchCenterId,
249
+ departmentId,
250
+ designationId,
105
251
  };
106
252
  return {
107
253
  status: true,
@@ -121,11 +267,25 @@ let AuthService = class AuthService {
121
267
  employeeId: user.referenceId,
122
268
  parentId: user.parentId,
123
269
  referenceId: user.referenceId,
270
+ branchId,
271
+ dispatchCenterId,
272
+ departmentId,
273
+ designationId,
274
+ profile_photo_url: `${this.uploadPhotoDir}`,
124
275
  },
125
276
  },
126
277
  access_token: this.jwtService.sign(payload),
127
278
  };
128
279
  }
280
+ /**
281
+ * Extract clean IPv4 address from request
282
+ * Handles various formats: IPv6-mapped, plain IPv4, descriptive text
283
+ */
284
+ extractUserIpv4(clientIp) {
285
+ if (!clientIp)
286
+ return '';
287
+ return this.normalizeIp(clientIp);
288
+ }
129
289
  async findUserById(id) {
130
290
  return this.userRepo.findOne({ where: { id } });
131
291
  }
@@ -0,0 +1,13 @@
1
+ export declare class EmployeeIPAccessEntity {
2
+ id: number;
3
+ user_id: number;
4
+ employee_id: number;
5
+ ip_address: string;
6
+ ip_address_name?: string;
7
+ created_at: Date;
8
+ created_by: number;
9
+ updated_at: Date;
10
+ updated_by: number;
11
+ deleted_by: number;
12
+ deleted_at: Date;
13
+ }
@@ -0,0 +1,73 @@
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.EmployeeIPAccessEntity = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let EmployeeIPAccessEntity = class EmployeeIPAccessEntity {
15
+ };
16
+ exports.EmployeeIPAccessEntity = EmployeeIPAccessEntity;
17
+ __decorate([
18
+ (0, typeorm_1.PrimaryGeneratedColumn)(),
19
+ __metadata("design:type", Number)
20
+ ], EmployeeIPAccessEntity.prototype, "id", void 0);
21
+ __decorate([
22
+ (0, typeorm_1.Column)({ name: 'user_id', type: 'int' }),
23
+ __metadata("design:type", Number)
24
+ ], EmployeeIPAccessEntity.prototype, "user_id", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)({ name: 'employee_id', type: 'int' }),
27
+ __metadata("design:type", Number)
28
+ ], EmployeeIPAccessEntity.prototype, "employee_id", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)({
31
+ name: 'ip_address',
32
+ type: 'inet',
33
+ nullable: false,
34
+ comment: 'IPv4 / IPv6 / CIDR notation',
35
+ }),
36
+ __metadata("design:type", String)
37
+ ], EmployeeIPAccessEntity.prototype, "ip_address", void 0);
38
+ __decorate([
39
+ (0, typeorm_1.Column)({
40
+ name: 'ip_address_name',
41
+ type: 'varchar',
42
+ length: 255,
43
+ nullable: true,
44
+ }),
45
+ __metadata("design:type", String)
46
+ ], EmployeeIPAccessEntity.prototype, "ip_address_name", void 0);
47
+ __decorate([
48
+ (0, typeorm_1.CreateDateColumn)({ type: 'timestamp' }),
49
+ __metadata("design:type", Date)
50
+ ], EmployeeIPAccessEntity.prototype, "created_at", void 0);
51
+ __decorate([
52
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
53
+ __metadata("design:type", Number)
54
+ ], EmployeeIPAccessEntity.prototype, "created_by", void 0);
55
+ __decorate([
56
+ (0, typeorm_1.UpdateDateColumn)({ type: 'timestamp', nullable: true }),
57
+ __metadata("design:type", Date)
58
+ ], EmployeeIPAccessEntity.prototype, "updated_at", void 0);
59
+ __decorate([
60
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
61
+ __metadata("design:type", Number)
62
+ ], EmployeeIPAccessEntity.prototype, "updated_by", void 0);
63
+ __decorate([
64
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
65
+ __metadata("design:type", Number)
66
+ ], EmployeeIPAccessEntity.prototype, "deleted_by", void 0);
67
+ __decorate([
68
+ (0, typeorm_1.DeleteDateColumn)({ type: 'timestamp', nullable: true }),
69
+ __metadata("design:type", Date)
70
+ ], EmployeeIPAccessEntity.prototype, "deleted_at", void 0);
71
+ exports.EmployeeIPAccessEntity = EmployeeIPAccessEntity = __decorate([
72
+ (0, typeorm_1.Entity)('tbl_hr_user_ip_access')
73
+ ], EmployeeIPAccessEntity);
@@ -0,0 +1,27 @@
1
+ export declare class UserLastLoginEntity {
2
+ id: number;
3
+ user_id: number;
4
+ email: string;
5
+ first_name: string;
6
+ last_name: string;
7
+ ip_address: string;
8
+ ip_address_name?: string;
9
+ browser: string;
10
+ device_type: string;
11
+ operating_system: string;
12
+ user_agent: string;
13
+ location: string;
14
+ module_id: number;
15
+ login_status: 'success' | 'failed' | 'blocked';
16
+ failure_reason: string;
17
+ session_duration_ms: number;
18
+ metadata: Record<string, any>;
19
+ login_time: Date;
20
+ logout_time: Date;
21
+ created_at: Date;
22
+ created_by: number;
23
+ updated_at: Date;
24
+ updated_by: number;
25
+ deleted_by: number;
26
+ deleted_at: Date;
27
+ }
@@ -0,0 +1,147 @@
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.UserLastLoginEntity = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let UserLastLoginEntity = class UserLastLoginEntity {
15
+ };
16
+ exports.UserLastLoginEntity = UserLastLoginEntity;
17
+ __decorate([
18
+ (0, typeorm_1.PrimaryGeneratedColumn)(),
19
+ __metadata("design:type", Number)
20
+ ], UserLastLoginEntity.prototype, "id", void 0);
21
+ __decorate([
22
+ (0, typeorm_1.Column)({ type: 'int', nullable: false }),
23
+ __metadata("design:type", Number)
24
+ ], UserLastLoginEntity.prototype, "user_id", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)({ length: 250, nullable: true }),
27
+ __metadata("design:type", String)
28
+ ], UserLastLoginEntity.prototype, "email", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
31
+ __metadata("design:type", String)
32
+ ], UserLastLoginEntity.prototype, "first_name", void 0);
33
+ __decorate([
34
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
35
+ __metadata("design:type", String)
36
+ ], UserLastLoginEntity.prototype, "last_name", void 0);
37
+ __decorate([
38
+ (0, typeorm_1.Column)({
39
+ name: 'ip_address',
40
+ type: 'varchar',
41
+ length: 45,
42
+ nullable: true,
43
+ comment: 'IPv4 or IPv6',
44
+ }),
45
+ __metadata("design:type", String)
46
+ ], UserLastLoginEntity.prototype, "ip_address", void 0);
47
+ __decorate([
48
+ (0, typeorm_1.Column)({
49
+ name: 'ip_address_name',
50
+ type: 'varchar',
51
+ length: 255,
52
+ nullable: true,
53
+ comment: 'Location/Name of IP (e.g., Office, Home)',
54
+ }),
55
+ __metadata("design:type", String)
56
+ ], UserLastLoginEntity.prototype, "ip_address_name", void 0);
57
+ __decorate([
58
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
59
+ __metadata("design:type", String)
60
+ ], UserLastLoginEntity.prototype, "browser", void 0);
61
+ __decorate([
62
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
63
+ __metadata("design:type", String)
64
+ ], UserLastLoginEntity.prototype, "device_type", void 0);
65
+ __decorate([
66
+ (0, typeorm_1.Column)({ length: 100, nullable: true }),
67
+ __metadata("design:type", String)
68
+ ], UserLastLoginEntity.prototype, "operating_system", void 0);
69
+ __decorate([
70
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
71
+ __metadata("design:type", String)
72
+ ], UserLastLoginEntity.prototype, "user_agent", void 0);
73
+ __decorate([
74
+ (0, typeorm_1.Column)({ length: 255, nullable: true }),
75
+ __metadata("design:type", String)
76
+ ], UserLastLoginEntity.prototype, "location", void 0);
77
+ __decorate([
78
+ (0, typeorm_1.Column)({
79
+ name: 'module_id',
80
+ type: 'int',
81
+ nullable: true,
82
+ }),
83
+ __metadata("design:type", Number)
84
+ ], UserLastLoginEntity.prototype, "module_id", void 0);
85
+ __decorate([
86
+ (0, typeorm_1.Column)({
87
+ name: 'login_status',
88
+ type: 'enum',
89
+ enum: ['success', 'failed', 'blocked'],
90
+ default: 'success',
91
+ }),
92
+ __metadata("design:type", String)
93
+ ], UserLastLoginEntity.prototype, "login_status", void 0);
94
+ __decorate([
95
+ (0, typeorm_1.Column)({
96
+ type: 'text',
97
+ nullable: true,
98
+ comment: 'Reason for failure or blocking (e.g., IP not whitelisted, wrong password)',
99
+ }),
100
+ __metadata("design:type", String)
101
+ ], UserLastLoginEntity.prototype, "failure_reason", void 0);
102
+ __decorate([
103
+ (0, typeorm_1.Column)({ type: 'bigint', nullable: true }),
104
+ __metadata("design:type", Number)
105
+ ], UserLastLoginEntity.prototype, "session_duration_ms", void 0);
106
+ __decorate([
107
+ (0, typeorm_1.Column)({ type: 'json', nullable: true }),
108
+ __metadata("design:type", Object)
109
+ ], UserLastLoginEntity.prototype, "metadata", void 0);
110
+ __decorate([
111
+ (0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
112
+ __metadata("design:type", Date)
113
+ ], UserLastLoginEntity.prototype, "login_time", void 0);
114
+ __decorate([
115
+ (0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
116
+ __metadata("design:type", Date)
117
+ ], UserLastLoginEntity.prototype, "logout_time", void 0);
118
+ __decorate([
119
+ (0, typeorm_1.CreateDateColumn)({ type: 'timestamp' }),
120
+ __metadata("design:type", Date)
121
+ ], UserLastLoginEntity.prototype, "created_at", void 0);
122
+ __decorate([
123
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
124
+ __metadata("design:type", Number)
125
+ ], UserLastLoginEntity.prototype, "created_by", void 0);
126
+ __decorate([
127
+ (0, typeorm_1.UpdateDateColumn)({ type: 'timestamp', nullable: true }),
128
+ __metadata("design:type", Date)
129
+ ], UserLastLoginEntity.prototype, "updated_at", void 0);
130
+ __decorate([
131
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
132
+ __metadata("design:type", Number)
133
+ ], UserLastLoginEntity.prototype, "updated_by", void 0);
134
+ __decorate([
135
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
136
+ __metadata("design:type", Number)
137
+ ], UserLastLoginEntity.prototype, "deleted_by", void 0);
138
+ __decorate([
139
+ (0, typeorm_1.DeleteDateColumn)({ type: 'timestamp', nullable: true }),
140
+ __metadata("design:type", Date)
141
+ ], UserLastLoginEntity.prototype, "deleted_at", void 0);
142
+ exports.UserLastLoginEntity = UserLastLoginEntity = __decorate([
143
+ (0, typeorm_1.Entity)('tbl_user_last_login'),
144
+ (0, typeorm_1.Index)(['user_id']),
145
+ (0, typeorm_1.Index)(['login_time']),
146
+ (0, typeorm_1.Unique)(['user_id'])
147
+ ], UserLastLoginEntity);
@@ -0,0 +1,48 @@
1
+ export declare enum EmployeeType {
2
+ PERMANENT = "PERMANENT",
3
+ CONTRACT = "CONTRACT",
4
+ INTERN = "INTERN",
5
+ CONSULTANT = "CONSULTANT"
6
+ }
7
+ export declare enum EmployeeStatus {
8
+ ACTIVE = "ACTIVE",
9
+ INACTIVE = "INACTIVE",
10
+ ON_LEAVE = "ON_LEAVE"
11
+ }
12
+ export declare enum WeekOffType {
13
+ FIXED = "FIXED",
14
+ ROTATIONAL = "ROTATIONAL",
15
+ FLEXIBLE = "FLEXIBLE"
16
+ }
17
+ export declare enum WeekOffBasedOn {
18
+ WORK_LOCATION = "WORK_LOCATION",
19
+ SHIFT = "SHIFT"
20
+ }
21
+ export declare class EmployeeWorkProfileEntity {
22
+ id: number;
23
+ employee_id: number;
24
+ department_id: number;
25
+ designation_id: number;
26
+ employee_type: EmployeeType;
27
+ employee_status?: EmployeeStatus;
28
+ shift_id: number;
29
+ role_id: number;
30
+ agency?: string;
31
+ reporting_manager_id?: number | null;
32
+ l2_manager_id?: number | null;
33
+ joining_date: Date;
34
+ resignation_date?: Date;
35
+ branch_id: number;
36
+ dispatch_center_id: number;
37
+ cost_center_id: number;
38
+ week_off_type: WeekOffType;
39
+ week_off_based_on: WeekOffBasedOn;
40
+ bioMetricCode: string;
41
+ linkedInProfile: string;
42
+ created_at: Date;
43
+ created_by?: number;
44
+ updated_at?: Date;
45
+ updated_by?: number;
46
+ deleted_by?: number;
47
+ deleted_at?: Date;
48
+ }