@tomei/sso 0.62.0 → 0.64.0-dev.1

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.
Files changed (177) hide show
  1. package/.commitlintrc.json +22 -22
  2. package/.gitlab-ci.yml +16 -16
  3. package/.husky/commit-msg +9 -15
  4. package/.husky/pre-commit +7 -7
  5. package/.prettierrc +4 -4
  6. package/Jenkinsfile +57 -57
  7. package/README.md +23 -23
  8. package/__tests__/unit/components/group/group.spec.ts +79 -79
  9. package/__tests__/unit/components/group-object-privilege/group-object-privilege.spec.ts +88 -88
  10. package/__tests__/unit/components/group-privilege/group-privilege.spec.ts +68 -68
  11. package/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts +66 -66
  12. package/__tests__/unit/components/group-system-access/group-system-access.spec.ts +83 -83
  13. package/__tests__/unit/components/login-user/l.spec.ts +746 -746
  14. package/__tests__/unit/components/login-user/login.spec.ts +1164 -1164
  15. package/__tests__/unit/components/password-hash/password-hash.service.spec.ts +31 -31
  16. package/__tests__/unit/components/system/system.spec.ts +254 -254
  17. package/__tests__/unit/components/system-privilege/system-privilege.spec.ts +83 -83
  18. package/__tests__/unit/components/user-group/user-group.spec.ts +86 -86
  19. package/__tests__/unit/components/user-object-privilege/user-object-privilege.spec.ts +78 -78
  20. package/__tests__/unit/components/user-privilege/user-privilege.spec.ts +72 -72
  21. package/__tests__/unit/components/user-system-access/user-system-access.spec.ts +89 -89
  22. package/__tests__/unit/redis-client/redis.service.spec.ts +23 -23
  23. package/__tests__/unit/session/session.service.spec.ts +47 -47
  24. package/__tests__/unit/system-privilege/system-privilage.spec.ts +91 -91
  25. package/create-sso-user.sql +39 -39
  26. package/dist/__tests__/unit/components/group-object-privilege/group-object-privilege.spec.js +1 -1
  27. package/dist/__tests__/unit/components/group-object-privilege/group-object-privilege.spec.js.map +1 -1
  28. package/dist/__tests__/unit/components/group-privilege/group-privilege.spec.js +2 -2
  29. package/dist/__tests__/unit/components/group-privilege/group-privilege.spec.js.map +1 -1
  30. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.d.ts +1 -0
  31. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js +71 -0
  32. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js.map +1 -0
  33. package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js +2 -2
  34. package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js.map +1 -1
  35. package/dist/__tests__/unit/components/login-user/login-user.spec.d.ts +0 -0
  36. package/dist/__tests__/unit/components/login-user/login-user.spec.js +6 -0
  37. package/dist/__tests__/unit/components/login-user/login-user.spec.js.map +1 -0
  38. package/dist/__tests__/unit/components/system/system.spec.js +4 -4
  39. package/dist/__tests__/unit/components/system/system.spec.js.map +1 -1
  40. package/dist/__tests__/unit/session/session.service.spec.js +2 -2
  41. package/dist/__tests__/unit/session/session.service.spec.js.map +1 -1
  42. package/dist/src/components/login-history/index.d.ts +1 -0
  43. package/dist/src/components/login-history/index.js +1 -0
  44. package/dist/src/components/login-history/index.js.map +1 -1
  45. package/dist/src/components/login-history/login-history.repository.d.ts +2 -2
  46. package/dist/src/components/login-history/login-history.repository.js.map +1 -1
  47. package/dist/src/components/login-user/interfaces/user-info.interface.d.ts +1 -0
  48. package/dist/src/components/login-user/login-user.js +1 -0
  49. package/dist/src/components/login-user/login-user.js.map +1 -1
  50. package/dist/src/components/login-user/user.d.ts +28 -3
  51. package/dist/src/components/login-user/user.js +363 -25
  52. package/dist/src/components/login-user/user.js.map +1 -1
  53. package/dist/src/components/user-system-access/user-system-access.js +1 -1
  54. package/dist/src/components/user-system-access/user-system-access.js.map +1 -1
  55. package/dist/src/models/login-history.entity.d.ts +2 -2
  56. package/dist/src/models/login-history.entity.js +13 -13
  57. package/dist/src/models/login-history.entity.js.map +1 -1
  58. package/dist/src/models/user.entity.d.ts +1 -0
  59. package/dist/src/models/user.entity.js +8 -0
  60. package/dist/src/models/user.entity.js.map +1 -1
  61. package/dist/tsconfig.tsbuildinfo +1 -1
  62. package/eslint.config.mjs +58 -58
  63. package/jest.config.js +14 -14
  64. package/migrations/20240314080602-create-user-table.js +124 -124
  65. package/migrations/20240314080603-create-user-group-table.js +85 -85
  66. package/migrations/20240314080604-create-user-user-group-table.js +55 -55
  67. package/migrations/20240314080605-create-login-history-table.js +53 -53
  68. package/migrations/20240527064925-create-system-table.js +78 -78
  69. package/migrations/20240527064926-create-system-privilege-table.js +71 -71
  70. package/migrations/20240527065342-create-group-table.js +93 -93
  71. package/migrations/20240527065633-create-group-reporting-user-table.js +76 -76
  72. package/migrations/20240528011551-create-group-system-access-table.js +72 -72
  73. package/migrations/20240528023018-user-system-access-table.js +75 -75
  74. package/migrations/20240528032229-user-privilege-table.js +76 -76
  75. package/migrations/20240528063003-create-group-privilege-table.js +76 -76
  76. package/migrations/20240528063051-create-group-object-privilege-table.js +84 -84
  77. package/migrations/20240528063107-create-user-object-privilege-table.js +84 -84
  78. package/migrations/20240528063108-create-api-key-table.js +85 -85
  79. package/migrations/20241104104802-create-building-table.js +95 -95
  80. package/migrations/20250108091132-add-area-manager-user-id-to-building-table.js +14 -14
  81. package/migrations/20250108091133-add-passcode-to-user-table.js +36 -36
  82. package/migrations/20250210115636-create-user-reporting-hierarchy.js +76 -76
  83. package/migrations/20250326043818-crate-user-password-history.js +42 -42
  84. package/migrations/20250610070720-added-MFBypassYN-to-sso-user.js +30 -0
  85. package/migrations/20250805085707-add-bulk-approval-code-to-sso-user.js +29 -0
  86. package/package.json +87 -90
  87. package/sampledotenv +7 -7
  88. package/src/components/login-history/index.ts +1 -0
  89. package/src/components/login-history/login-history.repository.ts +4 -4
  90. package/src/components/login-history/login-history.ts +124 -0
  91. package/src/components/login-user/interfaces/user-info.interface.ts +1 -0
  92. package/src/components/login-user/login-user.ts +1 -0
  93. package/src/components/login-user/user.ts +441 -27
  94. package/src/components/user-system-access/user-system-access.ts +1 -1
  95. package/src/interfaces/login-history-search-attr.interface.ts +8 -0
  96. package/src/interfaces/login-history.interface.ts +11 -0
  97. package/src/models/login-history.entity.ts +2 -2
  98. package/src/models/user.entity.ts +7 -0
  99. package/tsconfig.build.json +5 -5
  100. package/tsconfig.json +23 -23
  101. package/coverage/clover.xml +0 -1452
  102. package/coverage/coverage-final.json +0 -47
  103. package/coverage/lcov-report/base.css +0 -224
  104. package/coverage/lcov-report/block-navigation.js +0 -87
  105. package/coverage/lcov-report/components/group/group.repository.ts.html +0 -118
  106. package/coverage/lcov-report/components/group/group.ts.html +0 -328
  107. package/coverage/lcov-report/components/group/index.html +0 -131
  108. package/coverage/lcov-report/components/group-object-privilege/group-object-privilege.repository.ts.html +0 -118
  109. package/coverage/lcov-report/components/group-object-privilege/group-object-privilege.ts.html +0 -322
  110. package/coverage/lcov-report/components/group-object-privilege/index.html +0 -131
  111. package/coverage/lcov-report/components/group-privilege/group-privilege.repository.ts.html +0 -118
  112. package/coverage/lcov-report/components/group-privilege/group-privilege.ts.html +0 -304
  113. package/coverage/lcov-report/components/group-privilege/index.html +0 -131
  114. package/coverage/lcov-report/components/group-reporting-user/group-reporting-user.repository.ts.html +0 -118
  115. package/coverage/lcov-report/components/group-reporting-user/group-reporting-user.ts.html +0 -328
  116. package/coverage/lcov-report/components/group-reporting-user/index.html +0 -131
  117. package/coverage/lcov-report/components/group-system-access/group-system-access.repository.ts.html +0 -118
  118. package/coverage/lcov-report/components/group-system-access/group-system-access.ts.html +0 -310
  119. package/coverage/lcov-report/components/group-system-access/index.html +0 -131
  120. package/coverage/lcov-report/components/login-history/index.html +0 -116
  121. package/coverage/lcov-report/components/login-history/login-history.repository.ts.html +0 -118
  122. package/coverage/lcov-report/components/login-user/index.html +0 -131
  123. package/coverage/lcov-report/components/login-user/login-user.ts.html +0 -5008
  124. package/coverage/lcov-report/components/login-user/user.repository.ts.html +0 -118
  125. package/coverage/lcov-report/components/password-hash/index.html +0 -116
  126. package/coverage/lcov-report/components/password-hash/password-hash.service.ts.html +0 -127
  127. package/coverage/lcov-report/components/system/index.html +0 -131
  128. package/coverage/lcov-report/components/system/system.repository.ts.html +0 -118
  129. package/coverage/lcov-report/components/system/system.ts.html +0 -910
  130. package/coverage/lcov-report/components/system-privilege/index.html +0 -131
  131. package/coverage/lcov-report/components/system-privilege/system-privilege.repository.ts.html +0 -121
  132. package/coverage/lcov-report/components/system-privilege/system-privilege.ts.html +0 -391
  133. package/coverage/lcov-report/components/user-group/index.html +0 -131
  134. package/coverage/lcov-report/components/user-group/user-group.repository.ts.html +0 -118
  135. package/coverage/lcov-report/components/user-group/user-group.ts.html +0 -355
  136. package/coverage/lcov-report/components/user-object-privilege/index.html +0 -131
  137. package/coverage/lcov-report/components/user-object-privilege/user-object-privilege.repository.ts.html +0 -118
  138. package/coverage/lcov-report/components/user-object-privilege/user-object-privilege.ts.html +0 -313
  139. package/coverage/lcov-report/components/user-privilege/index.html +0 -131
  140. package/coverage/lcov-report/components/user-privilege/user-privilege.repository.ts.html +0 -118
  141. package/coverage/lcov-report/components/user-privilege/user-privilege.ts.html +0 -307
  142. package/coverage/lcov-report/components/user-system-access/index.html +0 -131
  143. package/coverage/lcov-report/components/user-system-access/user-system-access.repository.ts.html +0 -118
  144. package/coverage/lcov-report/components/user-system-access/user-system-access.ts.html +0 -313
  145. package/coverage/lcov-report/enum/group-type.enum.ts.html +0 -109
  146. package/coverage/lcov-report/enum/index.html +0 -161
  147. package/coverage/lcov-report/enum/index.ts.html +0 -94
  148. package/coverage/lcov-report/enum/user-status.enum.ts.html +0 -106
  149. package/coverage/lcov-report/enum/yn.enum.ts.html +0 -97
  150. package/coverage/lcov-report/favicon.png +0 -0
  151. package/coverage/lcov-report/index.html +0 -371
  152. package/coverage/lcov-report/models/group-object-privilege.entity.ts.html +0 -334
  153. package/coverage/lcov-report/models/group-privilege.entity.ts.html +0 -316
  154. package/coverage/lcov-report/models/group-reporting-user.entity.ts.html +0 -340
  155. package/coverage/lcov-report/models/group-system-access.entity.ts.html +0 -325
  156. package/coverage/lcov-report/models/group.entity.ts.html +0 -436
  157. package/coverage/lcov-report/models/index.html +0 -311
  158. package/coverage/lcov-report/models/login-history.entity.ts.html +0 -253
  159. package/coverage/lcov-report/models/staff.entity.ts.html +0 -412
  160. package/coverage/lcov-report/models/system-privilege.entity.ts.html +0 -355
  161. package/coverage/lcov-report/models/system.entity.ts.html +0 -424
  162. package/coverage/lcov-report/models/user-group.entity.ts.html +0 -355
  163. package/coverage/lcov-report/models/user-object-privilege.entity.ts.html +0 -331
  164. package/coverage/lcov-report/models/user-privilege.entity.ts.html +0 -316
  165. package/coverage/lcov-report/models/user-system-access.entity.ts.html +0 -316
  166. package/coverage/lcov-report/models/user.entity.ts.html +0 -523
  167. package/coverage/lcov-report/prettify.css +0 -1
  168. package/coverage/lcov-report/prettify.js +0 -2
  169. package/coverage/lcov-report/redis-client/index.html +0 -116
  170. package/coverage/lcov-report/redis-client/redis.service.ts.html +0 -241
  171. package/coverage/lcov-report/session/index.html +0 -116
  172. package/coverage/lcov-report/session/session.service.ts.html +0 -247
  173. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  174. package/coverage/lcov-report/sorter.js +0 -196
  175. package/coverage/lcov.info +0 -2490
  176. package/coverage/test-report.xml +0 -129
  177. package/sonar-project.properties +0 -23
