@tomei/sso 0.62.0 → 0.63.0
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.
- package/coverage/lcov-report/components/login-user/login-user.ts.html +80 -73
- package/dist/src/components/login-history/index.d.ts +1 -0
- package/dist/src/components/login-history/index.js +1 -0
- package/dist/src/components/login-history/index.js.map +1 -1
- package/dist/src/components/login-history/login-history.repository.d.ts +2 -2
- package/dist/src/components/login-history/login-history.repository.js.map +1 -1
- package/dist/src/components/login-user/interfaces/user-info.interface.d.ts +1 -0
- package/dist/src/components/login-user/login-user.js +1 -0
- package/dist/src/components/login-user/login-user.js.map +1 -1
- package/dist/src/components/login-user/user.d.ts +28 -3
- package/dist/src/components/login-user/user.js +360 -16
- package/dist/src/components/login-user/user.js.map +1 -1
- package/dist/src/components/user-system-access/user-system-access.js +1 -1
- package/dist/src/components/user-system-access/user-system-access.js.map +1 -1
- package/dist/src/models/login-history.entity.d.ts +2 -2
- package/dist/src/models/login-history.entity.js +13 -13
- package/dist/src/models/login-history.entity.js.map +1 -1
- package/dist/src/models/user.entity.d.ts +1 -0
- package/dist/src/models/user.entity.js +8 -0
- package/dist/src/models/user.entity.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migrations/20250610070720-added-MFBypassYN-to-sso-user.js +30 -0
- package/package.json +1 -1
- package/src/components/login-history/index.ts +1 -0
- package/src/components/login-history/login-history.repository.ts +4 -4
- package/src/components/login-history/login-history.ts +124 -0
- package/src/components/login-user/interfaces/user-info.interface.ts +1 -0
- package/src/components/login-user/login-user.ts +1 -0
- package/src/components/login-user/user.ts +438 -18
- package/src/components/user-system-access/user-system-access.ts +1 -1
- package/src/interfaces/login-history-search-attr.interface.ts +8 -0
- package/src/interfaces/login-history.interface.ts +11 -0
- package/src/models/login-history.entity.ts +2 -2
- package/src/models/user.entity.ts +7 -0
@@ -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,
|
@@ -574,8 +587,7 @@ export class User extends UserBase {
|
|
574
587
|
},
|
575
588
|
});
|
576
589
|
if (!system) {
|
577
|
-
|
578
|
-
throw new Error('Invalid credentials.');
|
590
|
+
throw new Error('Access denied: invalid or unauthorized system.');
|
579
591
|
}
|
580
592
|
|
581
593
|
// 1.5: Instantiate new PasswordHashService object and call PasswordHashService.verify method to check whether the param.Password is correct.
|
@@ -605,14 +617,14 @@ export class User extends UserBase {
|
|
605
617
|
await User.releaseLock(this.UserId, dbTransaction);
|
606
618
|
this.Status = UserStatus.ACTIVE;
|
607
619
|
} else {
|
608
|
-
|
609
|
-
|
620
|
+
throw new Error(
|
621
|
+
'Your account has been locked. Please contact the administrator for assistance.',
|
622
|
+
);
|
610
623
|
}
|
611
624
|
}
|
612
625
|
} catch (error) {
|
613
626
|
await this.incrementFailedLoginAttemptCount(dbTransaction);
|
614
|
-
|
615
|
-
throw new Error('Invalid credentials.');
|
627
|
+
throw error;
|
616
628
|
}
|
617
629
|
|
618
630
|
// 2.1: Call alertNewLogin to check whether the ip used is new ip and alert the user if it's new.
|
@@ -1580,6 +1592,7 @@ export class User extends UserBase {
|
|
1580
1592
|
LastLoginAt: null,
|
1581
1593
|
MFAEnabled: null,
|
1582
1594
|
MFAConfig: null,
|
1595
|
+
MFABypassYN: YN.No,
|
1583
1596
|
RecoveryEmail: null,
|
1584
1597
|
FailedLoginAttemptCount: 0,
|
1585
1598
|
LastFailedLoginAt: null,
|
@@ -1727,7 +1740,7 @@ export class User extends UserBase {
|
|
1727
1740
|
throw new ClassError(
|
1728
1741
|
'LoginUser',
|
1729
1742
|
'LoginUserErrMsg0X',
|
1730
|
-
'
|
1743
|
+
'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
1744
|
);
|
1732
1745
|
}
|
1733
1746
|
}
|
@@ -1998,15 +2011,31 @@ export class User extends UserBase {
|
|
1998
2011
|
}
|
1999
2012
|
|
2000
2013
|
// 3. Verify the mfaToken by calling speakeasy.totp.verify
|
2001
|
-
const
|
2014
|
+
const isCurrentValid = await speakeasy.totp.verify({
|
2002
2015
|
secret: userMFAConfig.totp.secret,
|
2003
2016
|
encoding: 'base32',
|
2004
2017
|
token: mfaToken,
|
2018
|
+
window: 0, // strict current time window
|
2005
2019
|
});
|
2020
|
+
if (!isCurrentValid) {
|
2021
|
+
const isExpired = await speakeasy.totp.verify({
|
2022
|
+
secret: userMFAConfig.totp.secret,
|
2023
|
+
encoding: 'base32',
|
2024
|
+
token: mfaToken,
|
2025
|
+
window: 2, // allow slight leeway: previous or next 2 time steps
|
2026
|
+
});
|
2006
2027
|
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2028
|
+
if (isExpired) {
|
2029
|
+
return {
|
2030
|
+
success: false,
|
2031
|
+
reason: 'MFA token has expired. Please try again.',
|
2032
|
+
};
|
2033
|
+
} else {
|
2034
|
+
return {
|
2035
|
+
success: false,
|
2036
|
+
reason: 'Invalid MFA token. Check your authenticator app.',
|
2037
|
+
};
|
2038
|
+
}
|
2010
2039
|
}
|
2011
2040
|
|
2012
2041
|
user.MFAEnabled = 1;
|
@@ -2035,7 +2064,7 @@ export class User extends UserBase {
|
|
2035
2064
|
const systemLogin = userSession.systemLogins.find(
|
2036
2065
|
(e) => e.code === systemCode,
|
2037
2066
|
);
|
2038
|
-
return `${userId}:${systemLogin.sessionId}
|
2067
|
+
return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
|
2039
2068
|
}
|
2040
2069
|
|
2041
2070
|
// This method will verify 2FA codes
|
@@ -2073,15 +2102,31 @@ export class User extends UserBase {
|
|
2073
2102
|
}
|
2074
2103
|
|
2075
2104
|
// 3. Verify the mfaToken by calling speakeasy.totp.verify
|
2076
|
-
const
|
2105
|
+
const isCurrentValid = await speakeasy.totp.verify({
|
2077
2106
|
secret: userMFAConfig.totp.secret,
|
2078
2107
|
encoding: 'base32',
|
2079
2108
|
token: mfaToken,
|
2109
|
+
window: 0, // strict current time window
|
2080
2110
|
});
|
2111
|
+
if (!isCurrentValid) {
|
2112
|
+
const isExpired = await speakeasy.totp.verify({
|
2113
|
+
secret: userMFAConfig.totp.secret,
|
2114
|
+
encoding: 'base32',
|
2115
|
+
token: mfaToken,
|
2116
|
+
window: 2, // allow slight leeway: previous or next 2 time steps
|
2117
|
+
});
|
2081
2118
|
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2119
|
+
if (isExpired) {
|
2120
|
+
return {
|
2121
|
+
success: false,
|
2122
|
+
reason: 'MFA token has expired. Please try again.',
|
2123
|
+
};
|
2124
|
+
} else {
|
2125
|
+
return {
|
2126
|
+
success: false,
|
2127
|
+
reason: 'Invalid MFA token. Check your authenticator app.',
|
2128
|
+
};
|
2129
|
+
}
|
2085
2130
|
}
|
2086
2131
|
|
2087
2132
|
// 5. Retrieve Session
|
@@ -2104,7 +2149,7 @@ export class User extends UserBase {
|
|
2104
2149
|
const systemLogin = userSession.systemLogins.find(
|
2105
2150
|
(e) => e.code === systemCode,
|
2106
2151
|
);
|
2107
|
-
return `${userId}:${systemLogin.sessionId}
|
2152
|
+
return { success: true, sessionId: `${userId}:${systemLogin.sessionId}` };
|
2108
2153
|
}
|
2109
2154
|
|
2110
2155
|
public async bypass2FA(systemCode: string, dbTransaction: any) {
|
@@ -2147,7 +2192,10 @@ export class User extends UserBase {
|
|
2147
2192
|
const systemLogin = userSession.systemLogins.find(
|
2148
2193
|
(e) => e.code === systemCode,
|
2149
2194
|
);
|
2150
|
-
return
|
2195
|
+
return {
|
2196
|
+
success: true,
|
2197
|
+
sessionId: `${this.UserId}:${systemLogin.sessionId}`,
|
2198
|
+
};
|
2151
2199
|
} catch (error) {
|
2152
2200
|
throw error;
|
2153
2201
|
}
|
@@ -2610,6 +2658,7 @@ export class User extends UserBase {
|
|
2610
2658
|
LastLoginAt: user.LastLoginAt,
|
2611
2659
|
MFAEnabled: user.MFAEnabled,
|
2612
2660
|
MFAConfig: user.MFAConfig,
|
2661
|
+
MFABypassYN: user.MFABypassYN,
|
2613
2662
|
RecoveryEmail: user.RecoveryEmail,
|
2614
2663
|
FailedLoginAttemptCount: user.FailedLoginAttemptCount,
|
2615
2664
|
LastFailedLoginAt: user.LastFailedLoginAt,
|
@@ -2736,6 +2785,7 @@ export class User extends UserBase {
|
|
2736
2785
|
LastLoginAt: user.LastLoginAt,
|
2737
2786
|
MFAEnabled: user.MFAEnabled,
|
2738
2787
|
MFAConfig: user.MFAConfig,
|
2788
|
+
MFABypassYN: user.MFABypassYN,
|
2739
2789
|
RecoveryEmail: user.RecoveryEmail,
|
2740
2790
|
FailedLoginAttemptCount: user.FailedLoginAttemptCount,
|
2741
2791
|
LastFailedLoginAt: user.LastFailedLoginAt,
|
@@ -2799,6 +2849,7 @@ export class User extends UserBase {
|
|
2799
2849
|
LastLoginAt: this.LastLoginAt,
|
2800
2850
|
MFAEnabled: this.MFAEnabled,
|
2801
2851
|
MFAConfig: this.MFAConfig,
|
2852
|
+
MFABypassYN: this.MFABypassYN,
|
2802
2853
|
RecoveryEmail: this.RecoveryEmail,
|
2803
2854
|
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
2804
2855
|
LastFailedLoginAt: this.LastFailedLoginAt,
|
@@ -2830,6 +2881,7 @@ export class User extends UserBase {
|
|
2830
2881
|
LastLoginAt: this.LastLoginAt,
|
2831
2882
|
MFAEnabled: this.MFAEnabled,
|
2832
2883
|
MFAConfig: this.MFAConfig,
|
2884
|
+
MFABypassYN: this.MFABypassYN,
|
2833
2885
|
RecoveryEmail: this.RecoveryEmail,
|
2834
2886
|
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
2835
2887
|
LastFailedLoginAt: this.LastFailedLoginAt,
|
@@ -2920,6 +2972,7 @@ export class User extends UserBase {
|
|
2920
2972
|
LastLoginAt: this.LastLoginAt,
|
2921
2973
|
MFAEnabled: this.MFAEnabled,
|
2922
2974
|
MFAConfig: this.MFAConfig,
|
2975
|
+
MFABypassYN: this.MFABypassYN,
|
2923
2976
|
RecoveryEmail: this.RecoveryEmail,
|
2924
2977
|
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
2925
2978
|
LastFailedLoginAt: this.LastFailedLoginAt,
|
@@ -2951,6 +3004,7 @@ export class User extends UserBase {
|
|
2951
3004
|
LastLoginAt: this.LastLoginAt,
|
2952
3005
|
MFAEnabled: this.MFAEnabled,
|
2953
3006
|
MFAConfig: this.MFAConfig,
|
3007
|
+
MFABypassYN: this.MFABypassYN,
|
2954
3008
|
RecoveryEmail: this.RecoveryEmail,
|
2955
3009
|
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
2956
3010
|
LastFailedLoginAt: this.LastFailedLoginAt,
|
@@ -3140,4 +3194,370 @@ export class User extends UserBase {
|
|
3140
3194
|
throw error;
|
3141
3195
|
}
|
3142
3196
|
}
|
3197
|
+
|
3198
|
+
public async enable2FABypass(loginUser: LoginUser, dbTransaction: any) {
|
3199
|
+
try {
|
3200
|
+
// 1. Check if MFABypassYN is already enabled
|
3201
|
+
if (this.MFABypassYN === YN.Yes) {
|
3202
|
+
throw new ClassError(
|
3203
|
+
'User',
|
3204
|
+
'UserErrMsg0X',
|
3205
|
+
'Bypass already enabled.',
|
3206
|
+
'enable2FABypass',
|
3207
|
+
);
|
3208
|
+
}
|
3209
|
+
|
3210
|
+
// 2. Check if user has MANAGE_MFA privilege
|
3211
|
+
const systemCode =
|
3212
|
+
ApplicationConfig.getComponentConfigValue('system-code');
|
3213
|
+
const isPrivileged = await loginUser.checkPrivileges(
|
3214
|
+
systemCode,
|
3215
|
+
'MANAGE_MFA',
|
3216
|
+
);
|
3217
|
+
if (!isPrivileged) {
|
3218
|
+
throw new ClassError(
|
3219
|
+
'LoginUser',
|
3220
|
+
'LoginUserErrMsg0X',
|
3221
|
+
'You do not have permission to enable MFA bypass.',
|
3222
|
+
);
|
3223
|
+
}
|
3224
|
+
|
3225
|
+
const entityValueBefore: IUserAttr = {
|
3226
|
+
UserId: this.UserId,
|
3227
|
+
UserName: this.UserName,
|
3228
|
+
FullName: this.FullName,
|
3229
|
+
IDNo: this.IDNo,
|
3230
|
+
IDType: this.IDType,
|
3231
|
+
ContactNo: this.ContactNo,
|
3232
|
+
Email: this.Email,
|
3233
|
+
Password: this.Password,
|
3234
|
+
Status: this.Status,
|
3235
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3236
|
+
FirstLoginAt: this.FirstLoginAt,
|
3237
|
+
LastLoginAt: this.LastLoginAt,
|
3238
|
+
MFAEnabled: this.MFAEnabled,
|
3239
|
+
MFAConfig: this.MFAConfig,
|
3240
|
+
MFABypassYN: this.MFABypassYN,
|
3241
|
+
RecoveryEmail: this.RecoveryEmail,
|
3242
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3243
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3244
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3245
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3246
|
+
CreatedById: this.CreatedById,
|
3247
|
+
CreatedAt: this.CreatedAt,
|
3248
|
+
UpdatedById: this.UpdatedById,
|
3249
|
+
UpdatedAt: this.UpdatedAt,
|
3250
|
+
PasscodeHash: this.PasscodeHash,
|
3251
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3252
|
+
};
|
3253
|
+
|
3254
|
+
// 3. Update user record
|
3255
|
+
this.MFABypassYN = YN.Yes;
|
3256
|
+
this.MFAEnabled = 0;
|
3257
|
+
this.UpdatedAt = new Date();
|
3258
|
+
this.UpdatedById = loginUser.UserId;
|
3259
|
+
|
3260
|
+
await User._Repository.update(
|
3261
|
+
{
|
3262
|
+
MFABypassYN: this.MFABypassYN,
|
3263
|
+
MFAEnabled: this.MFAEnabled,
|
3264
|
+
UpdatedAt: this.UpdatedAt,
|
3265
|
+
UpdatedById: this.UpdatedById,
|
3266
|
+
},
|
3267
|
+
{
|
3268
|
+
where: {
|
3269
|
+
UserId: this.UserId,
|
3270
|
+
},
|
3271
|
+
transaction: dbTransaction,
|
3272
|
+
},
|
3273
|
+
);
|
3274
|
+
|
3275
|
+
const entityValueAfter: IUserAttr = {
|
3276
|
+
UserId: this.UserId,
|
3277
|
+
UserName: this.UserName,
|
3278
|
+
FullName: this.FullName,
|
3279
|
+
IDNo: this.IDNo,
|
3280
|
+
IDType: this.IDType,
|
3281
|
+
ContactNo: this.ContactNo,
|
3282
|
+
Email: this.Email,
|
3283
|
+
Password: this.Password,
|
3284
|
+
Status: this.Status,
|
3285
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3286
|
+
FirstLoginAt: this.FirstLoginAt,
|
3287
|
+
LastLoginAt: this.LastLoginAt,
|
3288
|
+
MFAEnabled: this.MFAEnabled,
|
3289
|
+
MFAConfig: this.MFAConfig,
|
3290
|
+
MFABypassYN: this.MFABypassYN,
|
3291
|
+
RecoveryEmail: this.RecoveryEmail,
|
3292
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3293
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3294
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3295
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3296
|
+
CreatedById: this.CreatedById,
|
3297
|
+
CreatedAt: this.CreatedAt,
|
3298
|
+
UpdatedById: this.UpdatedById,
|
3299
|
+
UpdatedAt: this.UpdatedAt,
|
3300
|
+
PasscodeHash: this.PasscodeHash,
|
3301
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3302
|
+
};
|
3303
|
+
|
3304
|
+
// Record update activity using Activity class create method.
|
3305
|
+
const activity = new Activity();
|
3306
|
+
activity.ActivityId = activity.createId();
|
3307
|
+
activity.Action = ActionEnum.UPDATE;
|
3308
|
+
activity.Description = `Enable 2FA Bypass For User ${this.Email}`;
|
3309
|
+
activity.EntityType = this.ObjectType;
|
3310
|
+
activity.EntityId = this.UserId.toString();
|
3311
|
+
activity.EntityValueBefore = JSON.stringify(entityValueBefore);
|
3312
|
+
activity.EntityValueAfter = JSON.stringify(entityValueAfter);
|
3313
|
+
|
3314
|
+
await activity.create(loginUser.ObjectId, dbTransaction);
|
3315
|
+
} catch (error) {
|
3316
|
+
throw error;
|
3317
|
+
}
|
3318
|
+
}
|
3319
|
+
|
3320
|
+
public async disable2FABypass(loginUser: LoginUser, dbTransaction: any) {
|
3321
|
+
try {
|
3322
|
+
// 1. Check if MFABypassYN is already disabled
|
3323
|
+
if (this.MFABypassYN === YN.No) {
|
3324
|
+
throw new ClassError(
|
3325
|
+
'User',
|
3326
|
+
'UserErrMsg0X',
|
3327
|
+
'Bypass already disabled.',
|
3328
|
+
'disable2FABypass',
|
3329
|
+
);
|
3330
|
+
}
|
3331
|
+
|
3332
|
+
// 2. Check if user has MANAGE_MFA privilege
|
3333
|
+
const systemCode =
|
3334
|
+
ApplicationConfig.getComponentConfigValue('system-code');
|
3335
|
+
const isPrivileged = await loginUser.checkPrivileges(
|
3336
|
+
systemCode,
|
3337
|
+
'MANAGE_MFA',
|
3338
|
+
);
|
3339
|
+
if (!isPrivileged) {
|
3340
|
+
throw new ClassError(
|
3341
|
+
'LoginUser',
|
3342
|
+
'LoginUserErrMsg0X',
|
3343
|
+
'You do not have permission to enable MFA bypass.',
|
3344
|
+
);
|
3345
|
+
}
|
3346
|
+
|
3347
|
+
const entityValueBefore: IUserAttr = {
|
3348
|
+
UserId: this.UserId,
|
3349
|
+
UserName: this.UserName,
|
3350
|
+
FullName: this.FullName,
|
3351
|
+
IDNo: this.IDNo,
|
3352
|
+
IDType: this.IDType,
|
3353
|
+
ContactNo: this.ContactNo,
|
3354
|
+
Email: this.Email,
|
3355
|
+
Password: this.Password,
|
3356
|
+
Status: this.Status,
|
3357
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3358
|
+
FirstLoginAt: this.FirstLoginAt,
|
3359
|
+
LastLoginAt: this.LastLoginAt,
|
3360
|
+
MFAEnabled: this.MFAEnabled,
|
3361
|
+
MFAConfig: this.MFAConfig,
|
3362
|
+
MFABypassYN: this.MFABypassYN,
|
3363
|
+
RecoveryEmail: this.RecoveryEmail,
|
3364
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3365
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3366
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3367
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3368
|
+
CreatedById: this.CreatedById,
|
3369
|
+
CreatedAt: this.CreatedAt,
|
3370
|
+
UpdatedById: this.UpdatedById,
|
3371
|
+
UpdatedAt: this.UpdatedAt,
|
3372
|
+
PasscodeHash: this.PasscodeHash,
|
3373
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3374
|
+
};
|
3375
|
+
|
3376
|
+
// 3. Update user record
|
3377
|
+
this.MFABypassYN = YN.No;
|
3378
|
+
this.MFAEnabled = 0;
|
3379
|
+
this.UpdatedAt = new Date();
|
3380
|
+
this.UpdatedById = loginUser.UserId;
|
3381
|
+
|
3382
|
+
await User._Repository.update(
|
3383
|
+
{
|
3384
|
+
MFABypassYN: this.MFABypassYN,
|
3385
|
+
MFAEnabled: this.MFAEnabled,
|
3386
|
+
UpdatedAt: this.UpdatedAt,
|
3387
|
+
UpdatedById: this.UpdatedById,
|
3388
|
+
},
|
3389
|
+
{
|
3390
|
+
where: {
|
3391
|
+
UserId: this.UserId,
|
3392
|
+
},
|
3393
|
+
transaction: dbTransaction,
|
3394
|
+
},
|
3395
|
+
);
|
3396
|
+
|
3397
|
+
const entityValueAfter: IUserAttr = {
|
3398
|
+
UserId: this.UserId,
|
3399
|
+
UserName: this.UserName,
|
3400
|
+
FullName: this.FullName,
|
3401
|
+
IDNo: this.IDNo,
|
3402
|
+
IDType: this.IDType,
|
3403
|
+
ContactNo: this.ContactNo,
|
3404
|
+
Email: this.Email,
|
3405
|
+
Password: this.Password,
|
3406
|
+
Status: this.Status,
|
3407
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3408
|
+
FirstLoginAt: this.FirstLoginAt,
|
3409
|
+
LastLoginAt: this.LastLoginAt,
|
3410
|
+
MFAEnabled: this.MFAEnabled,
|
3411
|
+
MFAConfig: this.MFAConfig,
|
3412
|
+
MFABypassYN: this.MFABypassYN,
|
3413
|
+
RecoveryEmail: this.RecoveryEmail,
|
3414
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3415
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3416
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3417
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3418
|
+
CreatedById: this.CreatedById,
|
3419
|
+
CreatedAt: this.CreatedAt,
|
3420
|
+
UpdatedById: this.UpdatedById,
|
3421
|
+
UpdatedAt: this.UpdatedAt,
|
3422
|
+
PasscodeHash: this.PasscodeHash,
|
3423
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3424
|
+
};
|
3425
|
+
|
3426
|
+
// Record update activity using Activity class create method.
|
3427
|
+
const activity = new Activity();
|
3428
|
+
activity.ActivityId = activity.createId();
|
3429
|
+
activity.Action = ActionEnum.UPDATE;
|
3430
|
+
activity.Description = `Disable 2FA Bypass For User ${this.Email}`;
|
3431
|
+
activity.EntityType = this.ObjectType;
|
3432
|
+
activity.EntityId = this.UserId.toString();
|
3433
|
+
activity.EntityValueBefore = JSON.stringify(entityValueBefore);
|
3434
|
+
activity.EntityValueAfter = JSON.stringify(entityValueAfter);
|
3435
|
+
|
3436
|
+
await activity.create(loginUser.ObjectId, dbTransaction);
|
3437
|
+
} catch (error) {
|
3438
|
+
throw error;
|
3439
|
+
}
|
3440
|
+
}
|
3441
|
+
|
3442
|
+
public async reset2FA(loginUser: LoginUser, dbTransaction: any) {
|
3443
|
+
try {
|
3444
|
+
// 1. Check if MFABypassYN is already disabled
|
3445
|
+
if (this.MFAEnabled === 0) {
|
3446
|
+
throw new ClassError(
|
3447
|
+
'User',
|
3448
|
+
'UserErrMsg0X',
|
3449
|
+
'User not yet setup 2FA.',
|
3450
|
+
'reset2FA',
|
3451
|
+
);
|
3452
|
+
}
|
3453
|
+
|
3454
|
+
// 2. Check if user has MANAGE_MFA privilege
|
3455
|
+
const systemCode =
|
3456
|
+
ApplicationConfig.getComponentConfigValue('system-code');
|
3457
|
+
const isPrivileged = await loginUser.checkPrivileges(
|
3458
|
+
systemCode,
|
3459
|
+
'MANAGE_MFA',
|
3460
|
+
);
|
3461
|
+
if (!isPrivileged) {
|
3462
|
+
throw new ClassError(
|
3463
|
+
'LoginUser',
|
3464
|
+
'LoginUserErrMsg0X',
|
3465
|
+
'You do not have permission to reset 2FA.',
|
3466
|
+
);
|
3467
|
+
}
|
3468
|
+
|
3469
|
+
const entityValueBefore: IUserAttr = {
|
3470
|
+
UserId: this.UserId,
|
3471
|
+
UserName: this.UserName,
|
3472
|
+
FullName: this.FullName,
|
3473
|
+
IDNo: this.IDNo,
|
3474
|
+
IDType: this.IDType,
|
3475
|
+
ContactNo: this.ContactNo,
|
3476
|
+
Email: this.Email,
|
3477
|
+
Password: this.Password,
|
3478
|
+
Status: this.Status,
|
3479
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3480
|
+
FirstLoginAt: this.FirstLoginAt,
|
3481
|
+
LastLoginAt: this.LastLoginAt,
|
3482
|
+
MFAEnabled: this.MFAEnabled,
|
3483
|
+
MFAConfig: this.MFAConfig,
|
3484
|
+
MFABypassYN: this.MFABypassYN,
|
3485
|
+
RecoveryEmail: this.RecoveryEmail,
|
3486
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3487
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3488
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3489
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3490
|
+
CreatedById: this.CreatedById,
|
3491
|
+
CreatedAt: this.CreatedAt,
|
3492
|
+
UpdatedById: this.UpdatedById,
|
3493
|
+
UpdatedAt: this.UpdatedAt,
|
3494
|
+
PasscodeHash: this.PasscodeHash,
|
3495
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3496
|
+
};
|
3497
|
+
|
3498
|
+
// 3. Update user record
|
3499
|
+
this.MFAEnabled = 0;
|
3500
|
+
this.MFABypassYN = YN.No;
|
3501
|
+
this.UpdatedAt = new Date();
|
3502
|
+
this.UpdatedById = loginUser.UserId;
|
3503
|
+
|
3504
|
+
await User._Repository.update(
|
3505
|
+
{
|
3506
|
+
MFAEnabled: this.MFAEnabled,
|
3507
|
+
MFABypassYN: this.MFABypassYN,
|
3508
|
+
UpdatedAt: this.UpdatedAt,
|
3509
|
+
UpdatedById: this.UpdatedById,
|
3510
|
+
},
|
3511
|
+
{
|
3512
|
+
where: {
|
3513
|
+
UserId: this.UserId,
|
3514
|
+
},
|
3515
|
+
transaction: dbTransaction,
|
3516
|
+
},
|
3517
|
+
);
|
3518
|
+
|
3519
|
+
const entityValueAfter: IUserAttr = {
|
3520
|
+
UserId: this.UserId,
|
3521
|
+
UserName: this.UserName,
|
3522
|
+
FullName: this.FullName,
|
3523
|
+
IDNo: this.IDNo,
|
3524
|
+
IDType: this.IDType,
|
3525
|
+
ContactNo: this.ContactNo,
|
3526
|
+
Email: this.Email,
|
3527
|
+
Password: this.Password,
|
3528
|
+
Status: this.Status,
|
3529
|
+
DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
|
3530
|
+
FirstLoginAt: this.FirstLoginAt,
|
3531
|
+
LastLoginAt: this.LastLoginAt,
|
3532
|
+
MFAEnabled: this.MFAEnabled,
|
3533
|
+
MFAConfig: this.MFAConfig,
|
3534
|
+
MFABypassYN: this.MFABypassYN,
|
3535
|
+
RecoveryEmail: this.RecoveryEmail,
|
3536
|
+
FailedLoginAttemptCount: this.FailedLoginAttemptCount,
|
3537
|
+
LastFailedLoginAt: this.LastFailedLoginAt,
|
3538
|
+
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
3539
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
3540
|
+
CreatedById: this.CreatedById,
|
3541
|
+
CreatedAt: this.CreatedAt,
|
3542
|
+
UpdatedById: this.UpdatedById,
|
3543
|
+
UpdatedAt: this.UpdatedAt,
|
3544
|
+
PasscodeHash: this.PasscodeHash,
|
3545
|
+
PasscodeUpdatedAt: this.PasscodeUpdatedAt,
|
3546
|
+
};
|
3547
|
+
|
3548
|
+
// Record update activity using Activity class create method.
|
3549
|
+
const activity = new Activity();
|
3550
|
+
activity.ActivityId = activity.createId();
|
3551
|
+
activity.Action = ActionEnum.UPDATE;
|
3552
|
+
activity.Description = `Reset 2FA for User ${this.Email}`;
|
3553
|
+
activity.EntityType = this.ObjectType;
|
3554
|
+
activity.EntityId = this.UserId.toString();
|
3555
|
+
activity.EntityValueBefore = JSON.stringify(entityValueBefore);
|
3556
|
+
activity.EntityValueAfter = JSON.stringify(entityValueAfter);
|
3557
|
+
|
3558
|
+
await activity.create(loginUser.ObjectId, dbTransaction);
|
3559
|
+
} catch (error) {
|
3560
|
+
throw error;
|
3561
|
+
}
|
3562
|
+
}
|
3143
3563
|
}
|
@@ -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
|
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:
|
41
|
+
SystemCode: string;
|
42
42
|
|
43
43
|
@Column({
|
44
44
|
allowNull: true,
|