ecrs-auth-core 1.0.110 → 1.0.113

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.
@@ -28,8 +28,11 @@ let AuthCustomerController = class AuthCustomerController {
28
28
  const clientIp = this.getClientIp(request);
29
29
  const userAgent = request.get("user-agent") || "Unknown";
30
30
  const additionalData = this.extractClientData(request, userAgent);
31
- console.log(`📍 Customer Emplyee login attempt from IP: ${clientIp}, User-Agent: ${userAgent}`);
32
- const user = await this.authCustomerService.validateUser(body.email, body.password, clientIp);
31
+ const host = request.get("host") || "";
32
+ const hostParts = host.split(".");
33
+ const subdomain = hostParts.length > 2 ? hostParts[0] : null;
34
+ console.log(`📍 Customer Emplyee login attempt from IP: ${clientIp}, User-Agent: ${userAgent}, subdomain: ${subdomain ?? "none"}`);
35
+ const user = await this.authCustomerService.validateUser(body.email, body.password, clientIp, subdomain ?? undefined);
33
36
  if (!user) {
34
37
  await this.authCustomerService
35
38
  .saveLastLogin({ email: body.email }, clientIp, "failed", "Invalid credentials or IP not allowed", additionalData)
@@ -12,7 +12,7 @@ export declare class AuthCustomerService {
12
12
  private readonly employeeWorkProfileRepo;
13
13
  private uploadPhotoDir;
14
14
  constructor(jwtService: JwtService, options: AuthCustomerCoreOptions);
15
- validateUser(email: string, password: string, clientIp?: string): Promise<UserCustomer>;
15
+ validateUser(email: string, password: string, clientIp?: string, subdomain?: string): Promise<UserCustomer>;
16
16
  hasModuleAccess(userId: number, moduleId: number): Promise<boolean>;
17
17
  saveLastLogin(user: UserCustomer, clientIp: string, loginStatus?: "success" | "failed" | "blocked", failureReason?: string, additionalData?: {
18
18
  browser?: string;
@@ -63,5 +63,6 @@ export declare class AuthCustomerService {
63
63
  access_token: string;
64
64
  }>;
65
65
  findUserById(id: number): Promise<UserCustomer | null>;
66
+ getCustomerIdBySubdomain(subdomain: string): Promise<number | null>;
66
67
  findEcrsEmployeeInCustomer(ecrsEmployeeId: number): Promise<UserCustomer | null>;
67
68
  }
@@ -64,7 +64,7 @@ let AuthCustomerService = class AuthCustomerService {
64
64
  this.loginDetailsRepo = repositories.loginDetailsRepo || null;
65
65
  this.employeeWorkProfileRepo = repositories.employeeWorkProfileRepo || null;
66
66
  }
67
- async validateUser(email, password, clientIp) {
67
+ async validateUser(email, password, clientIp, subdomain) {
68
68
  if (!email || !password || password.length > 72 || email.length > 254) {
69
69
  throw new common_1.UnauthorizedException("Invalid credentials");
70
70
  }
@@ -82,8 +82,14 @@ let AuthCustomerService = class AuthCustomerService {
82
82
  if (!user || !isValid) {
83
83
  throw new common_1.UnauthorizedException("Invalid credentials");
84
84
  }
85
- if (clientIp && this.userLastLoginRepo) {
86
- // IP restriction check can be added here if needed
85
+ if (subdomain) {
86
+ const rows = await this.userRepo.query(`SELECT customer_id FROM public.tbl_super_customer_ets_setup_details WHERE subdomain = $1 LIMIT 1`, [subdomain]);
87
+ const customerId = rows[0]?.customer_id;
88
+ if (!customerId || customerId !== user.parentId) {
89
+ console.log(`❌ Domain not match: subdomain="${subdomain}" → customer_id=${customerId ?? "not found"}, user.parentId=${user.parentId}`);
90
+ throw new common_1.UnauthorizedException("Invalid domain");
91
+ }
92
+ console.log(`✅ Domain match: subdomain="${subdomain}" → customer_id=${customerId} matches user.parentId=${user.parentId}`);
87
93
  }
88
94
  return user;
89
95
  }
@@ -318,6 +324,10 @@ let AuthCustomerService = class AuthCustomerService {
318
324
  async findUserById(id) {
319
325
  return this.userRepo.findOne({ where: { id } });
320
326
  }
327
+ async getCustomerIdBySubdomain(subdomain) {
328
+ const rows = await this.userRepo.query(`SELECT customer_id FROM public.tbl_super_customer_ets_setup_details WHERE subdomain = $1 LIMIT 1`, [subdomain]);
329
+ return rows[0]?.customer_id ?? null;
330
+ }
321
331
  // In auth-customer.service.ts — add this new method
322
332
  async findEcrsEmployeeInCustomer(ecrsEmployeeId) {
323
333
  return this.userRepo.findOne({
@@ -82,8 +82,8 @@ let AuthService = class AuthService {
82
82
  const normalizedEmail = email.trim().toLowerCase();
83
83
  const whereClause = {
84
84
  email: (0, typeorm_1.ILike)(normalizedEmail),
85
- deletedBy: null,
86
- deletedAt: null,
85
+ deletedBy: (0, typeorm_1.IsNull)(),
86
+ deletedAt: (0, typeorm_1.IsNull)(),
87
87
  status: 1,
88
88
  };
89
89
  //role-id
@@ -100,7 +100,6 @@ let AuthService = class AuthService {
100
100
  else if (moduleId !== undefined && modulearray.includes(moduleId)) {
101
101
  whereClause.roleId = (0, typeorm_1.In)([1, 2, 3]);
102
102
  }
103
- console.log(`🔍 Validating user with email: ${normalizedEmail}, moduleId: ${moduleId}, whereClause:`, whereClause);
104
103
  const user = await this.userRepo.findOne({ where: whereClause });
105
104
  // Always run bcrypt.compare regardless of whether the user was found.
106
105
  // This prevents timing-based user enumeration: both paths take the same time.
@@ -108,13 +107,15 @@ let AuthService = class AuthService {
108
107
  // const DUMMY_HASH = '$2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012345';
109
108
  // const hashToCompare = user?.password ?? DUMMY_HASH;
110
109
  // const isValid = await bcrypt.compare(password, hashToCompare);
111
- console.log(`🔐 Comparing password for user ${user?.id}`);
112
- console.log(`🔐 Comparing password for user ${user?.password}`);
110
+ // console.log(`🔐 Comparing password for user ${user?.id}`);
111
+ // console.log(`🔐 Comparing password for user ${user?.password}`);
113
112
  const isValid = user
114
113
  ? await bcrypt.compare(password, user.password)
115
114
  : false;
116
115
  // Single generic message — never reveal whether the email exists
117
116
  if (!user || !isValid) {
117
+ console.log(`🔍 Validating user with email: ${normalizedEmail}, moduleId: ${moduleId}, whereClause:`, whereClause);
118
+ console.log(`🔐 Comparing password for user ${user?.id}`);
118
119
  throw new common_1.UnauthorizedException("Invalid credentials");
119
120
  }
120
121
  if (clientIp && this.ipRestrictionsRepo) {
@@ -4,13 +4,15 @@ declare const JwtCustomerStrategy_base: new (...args: any[]) => Strategy;
4
4
  export declare class JwtCustomerStrategy extends JwtCustomerStrategy_base {
5
5
  private readonly authCustomerService;
6
6
  constructor(authCustomerService: AuthCustomerService);
7
- validate(payload: any): Promise<{
7
+ validate(req: any, payload: any): Promise<{
8
8
  id: number;
9
9
  email: string;
10
10
  roleId: number;
11
11
  moduleId: any;
12
12
  is_ecrs_employee: boolean;
13
13
  referenceId: number;
14
+ customerIds: number[];
15
+ parentId: any;
14
16
  }>;
15
17
  }
16
18
  export {};
@@ -20,15 +20,55 @@ let JwtCustomerStrategy = class JwtCustomerStrategy extends (0, passport_1.Passp
20
20
  jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(),
21
21
  ignoreExpiration: false,
22
22
  secretOrKey: process.env.JWT_SECRET || "your-secret-key",
23
+ passReqToCallback: true,
23
24
  });
24
25
  this.authCustomerService = authCustomerService;
25
26
  }
26
- async validate(payload) {
27
- // 1. Find user in tbl_users_customer
27
+ async validate(req, payload) {
28
+ // 1. Subdomain check against tbl_super_customer_ets_setup_details
29
+ const host = req.get("host") || "";
30
+ const hostParts = host.split(".");
31
+ const subdomain = hostParts.length > 2 ? hostParts[0] : null;
32
+ if (subdomain) {
33
+ const customerId = await this.authCustomerService.getCustomerIdBySubdomain(subdomain);
34
+ if (!customerId) {
35
+ console.log(`❌ Domain not match: subdomain="${subdomain}" not found in setup details`);
36
+ throw new common_1.UnauthorizedException("INVALID_DOMAIN");
37
+ }
38
+ // Will be compared with user.parentId after user is loaded
39
+ payload._resolvedCustomerId = customerId;
40
+ console.log(`🔍 Subdomain "${subdomain}" → customer_id=${customerId}`);
41
+ }
42
+ // 2. Find user in tbl_users_customer
28
43
  const user = await this.authCustomerService.findUserById(payload.id);
29
44
  if (!user) {
30
45
  throw new common_1.UnauthorizedException("INVALID_USER");
31
46
  }
47
+ // 3a. Verify resolved customer_id matches user.parentId
48
+ if (payload._resolvedCustomerId !== undefined) {
49
+ if (user.roleId === 1) {
50
+ // Superadmin: skip domain restriction — _resolvedCustomerId is returned as parentId
51
+ console.log(`✅ Superadmin domain bypass: customer_id=${payload._resolvedCustomerId} accepted for superadmin user=${user.id}`);
52
+ }
53
+ else if (payload._resolvedCustomerId !== user.parentId &&
54
+ user.is_ecrs_employee !== true) {
55
+ console.log(`❌ Domain not match: customer_id=${payload._resolvedCustomerId} does not match user.parentId=${user.parentId}`);
56
+ throw new common_1.UnauthorizedException("INVALID_DOMAIN");
57
+ }
58
+ else if (user.is_ecrs_employee === true) {
59
+ const allowedIds = Array.isArray(user.customerIds)
60
+ ? user.customerIds
61
+ : [];
62
+ if (!allowedIds.includes(payload._resolvedCustomerId)) {
63
+ console.log(`❌ Domain not match for ECRS employee: customer_id=${payload._resolvedCustomerId} not found in user.customerIds=${JSON.stringify(allowedIds)}`);
64
+ throw new common_1.UnauthorizedException("INVALID_DOMAIN");
65
+ }
66
+ console.log(`✅ Domain match for ECRS employee: customer_id=${payload._resolvedCustomerId} found in user.customerIds=${JSON.stringify(allowedIds)}`);
67
+ }
68
+ else {
69
+ console.log(`✅ Domain match: customer_id=${payload._resolvedCustomerId} matches user.parentId=${user.parentId}`);
70
+ }
71
+ }
32
72
  // 2. Token version check — catches password change / logout-all
33
73
  if (user.token_version !== payload.tokenVersion) {
34
74
  console.warn(`⚠️ Token version mismatch for customer ${user.id}. ` +
@@ -68,6 +108,8 @@ let JwtCustomerStrategy = class JwtCustomerStrategy extends (0, passport_1.Passp
68
108
  moduleId: payload.moduleId ?? user.moduleId,
69
109
  is_ecrs_employee: user.is_ecrs_employee ?? false,
70
110
  referenceId: user.referenceId ?? null,
111
+ customerIds: user.customerIds ?? [],
112
+ parentId: payload._resolvedCustomerId ?? user.parentId,
71
113
  };
72
114
  }
73
115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecrs-auth-core",
3
- "version": "1.0.110",
3
+ "version": "1.0.113",
4
4
  "description": "Centralized authentication and authorization module for ECRS apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,13 +0,0 @@
1
- export declare class UserCustomerModuleAccess {
2
- id: number;
3
- customer_user_id: number;
4
- moduleId: number;
5
- accessLevel: string;
6
- status: number;
7
- permissions: string[];
8
- createdAt: Date;
9
- updatedAt: Date;
10
- createdBy: number;
11
- updatedBy?: number;
12
- isDeleted: number;
13
- }
@@ -1,76 +0,0 @@
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.UserCustomerModuleAccess = void 0;
13
- // src/entities/user-module-access.entity.ts
14
- const typeorm_1 = require("typeorm");
15
- let UserCustomerModuleAccess = class UserCustomerModuleAccess {
16
- };
17
- exports.UserCustomerModuleAccess = UserCustomerModuleAccess;
18
- __decorate([
19
- (0, typeorm_1.PrimaryGeneratedColumn)(),
20
- __metadata("design:type", Number)
21
- ], UserCustomerModuleAccess.prototype, "id", void 0);
22
- __decorate([
23
- (0, typeorm_1.Column)({ name: "customer_user_id" }),
24
- __metadata("design:type", Number)
25
- ], UserCustomerModuleAccess.prototype, "customer_user_id", void 0);
26
- __decorate([
27
- (0, typeorm_1.Column)({ name: "module_id" }),
28
- __metadata("design:type", Number)
29
- ], UserCustomerModuleAccess.prototype, "moduleId", void 0);
30
- __decorate([
31
- (0, typeorm_1.Column)({ name: "access_level", default: "view" }),
32
- __metadata("design:type", String)
33
- ], UserCustomerModuleAccess.prototype, "accessLevel", void 0);
34
- __decorate([
35
- (0, typeorm_1.Column)({ type: "smallint", default: 1 }),
36
- __metadata("design:type", Number)
37
- ], UserCustomerModuleAccess.prototype, "status", void 0);
38
- __decorate([
39
- (0, typeorm_1.Column)({
40
- name: "permissions",
41
- type: "json",
42
- nullable: true,
43
- }),
44
- __metadata("design:type", Array)
45
- ], UserCustomerModuleAccess.prototype, "permissions", void 0);
46
- __decorate([
47
- (0, typeorm_1.Column)({
48
- name: "created_at",
49
- type: "timestamp",
50
- default: () => "CURRENT_TIMESTAMP",
51
- }),
52
- __metadata("design:type", Date)
53
- ], UserCustomerModuleAccess.prototype, "createdAt", void 0);
54
- __decorate([
55
- (0, typeorm_1.Column)({
56
- name: "updated_at",
57
- type: "timestamp",
58
- default: () => "CURRENT_TIMESTAMP",
59
- }),
60
- __metadata("design:type", Date)
61
- ], UserCustomerModuleAccess.prototype, "updatedAt", void 0);
62
- __decorate([
63
- (0, typeorm_1.Column)({ name: "created_by" }),
64
- __metadata("design:type", Number)
65
- ], UserCustomerModuleAccess.prototype, "createdBy", void 0);
66
- __decorate([
67
- (0, typeorm_1.Column)({ name: "updated_by", nullable: true }),
68
- __metadata("design:type", Number)
69
- ], UserCustomerModuleAccess.prototype, "updatedBy", void 0);
70
- __decorate([
71
- (0, typeorm_1.Column)({ name: "is_deleted", type: "smallint", default: 0 }),
72
- __metadata("design:type", Number)
73
- ], UserCustomerModuleAccess.prototype, "isDeleted", void 0);
74
- exports.UserCustomerModuleAccess = UserCustomerModuleAccess = __decorate([
75
- (0, typeorm_1.Entity)({ name: "tbl_c_users_customer_module_access_new" })
76
- ], UserCustomerModuleAccess);