@tomei/sso 0.66.1-test.1 → 0.66.1-test.3
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/dist/src/components/login-user/login-user.d.ts +1 -0
- package/dist/src/components/login-user/login-user.js +135 -63
- package/dist/src/components/login-user/login-user.js.map +1 -1
- package/dist/src/components/login-user/user.d.ts +5 -0
- package/dist/src/components/login-user/user.js +39 -0
- package/dist/src/components/login-user/user.js.map +1 -1
- package/dist/src/components/user-password-history/user-password-history.d.ts +1 -0
- package/dist/src/components/user-password-history/user-password-history.js +41 -2
- package/dist/src/components/user-password-history/user-password-history.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/login-user/login-user.ts +192 -85
- package/src/components/login-user/user.ts +68 -0
- package/src/components/user-password-history/user-password-history.ts +57 -6
- package/coverage/cobertura-coverage.xml +0 -237
- package/coverage/test-report.xml +0 -33
package/package.json
CHANGED
|
@@ -20,6 +20,19 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
20
20
|
Id: null,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
private static isLoginApiEnabled(): boolean {
|
|
24
|
+
try {
|
|
25
|
+
const useLoginApiYN =
|
|
26
|
+
ComponentConfig.getComponentConfigValue(
|
|
27
|
+
'@tomei/sso',
|
|
28
|
+
'useLoginApiYN',
|
|
29
|
+
) || 'Y';
|
|
30
|
+
return String(useLoginApiYN).toUpperCase() !== 'N';
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
static async init(
|
|
24
37
|
sessionService: ISessionService,
|
|
25
38
|
userId?: number,
|
|
@@ -27,24 +40,43 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
27
40
|
): Promise<LoginUser> {
|
|
28
41
|
User._RedisService = await RedisService.init();
|
|
29
42
|
if (userId) {
|
|
30
|
-
|
|
31
|
-
'@tomei/sso',
|
|
32
|
-
'loginApiEndpoint',
|
|
33
|
-
);
|
|
43
|
+
let user: any;
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
if (LoginUser.isLoginApiEnabled()) {
|
|
46
|
+
const loginApiEndpoint = ComponentConfig.getComponentConfigValue(
|
|
47
|
+
'@tomei/sso',
|
|
48
|
+
'loginApiEndpoint',
|
|
49
|
+
);
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
let payload = { UserId: userId };
|
|
52
|
+
|
|
53
|
+
const { data } = await axios.post(
|
|
54
|
+
`${loginApiEndpoint.baseUrl}${loginApiEndpoint.getProfile}`,
|
|
55
|
+
payload,
|
|
56
|
+
{
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'x-api-key': loginApiEndpoint.apiKey,
|
|
60
|
+
},
|
|
44
61
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
);
|
|
63
|
+
user = data.loginUser;
|
|
64
|
+
} else {
|
|
65
|
+
if (dbTransaction) {
|
|
66
|
+
User._Repository = new UserRepository();
|
|
67
|
+
}
|
|
68
|
+
user = await User._Repository.findOne({
|
|
69
|
+
where: {
|
|
70
|
+
UserId: userId,
|
|
71
|
+
},
|
|
72
|
+
include: [
|
|
73
|
+
{
|
|
74
|
+
model: Staff,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
transaction: dbTransaction,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
48
80
|
|
|
49
81
|
if (!user) {
|
|
50
82
|
throw new Error('Invalid credentials.');
|
|
@@ -59,21 +91,21 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
59
91
|
IDType: user?.IdType || null,
|
|
60
92
|
ContactNo: user?.ContactNo || null,
|
|
61
93
|
Email: user.Email,
|
|
62
|
-
Password:
|
|
94
|
+
Password: user.Password,
|
|
63
95
|
Status: user.Status,
|
|
64
|
-
DefaultPasswordChangedYN:
|
|
65
|
-
FirstLoginAt:
|
|
66
|
-
LastLoginAt:
|
|
96
|
+
DefaultPasswordChangedYN: user.DefaultPasswordChangedYN,
|
|
97
|
+
FirstLoginAt: user.FirstLoginAt,
|
|
98
|
+
LastLoginAt: user.LastLoginAt,
|
|
67
99
|
MFAEnabled: user.MFAEnabled,
|
|
68
|
-
MFAConfig:
|
|
100
|
+
MFAConfig: user.MFAConfig,
|
|
69
101
|
MFABypassYN: user.MFABypassYN,
|
|
70
|
-
RecoveryEmail:
|
|
71
|
-
FailedLoginAttemptCount:
|
|
72
|
-
LastFailedLoginAt:
|
|
73
|
-
LastPasswordChangedAt:
|
|
74
|
-
NeedToChangePasswordYN:
|
|
75
|
-
PasscodeHash:
|
|
76
|
-
PasscodeUpdatedAt:
|
|
102
|
+
RecoveryEmail: user.RecoveryEmail,
|
|
103
|
+
FailedLoginAttemptCount: user.FailedLoginAttemptCount,
|
|
104
|
+
LastFailedLoginAt: user.LastFailedLoginAt,
|
|
105
|
+
LastPasswordChangedAt: user.LastPasswordChangedAt,
|
|
106
|
+
NeedToChangePasswordYN: user.NeedToChangePasswordYN,
|
|
107
|
+
PasscodeHash: user.PasscodeHash,
|
|
108
|
+
PasscodeUpdatedAt: user.PasscodeUpdatedAt,
|
|
77
109
|
CreatedById: user.CreatedById,
|
|
78
110
|
CreatedAt: user.CreatedAt,
|
|
79
111
|
UpdatedById: user.UpdatedById,
|
|
@@ -104,29 +136,45 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
104
136
|
throw new Error('Session name is not set');
|
|
105
137
|
}
|
|
106
138
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
if (LoginUser.isLoginApiEnabled()) {
|
|
140
|
+
const loginApiEndpoint = ComponentConfig.getComponentConfigValue(
|
|
141
|
+
'@tomei/sso',
|
|
142
|
+
'loginApiEndpoint',
|
|
143
|
+
);
|
|
144
|
+
let payload = {
|
|
145
|
+
SystemCode: systemCode,
|
|
146
|
+
SessionName: sessionName,
|
|
147
|
+
PrivilegeName: privilegeName,
|
|
148
|
+
UserId: this.ObjectId,
|
|
149
|
+
};
|
|
150
|
+
const { data } = await axios.post(
|
|
151
|
+
`${loginApiEndpoint.baseUrl}${loginApiEndpoint.checkPrivileges}`,
|
|
152
|
+
payload,
|
|
153
|
+
{
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
'x-api-key': loginApiEndpoint.apiKey,
|
|
157
|
+
},
|
|
124
158
|
},
|
|
125
|
-
|
|
159
|
+
);
|
|
160
|
+
return data.hasPrivilege;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const userSession = await this._SessionService.retrieveUserSession(
|
|
164
|
+
this.ObjectId,
|
|
165
|
+
sessionName,
|
|
126
166
|
);
|
|
127
|
-
const hasPrivilege = data.hasPrivilege;
|
|
128
167
|
|
|
129
|
-
|
|
168
|
+
const systemLogin = userSession.systemLogins.find(
|
|
169
|
+
(system) => system.code === systemCode,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (!systemLogin) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const privileges = systemLogin.privileges;
|
|
177
|
+
return privileges.includes(privilegeName);
|
|
130
178
|
} catch (error) {
|
|
131
179
|
throw error;
|
|
132
180
|
}
|
|
@@ -143,27 +191,53 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
143
191
|
if (!sessionName) {
|
|
144
192
|
throw new Error('Session name is not set');
|
|
145
193
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
194
|
+
|
|
195
|
+
if (LoginUser.isLoginApiEnabled()) {
|
|
196
|
+
const loginApiEndpoint = ComponentConfig.getComponentConfigValue(
|
|
197
|
+
'@tomei/sso',
|
|
198
|
+
'loginApiEndpoint',
|
|
199
|
+
);
|
|
200
|
+
let payload = {
|
|
201
|
+
SystemCode: systemCode,
|
|
202
|
+
SessionName: sessionName,
|
|
203
|
+
SessionId: sessionId,
|
|
204
|
+
UserId: userId,
|
|
205
|
+
};
|
|
206
|
+
const { data } = await axios.post(
|
|
207
|
+
`${loginApiEndpoint.baseUrl}${loginApiEndpoint.checkSession}`,
|
|
208
|
+
payload,
|
|
209
|
+
{
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
'x-api-key': loginApiEndpoint.apiKey,
|
|
213
|
+
},
|
|
163
214
|
},
|
|
164
|
-
|
|
215
|
+
);
|
|
216
|
+
return data.systemLogin;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const userSession = await this._SessionService.retrieveUserSession(
|
|
220
|
+
userId,
|
|
221
|
+
sessionName,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (userSession.systemLogins.length === 0) {
|
|
225
|
+
throw new Error('Session expired.');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const systemLogin = userSession.systemLogins.find(
|
|
229
|
+
(sl) => sl.code === systemCode,
|
|
165
230
|
);
|
|
166
|
-
|
|
231
|
+
|
|
232
|
+
if (!systemLogin) {
|
|
233
|
+
throw new Error('Session expired.');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (systemLogin.sessionId !== sessionId) {
|
|
237
|
+
throw new Error('Session expired.');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await this._SessionService.refreshDuration(userId, sessionName);
|
|
167
241
|
|
|
168
242
|
return systemLogin;
|
|
169
243
|
} catch (error) {
|
|
@@ -339,26 +413,59 @@ export class LoginUser extends User implements ILoginUser {
|
|
|
339
413
|
throw new Error('Session name is not set in the configuration');
|
|
340
414
|
}
|
|
341
415
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
416
|
+
if (LoginUser.isLoginApiEnabled()) {
|
|
417
|
+
const loginApiEndpoint = ComponentConfig.getComponentConfigValue(
|
|
418
|
+
'@tomei/sso',
|
|
419
|
+
'loginApiEndpoint',
|
|
420
|
+
);
|
|
421
|
+
let payload = {
|
|
422
|
+
SystemCode: systemCode,
|
|
423
|
+
SessionName: sessionName,
|
|
424
|
+
SessionId: sessionId,
|
|
425
|
+
UserId: this.ObjectId,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
await axios.post(
|
|
429
|
+
`${loginApiEndpoint.baseUrl}${loginApiEndpoint.setSession}`,
|
|
430
|
+
payload,
|
|
431
|
+
{
|
|
432
|
+
headers: {
|
|
433
|
+
'Content-Type': 'application/json',
|
|
434
|
+
'x-api-key': loginApiEndpoint.apiKey,
|
|
435
|
+
},
|
|
360
436
|
},
|
|
361
|
-
|
|
437
|
+
);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const userSession = await this._SessionService.retrieveUserSession(
|
|
442
|
+
this.ObjectId,
|
|
443
|
+
sessionName,
|
|
444
|
+
);
|
|
445
|
+
const systemLogin = userSession.systemLogins.find(
|
|
446
|
+
(system) => system.code === systemCode,
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
if (systemLogin) {
|
|
450
|
+
const privileges = await this.getPrivileges(systemCode, dbTransaction);
|
|
451
|
+
systemLogin.sessionId = sessionId;
|
|
452
|
+
systemLogin.privileges = privileges;
|
|
453
|
+
userSession.systemLogins.map((system) =>
|
|
454
|
+
system.code === systemCode ? systemLogin : system,
|
|
455
|
+
);
|
|
456
|
+
} else {
|
|
457
|
+
const newLogin = {
|
|
458
|
+
id: systemCode,
|
|
459
|
+
code: systemCode,
|
|
460
|
+
sessionId: sessionId,
|
|
461
|
+
privileges: await this.getPrivileges(systemCode, dbTransaction),
|
|
462
|
+
};
|
|
463
|
+
userSession.systemLogins.push(newLogin);
|
|
464
|
+
}
|
|
465
|
+
this._SessionService.setUserSession(
|
|
466
|
+
this.ObjectId,
|
|
467
|
+
userSession,
|
|
468
|
+
sessionName,
|
|
362
469
|
);
|
|
363
470
|
}
|
|
364
471
|
}
|
|
@@ -2988,6 +2988,7 @@ export class User extends UserBase {
|
|
|
2988
2988
|
|
|
2989
2989
|
this.Password = passwordHash;
|
|
2990
2990
|
this.LastPasswordChangedAt = new Date();
|
|
2991
|
+
this.NeedToChangePasswordYN = YN.No;
|
|
2991
2992
|
|
|
2992
2993
|
const entityValueAfter: IUserAttr = {
|
|
2993
2994
|
UserId: this.UserId,
|
|
@@ -3022,6 +3023,7 @@ export class User extends UserBase {
|
|
|
3022
3023
|
{
|
|
3023
3024
|
Password: this.Password,
|
|
3024
3025
|
LastPasswordChangedAt: this.LastPasswordChangedAt,
|
|
3026
|
+
NeedToChangePasswordYN: this.NeedToChangePasswordYN,
|
|
3025
3027
|
},
|
|
3026
3028
|
{
|
|
3027
3029
|
where: {
|
|
@@ -3053,6 +3055,72 @@ export class User extends UserBase {
|
|
|
3053
3055
|
}
|
|
3054
3056
|
}
|
|
3055
3057
|
|
|
3058
|
+
// PASSWORD_EXPIRY_MONTHS=0 disables the policy entirely (AC#1) - checked before NeedToChangePasswordYN/history.
|
|
3059
|
+
async getPasswordExpiryStatus(dbTransaction?: any): Promise<{
|
|
3060
|
+
expired: boolean;
|
|
3061
|
+
expiringSoon: boolean;
|
|
3062
|
+
expiresInDays: number | null;
|
|
3063
|
+
}> {
|
|
3064
|
+
try {
|
|
3065
|
+
const expiryMonthsRaw = Number(process.env.PASSWORD_EXPIRY_MONTHS);
|
|
3066
|
+
const expiryMonths = Number.isFinite(expiryMonthsRaw)
|
|
3067
|
+
? Math.max(0, Math.floor(expiryMonthsRaw))
|
|
3068
|
+
: 0;
|
|
3069
|
+
|
|
3070
|
+
if (expiryMonths === 0) {
|
|
3071
|
+
return { expired: false, expiringSoon: false, expiresInDays: null };
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// Forces first-login change for temp-password/admin-created accounts (AC#4), regardless of password age.
|
|
3075
|
+
if (this.NeedToChangePasswordYN === YN.Yes) {
|
|
3076
|
+
return { expired: true, expiringSoon: false, expiresInDays: null };
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
const warningDaysRaw = Number(process.env.PASSWORD_EXPIRY_WARNING_DAYS);
|
|
3080
|
+
const warningDays = Number.isFinite(warningDaysRaw)
|
|
3081
|
+
? Math.max(0, Math.floor(warningDaysRaw))
|
|
3082
|
+
: 14;
|
|
3083
|
+
|
|
3084
|
+
const latestHistory = await UserPasswordHistory.getLatestByUserId(
|
|
3085
|
+
dbTransaction,
|
|
3086
|
+
this.UserId,
|
|
3087
|
+
);
|
|
3088
|
+
|
|
3089
|
+
if (!latestHistory) {
|
|
3090
|
+
// Legacy/pre-existing accounts without a history row.
|
|
3091
|
+
return { expired: true, expiringSoon: false, expiresInDays: null };
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
const createdAt = latestHistory.CreatedAt;
|
|
3095
|
+
const expiryDate = new Date(
|
|
3096
|
+
Date.UTC(
|
|
3097
|
+
createdAt.getUTCFullYear(),
|
|
3098
|
+
createdAt.getUTCMonth() + expiryMonths,
|
|
3099
|
+
createdAt.getUTCDate(),
|
|
3100
|
+
createdAt.getUTCHours(),
|
|
3101
|
+
createdAt.getUTCMinutes(),
|
|
3102
|
+
createdAt.getUTCSeconds(),
|
|
3103
|
+
),
|
|
3104
|
+
);
|
|
3105
|
+
|
|
3106
|
+
const now = new Date();
|
|
3107
|
+
if (now.getTime() >= expiryDate.getTime()) {
|
|
3108
|
+
return { expired: true, expiringSoon: false, expiresInDays: null };
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
3112
|
+
const expiringSoon =
|
|
3113
|
+
now.getTime() >= expiryDate.getTime() - warningDays * msPerDay;
|
|
3114
|
+
const expiresInDays = Math.ceil(
|
|
3115
|
+
(expiryDate.getTime() - now.getTime()) / msPerDay,
|
|
3116
|
+
);
|
|
3117
|
+
|
|
3118
|
+
return { expired: false, expiringSoon, expiresInDays };
|
|
3119
|
+
} catch (error) {
|
|
3120
|
+
throw error;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3056
3124
|
async verifyPasscode(passcode: string) {
|
|
3057
3125
|
try {
|
|
3058
3126
|
// Return error if this._Userid is empty "User is not created yet."
|
|
@@ -76,16 +76,30 @@ export class UserPasswordHistory
|
|
|
76
76
|
): Promise<void> {
|
|
77
77
|
// This method used to check if password entered is valid by checking previous password history
|
|
78
78
|
try {
|
|
79
|
-
// Part 1-2: Retrieve password history policy
|
|
79
|
+
// Part 1-2: Retrieve password history policy: env var takes precedence over component config, call ComponentConfig. by passing:
|
|
80
80
|
// - ComponentName: "@tomei/sso"
|
|
81
81
|
// - ConfigKey: "passwordHistory"
|
|
82
82
|
// If no password history set, use default value 3
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const envPasswordHistoryPolicy = Number(
|
|
85
|
+
process.env.PASSWORD_HISTORY_LIMIT,
|
|
86
|
+
);
|
|
87
|
+
const componentPasswordHistoryPolicy = Number(
|
|
85
88
|
ComponentConfig.getComponentConfigValue(
|
|
86
89
|
'@tomei/sso',
|
|
87
90
|
'passwordHistory',
|
|
88
|
-
)
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
const passwordHistoryPolicy =
|
|
94
|
+
(Number.isFinite(envPasswordHistoryPolicy) &&
|
|
95
|
+
envPasswordHistoryPolicy > 0
|
|
96
|
+
? Math.floor(envPasswordHistoryPolicy)
|
|
97
|
+
: 0) ||
|
|
98
|
+
(Number.isFinite(componentPasswordHistoryPolicy) &&
|
|
99
|
+
componentPasswordHistoryPolicy > 0
|
|
100
|
+
? Math.floor(componentPasswordHistoryPolicy)
|
|
101
|
+
: 0) ||
|
|
102
|
+
3;
|
|
89
103
|
|
|
90
104
|
// Part 3-4: Retrieve records from the table by using class._repo findAll() by passing:
|
|
91
105
|
// where: { UserId: params.UserId }
|
|
@@ -137,16 +151,30 @@ export class UserPasswordHistory
|
|
|
137
151
|
): Promise<void> {
|
|
138
152
|
// This method used to check if password entered is valid by checking previous password history
|
|
139
153
|
try {
|
|
140
|
-
// Part 1-2: Retrieve password history policy
|
|
154
|
+
// Part 1-2: Retrieve password history policy: env var takes precedence over component config, call ComponentConfig. by passing:
|
|
141
155
|
// - ComponentName: "@tomei/sso"
|
|
142
156
|
// - ConfigKey: "passwordHistory"
|
|
143
157
|
// If no password history set, use default value 3
|
|
144
158
|
|
|
145
|
-
const
|
|
159
|
+
const envPasswordHistoryPolicy = Number(
|
|
160
|
+
process.env.PASSWORD_HISTORY_LIMIT,
|
|
161
|
+
);
|
|
162
|
+
const componentPasswordHistoryPolicy = Number(
|
|
146
163
|
ComponentConfig.getComponentConfigValue(
|
|
147
164
|
'@tomei/sso',
|
|
148
165
|
'passwordHistory',
|
|
149
|
-
)
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
const passwordHistoryPolicy =
|
|
169
|
+
(Number.isFinite(envPasswordHistoryPolicy) &&
|
|
170
|
+
envPasswordHistoryPolicy > 0
|
|
171
|
+
? Math.floor(envPasswordHistoryPolicy)
|
|
172
|
+
: 0) ||
|
|
173
|
+
(Number.isFinite(componentPasswordHistoryPolicy) &&
|
|
174
|
+
componentPasswordHistoryPolicy > 0
|
|
175
|
+
? Math.floor(componentPasswordHistoryPolicy)
|
|
176
|
+
: 0) ||
|
|
177
|
+
3;
|
|
150
178
|
|
|
151
179
|
// Part 3: Insert new password history by calling class _repo create() method.
|
|
152
180
|
const userPasswordHistory = new UserPasswordHistory();
|
|
@@ -184,4 +212,27 @@ export class UserPasswordHistory
|
|
|
184
212
|
throw error;
|
|
185
213
|
}
|
|
186
214
|
}
|
|
215
|
+
|
|
216
|
+
// Latest history row's CreatedAt is the source of truth for password age (used by User.getPasswordExpiryStatus()).
|
|
217
|
+
public static async getLatestByUserId(
|
|
218
|
+
dbTransaction: any,
|
|
219
|
+
UserId: number,
|
|
220
|
+
): Promise<UserPasswordHistory | null> {
|
|
221
|
+
try {
|
|
222
|
+
const passwordHistory = await UserPasswordHistory._Repo.findAll({
|
|
223
|
+
where: { UserId: UserId },
|
|
224
|
+
order: [['CreatedAt', 'DESC']],
|
|
225
|
+
limit: 1,
|
|
226
|
+
transaction: dbTransaction,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!passwordHistory || passwordHistory.length < 1) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new UserPasswordHistory(passwordHistory[0].get({ plain: true }));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
187
238
|
}
|