@@ -49,6 +49,7 @@ export class User extends UserBase {
49
49
  private _LastLoginAt: Date;
50
50
  private _MFAEnabled: number;
51
51
  private _MFAConfig: string;
52
+ private _MFABypassYN: string;
52
53
  private _RecoveryEmail: string;
53
54
  private _FailedLoginAttemptCount: number;
54
55
  private _LastFailedLoginAt: Date;
@@ -162,6 +163,14 @@ export class User extends UserBase {
162
163
  this._MFAConfig = value;
163
164
  }
164
165
 
166
+ get MFABypassYN(): string {
167
+ return this._MFABypassYN;
168
+ }
169
+
170
+ private set MFABypassYN(value: string) {
171
+ this._MFABypassYN = value;
172
+ }
173
+
165
174
  get RecoveryEmail(): string {
166
175
  return this._RecoveryEmail;
167
176
  }
@@ -296,6 +305,7 @@ export class User extends UserBase {
296
305
  this.LastLoginAt = userInfo.LastLoginAt;
297
306
  this.MFAEnabled = userInfo.MFAEnabled;
298
307
  this.MFAConfig = userInfo.MFAConfig;
308
+ this.MFABypassYN = userInfo.MFABypassYN;
299
309
  this.RecoveryEmail = userInfo.RecoveryEmail;
300
310
  this.FailedLoginAttemptCount = userInfo.FailedLoginAttemptCount;
301
311
  this.LastFailedLoginAt = userInfo.LastFailedLoginAt;
@@ -352,6 +362,7 @@ export class User extends UserBase {
352
362
  LastLoginAt: user.LastLoginAt,
353
363
  MFAEnabled: user.MFAEnabled,
354
364
  MFAConfig: user.MFAConfig,
365
+ MFABypassYN: user.MFABypassYN,
355
366
  RecoveryEmail: user.RecoveryEmail,
356
367
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
357
368
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -416,6 +427,7 @@ export class User extends UserBase {
416
427
  LastLoginAt: user.LastLoginAt,
417
428
  MFAEnabled: user.MFAEnabled,
418
429
  MFAConfig: user.MFAConfig,
430
+ MFABypassYN: user.MFABypassYN,
419
431
  RecoveryEmail: user.RecoveryEmail,
420
432
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
421
433
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -510,6 +522,7 @@ export class User extends UserBase {
510
522
  LastLoginAt: user.LastLoginAt,
511
523
  MFAEnabled: user.MFAEnabled,
512
524
  MFAConfig: user.MFAConfig,
525
+ MFABypassYN: user.MFABypassYN,
513
526
  RecoveryEmail: user.RecoveryEmail,
514
527
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
515
528
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -547,13 +560,11 @@ export class User extends UserBase {
547
560
  this.UpdatedAt = userAttr.UpdatedAt;
548
561
  this.staffs = userAttr.staffs;
549
562
  } else {
550
- console.error('User not found for email:', email);
551
563
  throw new ClassError('User', 'UserErrMsg0X', 'Invalid Credentials');
552
564
  }
553
565
  }
554
566
 
555
567
  if (this.ObjectId && this.Email !== email) {
556
- console.error('Email mismatch:', this.Email, email);
557
568
  throw new Error('Invalid credentials.');
558
569
  }
559
570
 
@@ -574,8 +585,7 @@ export class User extends UserBase {
574
585
  },
575
586
  });
576
587
  if (!system) {
577
- console.error('Invalid system code:', systemCode);
578
- throw new Error('Invalid credentials.');
588
+ throw new Error('Access denied: invalid or unauthorized system.');
579
589
  }
580
590
 
581
591
  // 1.5: Instantiate new PasswordHashService object and call PasswordHashService.verify method to check whether the param.Password is correct.
@@ -586,7 +596,6 @@ export class User extends UserBase {
586
596
  this.Password,
587
597
  );
588
598
  if (!isPasswordValid) {
589
- console.error('Invalid password for user:', this.UserId);
590
599
  throw new Error('Invalid credentials.');
591
600
  }
592
601
 
@@ -605,14 +614,14 @@ export class User extends UserBase {
605
614
  await User.releaseLock(this.UserId, dbTransaction);
606
615
  this.Status = UserStatus.ACTIVE;
607
616
  } else {
608
- console.error('User is still locked:', this.UserId);
609
- throw new Error('Invalid credentials.');
617
+ throw new Error(
618
+ 'Your account has been locked. Please contact the administrator for assistance.',
619
+ );
610
620
  }
611
621
  }
612
622
  } catch (error) {
613
623
  await this.incrementFailedLoginAttemptCount(dbTransaction);
614
- console.error('Login failed for user:', this.UserId, error);
615
- throw new Error('Invalid credentials.');
624
+ throw error;
616
625
  }
