@tomei/sso 0.60.4-dev.1 → 0.60.4-dev.10

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 (44) hide show
  1. package/coverage/lcov-report/components/login-user/login-user.ts.html +80 -73
  2. package/dist/src/components/login-history/index.d.ts +1 -0
  3. package/dist/src/components/login-history/index.js +1 -0
  4. package/dist/src/components/login-history/index.js.map +1 -1
  5. package/dist/src/components/login-history/login-history.d.ts +23 -0
  6. package/dist/src/components/login-history/login-history.js +88 -0
  7. package/dist/src/components/login-history/login-history.js.map +1 -0
  8. package/dist/src/components/login-history/login-history.repository.d.ts +2 -2
  9. package/dist/src/components/login-history/login-history.repository.js.map +1 -1
  10. package/dist/src/components/login-user/interfaces/user-info.interface.d.ts +1 -0
  11. package/dist/src/components/login-user/login-user.js +1 -0
  12. package/dist/src/components/login-user/login-user.js.map +1 -1
  13. package/dist/src/components/login-user/user.d.ts +28 -3
  14. package/dist/src/components/login-user/user.js +356 -13
  15. package/dist/src/components/login-user/user.js.map +1 -1
  16. package/dist/src/components/user-system-access/user-system-access.js +1 -1
  17. package/dist/src/components/user-system-access/user-system-access.js.map +1 -1
  18. package/dist/src/interfaces/login-history-search-attr.interface.d.ts +8 -0
  19. package/dist/src/interfaces/login-history-search-attr.interface.js +3 -0
  20. package/dist/src/interfaces/login-history-search-attr.interface.js.map +1 -0
  21. package/dist/src/interfaces/login-history.interface.d.ts +11 -0
  22. package/dist/src/interfaces/login-history.interface.js +3 -0
  23. package/dist/src/interfaces/login-history.interface.js.map +1 -0
  24. package/dist/src/models/login-history.entity.d.ts +2 -2
  25. package/dist/src/models/login-history.entity.js +13 -13
  26. package/dist/src/models/login-history.entity.js.map +1 -1
  27. package/dist/src/models/user.entity.d.ts +1 -0
  28. package/dist/src/models/user.entity.js +8 -0
  29. package/dist/src/models/user.entity.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/migrations/20250610070720-added-MFBypassYN-to-sso-user.js +30 -0
  32. package/package.json +1 -1
  33. package/src/components/login-history/index.ts +1 -0
  34. package/src/components/login-history/login-history.repository.ts +4 -4
  35. package/src/components/login-history/login-history.ts +124 -0
  36. package/src/components/login-user/interfaces/user-info.interface.ts +1 -0
  37. package/src/components/login-user/login-user.ts +1 -0
  38. package/src/components/login-user/user.ts +434 -15
  39. package/src/components/user-system-access/user-system-access.ts +1 -1
  40. package/src/interfaces/login-history-search-attr.interface.ts +8 -0
  41. package/src/interfaces/login-history.interface.ts +11 -0
  42. package/src/models/login-history.entity.ts +2 -2
  43. package/src/models/user.entity.ts +7 -0
  44. 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,
@@ -572,7 +585,7 @@ export class User extends UserBase {
572
585
  },
573
586
  });
574
587
  if (!system) {
575
- throw new Error('Invalid credentials.');
588
+ throw new Error('Access denied: invalid or unauthorized system.');
576
589
  }
577
590
 
578
591
  // 1.5: Instantiate new PasswordHashService object and call PasswordHashService.verify method to check whether the param.Password is correct.
@@ -601,12 +614,14 @@ export class User extends UserBase {
601
614
  await User.releaseLock(this.UserId, dbTransaction);
602
615
  this.Status = UserStatus.ACTIVE;
603
616
  } else {
604
- throw new Error('Invalid credentials.');
617
+ throw new Error(
618
+ 'Your account has been locked. Please contact the administrator for assistance.',
619
+ );
605
620
  }
606
621
  }
607
622
  } catch (error) {
608
623
  await this.incrementFailedLoginAttemptCount(dbTransaction);
609
- throw new Error('Invalid credentials.');
624
+ throw error;
610
625
  }
611
626
 
612
627
  // 2.1: Call alertNewLogin to check whether the ip used is new ip and alert the user if it's new.
