@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.
- 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.d.ts +23 -0
- package/dist/src/components/login-history/login-history.js +88 -0
- package/dist/src/components/login-history/login-history.js.map +1 -0
- 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 +356 -13
- 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/interfaces/login-history-search-attr.interface.d.ts +8 -0
- package/dist/src/interfaces/login-history-search-attr.interface.js +3 -0
- package/dist/src/interfaces/login-history-search-attr.interface.js.map +1 -0
- package/dist/src/interfaces/login-history.interface.d.ts +11 -0
- package/dist/src/interfaces/login-history.interface.js +3 -0
- package/dist/src/interfaces/login-history.interface.js.map +1 -0
- 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 +434 -15
- 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
- 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('
|
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(
|
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
|
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
|
-
'
|
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
|
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
|
-
|
1999
|
-
|
2000
|
-
|
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
|
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
|
-
|
2074
|
-
|
2075
|
-
|
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
|
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
|
}
|
@@ -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,
|
package/sonar-project.properties
DELETED
@@ -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
|