617
626
 
618
627
  // 2.1: Call alertNewLogin to check whether the ip used is new ip and alert the user if it's new.
@@ -667,7 +676,6 @@ export class User extends UserBase {
667
676
  ApplicationConfig.getComponentConfigValue('sessionName');
668
677
 
669
678
  if (!sessionName) {
670
- console.error('Session name is not set in the configuration');
671
679
  throw new Error('Session name is not set in the configuration');
672
680
  }
673
681
 
@@ -759,7 +767,6 @@ export class User extends UserBase {
759
767
  },
760
768
  );
761
769
  }
762
- console.error('Login failed:', error);
763
770
  throw error;
764
771
  }
765
772
  }
@@ -798,7 +805,7 @@ export class User extends UserBase {
798
805
  dbTransaction,
799
806
  });
800
807
 
801
- outer: for (const usergroup of userGroups) {
808
+ for (const usergroup of userGroups) {
802
809
  const group = usergroup.Group;
803
810
  const groupSystemAccess = await User.getInheritedSystemAccess(
804
811
  dbTransaction,
@@ -808,7 +815,7 @@ export class User extends UserBase {
808
815
  for (const system of groupSystemAccess) {
809
816
  if (system.SystemCode === systemCode) {
810
817
  isUserHaveAccess = true;
811
- break outer;
818
+ break;
812
819
  }
813
820
  }
814
821
  }
@@ -818,7 +825,6 @@ export class User extends UserBase {
818
825
  throw new Error("User don't have access to the system.");
819
826
  }
820
827
  } catch (error) {
821
- console.error('Error checking system access:', error);
822
828
  throw error;
823
829
  }
824
830
  }
@@ -1580,6 +1586,7 @@ export class User extends UserBase {
1580
1586
  LastLoginAt: null,
1581
1587
  MFAEnabled: null,
1582
1588
  MFAConfig: null,
1589
+ MFABypassYN: YN.No,
1583
1590
  RecoveryEmail: null,
1584
1591
  FailedLoginAttemptCount: 0,
1585
1592
  LastFailedLoginAt: null,
@@ -1727,7 +1734,7 @@ export class User extends UserBase {
1727
1734
  throw new ClassError(
1728
1735
  'LoginUser',
1729
1736
  'LoginUserErrMsg0X',
1730
- 'Invalid credentials.',
1737
+ 'Your account has been locked due to too many failed login attempts, please contact IT Support for instructions on how to unlock your account.',
1731
1738
  );
1732
1739
  }
1733
1740
  }
@@ -1813,7 +1820,7 @@ export class User extends UserBase {
1813
1820
 
1814
1821
  // Part 2: Retrieve Parent Group System Access If Applicable
1815
1822
  // 2.1 Check if Params.group.InheritParentSystemAccessYN is "Y" and Params.group.ParentGroupCode is not empty
1816
- if (group.InheritParentPrivilegeYN === 'Y' && group.ParentGroupCode) {
1823
+ if (group.InheritParentSystemAccessYN === 'Y' && group.ParentGroupCode) {
1817
1824
  const GroupCode = group.ParentGroupCode;
1818
1825
  const parentGroup = await User._GroupRepo.findByPk(
1819
1826
  GroupCode,
@@ -1998,15 +2005,31 @@ export class User extends UserBase {
1998
2005
  }
1999
2006
 
2000
2007
  // 3. Verify the mfaToken by calling speakeasy.totp.verify
2001
- const isVerified = await speakeasy.totp.verify({
2008
+ const isCurrentValid = await speakeasy.totp.verify({
2002
2009
  secret: userMFAConfig.totp.secret,
2003
2010
  encoding: 'base32',
2004
2011
  token: mfaToken,
2012
+ window: 0, // strict current time window
2005
2013
  });
2014
+ if (!isCurrentValid) {
2015
+ const isExpired = await speakeasy.totp.verify({
2016
+ secret: userMFAConfig.totp.secret,
2017
+ encoding: 'base32',
2018
+ token: mfaToken,
2019
+ window: 2, // allow slight leeway: previous or next 2 time steps
2020
+ });
2006
2021
 
2007
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
2008
- if (!isVerified) {
2009
- return false;
2022
+ if (isExpired) {
2023
+ return {
2024
+ success: false,
2025
+ reason: 'MFA token has expired. Please try again.',
2026
+ };
2027
+ } else {
2028
+ return {
2029
+ success: false,
2030
+ reason: 'Invalid MFA token. Check your authenticator app.',
2031
+ };
2032
+ }
2010
2033
  }
2011
2034
 
2012
2035
  user.MFAEnabled = 1;
@@ -2035,7 +2058,7 @@ export class User extends UserBase {
2035
2058
  const systemLogin = userSession.systemLogins.find(
2036
2059
  (e) => e.code === systemCode,
2037
2060
  );
2038
- return `${userId}:${systemLogin.sessionId}`;
2061
+ return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
2039
2062
  }
2040
2063
 
2041
2064
  // This method will verify 2FA codes
@@ -2073,15 +2096,31 @@ export class User extends UserBase {
2073
2096
  }
2074
2097
 
2075
2098
  // 3. Verify the mfaToken by calling speakeasy.totp.verify
2076
- const isVerified = await speakeasy.totp.verify({
2099
+ const isCurrentValid = await speakeasy.totp.verify({
2077
2100
  secret: userMFAConfig.totp.secret,
2078
2101
  encoding: 'base32',
2079
2102
  token: mfaToken,
2103
+ window: 0, // strict current time window
2080
2104
  });
2105
+ if (!isCurrentValid) {
2106
+ const isExpired = await speakeasy.totp.verify({
2107
+ secret: userMFAConfig.totp.secret,
2108
+ encoding: 'base32',
2109
+ token: mfaToken,
2110
+ window: 2, // allow slight leeway: previous or next 2 time steps
2111
+ });
2081
2112
 
2082
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
2083
- if (!isVerified) {
2084
- return false;
2113
+ if (isExpired) {
2114
+ return {
2115
+ success: false,
2116
+ reason: 'MFA token has expired. Please try again.',
2117
+ };
2118
+ } else {
2119
+ return {
2120
+ success: false,
2121
+ reason: 'Invalid MFA token. Check your authenticator app.',
2122
+ };
2123
+ }
2085
2124
  }
2086
2125
 
2087
2126
  // 5. Retrieve Session
@@ -2104,7 +2143,7 @@ export class User extends UserBase {
2104
2143
  const systemLogin = userSession.systemLogins.find(
2105
2144
  (e) => e.code === systemCode,
2106
2145
  );
2107
- return `${userId}:${systemLogin.sessionId}`;
2146
+ return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
2108
2147
  }
2109
2148
 
2110
2149
  public async bypass2FA(systemCode: string, dbTransaction: any) {
@@ -2147,7 +2186,10 @@ export class User extends UserBase {
2147
2186
  const systemLogin = userSession.systemLogins.find(
2148
2187
  (e) => e.code === systemCode,
2149
2188
  );
2150
- return `${this.UserId}:${systemLogin.sessionId}`;
2189
+ return {
2190
+ success: true,
2191
+ sessionId: `${this.UserId}:${systemLogin.sessionId}`,
2192
+ };
2151
2193
  } catch (error) {
2152
2194
  throw error;
2153
2195
  }
@@ -2610,6 +2652,7 @@ export class User extends UserBase {
2610
2652
  LastLoginAt: user.LastLoginAt,
2611
2653
  MFAEnabled: user.MFAEnabled,
2612
2654
  MFAConfig: user.MFAConfig,
2655
+ MFABypassYN: user.MFABypassYN,
2613
2656
  RecoveryEmail: user.RecoveryEmail,
2614
2657
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
2615
2658
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -2736,6 +2779,7 @@ export class User extends UserBase {
2736
2779
  LastLoginAt: user.LastLoginAt,
2737
2780
  MFAEnabled: user.MFAEnabled,
2738
2781
  MFAConfig: user.MFAConfig,
2782
+ MFABypassYN: user.MFABypassYN,
2739
2783
  RecoveryEmail: user.RecoveryEmail,
2740
2784
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
2741
2785
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -2799,6 +2843,7 @@ export class User extends UserBase {
2799
2843
  LastLoginAt: this.LastLoginAt,
2800
2844
  MFAEnabled: this.MFAEnabled,
2801
2845
  MFAConfig: this.MFAConfig,
2846
+ MFABypassYN: this.MFABypassYN,
2802
2847
  RecoveryEmail: this.RecoveryEmail,
2803
2848
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2804
2849
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2830,6 +2875,7 @@ export class User extends UserBase {
2830
2875
  LastLoginAt: this.LastLoginAt,
2831
2876
  MFAEnabled: this.MFAEnabled,
2832
2877
  MFAConfig: this.MFAConfig,
2878
+ MFABypassYN: this.MFABypassYN,
2833
2879
  RecoveryEmail: this.RecoveryEmail,
2834
2880
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2835
2881
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2920,6 +2966,7 @@ export class User extends UserBase {
2920
2966
  LastLoginAt: this.LastLoginAt,
2921
2967
  MFAEnabled: this.MFAEnabled,
2922
2968
  MFAConfig: this.MFAConfig,
2969
+ MFABypassYN: this.MFABypassYN,
2923
2970
  RecoveryEmail: this.RecoveryEmail,
2924
2971
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2925
2972
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2951,6 +2998,7 @@ export class User extends UserBase {
2951
2998
  LastLoginAt: this.LastLoginAt,
2952
2999
  MFAEnabled: this.MFAEnabled,
2953
3000
  MFAConfig: this.MFAConfig,
3001
+ MFABypassYN: this.MFABypassYN,
2954
3002
  RecoveryEmail: this.RecoveryEmail,
2955
3003
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2956
3004
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -3140,4 +3188,370 @@ export class User extends UserBase {
3140
3188
  throw error;
3141
3189
  }
3142
3190
  }
3191
+
3192
+ public async enable2FABypass(loginUser: LoginUser, dbTransaction: any) {
3193
+ try {
3194
+ // 1. Check if MFABypassYN is already enabled
3195
+ if (this.MFABypassYN === YN.Yes) {
3196
+ throw new ClassError(
3197
+ 'User',
3198
+ 'UserErrMsg0X',
3199
+ 'Bypass already enabled.',
3200
+ 'enable2FABypass',
3201
+ );
3202
+ }
3203
+
3204
+ // 2. Check if user has MANAGE_MFA privilege
3205
+ const systemCode =
3206
+ ApplicationConfig.getComponentConfigValue('system-code');
3207
+ const isPrivileged = await loginUser.checkPrivileges(
3208
+ systemCode,
3209
+ 'MANAGE_MFA',
3210
+ );
3211
+ if (!isPrivileged) {
3212
+ throw new ClassError(
3213
+ 'LoginUser',
3214
+ 'LoginUserErrMsg0X',
3215
+ 'You do not have permission to enable MFA bypass.',
3216
+ );
3217
+ }
3218
+
3219
+ const entityValueBefore: IUserAttr = {
3220
+ UserId: this.UserId,
3221
+ UserName: this.UserName,
3222
+ FullName: this.FullName,
3223
+ IDNo: this.IDNo,
3224
+ IDType: this.IDType,
3225
+ ContactNo: this.ContactNo,
3226
+ Email: this.Email,
3227
+ Password: this.Password,
3228
+ Status: this.Status,
3229
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3230
+ FirstLoginAt: this.FirstLoginAt,
3231
+ LastLoginAt: this.LastLoginAt,
3232
+ MFAEnabled: this.MFAEnabled,
3233
+ MFAConfig: this.MFAConfig,
3234
+ MFABypassYN: this.MFABypassYN,
3235
+ RecoveryEmail: this.RecoveryEmail,
3236
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3237
+ LastFailedLoginAt: this.LastFailedLoginAt,
3238
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3239
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3240
+ CreatedById: this.CreatedById,
3241
+ CreatedAt: this.CreatedAt,
3242
+ UpdatedById: this.UpdatedById,
3243
+ UpdatedAt: this.UpdatedAt,
3244
+ PasscodeHash: this.PasscodeHash,
3245
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3246
+ };
3247
+
3248
+ // 3. Update user record
3249
+ this.MFABypassYN = YN.Yes;
3250
+ this.MFAEnabled = 0;
3251
+ this.UpdatedAt = new Date();
3252
+ this.UpdatedById = loginUser.UserId;
3253
+
3254
+ await User._Repository.update(
3255
+ {
3256
+ MFABypassYN: this.MFABypassYN,
3257
+ MFAEnabled: this.MFAEnabled,
3258
+ UpdatedAt: this.UpdatedAt,
3259
+ UpdatedById: this.UpdatedById,
3260
+ },
3261
+ {
3262
+ where: {
3263
+ UserId: this.UserId,
3264
+ },
3265
+ transaction: dbTransaction,
3266
+ },
3267
+ );
3268
+
3269
+ const entityValueAfter: IUserAttr = {
3270
+ UserId: this.UserId,
3271
+ UserName: this.UserName,
3272
+ FullName: this.FullName,
3273
+ IDNo: this.IDNo,
3274
+ IDType: this.IDType,
3275
+ ContactNo: this.ContactNo,
3276
+ Email: this.Email,
3277
+ Password: this.Password,
3278
+ Status: this.Status,
3279
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3280
+ FirstLoginAt: this.FirstLoginAt,
3281
+ LastLoginAt: this.LastLoginAt,
3282
+ MFAEnabled: this.MFAEnabled,
3283
+ MFAConfig: this.MFAConfig,
3284
+ MFABypassYN: this.MFABypassYN,
3285
+ RecoveryEmail: this.RecoveryEmail,
3286
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3287
+ LastFailedLoginAt: this.LastFailedLoginAt,
3288
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3289
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3290
+ CreatedById: this.CreatedById,
3291
+ CreatedAt: this.CreatedAt,
3292
+ UpdatedById: this.UpdatedById,
3293
+ UpdatedAt: this.UpdatedAt,
3294
+ PasscodeHash: this.PasscodeHash,
3295
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3296
+ };
3297
+
3298
+ // Record update activity using Activity class create method.
3299
+ const activity = new Activity();
3300
+ activity.ActivityId = activity.createId();
3301
+ activity.Action = ActionEnum.UPDATE;
3302
+ activity.Description = `Enable 2FA Bypass For User ${this.Email}`;
3303
+ activity.EntityType = this.ObjectType;
3304
+ activity.EntityId = this.UserId.toString();
3305
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3306
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3307
+
3308
+ await activity.create(loginUser.ObjectId, dbTransaction);
3309
+ } catch (error) {
3310
+ throw error;
3311
+ }
3312
+ }
3313
+
3314
+ public async disable2FABypass(loginUser: LoginUser, dbTransaction: any) {
3315
+ try {
3316
+ // 1. Check if MFABypassYN is already disabled
3317
+ if (this.MFABypassYN === YN.No) {
3318
+ throw new ClassError(
3319
+ 'User',
3320
+ 'UserErrMsg0X',
3321
+ 'Bypass already disabled.',
3322
+ 'disable2FABypass',
3323
+ );
3324
+ }
3325
+
3326
+ // 2. Check if user has MANAGE_MFA privilege
3327
+ const systemCode =
3328
+ ApplicationConfig.getComponentConfigValue('system-code');
3329
+ const isPrivileged = await loginUser.checkPrivileges(
3330
+ systemCode,
3331
+ 'MANAGE_MFA',
3332
+ );
3333
+ if (!isPrivileged) {
3334
+ throw new ClassError(
3335
+ 'LoginUser',
3336
+ 'LoginUserErrMsg0X',
3337
+ 'You do not have permission to enable MFA bypass.',
3338
+ );
3339
+ }
3340
+
3341
+ const entityValueBefore: IUserAttr = {
3342
+ UserId: this.UserId,
3343
+ UserName: this.UserName,
3344
+ FullName: this.FullName,
3345
+ IDNo: this.IDNo,
3346
+ IDType: this.IDType,
3347
+ ContactNo: this.ContactNo,
3348
+ Email: this.Email,
3349
+ Password: this.Password,
3350
+ Status: this.Status,
3351
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3352
+ FirstLoginAt: this.FirstLoginAt,
3353
+ LastLoginAt: this.LastLoginAt,
3354
+ MFAEnabled: this.MFAEnabled,
3355
+ MFAConfig: this.MFAConfig,
3356
+ MFABypassYN: this.MFABypassYN,
3357
+ RecoveryEmail: this.RecoveryEmail,
3358
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3359
+ LastFailedLoginAt: this.LastFailedLoginAt,
3360
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3361
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3362
+ CreatedById: this.CreatedById,
3363
+ CreatedAt: this.CreatedAt,
3364
+ UpdatedById: this.UpdatedById,
3365
+ UpdatedAt: this.UpdatedAt,
3366
+ PasscodeHash: this.PasscodeHash,
3367
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3368
+ };
3369
+
3370
+ // 3. Update user record
3371
+ this.MFABypassYN = YN.No;
3372
+ this.MFAEnabled = 0;
3373
+ this.UpdatedAt = new Date();
3374
+ this.UpdatedById = loginUser.UserId;
3375
+
3376
+ await User._Repository.update(
3377
+ {
3378
+ MFABypassYN: this.MFABypassYN,
3379
+ MFAEnabled: this.MFAEnabled,
3380
+ UpdatedAt: this.UpdatedAt,
3381
+ UpdatedById: this.UpdatedById,
3382
+ },
3383
+ {
3384
+ where: {
3385
+ UserId: this.UserId,
3386
+ },
3387
+ transaction: dbTransaction,
3388
+ },
3389
+ );
3390
+
3391
+ const entityValueAfter: IUserAttr = {
3392
+ UserId: this.UserId,
3393
+ UserName: this.UserName,
3394
+ FullName: this.FullName,
3395
+ IDNo: this.IDNo,
3396
+ IDType: this.IDType,
3397
+ ContactNo: this.ContactNo,
3398
+ Email: this.Email,
3399
+ Password: this.Password,
3400
+ Status: this.Status,
3401
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3402
+ FirstLoginAt: this.FirstLoginAt,
3403
+ LastLoginAt: this.LastLoginAt,
3404
+ MFAEnabled: this.MFAEnabled,
3405
+ MFAConfig: this.MFAConfig,
3406
+ MFABypassYN: this.MFABypassYN,
3407
+ RecoveryEmail: this.RecoveryEmail,
3408
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3409
+ LastFailedLoginAt: this.LastFailedLoginAt,
3410
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3411
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3412
+ CreatedById: this.CreatedById,
3413
+ CreatedAt: this.CreatedAt,
3414
+ UpdatedById: this.UpdatedById,
3415
+ UpdatedAt: this.UpdatedAt,
3416
+ PasscodeHash: this.PasscodeHash,
3417
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3418
+ };
3419
+
3420
+ // Record update activity using Activity class create method.
3421
+ const activity = new Activity();
3422
+ activity.ActivityId = activity.createId();
3423
+ activity.Action = ActionEnum.UPDATE;
3424
+ activity.Description = `Disable 2FA Bypass For User ${this.Email}`;
3425
+ activity.EntityType = this.ObjectType;
3426
+ activity.EntityId = this.UserId.toString();
3427
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3428
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3429
+
3430
+ await activity.create(loginUser.ObjectId, dbTransaction);
3431
+ } catch (error) {
3432
+ throw error;
3433
+ }
3434
+ }
3435
+
3436
+ public async reset2FA(loginUser: LoginUser, dbTransaction: any) {
3437
+ try {
3438
+ // 1. Check if MFABypassYN is already disabled
3439
+ if (this.MFAEnabled === 0) {
3440
+ throw new ClassError(
3441
+ 'User',
3442
+ 'UserErrMsg0X',
3443
+ 'User not yet setup 2FA.',
3444
+ 'reset2FA',
3445
+ );
3446
+ }
3447
+
3448
+ // 2. Check if user has MANAGE_MFA privilege
3449
+ const systemCode =
3450
+ ApplicationConfig.getComponentConfigValue('system-code');
3451
+ const isPrivileged = await loginUser.checkPrivileges(
3452
+ systemCode,
3453
+ 'MANAGE_MFA',
3454
+ );
3455
+ if (!isPrivileged) {
3456
+ throw new ClassError(
3457
+ 'LoginUser',
3458
+ 'LoginUserErrMsg0X',
3459
+ 'You do not have permission to reset 2FA.',
3460
+ );
3461
+ }
3462
+
3463
+ const entityValueBefore: IUserAttr = {
3464
+ UserId: this.UserId,
3465
+ UserName: this.UserName,
3466
+ FullName: this.FullName,
3467
+ IDNo: this.IDNo,
3468
+ IDType: this.IDType,
3469
+ ContactNo: this.ContactNo,
3470
+ Email: this.Email,
3471
+ Password: this.Password,
3472
+ Status: this.Status,
3473
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3474
+ FirstLoginAt: this.FirstLoginAt,
3475
+ LastLoginAt: this.LastLoginAt,
3476
+ MFAEnabled: this.MFAEnabled,
3477
+ MFAConfig: this.MFAConfig,
3478
+ MFABypassYN: this.MFABypassYN,
3479
+ RecoveryEmail: this.RecoveryEmail,
3480
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3481
+ LastFailedLoginAt: this.LastFailedLoginAt,
3482
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3483
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3484
+ CreatedById: this.CreatedById,
3485
+ CreatedAt: this.CreatedAt,
3486
+ UpdatedById: this.UpdatedById,
3487
+ UpdatedAt: this.UpdatedAt,
3488
+ PasscodeHash: this.PasscodeHash,
3489
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3490
+ };
3491
+
3492
+ // 3. Update user record
3493
+ this.MFAEnabled = 0;
3494
+ this.MFABypassYN = YN.No;
3495
+ this.UpdatedAt = new Date();
3496
+ this.UpdatedById = loginUser.UserId;
3497
+
3498
+ await User._Repository.update(
3499
+ {
3500
+ MFAEnabled: this.MFAEnabled,
3501
+ MFABypassYN: this.MFABypassYN,
3502
+ UpdatedAt: this.UpdatedAt,
3503
+ UpdatedById: this.UpdatedById,
3504
+ },
3505
+ {
3506
+ where: {
3507
+ UserId: this.UserId,
3508
+ },
3509
+ transaction: dbTransaction,
3510
+ },
3511
+ );
3512
+
3513
+ const entityValueAfter: IUserAttr = {
3514
+ UserId: this.UserId,
3515
+ UserName: this.UserName,
3516
+ FullName: this.FullName,
3517
+ IDNo: this.IDNo,
3518
+ IDType: this.IDType,
3519
+ ContactNo: this.ContactNo,
3520
+ Email: this.Email,
3521
+ Password: this.Password,
3522
+ Status: this.Status,
3523
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3524
+ FirstLoginAt: this.FirstLoginAt,
3525
+ LastLoginAt: this.LastLoginAt,
3526
+ MFAEnabled: this.MFAEnabled,
3527
+ MFAConfig: this.MFAConfig,
3528
+ MFABypassYN: this.MFABypassYN,
3529
+ RecoveryEmail: this.RecoveryEmail,
3530
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3531
+ LastFailedLoginAt: this.LastFailedLoginAt,
3532
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3533
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3534
+ CreatedById: this.CreatedById,
3535
+ CreatedAt: this.CreatedAt,
3536
+ UpdatedById: this.UpdatedById,
3537
+ UpdatedAt: this.UpdatedAt,
3538
+ PasscodeHash: this.PasscodeHash,
3539
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3540
+ };
3541
+
3542
+ // Record update activity using Activity class create method.
3543
+ const activity = new Activity();
3544
+ activity.ActivityId = activity.createId();
3545
+ activity.Action = ActionEnum.UPDATE;
3546
+ activity.Description = `Reset 2FA for User ${this.Email}`;
3547
+ activity.EntityType = this.ObjectType;
3548
+ activity.EntityId = this.UserId.toString();
3549
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3550
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3551
+
3552
+ await activity.create(loginUser.ObjectId, dbTransaction);
3553
+ } catch (error) {
3554
+ throw error;
3555
+ }
3556
+ }
3143
3557
  }
@@ -249,7 +249,7 @@ export class UserSystemAccess extends ObjectBase {
249
249
  Status: UserStatus.ACTIVE,
250
250
  },
251
251
  as: 'User',
252
- attributes: ['UserId', 'FullName'],
252
+ attributes: ['UserId', 'FullName', 'Email'],
253
253
  },
254
254
  ],
255
255
  };
@@ -0,0 +1,8 @@
1
+ export interface ILoginHistorySearchAttr {
2
+ UserId?: number;
3
+ SystemCode?: string;
4
+ LoginStatus?: string;
5
+ OriginIP?: string;
6
+ DateFrom?: Date;
7
+ DateTo?: Date;
8
+ }