@@ -1571,6 +1586,7 @@ export class User extends UserBase {
1571
1586
  LastLoginAt: null,
1572
1587
  MFAEnabled: null,
1573
1588
  MFAConfig: null,
1589
+ MFABypassYN: YN.No,
1574
1590
  RecoveryEmail: null,
1575
1591
  FailedLoginAttemptCount: 0,
1576
1592
  LastFailedLoginAt: null,
@@ -1718,7 +1734,7 @@ export class User extends UserBase {
1718
1734
  throw new ClassError(
1719
1735
  'LoginUser',
1720
1736
  'LoginUserErrMsg0X',
1721
- '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.',
1722
1738
  );
1723
1739
  }
1724
1740
  }
@@ -1989,15 +2005,31 @@ export class User extends UserBase {
1989
2005
  }
1990
2006
 
1991
2007
  // 3. Verify the mfaToken by calling speakeasy.totp.verify
1992
- const isVerified = await speakeasy.totp.verify({
2008
+ const isCurrentValid = await speakeasy.totp.verify({
1993
2009
  secret: userMFAConfig.totp.secret,
1994
2010
  encoding: 'base32',
1995
2011
  token: mfaToken,
2012
+ window: 0, // strict current time window
1996
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
+ });
1997
2021
 
1998
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
1999
- if (!isVerified) {
2000
- 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
+ }
2001
2033
  }
2002
2034
 
2003
2035
  user.MFAEnabled = 1;
@@ -2026,7 +2058,7 @@ export class User extends UserBase {
2026
2058
  const systemLogin = userSession.systemLogins.find(
2027
2059
  (e) => e.code === systemCode,
2028
2060
  );
2029
- return `${userId}:${systemLogin.sessionId}`;
2061
+ return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
2030
2062
  }
2031
2063
 
2032
2064
  // This method will verify 2FA codes
@@ -2064,15 +2096,31 @@ export class User extends UserBase {
2064
2096
  }
2065
2097
 
2066
2098
  // 3. Verify the mfaToken by calling speakeasy.totp.verify
2067
- const isVerified = await speakeasy.totp.verify({
2099
+ const isCurrentValid = await speakeasy.totp.verify({
2068
2100
  secret: userMFAConfig.totp.secret,
2069
2101
  encoding: 'base32',
2070
2102
  token: mfaToken,
2103
+ window: 0, // strict current time window
2071
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
+ });
2072
2112
 
2073
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
2074
- if (!isVerified) {
2075
- 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
+ }
2076
2124
  }
2077
2125
 
2078
2126
  // 5. Retrieve Session
@@ -2095,7 +2143,7 @@ export class User extends UserBase {
2095
2143
  const systemLogin = userSession.systemLogins.find(
2096
2144
  (e) => e.code === systemCode,
2097
2145
  );
2098
- return `${userId}:${systemLogin.sessionId}`;
2146
+ return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
2099
2147
  }
2100
2148
 
2101
2149
  public async bypass2FA(systemCode: string, dbTransaction: any) {
@@ -2138,7 +2186,10 @@ export class User extends UserBase {
2138
2186
  const systemLogin = userSession.systemLogins.find(
2139
2187
  (e) => e.code === systemCode,
2140
2188
  );
2141
- return `${this.UserId}:${systemLogin.sessionId}`;
2189
+ return {
2190
+ success: true,
2191
+ sessionId: `${this.UserId}:${systemLogin.sessionId}`,
2192
+ };
2142
2193
  } catch (error) {
2143
2194
  throw error;
2144
2195
  }
@@ -2601,6 +2652,7 @@ export class User extends UserBase {
2601
2652
  LastLoginAt: user.LastLoginAt,
2602
2653
  MFAEnabled: user.MFAEnabled,
2603
2654
  MFAConfig: user.MFAConfig,
2655
+ MFABypassYN: user.MFABypassYN,
2604
2656
  RecoveryEmail: user.RecoveryEmail,
2605
2657
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
2606
2658
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -2727,6 +2779,7 @@ export class User extends UserBase {
2727
2779
  LastLoginAt: user.LastLoginAt,
2728
2780
  MFAEnabled: user.MFAEnabled,
2729
2781
  MFAConfig: user.MFAConfig,
2782
+ MFABypassYN: user.MFABypassYN,
2730
2783
  RecoveryEmail: user.RecoveryEmail,
2731
2784
  FailedLoginAttemptCount: user.FailedLoginAttemptCount,
2732
2785
  LastFailedLoginAt: user.LastFailedLoginAt,
@@ -2790,6 +2843,7 @@ export class User extends UserBase {
2790
2843
  LastLoginAt: this.LastLoginAt,
2791
2844
  MFAEnabled: this.MFAEnabled,
2792
2845
  MFAConfig: this.MFAConfig,
2846
+ MFABypassYN: this.MFABypassYN,
2793
2847
  RecoveryEmail: this.RecoveryEmail,
2794
2848
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2795
2849
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2821,6 +2875,7 @@ export class User extends UserBase {
2821
2875
  LastLoginAt: this.LastLoginAt,
2822
2876
  MFAEnabled: this.MFAEnabled,
2823
2877
  MFAConfig: this.MFAConfig,
2878
+ MFABypassYN: this.MFABypassYN,
2824
2879
  RecoveryEmail: this.RecoveryEmail,
2825
2880
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2826
2881
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2911,6 +2966,7 @@ export class User extends UserBase {
2911
2966
  LastLoginAt: this.LastLoginAt,
2912
2967
  MFAEnabled: this.MFAEnabled,
2913
2968
  MFAConfig: this.MFAConfig,
2969
+ MFABypassYN: this.MFABypassYN,
2914
2970
  RecoveryEmail: this.RecoveryEmail,
2915
2971
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2916
2972
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -2942,6 +2998,7 @@ export class User extends UserBase {
2942
2998
  LastLoginAt: this.LastLoginAt,
2943
2999
  MFAEnabled: this.MFAEnabled,
2944
3000
  MFAConfig: this.MFAConfig,
3001
+ MFABypassYN: this.MFABypassYN,
2945
3002
  RecoveryEmail: this.RecoveryEmail,
2946
3003
  FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2947
3004
  LastFailedLoginAt: this.LastFailedLoginAt,
@@ -3131,4 +3188,366 @@ export class User extends UserBase {
3131
3188
  throw error;
3132
3189
  }
3133
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.UpdatedAt = new Date();
3251
+ this.UpdatedById = loginUser.UserId;
3252
+
3253
+ await User._Repository.update(
3254
+ {
3255
+ MFABypassYN: this.MFABypassYN,
3256
+ UpdatedAt: this.UpdatedAt,
3257
+ UpdatedById: this.UpdatedById,
3258
+ },
3259
+ {
3260
+ where: {
3261
+ UserId: this.UserId,
3262
+ },
3263
+ transaction: dbTransaction,
3264
+ },
3265
+ );
3266
+
3267
+ const entityValueAfter: IUserAttr = {
3268
+ UserId: this.UserId,
3269
+ UserName: this.UserName,
3270
+ FullName: this.FullName,
3271
+ IDNo: this.IDNo,
3272
+ IDType: this.IDType,
3273
+ ContactNo: this.ContactNo,
3274
+ Email: this.Email,
3275
+ Password: this.Password,
3276
+ Status: this.Status,
3277
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3278
+ FirstLoginAt: this.FirstLoginAt,
3279
+ LastLoginAt: this.LastLoginAt,
3280
+ MFAEnabled: this.MFAEnabled,
3281
+ MFAConfig: this.MFAConfig,
3282
+ MFABypassYN: this.MFABypassYN,
3283
+ RecoveryEmail: this.RecoveryEmail,
3284
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3285
+ LastFailedLoginAt: this.LastFailedLoginAt,
3286
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3287
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3288
+ CreatedById: this.CreatedById,
3289
+ CreatedAt: this.CreatedAt,
3290
+ UpdatedById: this.UpdatedById,
3291
+ UpdatedAt: this.UpdatedAt,
3292
+ PasscodeHash: this.PasscodeHash,
3293
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3294
+ };
3295
+
3296
+ // Record update activity using Activity class create method.
3297
+ const activity = new Activity();
3298
+ activity.ActivityId = activity.createId();
3299
+ activity.Action = ActionEnum.UPDATE;
3300
+ activity.Description = `Enable 2FA Bypass For User ${this.Email}`;
3301
+ activity.EntityType = this.ObjectType;
3302
+ activity.EntityId = this.UserId.toString();
3303
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3304
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3305
+
3306
+ await activity.create(loginUser.ObjectId, dbTransaction);
3307
+ } catch (error) {
3308
+ throw error;
3309
+ }
3310
+ }
3311
+
3312
+ public async disable2FABypass(loginUser: LoginUser, dbTransaction: any) {
3313
+ try {
3314
+ // 1. Check if MFABypassYN is already disabled
3315
+ if (this.MFABypassYN === YN.No) {
3316
+ throw new ClassError(
3317
+ 'User',
3318
+ 'UserErrMsg0X',
3319
+ 'Bypass already disabled.',
3320
+ 'disable2FABypass',
3321
+ );
3322
+ }
3323
+
3324
+ // 2. Check if user has MANAGE_MFA privilege
3325
+ const systemCode =
3326
+ ApplicationConfig.getComponentConfigValue('system-code');
3327
+ const isPrivileged = await loginUser.checkPrivileges(
3328
+ systemCode,
3329
+ 'MANAGE_MFA',
3330
+ );
3331
+ if (!isPrivileged) {
3332
+ throw new ClassError(
3333
+ 'LoginUser',
3334
+ 'LoginUserErrMsg0X',
3335
+ 'You do not have permission to enable MFA bypass.',
3336
+ );
3337
+ }
3338
+
3339
+ const entityValueBefore: IUserAttr = {
3340
+ UserId: this.UserId,
3341
+ UserName: this.UserName,
3342
+ FullName: this.FullName,
3343
+ IDNo: this.IDNo,
3344
+ IDType: this.IDType,
3345
+ ContactNo: this.ContactNo,
3346
+ Email: this.Email,
3347
+ Password: this.Password,
3348
+ Status: this.Status,
3349
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3350
+ FirstLoginAt: this.FirstLoginAt,
3351
+ LastLoginAt: this.LastLoginAt,
3352
+ MFAEnabled: this.MFAEnabled,
3353
+ MFAConfig: this.MFAConfig,
3354
+ MFABypassYN: this.MFABypassYN,
3355
+ RecoveryEmail: this.RecoveryEmail,
3356
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3357
+ LastFailedLoginAt: this.LastFailedLoginAt,
3358
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3359
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3360
+ CreatedById: this.CreatedById,
3361
+ CreatedAt: this.CreatedAt,
3362
+ UpdatedById: this.UpdatedById,
3363
+ UpdatedAt: this.UpdatedAt,
3364
+ PasscodeHash: this.PasscodeHash,
3365
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3366
+ };
3367
+
3368
+ // 3. Update user record
3369
+ this.MFABypassYN = YN.No;
3370
+ this.UpdatedAt = new Date();
3371
+ this.UpdatedById = loginUser.UserId;
3372
+
3373
+ await User._Repository.update(
3374
+ {
3375
+ MFABypassYN: this.MFABypassYN,
3376
+ UpdatedAt: this.UpdatedAt,
3377
+ UpdatedById: this.UpdatedById,
3378
+ },
3379
+ {
3380
+ where: {
3381
+ UserId: this.UserId,
3382
+ },
3383
+ transaction: dbTransaction,
3384
+ },
3385
+ );
3386
+
3387
+ const entityValueAfter: IUserAttr = {
3388
+ UserId: this.UserId,
3389
+ UserName: this.UserName,
3390
+ FullName: this.FullName,
3391
+ IDNo: this.IDNo,
3392
+ IDType: this.IDType,
3393
+ ContactNo: this.ContactNo,
3394
+ Email: this.Email,
3395
+ Password: this.Password,
3396
+ Status: this.Status,
3397
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3398
+ FirstLoginAt: this.FirstLoginAt,
3399
+ LastLoginAt: this.LastLoginAt,
3400
+ MFAEnabled: this.MFAEnabled,
3401
+ MFAConfig: this.MFAConfig,
3402
+ MFABypassYN: this.MFABypassYN,
3403
+ RecoveryEmail: this.RecoveryEmail,
3404
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3405
+ LastFailedLoginAt: this.LastFailedLoginAt,
3406
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3407
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3408
+ CreatedById: this.CreatedById,
3409
+ CreatedAt: this.CreatedAt,
3410
+ UpdatedById: this.UpdatedById,
3411
+ UpdatedAt: this.UpdatedAt,
3412
+ PasscodeHash: this.PasscodeHash,
3413
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3414
+ };
3415
+
3416
+ // Record update activity using Activity class create method.
3417
+ const activity = new Activity();
3418
+ activity.ActivityId = activity.createId();
3419
+ activity.Action = ActionEnum.UPDATE;
3420
+ activity.Description = `Disable 2FA Bypass For User ${this.Email}`;
3421
+ activity.EntityType = this.ObjectType;
3422
+ activity.EntityId = this.UserId.toString();
3423
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3424
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3425
+
3426
+ await activity.create(loginUser.ObjectId, dbTransaction);
3427
+ } catch (error) {
3428
+ throw error;
3429
+ }
3430
+ }
3431
+
3432
+ public async reset2FA(loginUser: LoginUser, dbTransaction: any) {
3433
+ try {
3434
+ // 1. Check if MFABypassYN is already disabled
3435
+ if (this.MFAEnabled === 0) {
3436
+ throw new ClassError(
3437
+ 'User',
3438
+ 'UserErrMsg0X',
3439
+ 'User not yet setup 2FA.',
3440
+ 'reset2FA',
3441
+ );
3442
+ }
3443
+
3444
+ // 2. Check if user has MANAGE_MFA privilege
3445
+ const systemCode =
3446
+ ApplicationConfig.getComponentConfigValue('system-code');
3447
+ const isPrivileged = await loginUser.checkPrivileges(
3448
+ systemCode,
3449
+ 'MANAGE_MFA',
3450
+ );
3451
+ if (!isPrivileged) {
3452
+ throw new ClassError(
3453
+ 'LoginUser',
3454
+ 'LoginUserErrMsg0X',
3455
+ 'You do not have permission to reset 2FA.',
3456
+ );
3457
+ }
3458
+
3459
+ const entityValueBefore: IUserAttr = {
3460
+ UserId: this.UserId,
3461
+ UserName: this.UserName,
3462
+ FullName: this.FullName,
3463
+ IDNo: this.IDNo,
3464
+ IDType: this.IDType,
3465
+ ContactNo: this.ContactNo,
3466
+ Email: this.Email,
3467
+ Password: this.Password,
3468
+ Status: this.Status,
3469
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3470
+ FirstLoginAt: this.FirstLoginAt,
3471
+ LastLoginAt: this.LastLoginAt,
3472
+ MFAEnabled: this.MFAEnabled,
3473
+ MFAConfig: this.MFAConfig,
3474
+ MFABypassYN: this.MFABypassYN,
3475
+ RecoveryEmail: this.RecoveryEmail,
3476
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3477
+ LastFailedLoginAt: this.LastFailedLoginAt,
3478
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3479
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3480
+ CreatedById: this.CreatedById,
3481
+ CreatedAt: this.CreatedAt,
3482
+ UpdatedById: this.UpdatedById,
3483
+ UpdatedAt: this.UpdatedAt,
3484
+ PasscodeHash: this.PasscodeHash,
3485
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3486
+ };
3487
+
3488
+ // 3. Update user record
3489
+ this.MFAEnabled = 0;
3490
+ this.MFABypassYN = YN.No;
3491
+ this.UpdatedAt = new Date();
3492
+ this.UpdatedById = loginUser.UserId;
3493
+
3494
+ await User._Repository.update(
3495
+ {
3496
+ MFAEnabled: this.MFAEnabled,
3497
+ MFABypassYN: this.MFABypassYN,
3498
+ UpdatedAt: this.UpdatedAt,
3499
+ UpdatedById: this.UpdatedById,
3500
+ },
3501
+ {
3502
+ where: {
3503
+ UserId: this.UserId,
3504
+ },
3505
+ transaction: dbTransaction,
3506
+ },
3507
+ );
3508
+
3509
+ const entityValueAfter: IUserAttr = {
3510
+ UserId: this.UserId,
3511
+ UserName: this.UserName,
3512
+ FullName: this.FullName,
3513
+ IDNo: this.IDNo,
3514
+ IDType: this.IDType,
3515
+ ContactNo: this.ContactNo,
3516
+ Email: this.Email,
3517
+ Password: this.Password,
3518
+ Status: this.Status,
3519
+ DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
3520
+ FirstLoginAt: this.FirstLoginAt,
3521
+ LastLoginAt: this.LastLoginAt,
3522
+ MFAEnabled: this.MFAEnabled,
3523
+ MFAConfig: this.MFAConfig,
3524
+ MFABypassYN: this.MFABypassYN,
3525
+ RecoveryEmail: this.RecoveryEmail,
3526
+ FailedLoginAttemptCount: this.FailedLoginAttemptCount,
3527
+ LastFailedLoginAt: this.LastFailedLoginAt,
3528
+ LastPasswordChangedAt: this.LastPasswordChangedAt,
3529
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
3530
+ CreatedById: this.CreatedById,
3531
+ CreatedAt: this.CreatedAt,
3532
+ UpdatedById: this.UpdatedById,
3533
+ UpdatedAt: this.UpdatedAt,
3534
+ PasscodeHash: this.PasscodeHash,
3535
+ PasscodeUpdatedAt: this.PasscodeUpdatedAt,
3536
+ };
3537
+
3538
+ // Record update activity using Activity class create method.
3539
+ const activity = new Activity();
3540
+ activity.ActivityId = activity.createId();
3541
+ activity.Action = ActionEnum.UPDATE;
3542
+ activity.Description = `Reset 2FA for User ${this.Email}`;
3543
+ activity.EntityType = this.ObjectType;
3544
+ activity.EntityId = this.UserId.toString();
3545
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
3546
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
3547
+
3548
+ await activity.create(loginUser.ObjectId, dbTransaction);
3549
+ } catch (error) {
3550
+ throw error;
3551
+ }
3552
+ }
3134
3553
  }
@@ -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
+ }
@@ -0,0 +1,11 @@
1
+ export interface ILoginHistoryAttr {
2
+ HistoryId: number;
3
+ UserId: number;
4
+ User?: {
5
+ UserName: string;
6
+ };
7
+ OriginIp: string;
8
+ SystemCode: string;
9
+ LoginStatus: string;
10
+ CreatedAt: Date;
11
+ }
@@ -17,7 +17,7 @@ import { LoginStatusEnum } from '../enum/login-status.enum';
17
17
  createdAt: 'CreatedAt',
18
18
  updatedAt: false,
19
19
  })
20
- export default class LoginHistory extends Model {
20
+ export default class LoginHistoryModel extends Model {
21
21
  @Column({
22
22
  primaryKey: true,
23
23
  allowNull: false,
@@ -38,7 +38,7 @@ export default class LoginHistory extends Model {
38
38
  allowNull: true,
39
39
  type: DataType.STRING(10),
40
40
  })
41
- SystemCode: number;
41
+ SystemCode: string;
42
42
 
43
43
  @Column({
44
44
  allowNull: true,
@@ -88,6 +88,13 @@ export default class User extends Model {
88
88
  })
89
89
  DefaultPasswordChangedYN: YN;
90
90
 
91
+ @Column({
92
+ allowNull: true,
93
+ type: DataType.CHAR(1),
94
+ defaultValue: 'N',
95
+ })
96
+ MFABypassYN: YN;
97
+
91
98
  @Column({
92
99
  type: DataType.DATE,
93
100
  })
@@ -1,23 +0,0 @@
1
- sonar.projectKey=all-tomei-projects_sso
2
- sonar.organization=all-tomei-projects
3
- sonar.exclusions=**/*.js,test-data,dist,coverage, node_modules, __tests__, **/*.spec.ts, __mocks__
4
- sonar.scm.provider=git
5
-
6
- sonar.sources=src
7
- sonar.test=__tests__
8
- sonar.test.inclusions=src/**/*.spec.ts
9
-
10
- sonar.javascript.lcov.reportPaths=./coverage/lcov.info
11
- sonar.testExecutionReportPaths=coverage/test-report.xml
12
- sonar.sourceEnconding=UTF-8
13
-
14
- # This is the name and version displayed in the SonarCloud UI.
15
- #sonar.projectName=sso
16
- #sonar.projectVersion=1.0
17
-
18
-
19
- # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
20
- #sonar.sources=.
21
-
22
- # Encoding of the source code. Default is default system encoding
23
- #sonar.sourceEncoding=UTF-8