@tomei/sso 0.60.1 → 0.60.2

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 (26) hide show
  1. package/dist/src/components/login-user/user.js +8 -1
  2. package/dist/src/components/login-user/user.js.map +1 -1
  3. package/dist/src/components/user-password-history/index.d.ts +2 -0
  4. package/dist/src/components/user-password-history/index.js +19 -0
  5. package/dist/src/components/user-password-history/index.js.map +1 -0
  6. package/dist/src/components/user-password-history/user-password-history.d.ts +20 -0
  7. package/dist/src/components/user-password-history/user-password-history.js +112 -0
  8. package/dist/src/components/user-password-history/user-password-history.js.map +1 -0
  9. package/dist/src/components/user-password-history/user-password-history.repository.d.ts +8 -0
  10. package/dist/src/components/user-password-history/user-password-history.repository.js +49 -0
  11. package/dist/src/components/user-password-history/user-password-history.repository.js.map +1 -0
  12. package/dist/src/interfaces/user-password-history.interface.d.ts +6 -0
  13. package/dist/src/interfaces/user-password-history.interface.js +3 -0
  14. package/dist/src/interfaces/user-password-history.interface.js.map +1 -0
  15. package/dist/src/models/user-password-history.d.ts +10 -0
  16. package/dist/src/models/user-password-history.js +60 -0
  17. package/dist/src/models/user-password-history.js.map +1 -0
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/migrations/20250326043818-crate-user-password-history.js +41 -0
  20. package/package.json +1 -1
  21. package/src/components/login-user/user.ts +46 -1
  22. package/src/components/user-password-history/index.ts +2 -0
  23. package/src/components/user-password-history/user-password-history.repository.ts +39 -0
  24. package/src/components/user-password-history/user-password-history.ts +185 -0
  25. package/src/interfaces/user-password-history.interface.ts +6 -0
  26. package/src/models/user-password-history.ts +52 -0
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ /** @type {import('sequelize-cli').Migration} */
4
+ module.exports = {
5
+ async up(queryInterface, Sequelize) {
6
+ await queryInterface.createTable('sso_UserPasswordHistory', {
7
+ HistoryId: {
8
+ type: Sequelize.INTEGER,
9
+ autoIncrement: true,
10
+ primaryKey: true,
11
+ allowNull: false,
12
+ },
13
+ UserId: {
14
+ type: Sequelize.INTEGER,
15
+ allowNull: false,
16
+ references: {
17
+ model: 'sso_User',
18
+ key: 'UserId',
19
+ },
20
+ onUpdate: 'CASCADE',
21
+ onDelete: 'CASCADE',
22
+ },
23
+ PasswordHash: {
24
+ type: Sequelize.STRING(225),
25
+ allowNull: false,
26
+ },
27
+ CreatedAt: {
28
+ type: Sequelize.DATE,
29
+ allowNull: false,
30
+ },
31
+ UpdatedAt: {
32
+ type: Sequelize.DATE,
33
+ allowNull: false,
34
+ },
35
+ });
36
+ },
37
+
38
+ async down(queryInterface, Sequelize) {
39
+ await queryInterface.dropTable('sso_UserPasswordHistory');
40
+ },
41
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomei/sso",
3
- "version": "0.60.1",
3
+ "version": "0.60.2",
4
4
  "description": "Tomei SSO Package",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -36,6 +36,7 @@ import { AuthContext } from 'types';
36
36
  import UserModel from '../../models/user.entity';
37
37
  import { UserReportingHierarchyRepository } from '../user-reporting-hierarchy/user-reporting-hierarchy.repository';
38
38
  import { IUserReportingHierarchyAttr } from 'interfaces/user-reporting-hierarchy.interface';
39
+ import { UserPasswordHistory } from '../user-password-history/user-password-history';
39
40
 
40
41
  export class User extends UserBase {
41
42
  ObjectId: string;
@@ -1462,6 +1463,19 @@ export class User extends UserBase {
1462
1463
  await sessionService.deleteAuthorizationCode(hashedSubmittedToken);
1463
1464
  console.log(`Token verified for user: ${userId}`);
1464
1465
 
1466
+ // Part 2: Validate Password History
1467
+ // Call by passing:
1468
+ // dbTransaction
1469
+ // UserId: loginUser.ObjectId
1470
+ // Password: password
1471
+ const passwordHashService = new PasswordHashService();
1472
+ await UserPasswordHistory.validate(
1473
+ dbTransaction,
1474
+ parseInt(userId),
1475
+ password,
1476
+ passwordHashService,
1477
+ );
1478
+
1465
1479
  //Instantiate user
1466
1480
  const user = await User.init(
1467
1481
  sessionService,
@@ -1473,7 +1487,7 @@ export class User extends UserBase {
1473
1487
  await User.setPassword(dbTransaction, user, password);
1474
1488
 
1475
1489
  //Update user record
1476
- await User._Repository.update(
1490
+ let userData = await User._Repository.update(
1477
1491
  {
1478
1492
  Password: user._Password,
1479
1493
  DefaultPasswordChangedYN: YN.Yes,
@@ -1486,6 +1500,12 @@ export class User extends UserBase {
1486
1500
  transaction: dbTransaction,
1487
1501
  },
1488
1502
  );
1503
+
1504
+ await UserPasswordHistory.create(
1505
+ dbTransaction,
1506
+ parseInt(userId),
1507
+ userData.Password,
1508
+ );
1489
1509
  } catch (error) {
1490
1510
  throw error;
1491
1511
  }
@@ -1603,6 +1623,12 @@ export class User extends UserBase {
1603
1623
  userInfo,
1604
1624
  );
1605
1625
 
1626
+ await UserPasswordHistory.create(
1627
+ dbTransaction,
1628
+ userToBeCreated.UserId,
1629
+ userToBeCreated.Password,
1630
+ );
1631
+
1606
1632
  //Part 5: Record Create User Activity
1607
1633
  const activity = new Activity();
1608
1634
  activity.ActivityId = activity.createId();
@@ -2845,6 +2871,19 @@ export class User extends UserBase {
2845
2871
  ) {
2846
2872
  try {
2847
2873
  const passwordHashService = new PasswordHashService();
2874
+
2875
+ // Part 2: Validate Password History
2876
+ // Call by passing:
2877
+ // dbTransaction
2878
+ // UserId: loginUser.ObjectId
2879
+ // Password: password
2880
+ await UserPasswordHistory.validate(
2881
+ dbTransaction,
2882
+ loginUser.UserId,
2883
+ newPassword,
2884
+ passwordHashService,
2885
+ );
2886
+
2848
2887
  const isPasswordValid = await passwordHashService.verify(
2849
2888
  oldPassword,
2850
2889
  this.Password,
@@ -2929,6 +2968,12 @@ export class User extends UserBase {
2929
2968
  },
2930
2969
  );
2931
2970
 
2971
+ await UserPasswordHistory.create(
2972
+ dbTransaction,
2973
+ this.UserId,
2974
+ this.Password,
2975
+ );
2976
+
2932
2977
  // Record update activity using Activity class create method.
2933
2978
  const activity = new Activity();
2934
2979
  activity.ActivityId = activity.createId();
@@ -0,0 +1,2 @@
1
+ export * from './user-password-history';
2
+ export * from './user-password-history.repository';
@@ -0,0 +1,39 @@
1
+ import { RepositoryBase, IRepositoryBase } from '@tomei/general';
2
+ import UserPasswordHistoryModel from '../../models/user-password-history';
3
+ import { Op } from 'sequelize';
4
+
5
+ export class UserPasswordHistoryRepository
6
+ extends RepositoryBase<UserPasswordHistoryModel>
7
+ implements IRepositoryBase<UserPasswordHistoryModel>
8
+ {
9
+ constructor() {
10
+ super(UserPasswordHistoryModel);
11
+ }
12
+
13
+ async findByPk(id: string, options?: any): Promise<UserPasswordHistoryModel> {
14
+ return await UserPasswordHistoryModel.findByPk(parseInt(id), options);
15
+ }
16
+
17
+ async destroy(HistoryId: number, dbTransaction: any): Promise<void> {
18
+ await UserPasswordHistoryModel.destroy({
19
+ where: {
20
+ HistoryId: HistoryId,
21
+ },
22
+ transaction: dbTransaction,
23
+ });
24
+ }
25
+
26
+ async destroyMultiple(
27
+ HistoryIdList: number[],
28
+ dbTransaction: any,
29
+ ): Promise<void> {
30
+ await UserPasswordHistoryModel.destroy({
31
+ where: {
32
+ HistoryId: {
33
+ [Op.in]: HistoryIdList,
34
+ },
35
+ },
36
+ transaction: dbTransaction,
37
+ });
38
+ }
39
+ }
@@ -0,0 +1,185 @@
1
+ import { ClassError, ObjectBase } from '@tomei/general';
2
+ import { ComponentConfig } from '@tomei/config';
3
+ import { IUserPasswordHistoryAttr } from '../../interfaces/user-password-history.interface';
4
+ import { UserPasswordHistoryRepository } from './user-password-history.repository';
5
+ import { PasswordHashService } from '../../components/password-hash';
6
+
7
+ export class UserPasswordHistory
8
+ extends ObjectBase
9
+ implements IUserPasswordHistoryAttr
10
+ {
11
+ ObjectId: string;
12
+ ObjectName: string;
13
+ ObjectType = 'UserPasswordHistory';
14
+ TableName = 'sso_UserPasswordHistory';
15
+ UserId: number;
16
+ PasswordHash: string;
17
+ private _CreatedAt: Date;
18
+
19
+ private static _Repo = new UserPasswordHistoryRepository();
20
+
21
+ get HistoryId(): number {
22
+ return parseInt(this.ObjectId);
23
+ }
24
+
25
+ set HistoryId(value: number) {
26
+ this.ObjectId = value.toString();
27
+ }
28
+
29
+ get CreatedAt(): Date {
30
+ return this._CreatedAt;
31
+ }
32
+
33
+ private constructor(params?: IUserPasswordHistoryAttr) {
34
+ super();
35
+ if (params) {
36
+ this.ObjectId = params.HistoryId.toString();
37
+ this.UserId = params.UserId;
38
+ this.PasswordHash = params.PasswordHash;
39
+ this._CreatedAt = params.CreatedAt;
40
+ }
41
+ }
42
+
43
+ public static async init(
44
+ historyId?: number,
45
+ dbTransaction?: any,
46
+ ): Promise<UserPasswordHistory> {
47
+ try {
48
+ if (historyId) {
49
+ const data = await UserPasswordHistory._Repo.findByPk(
50
+ historyId.toString(),
51
+ dbTransaction,
52
+ );
53
+ if (!data) {
54
+ throw new ClassError(
55
+ 'UserPasswordHistory',
56
+ 'UserPasswordHistoryErrMsg01',
57
+ 'UserPasswordHistory not found',
58
+ 'init',
59
+ 400,
60
+ );
61
+ }
62
+
63
+ return new UserPasswordHistory(data.get({ plain: true }));
64
+ }
65
+ return new UserPasswordHistory();
66
+ } catch (error) {
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ public static async validate(
72
+ dbTransaction: any,
73
+ UserId: number,
74
+ Password: string,
75
+ passwordHashService: PasswordHashService,
76
+ ): Promise<void> {
77
+ // This method used to check if password entered is valid by checking previous password history
78
+ try {
79
+ // Part 1-2: Retrieve password history policy by using component config, call ComponentConfig. by passing:
80
+ // - ComponentName: "@tomei/sso"
81
+ // - ConfigKey: "passwordHistory"
82
+ // If no password history set, use default value 3
83
+
84
+ const passwordHistoryPolicy =
85
+ ComponentConfig.getComponentConfigValue(
86
+ '@tomei/sso',
87
+ 'passwordHistory',
88
+ ) || 3;
89
+
90
+ // Part 3-4: Retrieve records from the table by using class._repo findAll() by passing:
91
+ // where: { UserId: params.UserId }
92
+ // order: [['CreatedAt', 'DESC']]
93
+ // limit: passwordHistory count above.
94
+ // If no record found, return null.
95
+
96
+ let passwordHistory = await UserPasswordHistory._Repo.findAll({
97
+ where: { UserId: UserId },
98
+ order: [['CreatedAt', 'DESC']],
99
+ limit: passwordHistoryPolicy,
100
+ transaction: dbTransaction,
101
+ });
102
+
103
+ if (passwordHistory?.length < 1) {
104
+ return null;
105
+ } else {
106
+ // Part 5: If record found, map each record to compare params.Password and record.PasswordHash using the params.passwordHashService. If match, stop the mapping, and return ClassError:
107
+ // ClassName: "UserPasswordHistory"
108
+ // MethodName: "validate"
109
+ // MessageCode: "UserPasswordHistory01"
110
+ // Message: You cannot reuse your last ${passwordHistory} passwords. Please choose a new and unique password.
111
+ for (let index = 0; index < passwordHistory.length; index++) {
112
+ const isPasswordSame = await passwordHashService.verify(
113
+ Password,
114
+ passwordHistory[index].PasswordHash,
115
+ );
116
+
117
+ if (isPasswordSame) {
118
+ throw new ClassError(
119
+ 'UserPasswordHistory',
120
+ 'UserPasswordHistory01',
121
+ `You cannot reuse your last ${passwordHistoryPolicy} passwords. Please choose a new and unique password.`,
122
+ 'validate',
123
+ 403,
124
+ );
125
+ }
126
+ }
127
+ }
128
+ } catch (error) {
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ public static async create(
134
+ dbTransaction: any,
135
+ UserId: number,
136
+ PasswordHash: string,
137
+ ): Promise<void> {
138
+ // This method used to check if password entered is valid by checking previous password history
139
+ try {
140
+ // Part 1-2: Retrieve password history policy by using component config, call ComponentConfig. by passing:
141
+ // - ComponentName: "@tomei/sso"
142
+ // - ConfigKey: "passwordHistory"
143
+ // If no password history set, use default value 3
144
+
145
+ const passwordHistoryPolicy =
146
+ ComponentConfig.getComponentConfigValue(
147
+ '@tomei/sso',
148
+ 'passwordHistory',
149
+ ) || 3;
150
+
151
+ // Part 3: Insert new password history by calling class _repo create() method.
152
+ let passwordHistory = await UserPasswordHistory._Repo.create(
153
+ {
154
+ UserId: UserId,
155
+ PasswordHash: PasswordHash,
156
+ },
157
+ {
158
+ transaction: dbTransaction,
159
+ },
160
+ );
161
+
162
+ // Part 3: When inserted successfully, retrieve all the password history for the user to check
163
+ // how many previous password records. If records more than the passwordHistory count from
164
+ // config. Remove the oldest record.
165
+ if (passwordHistory) {
166
+ let passwordHistoryList = await UserPasswordHistory._Repo.findAll({
167
+ where: { UserId: UserId },
168
+ order: [['CreatedAt', 'DESC']],
169
+ transaction: dbTransaction,
170
+ });
171
+
172
+ if (passwordHistoryList.length > passwordHistoryPolicy) {
173
+ let deleteList = passwordHistoryList.slice(passwordHistoryPolicy);
174
+ let historyIdList = deleteList.map((record) => record.HistoryId);
175
+ await UserPasswordHistory._Repo.destroyMultiple(
176
+ historyIdList,
177
+ dbTransaction,
178
+ );
179
+ }
180
+ }
181
+ } catch (error) {
182
+ throw error;
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,6 @@
1
+ export interface IUserPasswordHistoryAttr {
2
+ HistoryId: number;
3
+ UserId: number;
4
+ PasswordHash: string;
5
+ CreatedAt: Date;
6
+ }
@@ -0,0 +1,52 @@
1
+ import {
2
+ BelongsTo,
3
+ Column,
4
+ CreatedAt,
5
+ DataType,
6
+ ForeignKey,
7
+ Model,
8
+ Table,
9
+ } from 'sequelize-typescript';
10
+ import User from './user.entity';
11
+ import { IUserPasswordHistoryAttr } from '../interfaces/user-password-history.interface';
12
+
13
+ @Table({
14
+ tableName: 'sso_UserPasswordHistory',
15
+ timestamps: true,
16
+ createdAt: 'CreatedAt',
17
+ updatedAt: 'UpdatedAt',
18
+ })
19
+ export default class UserPasswordHistoryModel
20
+ extends Model
21
+ implements IUserPasswordHistoryAttr
22
+ {
23
+ @Column({
24
+ primaryKey: true,
25
+ allowNull: false,
26
+ type: DataType.INTEGER,
27
+ autoIncrement: true,
28
+ })
29
+ HistoryId: number;
30
+
31
+ @ForeignKey(() => User)
32
+ @Column({
33
+ allowNull: false,
34
+ type: DataType.INTEGER,
35
+ })
36
+ UserId: number;
37
+
38
+ @Column({
39
+ allowNull: false,
40
+ type: DataType.STRING(225),
41
+ })
42
+ PasswordHash: string;
43
+
44
+ @CreatedAt
45
+ CreatedAt: Date;
46
+
47
+ @BelongsTo(() => User, {
48
+ as: 'User',
49
+ foreignKey: 'UserId',
50
+ })
51
+ User: User;
52
+ }