@tomei/sso 0.34.1 → 0.34.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,298 +1,29 @@
1
- import { ClassError, LoginUserBase } from '@tomei/general';
2
- import { ISessionService } from '../../session/interfaces/session-service.interface';
3
- import { IUserAttr, IUserInfo } from './interfaces/user-info.interface';
4
- import { UserRepository } from './user.repository';
5
- import { SystemRepository } from '../system/system.repository';
6
- import { LoginHistoryRepository } from '../login-history/login-history.repository';
7
- import { PasswordHashService } from '../password-hash/password-hash.service';
8
- import { UserGroupRepository } from '../user-group/user-group.repository';
9
- import { SMTPMailer } from '@tomei/mailer';
1
+ import { ILoginUser } from '@tomei/general';
2
+ import { User } from './user';
10
3
  import { ISystemLogin } from '../../../src/interfaces/system-login.interface';
11
- import Staff from '../../models/staff.entity';
12
- import SystemPrivilege from '../../models/system-privilege.entity';
13
- import LoginHistory from '../../models/login-history.entity';
14
- import { YN } from '../../enum/yn.enum';
15
- import { UserStatus } from '../../enum';
16
- import { ApplicationConfig, ComponentConfig } from '@tomei/config';
17
- import { ICheckUserInfoDuplicatedQuery } from './interfaces/check-user-info-duplicated.interface';
18
- import { Op, where } from 'sequelize';
19
- import { ActionEnum, Activity } from '@tomei/activity-history';
20
- import User from '../../models/user.entity';
21
4
  import GroupModel from '../../models/group.entity';
22
- import { GroupSystemAccessRepository } from '../group-system-access/group-system-access.repository';
23
- import { GroupRepository } from '../group/group.repository';
24
- import SystemModel from '../../models/system.entity';
25
- import { ISystemAccess } from './interfaces/system-access.interface';
26
- import { UserSystemAccessRepository } from '../user-system-access/user-system-access.repository';
27
- import GroupSystemAccessModel from '../../models/group-system-access.entity';
28
- import { UserPrivilegeRepository } from '../user-privilege/user-privilege.repository';
29
- import { UserObjectPrivilegeRepository } from '../user-object-privilege/user-object-privilege.repository';
30
- import GroupPrivilegeModel from '../../models/group-privilege.entity';
31
- import { GroupObjectPrivilegeRepository } from '../group-object-privilege/group-object-privilege.repository';
32
- import * as speakeasy from 'speakeasy';
33
- import { LoginStatusEnum } from '../../enum/login-status.enum';
5
+ import { ISessionService } from '../../session/interfaces/session-service.interface';
34
6
  import { RedisService } from '../../redis-client/redis.service';
7
+ import { UserRepository } from './user.repository';
8
+ import { IUserAttr, IUserInfo } from './interfaces/user-info.interface';
9
+ import Staff from '../../models/staff.entity';
35
10
 
36
- export class LoginUser extends LoginUserBase {
37
- ObjectId: string;
38
- Email: string;
39
- private _UserName: string;
40
- private _Password: string;
41
- private _Status: UserStatus;
42
- private _DefaultPasswordChangedYN: YN;
43
- private _FirstLoginAt: Date;
44
- private _LastLoginAt: Date;
45
- private _MFAEnabled: number;
46
- private _MFAConfig: string;
47
- private _RecoveryEmail: string;
48
- private _FailedLoginAttemptCount: number;
49
- private _LastFailedLoginAt: Date;
50
- private _LastPasswordChangedAt: Date;
51
- private _NeedToChangePasswordYN: YN;
52
- private _CreatedById: number;
53
- private _CreatedAt: Date;
54
- private _UpdatedById: number;
55
- private _UpdatedAt: Date;
56
- ObjectName = 'User';
57
- TableName = 'sso_Users';
58
- ObjectType = 'User';
59
- staffs: any;
60
-
61
- private _OriginIP: string;
62
- private _SessionService: ISessionService;
63
- private static _RedisService: RedisService;
64
- private static _Repository = new UserRepository();
65
-
66
- private static _LoginHistoryRepository = new LoginHistoryRepository();
67
- private static _UserGroupRepo = new UserGroupRepository();
68
- private static _UserPrivilegeRepo = new UserPrivilegeRepository();
69
- private static _UserObjectPrivilegeRepo = new UserObjectPrivilegeRepository();
70
- private static _GroupObjectPrivilegeRepo =
71
- new GroupObjectPrivilegeRepository();
72
-
73
- private static _SystemRepository = new SystemRepository();
74
- private static _UserSystemAccessRepo = new UserSystemAccessRepository();
75
- private static _GroupSystemAccessRepo = new GroupSystemAccessRepository();
76
- private static _GroupRepo = new GroupRepository();
77
-
78
- private _dbTransaction: any;
79
-
80
- get SessionService(): ISessionService {
81
- return this._SessionService;
82
- }
83
-
84
- get UserId(): number {
85
- return parseInt(this.ObjectId);
86
- }
87
-
88
- private set UserId(value: number) {
89
- this.ObjectId = value.toString();
90
- }
91
-
92
- get Password(): string {
93
- return this._Password;
94
- }
95
-
96
- private set Password(value: string) {
97
- this._Password = value;
98
- }
99
-
100
- get Status(): UserStatus {
101
- return this._Status;
102
- }
103
-
104
- private set Status(value: UserStatus) {
105
- this._Status = value;
106
- }
107
-
108
- get UserName(): string {
109
- return this._UserName;
110
- }
111
-
112
- set UserName(value: string) {
113
- this._UserName = value;
114
- }
115
-
116
- get DefaultPasswordChangedYN(): YN {
117
- return this._DefaultPasswordChangedYN;
118
- }
119
-
120
- private set DefaultPasswordChangedYN(value: YN) {
121
- this._DefaultPasswordChangedYN = value;
122
- }
123
-
124
- get FirstLoginAt(): Date {
125
- return this._FirstLoginAt;
126
- }
127
-
128
- private set FirstLoginAt(value: Date) {
129
- this._FirstLoginAt = value;
130
- }
131
-
132
- get LastLoginAt(): Date {
133
- return this._LastLoginAt;
134
- }
135
-
136
- private set LastLoginAt(value: Date) {
137
- this._LastLoginAt = value;
138
- }
139
-
140
- get MFAEnabled(): number {
141
- return this._MFAEnabled;
142
- }
143
-
144
- private set MFAEnabled(value: number) {
145
- this._MFAEnabled = value;
146
- }
147
-
148
- get MFAConfig(): string {
149
- return this._MFAConfig;
150
- }
151
-
152
- private set MFAConfig(value: string) {
153
- this._MFAConfig = value;
154
- }
155
-
156
- get RecoveryEmail(): string {
157
- return this._RecoveryEmail;
158
- }
159
-
160
- private set RecoveryEmail(value: string) {
161
- this._RecoveryEmail = value;
162
- }
163
-
164
- get FailedLoginAttemptCount(): number {
165
- return this._FailedLoginAttemptCount;
166
- }
167
-
168
- private set FailedLoginAttemptCount(value: number) {
169
- this._FailedLoginAttemptCount = value;
170
- }
171
-
172
- get LastFailedLoginAt(): Date {
173
- return this._LastFailedLoginAt;
174
- }
175
-
176
- private set LastFailedLoginAt(value: Date) {
177
- this._LastFailedLoginAt = value;
178
- }
179
-
180
- get LastPasswordChangedAt(): Date {
181
- return this._LastPasswordChangedAt;
182
- }
183
-
184
- private set LastPasswordChangedAt(value: Date) {
185
- this._LastPasswordChangedAt = value;
186
- }
187
-
188
- get NeedToChangePasswordYN(): YN {
189
- return this._NeedToChangePasswordYN;
190
- }
191
-
192
- private set NeedToChangePasswordYN(value: YN) {
193
- this._NeedToChangePasswordYN = value;
194
- }
195
-
196
- get CreatedById(): number {
197
- return this._CreatedById;
198
- }
199
-
200
- private set CreatedById(value: number) {
201
- this._CreatedById = value;
202
- }
203
-
204
- get CreatedAt(): Date {
205
- return this._CreatedAt;
206
- }
207
-
208
- private set CreatedAt(value: Date) {
209
- this._CreatedAt = value;
210
- }
211
-
212
- get UpdatedById(): number {
213
- return this._UpdatedById;
214
- }
215
-
216
- private set UpdatedById(value: number) {
217
- this._UpdatedById = value;
218
- }
219
-
220
- get UpdatedAt(): Date {
221
- return this._UpdatedAt;
222
- }
223
-
224
- private set UpdatedAt(value: Date) {
225
- this._UpdatedAt = value;
226
- }
227
-
228
- async getDetails(): Promise<{
229
- FullName: string;
230
- UserName: string;
231
- IDNo: string;
232
- IDType: string;
233
- Email: string;
234
- ContactNo: string;
235
- }> {
236
- return {
237
- FullName: this.FullName,
238
- UserName: this.UserName,
239
- IDNo: this.IDNo,
240
- IDType: this.IDType,
241
- Email: this.Email,
242
- ContactNo: this.ContactNo,
243
- };
244
- }
245
-
246
- private constructor(
247
- sessionService: ISessionService,
248
- dbTransaction?: any,
249
- userInfo?: IUserAttr,
250
- ) {
251
- super();
252
- this._SessionService = sessionService;
253
-
254
- if (dbTransaction) {
255
- this._dbTransaction = dbTransaction;
256
- }
257
- // set all the class properties
258
- if (userInfo) {
259
- this.UserId = userInfo.UserId;
260
- this.UserName = userInfo.FullName;
261
- this.FullName = userInfo.FullName;
262
- this.IDNo = userInfo.IDNo;
263
- this.Email = userInfo.Email;
264
- this.ContactNo = userInfo.ContactNo;
265
- this.Password = userInfo.Password;
266
- this.staffs = userInfo.staffs;
267
- this.Status = userInfo.Status;
268
- this.DefaultPasswordChangedYN = userInfo.DefaultPasswordChangedYN;
269
- this.FirstLoginAt = userInfo.FirstLoginAt;
270
- this.LastLoginAt = userInfo.LastLoginAt;
271
- this.MFAEnabled = userInfo.MFAEnabled;
272
- this.MFAConfig = userInfo.MFAConfig;
273
- this.RecoveryEmail = userInfo.RecoveryEmail;
274
- this.FailedLoginAttemptCount = userInfo.FailedLoginAttemptCount;
275
- this.LastFailedLoginAt = userInfo.LastFailedLoginAt;
276
- this.LastPasswordChangedAt = userInfo.LastPasswordChangedAt;
277
- this.NeedToChangePasswordYN = userInfo.NeedToChangePasswordYN;
278
- this.CreatedById = userInfo.CreatedById;
279
- this.CreatedAt = userInfo.CreatedAt;
280
- this.UpdatedById = userInfo.UpdatedById;
281
- this.UpdatedAt = userInfo.UpdatedAt;
282
- }
283
- }
11
+ export class LoginUser extends User implements ILoginUser {
12
+ session = {
13
+ Id: null,
14
+ };
284
15
 
285
16
  static async init(
286
17
  sessionService: ISessionService,
287
18
  userId?: number,
288
19
  dbTransaction = null,
289
20
  ): Promise<LoginUser> {
290
- LoginUser._RedisService = await RedisService.init();
21
+ User._RedisService = await RedisService.init();
291
22
  if (userId) {
292
23
  if (dbTransaction) {
293
- LoginUser._Repository = new UserRepository();
24
+ User._Repository = new UserRepository();
294
25
  }
295
- const user = await LoginUser._Repository.findOne({
26
+ const user = await User._Repository.findOne({
296
27
  where: {
297
28
  UserId: userId,
298
29
  },
@@ -343,1998 +74,142 @@ export class LoginUser extends LoginUserBase {
343
74
  return new LoginUser(sessionService, dbTransaction);
344
75
  }
345
76
 
346
- async setEmail(email: string, dbTransaction): Promise<void> {
347
- try {
348
- //Check if email is not the same as the current email if it is, skip all the steps
349
- if (this.Email === email) {
350
- return;
351
- }
352
-
353
- //Check if email is duplicated, if yes, throw error
354
- const user = await LoginUser._Repository.findOne({
355
- where: {
356
- Email: email,
357
- },
358
- transaction: dbTransaction,
359
- });
360
-
361
- if (user) {
362
- throw new ClassError(
363
- 'LoginUser',
364
- 'LoginUserErrMsg0X',
365
- 'Email already exists',
366
- );
367
- }
368
-
369
- //Update the email
370
- this.Email = email;
371
- } catch (error) {
372
- throw error;
373
- }
374
- }
375
-
376
- async login(
77
+ async checkPrivileges(
377
78
  systemCode: string,
378
- email: string,
379
- password: string,
380
- ipAddress: string,
381
- dbTransaction,
382
- ): Promise<string> {
79
+ privilegeName: string,
80
+ ): Promise<boolean> {
383
81
  try {
384
- //validate email
385
82
  if (!this.ObjectId) {
386
- // 1.1: Retrieve user data by calling _Repo.findOne with below parameter
387
- const user = await LoginUser._Repository.findOne({
388
- transaction: dbTransaction,
389
- where: {
390
- Email: email,
391
- Status: {
392
- [Op.or]: [UserStatus.ACTIVE, UserStatus.LOCKED],
393
- },
394
- },
395
- include: [
396
- {
397
- model: Staff,
398
- },
399
- ],
400
- });
401
-
402
- // 1.2: If Exist populate all of the object attributes with the user data retrieved from previous step. if not throw Class Error
403
- if (user) {
404
- const userAttr: IUserAttr = {
405
- UserId: user.UserId,
406
- UserName: user.UserName,
407
- FullName: user?.Staff?.FullName || null,
408
- IDNo: user?.Staff?.IdNo || null,
409
- ContactNo: user?.Staff?.Mobile || null,
410
- Email: user.Email,
411
- Password: user.Password,
412
- Status: user.Status,
413
- DefaultPasswordChangedYN: user.DefaultPasswordChangedYN,
414
- FirstLoginAt: user.FirstLoginAt,
415
- LastLoginAt: user.LastLoginAt,
416
- MFAEnabled: user.MFAEnabled,
417
- MFAConfig: user.MFAConfig,
418
- RecoveryEmail: user.RecoveryEmail,
419
- FailedLoginAttemptCount: user.FailedLoginAttemptCount,
420
- LastFailedLoginAt: user.LastFailedLoginAt,
421
- LastPasswordChangedAt: user.LastPasswordChangedAt,
422
- NeedToChangePasswordYN: user.NeedToChangePasswordYN,
423
- CreatedById: user.CreatedById,
424
- CreatedAt: user.CreatedAt,
425
- UpdatedById: user.UpdatedById,
426
- UpdatedAt: user.UpdatedAt,
427
- staffs: user?.Staff || null,
428
- };
429
-
430
- this.UserId = userAttr.UserId;
431
- this.FullName = userAttr.FullName;
432
- this.IDNo = userAttr.IDNo;
433
- this.Email = userAttr.Email;
434
- this.ContactNo = userAttr.ContactNo;
435
- this.Password = userAttr.Password;
436
- this.Status = userAttr.Status;
437
- this.DefaultPasswordChangedYN = userAttr.DefaultPasswordChangedYN;
438
- this.FirstLoginAt = userAttr.FirstLoginAt;
439
- this.LastLoginAt = userAttr.LastLoginAt;
440
- this.MFAEnabled = userAttr.MFAEnabled;
441
- this.MFAConfig = userAttr.MFAConfig;
442
- this.RecoveryEmail = userAttr.RecoveryEmail;
443
- this.FailedLoginAttemptCount = userAttr.FailedLoginAttemptCount;
444
- this.LastFailedLoginAt = userAttr.LastFailedLoginAt;
445
- this.LastPasswordChangedAt = userAttr.LastPasswordChangedAt;
446
- this.NeedToChangePasswordYN = userAttr.NeedToChangePasswordYN;
447
- this.CreatedById = userAttr.CreatedById;
448
- this.CreatedAt = userAttr.CreatedAt;
449
- this.UpdatedById = userAttr.UpdatedById;
450
- this.UpdatedAt = userAttr.UpdatedAt;
451
- this.staffs = userAttr.staffs;
452
- } else {
453
- throw new ClassError(
454
- 'LoginUser',
455
- 'LoginUserErrMsg0X',
456
- 'Invalid Credentials',
457
- );
458
- }
459
- }
460
-
461
- if (this.ObjectId && this.Email !== email) {
462
- throw new Error('Invalid credentials.');
463
- }
464
-
465
- //Call LoginUser.check2FA
466
- const check2FA = await LoginUser.check2FA(this, dbTransaction);
467
-
468
- //validate system code
469
-
470
- // 1.3: From here on until step 1.8. If any of the validation is failed, skip the other step and call incrementFailedLoginAttemptCount
471
-
472
- try {
473
- // 1.4: Validate the system user trying to access by calling __SystemRepository.findOne with below parameter.
474
- // If system does not exist, skip all below step and return to step 3
475
- const system = await LoginUser._SystemRepository.findOne({
476
- where: {
477
- SystemCode: systemCode,
478
- Status: 'Active',
479
- },
480
- });
481
- if (!system) {
482
- throw new Error('Invalid credentials.');
483
- }
484
-
485
- // 1.5: Instantiate new PasswordHashService object and call PasswordHashService.verify method to check whether the param.Password is correct.
486
- // If not, skip all below step and return to step 3.
487
- const passwordHashService = new PasswordHashService();
488
- const isPasswordValid = await passwordHashService.verify(
489
- password,
490
- this.Password,
491
- );
492
- if (!isPasswordValid) {
493
- throw new Error('Invalid credentials.');
494
- }
495
-
496
- // 1.6: Validate the user access to the system by calling . if it return false, skip all below step and return to step 3
497
- await this.checkSystemAccess(
498
- this.UserId,
499
- system.SystemCode,
500
- dbTransaction,
501
- );
502
-
503
- // 1.7: f this.Status = "Locked" , call shouldReleaseLock
504
- if (this.Status === UserStatus.LOCKED) {
505
- const isReleaseLock = LoginUser.shouldReleaseLock(
506
- this.LastFailedLoginAt,
507
- );
508
- // 1.8: if the previous step returns true, call releaseLock then update this.Status = "Active". if false, skip all below step and return to step 3
509
- if (isReleaseLock) {
510
- await LoginUser.releaseLock(this.UserId, dbTransaction);
511
- this.Status = UserStatus.ACTIVE;
512
- } else {
513
- throw new Error('Invalid credentials.');
514
- }
515
- }
516
- } catch (error) {
517
- await this.incrementFailedLoginAttemptCount(dbTransaction);
518
- }
519
-
520
- // 2.1: Call alertNewLogin to check whether the ip used is new ip and alert the user if it's new.
521
- const system = await LoginUser._SystemRepository.findOne({
522
- where: {
523
- SystemCode: systemCode,
524
- },
525
- });
526
- await this.alertNewLogin(this.ObjectId, system.SystemCode, ipAddress);
527
-
528
- // 2.2 : Set below properties :
529
- // FailedLoginAttemptCount: 0.
530
- // If FirstLoginAt is empty, this.FirstLoginAt = <current timestamp>
531
- // LastLoginAt = <current timestamp>
532
- this.FailedLoginAttemptCount = 0;
533
- this.LastLoginAt = new Date();
534
- if (!this.FirstLoginAt) {
535
- this.FirstLoginAt = new Date();
83
+ throw new Error('ObjectId(UserId) is not set');
536
84
  }
537
85
 
538
- // 2.3: Call _Repo.update and update user data in the db to the current object properties value. Dont forget to use dbTransaction.
539
- await LoginUser._Repository.update(
540
- {
541
- FullName: this.FullName,
542
- UserName: this.UserName,
543
- IDNo: this.IDNo,
544
- Email: this.Email,
545
- ContactNo: this.ContactNo,
546
- Password: this.Password,
547
- Status: this.Status,
548
- DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
549
- FirstLoginAt: this.FirstLoginAt,
550
- LastLoginAt: this.LastLoginAt,
551
- MFAEnabled: this.MFAEnabled,
552
- MFAConfig: this.MFAConfig,
553
- RecoveryEmail: this.RecoveryEmail,
554
- FailedLoginAttemptCount: this.FailedLoginAttemptCount,
555
- LastFailedLoginAt: this.LastFailedLoginAt,
556
- LastPasswordChangedAt: this.LastPasswordChangedAt,
557
- NeedToChangePasswordYN: this.NeedToChangePasswordYN,
558
- },
559
- {
560
- where: {
561
- UserId: this.UserId,
562
- },
563
- transaction: dbTransaction,
564
- },
565
- );
566
-
567
- // fetch user session if exists
568
86
  const userSession = await this._SessionService.retrieveUserSession(
569
87
  this.ObjectId,
570
88
  );
571
- let systemLogin = userSession.systemLogins.find(
572
- (system) => system.code === systemCode,
573
- );
574
-
575
- // generate new session id
576
- const { randomUUID } = require('crypto');
577
- const sessionId = randomUUID();
578
-
579
- if (systemLogin) {
580
- systemLogin = systemLogin.sessionId = sessionId;
581
- userSession.systemLogins.map((system) =>
582
- system.code === systemCode ? systemLogin : system,
583
- );
584
- } else {
585
- // if not, add new system login into the userSession
586
- const newLogin = {
587
- id: system.SystemCode,
588
- code: system.SystemCode,
589
- sessionId: sessionId,
590
- privileges: await this.getPrivileges(
591
- system.SystemCode,
592
- dbTransaction,
593
- ),
594
- };
595
- userSession.systemLogins.push(newLogin);
596
- }
597
- // then update userSession inside the redis storage with 1 day duration of time-to-live
598
- this._SessionService.setUserSession(this.ObjectId, userSession);
599
-
600
- // record new login history
601
- await LoginUser._LoginHistoryRepository.create(
602
- {
603
- UserId: this.UserId,
604
- SystemCode: system.SystemCode,
605
- OriginIp: ipAddress,
606
- CreatedAt: new Date(),
607
- LoginStatus: LoginStatusEnum.SUCCESS,
608
- },
609
- {
610
- transaction: dbTransaction,
611
- },
612
- );
613
89
 
614
- // Retrieve is2FAEnabledYN from sso-config with ComponentConfig.
615
- const is2FAEnabledYN = ComponentConfig.getComponentConfigValue(
616
- '@tomei/sso',
617
- 'is2FAEnabledYN',
90
+ const systemLogin = userSession.systemLogins.find(
91
+ (system) => system.code === systemCode,
618
92
  );
619
93
 
620
- if (is2FAEnabledYN === 'Y') {
621
- return `${this.UserId}:`;
94
+ if (!systemLogin) {
95
+ return false;
622
96
  }
623
97
 
624
- return `${this.UserId}:${sessionId}`;
98
+ const privileges = systemLogin.privileges;
99
+ const hasPrivilege = privileges.includes(privilegeName);
100
+ return hasPrivilege;
625
101
  } catch (error) {
626
- if (this.ObjectId) {
627
- await LoginUser._LoginHistoryRepository.create(
628
- {
629
- UserId: this.UserId,
630
- SystemCode: systemCode,
631
- OriginIp: ipAddress,
632
- LoginStatus: LoginStatusEnum.FAILURE,
633
- CreatedAt: new Date(),
634
- },
635
- {
636
- transaction: dbTransaction,
637
- },
638
- );
639
- }
640
102
  throw error;
641
103
  }
642
104
  }
643
105
 
644
- private async checkSystemAccess(
645
- userId: number,
106
+ // private async checkSystemAccess(
107
+ // userId: number,
108
+ // systemCode: string,
109
+ // dbTransaction?: any,
110
+ // ): Promise<void> {
111
+ // try {
112
+ // let isUserHaveAccess = false;
113
+
114
+ // const systemAccess = await User._UserSystemAccessRepo.findOne({
115
+ // where: {
116
+ // UserId: userId,
117
+ // SystemCode: systemCode,
118
+ // Status: 'Active',
119
+ // },
120
+ // dbTransaction,
121
+ // });
122
+
123
+ // if (systemAccess) {
124
+ // isUserHaveAccess = true;
125
+ // } else {
126
+ // const userGroups = await User._UserGroupRepo.findAll({
127
+ // where: {
128
+ // UserId: userId,
129
+ // InheritGroupAccessYN: 'Y',
130
+ // Status: 'Active',
131
+ // },
132
+ // include: [
133
+ // {
134
+ // model: GroupModel,
135
+ // },
136
+ // ],
137
+ // dbTransaction,
138
+ // });
139
+
140
+ // for (const usergroup of userGroups) {
141
+ // const group = usergroup.Group;
142
+ // const groupSystemAccess = await User.getInheritedSystemAccess(
143
+ // dbTransaction,
144
+ // group,
145
+ // );
146
+
147
+ // for (const system of groupSystemAccess) {
148
+ // if (system.SystemCode === systemCode) {
149
+ // isUserHaveAccess = true;
150
+ // break;
151
+ // }
152
+ // }
153
+ // }
154
+ // }
155
+
156
+ // if (!isUserHaveAccess) {
157
+ // throw new Error("User don't have access to the system.");
158
+ // }
159
+ // } catch (error) {
160
+ // throw error;
161
+ // }
162
+ // }
163
+
164
+ async checkSession(
646
165
  systemCode: string,
647
- dbTransaction?: any,
648
- ): Promise<void> {
166
+ sessionId: string,
167
+ userId: string,
168
+ ): Promise<ISystemLogin> {
649
169
  try {
650
- let isUserHaveAccess = false;
651
-
652
- const systemAccess = await LoginUser._UserSystemAccessRepo.findOne({
653
- where: {
654
- UserId: userId,
655
- SystemCode: systemCode,
656
- Status: 'Active',
657
- },
658
- dbTransaction,
659
- });
660
-
661
- if (systemAccess) {
662
- isUserHaveAccess = true;
663
- } else {
664
- const userGroups = await LoginUser._UserGroupRepo.findAll({
665
- where: {
666
- UserId: userId,
667
- InheritGroupAccessYN: 'Y',
668
- Status: 'Active',
669
- },
670
- include: [
671
- {
672
- model: GroupModel,
673
- },
674
- ],
675
- dbTransaction,
676
- });
677
-
678
- for (const usergroup of userGroups) {
679
- const group = usergroup.Group;
680
- const groupSystemAccess = await LoginUser.getInheritedSystemAccess(
681
- dbTransaction,
682
- group,
683
- );
170
+ const userSession = await this._SessionService.retrieveUserSession(
171
+ userId,
172
+ );
684
173
 
685
- for (const system of groupSystemAccess) {
686
- if (system.SystemCode === systemCode) {
687
- isUserHaveAccess = true;
688
- break;
689
- }
690
- }
691
- }
174
+ if (userSession.systemLogins.length === 0) {
175
+ throw new Error('Session expired.');
692
176
  }
693
177
 
694
- if (!isUserHaveAccess) {
695
- throw new Error("User don't have access to the system.");
696
- }
697
- } catch (error) {
698
- throw error;
699
- }
700
- }
178
+ const systemLogin = userSession.systemLogins.find(
179
+ (sl) => sl.code === systemCode,
180
+ );
701
181
 
702
- private async alertNewLogin(
703
- userId: string,
704
- systemCode: string,
705
- ipAddress: string,
706
- ) {
707
- try {
708
- const userLogins = await LoginUser._LoginHistoryRepository.findAll({
709
- where: {
710
- UserId: userId,
711
- SystemCode: systemCode,
712
- },
713
- });
182
+ if (!systemLogin) {
183
+ throw new Error('Session expired.');
184
+ }
714
185
 
715
- const gotPreviousLogins = userLogins?.length !== 0;
716
- let ipFound: LoginHistory | undefined = undefined;
717
- if (gotPreviousLogins) {
718
- ipFound = userLogins.find((item) => item.OriginIp === ipAddress);
186
+ if (systemLogin.sessionId !== sessionId) {
187
+ throw new Error('Session expired.');
719
188
  }
720
189
 
721
- // if (gotPreviousLogins && !ipFound) {
722
- // const EMAIL_SENDER =
723
- // process.env.EMAIL_SENDER || 'itd-system@tomei.com.my';
724
- // const transporter = new SMTPMailer();
190
+ await this._SessionService.refreshDuration(userId);
725
191
 
726
- // await transporter.sendMail({
727
- // from: EMAIL_SENDER,
728
- // to: this.Email,
729
- // subject: 'New Login Alert',
730
- // html: `<p>Dear ${this.FullName},</p>
731
- // <p>There was a new login to your account from ${ipAddress} on ${new Date().toLocaleString()}.</p>
732
- // <p>If this was you, you can safely ignore this email.</p>
733
- // <p>If you suspect that someone else is trying to access your account, please contact us immediately at itd-support@tomei.com.my.</p>
734
- // <p>Thank you!,</p>
735
- // <p>
736
- // Best Regards,
737
- // IT Department
738
- // </p>`,
739
- // });
740
- // }
192
+ return systemLogin;
741
193
  } catch (error) {
742
194
  throw error;
743
195
  }
744
196
  }
745
197
 
746
- private async getPrivileges(
747
- systemCode: string,
748
- dbTransaction?: any,
749
- ): Promise<string[]> {
198
+ async logout(systemCode: string) {
750
199
  try {
751
- const system = await LoginUser._SystemRepository.findOne({
752
- where: {
753
- SystemCode: systemCode,
754
- },
755
- transaction: dbTransaction,
756
- });
757
-
758
- if (!system) {
759
- throw new Error('Invalid system code.');
200
+ if (!this.ObjectId) {
201
+ throw new Error('ObjectId(UserId) is not set');
760
202
  }
761
-
762
- /**
763
- * Ways user can get privileges:
764
- * 1. Privileges directly assigned to the user using UserPrivilege
765
- * 2. User have object that have privileges
766
- * 3. User have group that can inherit privileges
767
- * 3. User have group that have parent group that can inherit privileges to said group
768
- */
769
-
770
- //Retrive privileges directly assigned to the user
771
- const userPrivileges = await this.getUserPersonalPrivileges(
772
- systemCode,
773
- dbTransaction,
203
+ const userSession = await this._SessionService.retrieveUserSession(
204
+ this.ObjectId,
774
205
  );
775
-
776
- //Retrieve privileges from object that user have
777
- const objectPrivileges = await this.getObjectPrivileges(
778
- systemCode,
779
- dbTransaction,
206
+ const index = userSession.systemLogins.findIndex(
207
+ (system) => system.code === systemCode,
780
208
  );
781
-
782
- //Retrieve privileges from group that able to inherit privileges to user
783
- //Retrieve all user groups own by user that can inherit privileges for the system
784
- const userGroupOwnByUser = await LoginUser._UserGroupRepo.findAll({
785
- where: {
786
- UserId: this.UserId,
787
- InheritGroupSystemAccessYN: 'Y',
788
- InheritGroupPrivilegeYN: 'Y',
789
- Status: 'Active',
790
- },
791
- include: [
792
- {
793
- model: GroupModel,
794
- where: {
795
- Status: 'Active',
796
- },
797
- include: [
798
- {
799
- model: GroupSystemAccessModel,
800
- where: {
801
- SystemCode: systemCode,
802
- },
803
- },
804
- ],
805
- },
806
- ],
807
- transaction: dbTransaction,
808
- });
809
-
810
- //Get all privileges from groups data
811
- let groupsPrivileges: string[] = [];
812
- for (const userGroup of userGroupOwnByUser) {
813
- const gp: string[] = await this.getInheritedPrivileges(
814
- userGroup.GroupCode,
815
- systemCode,
816
- dbTransaction,
817
- );
818
- groupsPrivileges = [...groupsPrivileges, ...gp];
819
- }
820
-
821
- //Map all privileges to a single array
822
- const privileges: string[] = [
823
- ...userPrivileges,
824
- ...objectPrivileges,
825
- ...groupsPrivileges,
826
- ];
827
- return privileges;
209
+ userSession.systemLogins.splice(index, 1);
210
+ this._SessionService.setUserSession(this.ObjectId, userSession);
828
211
  } catch (error) {
829
212
  throw error;
830
213
  }
831
214
  }
832
-
833
- private async getInheritedPrivileges(
834
- groupCode: string,
835
- systemCode: string,
836
- dbTransaction?: string,
837
- ): Promise<string[]> {
838
- try {
839
- // Retrieve Group from the database based on groupCode
840
- const group = await LoginUser._GroupRepo.findOne({
841
- where: {
842
- GroupCode: groupCode,
843
- Status: 'Active',
844
- },
845
- include: [
846
- {
847
- model: GroupPrivilegeModel,
848
- where: {
849
- Status: 'Active',
850
- },
851
- include: [
852
- {
853
- model: SystemPrivilege,
854
- where: {
855
- SystemCode: systemCode,
856
- Status: 'Active',
857
- },
858
- },
859
- ],
860
- },
861
- ],
862
- transaction: dbTransaction,
863
- });
864
-
865
- // retrieve group ObjectPrivileges
866
- const objectPrivileges =
867
- await LoginUser._GroupObjectPrivilegeRepo.findAll({
868
- where: {
869
- GroupCode: groupCode,
870
- },
871
- include: {
872
- model: SystemPrivilege,
873
- where: {
874
- SystemCode: systemCode,
875
- Status: 'Active',
876
- },
877
- },
878
- transaction: dbTransaction,
879
- });
880
-
881
- let privileges: string[] = [];
882
- // Add privileges from the group to the privileges array
883
- const groupPrivileges: string[] = [];
884
- for (const groupPrivilege of group.GroupPrivileges) {
885
- groupPrivileges.push(groupPrivilege.Privilege.PrivilegeCode);
886
- }
887
-
888
- const ops: string[] = [];
889
- for (const objectPrivilege of objectPrivileges) {
890
- ops.push(objectPrivilege.Privilege.PrivilegeCode);
891
- }
892
-
893
- privileges = [...privileges, ...groupPrivileges, ...ops];
894
-
895
- // Recursive call if not root and allow inherit privileges from parent group
896
- if (group.ParentGroupCode && group.InheritParentPrivilegeYN === 'Y') {
897
- const parentGroupPrivileges = await this.getInheritedPrivileges(
898
- group.ParentGroupCode,
899
- systemCode,
900
- dbTransaction,
901
- );
902
- privileges = [...privileges, ...parentGroupPrivileges];
903
- }
904
-
905
- return privileges;
906
- } catch (error) {
907
- throw error;
908
- }
909
- }
910
-
911
- private async getUserPersonalPrivileges(
912
- systemCode: string,
913
- dbTransaction?: any,
914
- ): Promise<string[]> {
915
- try {
916
- const userPrivileges = await LoginUser._UserPrivilegeRepo.findAll({
917
- where: {
918
- UserId: this.UserId,
919
- Status: 'Active',
920
- },
921
- include: {
922
- model: SystemPrivilege,
923
- where: {
924
- SystemCode: systemCode,
925
- Status: 'Active',
926
- },
927
- },
928
- transaction: dbTransaction,
929
- });
930
-
931
- const privileges: string[] = userPrivileges.map(
932
- (u) => u.Privilege.PrivilegeCode,
933
- );
934
- return privileges;
935
- } catch (error) {
936
- throw error;
937
- }
938
- }
939
-
940
- private async getObjectPrivileges(
941
- systemCode: string,
942
- dbTransaction?: any,
943
- ): Promise<string[]> {
944
- try {
945
- const userObjectPrivileges =
946
- await LoginUser._UserObjectPrivilegeRepo.findAll({
947
- where: {
948
- UserId: this.UserId,
949
- },
950
- include: {
951
- model: SystemPrivilege,
952
- where: {
953
- SystemCode: systemCode,
954
- Status: 'Active',
955
- },
956
- },
957
- transaction: dbTransaction,
958
- });
959
-
960
- const privilegesCodes: string[] = userObjectPrivileges.map(
961
- (u) => u.Privilege.PrivilegeCode,
962
- );
963
- return privilegesCodes;
964
- } catch (error) {
965
- throw error;
966
- }
967
- }
968
-
969
- async checkPrivileges(
970
- systemCode: string,
971
- privilegeName: string,
972
- ): Promise<boolean> {
973
- try {
974
- if (!this.ObjectId) {
975
- throw new Error('ObjectId(UserId) is not set');
976
- }
977
-
978
- const userSession = await this._SessionService.retrieveUserSession(
979
- this.ObjectId,
980
- );
981
-
982
- const systemLogin = userSession.systemLogins.find(
983
- (system) => system.code === systemCode,
984
- );
985
-
986
- if (!systemLogin) {
987
- return false;
988
- }
989
-
990
- const privileges = systemLogin.privileges;
991
- const hasPrivilege = privileges.includes(privilegeName);
992
- return hasPrivilege;
993
- } catch (error) {
994
- throw error;
995
- }
996
- }
997
-
998
- async checkSession(
999
- systemCode: string,
1000
- sessionId: string,
1001
- userId: string,
1002
- ): Promise<ISystemLogin> {
1003
- try {
1004
- const userSession = await this._SessionService.retrieveUserSession(
1005
- userId,
1006
- );
1007
-
1008
- if (userSession.systemLogins.length === 0) {
1009
- throw new Error('Session expired.');
1010
- }
1011
-
1012
- const systemLogin = userSession.systemLogins.find(
1013
- (sl) => sl.code === systemCode,
1014
- );
1015
-
1016
- if (!systemLogin) {
1017
- throw new Error('Session expired.');
1018
- }
1019
-
1020
- if (systemLogin.sessionId !== sessionId) {
1021
- throw new Error('Session expired.');
1022
- }
1023
-
1024
- await this._SessionService.refreshDuration(userId);
1025
-
1026
- return systemLogin;
1027
- } catch (error) {
1028
- throw error;
1029
- }
1030
- }
1031
-
1032
- async logout(systemCode: string) {
1033
- try {
1034
- if (!this.ObjectId) {
1035
- throw new Error('ObjectId(UserId) is not set');
1036
- }
1037
- const userSession = await this._SessionService.retrieveUserSession(
1038
- this.ObjectId,
1039
- );
1040
- const index = userSession.systemLogins.findIndex(
1041
- (system) => system.code === systemCode,
1042
- );
1043
- userSession.systemLogins.splice(index, 1);
1044
- this._SessionService.setUserSession(this.ObjectId, userSession);
1045
- } catch (error) {
1046
- throw error;
1047
- }
1048
- }
1049
-
1050
- private static async checkUserInfoDuplicated(
1051
- dbTransaction: any,
1052
- query: ICheckUserInfoDuplicatedQuery,
1053
- ) {
1054
- //This method if check if duplicate user info found.
1055
- try {
1056
- //Part 1: Prepare Query Params
1057
- //Params is all optional but at least one is required.
1058
- const { Email, UserName, IdType, IdNo, ContactNo } = query;
1059
- //Prepare the Params to be used as OR operation in SQL query.
1060
- const where = {
1061
- [Op.or]: {},
1062
- };
1063
- if (Email) {
1064
- where[Op.or]['Email'] = Email;
1065
- }
1066
-
1067
- if (UserName) {
1068
- where[Op.or]['UserName'] = UserName;
1069
- }
1070
- //If Params.IdNo is not null, then Params.IdType is required and vice versa.
1071
- if (IdType && IdNo) {
1072
- where[Op.or]['IdType'] = IdType;
1073
- where[Op.or]['IdNo'] = IdNo;
1074
- }
1075
- if (ContactNo) {
1076
- where[Op.or]['ContactNo'] = ContactNo;
1077
- }
1078
- //Call LoginUser._Repo findOne method by passing the OR operation object based on query params in Part 1. Code example can be referred at bottom part. Make sure to pass the dbTransaction
1079
- const user = await LoginUser._Repository.findAll({
1080
- where,
1081
- transaction: dbTransaction,
1082
- });
1083
-
1084
- if (user && user.length > 0) {
1085
- throw new ClassError(
1086
- 'LoginUser',
1087
- 'LoginUserErrMsg0X',
1088
- 'User info already exists',
1089
- );
1090
- }
1091
- } catch (error) {
1092
- throw error;
1093
- }
1094
- }
1095
-
1096
- private static generateDefaultPassword(): string {
1097
- //This method will generate default password for user.
1098
- try {
1099
- //Part 1: Retrieve Password Policy
1100
- //Retrieve all password policy from component config, call ComponentConfig.getComponentConfigValue method
1101
- const passwordPolicy = ComponentConfig.getComponentConfigValue(
1102
- '@tomei/sso',
1103
- 'passwordPolicy',
1104
- );
1105
-
1106
- //Make sure all passwordPolicy keys got values, if not throw new ClassError
1107
- if (
1108
- !passwordPolicy ||
1109
- !passwordPolicy.maxLen ||
1110
- !passwordPolicy.minLen ||
1111
- !passwordPolicy.nonAcceptableChar ||
1112
- !passwordPolicy.numOfCapitalLetters ||
1113
- !passwordPolicy.numOfNumbers ||
1114
- !passwordPolicy.numOfSpecialChars
1115
- ) {
1116
- throw new ClassError(
1117
- 'LoginUser',
1118
- 'LoginUserErrMsg0X',
1119
- 'Missing password policy. Please set in config file.',
1120
- );
1121
- }
1122
-
1123
- if (
1124
- passwordPolicy.numOfCapitalLetters +
1125
- passwordPolicy.numOfNumbers +
1126
- passwordPolicy.numOfSpecialChars >
1127
- passwordPolicy.maxLen
1128
- ) {
1129
- throw new ClassError(
1130
- 'LoginUser',
1131
- 'LoginUserErrMsg0X',
1132
- 'Password policy is invalid. Please set in config file.',
1133
- );
1134
- }
1135
-
1136
- //Part 2: Generate Random Password and returns
1137
- //Generate random password based on passwordPolicy
1138
- const {
1139
- maxLen,
1140
- minLen,
1141
- nonAcceptableChar,
1142
- numOfCapitalLetters,
1143
- numOfNumbers,
1144
- numOfSpecialChars,
1145
- } = passwordPolicy;
1146
- const passwordLength =
1147
- Math.floor(Math.random() * (maxLen - minLen + 1)) + minLen;
1148
- const words = 'abcdefghijklmnopqrstuvwxyz';
1149
- const capitalLetters = words.toUpperCase();
1150
- const numbers = '0123456789';
1151
- const specialChars = '!@#$%^&*()_+-={}[]|:;"<>,.?/~`';
1152
- const nonAcceptableChars: string[] = nonAcceptableChar.split(',');
1153
-
1154
- const filteredWords: string[] = words
1155
- .split('')
1156
- .filter((word) => !nonAcceptableChars.includes(word));
1157
- const filteredCapitalLetters: string[] = capitalLetters
1158
- .split('')
1159
- .filter((word) => !nonAcceptableChars.includes(word));
1160
- const filteredNumbers: string[] = numbers
1161
- .split('')
1162
- .filter((word) => !nonAcceptableChars.includes(word));
1163
- const filteredSpecialChars: string[] = specialChars
1164
- .split('')
1165
- .filter((word) => !nonAcceptableChars.includes(word));
1166
-
1167
- const generatedCapitalLetters: string[] = [];
1168
- const generatedNumbers: string[] = [];
1169
- const generatedSpecialChars: string[] = [];
1170
- const generatedWords: string[] = [];
1171
-
1172
- for (let i = 0; i < numOfCapitalLetters; i++) {
1173
- const randomIndex = Math.floor(
1174
- Math.random() * filteredCapitalLetters.length,
1175
- );
1176
- generatedCapitalLetters.push(filteredCapitalLetters[randomIndex]);
1177
- }
1178
-
1179
- for (let i = 0; i < numOfNumbers; i++) {
1180
- const randomIndex = Math.floor(Math.random() * filteredNumbers.length);
1181
- generatedNumbers.push(filteredNumbers[randomIndex]);
1182
- }
1183
-
1184
- for (let i = 0; i < numOfSpecialChars; i++) {
1185
- const randomIndex = Math.floor(
1186
- Math.random() * filteredSpecialChars.length,
1187
- );
1188
- generatedSpecialChars.push(filteredSpecialChars[randomIndex]);
1189
- }
1190
-
1191
- for (
1192
- let i = 0;
1193
- i <
1194
- passwordLength -
1195
- (numOfCapitalLetters + numOfNumbers + numOfSpecialChars);
1196
- i++
1197
- ) {
1198
- const randomIndex = Math.floor(Math.random() * filteredWords.length);
1199
- generatedWords.push(filteredWords[randomIndex]);
1200
- }
1201
-
1202
- //Combine all generated words, capitalLetters, numbers and specialChars and shuffle it
1203
- let generatedPassword = '';
1204
- const allGeneratedChars = generatedCapitalLetters.concat(
1205
- generatedNumbers,
1206
- generatedSpecialChars,
1207
- generatedWords,
1208
- );
1209
- allGeneratedChars.sort(() => Math.random() - 0.5);
1210
- generatedPassword = allGeneratedChars.join('');
1211
- return generatedPassword;
1212
- } catch (error) {
1213
- throw error;
1214
- }
1215
- }
1216
-
1217
- private static async setPassword(
1218
- dbTransaction: any,
1219
- user: LoginUser,
1220
- password: string,
1221
- ): Promise<LoginUser> {
1222
- //This method will set password for the user.
1223
- try {
1224
- //Part 1: Verify Password
1225
- //Retrieve all password policy from component config
1226
- const passwordPolicy = ComponentConfig.getComponentConfigValue(
1227
- '@tomei/sso',
1228
- 'passwordPolicy',
1229
- );
1230
-
1231
- //Make sure all passwordPolicy keys got values, if not throw a ClassError
1232
- if (
1233
- !passwordPolicy ||
1234
- !passwordPolicy.maxLen ||
1235
- !passwordPolicy.minLen ||
1236
- !passwordPolicy.nonAcceptableChar ||
1237
- !passwordPolicy.numOfCapitalLetters ||
1238
- !passwordPolicy.numOfNumbers ||
1239
- !passwordPolicy.numOfSpecialChars
1240
- ) {
1241
- throw new ClassError(
1242
- 'LoginUser',
1243
- 'LoginUserErrMsg0X',
1244
- 'Missing password policy. Please set in config file.',
1245
- );
1246
- }
1247
-
1248
- //Compare Params.password with the password policy. If not matched, throw new ClassError
1249
- try {
1250
- //Check if password length is more than passwordPolicy.minLen
1251
- if (password.length < passwordPolicy.minLen) {
1252
- throw Error('Password is too short');
1253
- }
1254
-
1255
- //Check if password length is less than passwordPolicy.maxLen
1256
- if (password.length > passwordPolicy.maxLen) {
1257
- throw Error('Password is too long');
1258
- }
1259
-
1260
- //Check if password contains nonAcceptableChar
1261
- const nonAcceptableChars: string[] =
1262
- passwordPolicy.nonAcceptableChar.split(',');
1263
- const nonAcceptableCharsFound = nonAcceptableChars.some((char) =>
1264
- password.includes(char),
1265
- );
1266
- if (nonAcceptableCharsFound) {
1267
- throw Error('Password contains unacceptable characters');
1268
- }
1269
-
1270
- //Check if password contains the correct amount of capital letter required from numOfCapitalLetters
1271
- const capitalLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
1272
- const numOfCapitalLetters = passwordPolicy.numOfCapitalLetters;
1273
- const capitalLettersFound = capitalLetters
1274
- .split('')
1275
- .filter((char) => password.includes(char)).length;
1276
- if (capitalLettersFound < numOfCapitalLetters) {
1277
- throw Error('Password does not contain enough capital letters');
1278
- }
1279
-
1280
- //Check if password contains the correct amount of numbers required from numOfNumbers
1281
- const numbers = '0123456789';
1282
- const numOfNumbers = passwordPolicy.numOfNumbers;
1283
- const numbersFound = numbers
1284
- .split('')
1285
- .filter((char) => password.includes(char)).length;
1286
- if (numbersFound < numOfNumbers) {
1287
- throw Error('Password does not contain enough numbers');
1288
- }
1289
-
1290
- //Check if password contains the correct amount of special characters required from numOfSpecialChars
1291
- const specialChars = '!@#$%^&*()_+-={}[]|:;"<>,.?/~`';
1292
- const numOfSpecialChars = passwordPolicy.numOfSpecialChars;
1293
- const specialCharsFound = specialChars
1294
- .split('')
1295
- .filter((char) => password.includes(char)).length;
1296
- if (specialCharsFound < numOfSpecialChars) {
1297
- throw Error('Password does not contain enough special characters');
1298
- }
1299
- } catch (error) {
1300
- throw new ClassError(
1301
- 'LoginUser',
1302
- 'LoginUserErrMsg0X',
1303
- "Your password doesn't meet security requirements. Try using a mix of uppercase and lowercase letters, numbers, and symbols.",
1304
- );
1305
- }
1306
-
1307
- //Part 2: Hash Password
1308
- //Hash the Params.password using PasswordHashService.hashPassword method
1309
- const passwordHashService = new PasswordHashService();
1310
- const hashedPassword = await passwordHashService.hashPassword(password);
1311
-
1312
- //Part 3: Return Updated User Instance
1313
- user._Password = hashedPassword;
1314
- return user;
1315
- } catch (error) {
1316
- throw error;
1317
- }
1318
- }
1319
-
1320
- public static async create(
1321
- loginUser: LoginUser,
1322
- dbTransaction: any,
1323
- user: LoginUser,
1324
- ): Promise<LoginUser> {
1325
- try {
1326
- //This method will insert new user record
1327
- //Part 1: Privilege Checking
1328
- const systemCode =
1329
- ApplicationConfig.getComponentConfigValue('system-code');
1330
- const isPrivileged = await loginUser.checkPrivileges(
1331
- systemCode,
1332
- 'User - Create',
1333
- );
1334
-
1335
- //If user does not have privilege to create user, throw a ClassError
1336
- if (!isPrivileged) {
1337
- throw new ClassError(
1338
- 'LoginUser',
1339
- 'LoginUserErrMsg0X',
1340
- 'You do not have the privilege to create user',
1341
- );
1342
- }
1343
-
1344
- //Part 2: Validation
1345
- //Make sure Params.user.Email got values. If not, throw new ClassError
1346
- if (!user.Email && !user.UserName) {
1347
- throw new ClassError(
1348
- 'LoginUser',
1349
- 'LoginUserErrMsg0X',
1350
- 'Email and Username is required',
1351
- );
1352
- }
1353
- //Check if user info exists, call LoginUser.CheckUserInfoDuplicated method
1354
- await LoginUser.checkUserInfoDuplicated(dbTransaction, {
1355
- Email: user.Email,
1356
- UserName: user.UserName,
1357
- IdType: user.IDType,
1358
- IdNo: user.IDNo,
1359
- ContactNo: user.ContactNo,
1360
- });
1361
-
1362
- //Part 3: Generate Default Password
1363
- const defaultPassword = LoginUser.generateDefaultPassword();
1364
- user = await LoginUser.setPassword(dbTransaction, user, defaultPassword);
1365
- //Part 4: Insert User Record
1366
- //Set userToBeCreated to the instantiation of new LoginUser (using private constructor)
1367
- const userInfo: IUserAttr = {
1368
- UserName: user.UserName,
1369
- FullName: user.FullName,
1370
- IDNo: user.IDNo,
1371
- Email: user.Email,
1372
- ContactNo: user.ContactNo,
1373
- Password: user.Password,
1374
- Status: UserStatus.ACTIVE,
1375
- FirstLoginAt: null,
1376
- LastLoginAt: null,
1377
- MFAEnabled: null,
1378
- MFAConfig: null,
1379
- RecoveryEmail: null,
1380
- FailedLoginAttemptCount: 0,
1381
- LastFailedLoginAt: null,
1382
- LastPasswordChangedAt: null,
1383
- DefaultPasswordChangedYN: YN.No,
1384
- NeedToChangePasswordYN: YN.Yes,
1385
- CreatedById: loginUser.UserId,
1386
- CreatedAt: new Date(),
1387
- UpdatedById: loginUser.UserId,
1388
- UpdatedAt: new Date(),
1389
- UserId: null,
1390
- };
1391
- //Call LoginUser._Repo create method to insert new user record
1392
- const newUser = await LoginUser._Repository.create(
1393
- {
1394
- Email: userInfo.Email,
1395
- UserName: userInfo.UserName,
1396
- Password: userInfo.Password,
1397
- Status: userInfo.Status,
1398
- DefaultPasswordChangedYN: userInfo.DefaultPasswordChangedYN,
1399
- FirstLoginAt: userInfo.FirstLoginAt,
1400
- LastLoginAt: userInfo.LastLoginAt,
1401
- MFAEnabled: userInfo.MFAEnabled,
1402
- MFAConfig: userInfo.MFAConfig,
1403
- RecoveryEmail: userInfo.RecoveryEmail,
1404
- FailedLoginAttemptCount: userInfo.FailedLoginAttemptCount,
1405
- LastFailedLoginAt: userInfo.LastFailedLoginAt,
1406
- LastPasswordChangedAt: userInfo.LastPasswordChangedAt,
1407
- NeedToChangePasswordYN: userInfo.NeedToChangePasswordYN,
1408
- CreatedById: userInfo.CreatedById,
1409
- CreatedAt: userInfo.CreatedAt,
1410
- UpdatedById: userInfo.UpdatedById,
1411
- UpdatedAt: userInfo.UpdatedAt,
1412
- },
1413
- {
1414
- transaction: dbTransaction,
1415
- },
1416
- );
1417
-
1418
- userInfo.UserId = newUser.UserId;
1419
- const userToBeCreated = new LoginUser(
1420
- loginUser.SessionService,
1421
- dbTransaction,
1422
- userInfo,
1423
- );
1424
-
1425
- //Part 5: Record Create User Activity
1426
- const activity = new Activity();
1427
- activity.ActivityId = activity.createId();
1428
- activity.Action = ActionEnum.ADD;
1429
- activity.Description = 'Create User';
1430
- activity.EntityType = 'LoginUser';
1431
- activity.EntityId = newUser.UserId.toString();
1432
- activity.EntityValueBefore = JSON.stringify({});
1433
- activity.EntityValueAfter = JSON.stringify(newUser.get({ plain: true }));
1434
-
1435
- await activity.create(loginUser.ObjectId, dbTransaction);
1436
- return userToBeCreated;
1437
- } catch (error) {
1438
- throw error;
1439
- }
1440
- }
1441
-
1442
- private async incrementFailedLoginAttemptCount(dbTransaction: any) {
1443
- // This method is used to process all the necessary step after login failed by invalid credential
1444
-
1445
- // 1. Retrieve maxFailedLoginAttempts and autoReleaseYN from component config, call ComponentConfig.getComponentConfigValue
1446
- const maxFailedLoginAttempts = ComponentConfig.getComponentConfigValue(
1447
- '@tomei/sso',
1448
- 'maxFailedLoginAttempts',
1449
- );
1450
-
1451
- const autoReleaseYN = ComponentConfig.getComponentConfigValue(
1452
- '@tomei/sso',
1453
- 'autoReleaseYN',
1454
- );
1455
-
1456
- // 2. Make sure all maxFailedLoginAttempts keys got values
1457
- if (!maxFailedLoginAttempts || !autoReleaseYN) {
1458
- throw new ClassError(
1459
- 'LoginUser',
1460
- 'LoginUserErrMsg0X',
1461
- 'Missing maxFailedLoginAttempts and or autoReleaseYN. Please set in config file.',
1462
- );
1463
- }
1464
-
1465
- // 3. Set below object property FailedLoginAttemptCount & LastFailedLoginAt
1466
- const FailedLoginAttemptCount = this.FailedLoginAttemptCount + 1;
1467
- const LastFailedLoginAt = new Date();
1468
-
1469
- // 4. If this.FailedLoginAttemptCount > config.maxFailedLogginAttempts, then set this.Status = 'Locked'
1470
- if (FailedLoginAttemptCount > maxFailedLoginAttempts) {
1471
- this.Status = UserStatus.LOCKED;
1472
- }
1473
-
1474
- // 5. Update the user data by calling _Repo.update with these parameter
1475
- await LoginUser._Repository.update(
1476
- {
1477
- FailedLoginAttemptCount: FailedLoginAttemptCount,
1478
- LastFailedLoginAt: LastFailedLoginAt,
1479
- Status: this.Status,
1480
- },
1481
- {
1482
- where: {
1483
- UserId: this.UserId,
1484
- },
1485
- transaction: dbTransaction,
1486
- },
1487
- );
1488
-
1489
- // 6. Depending on the Status and config.AutoReleaseYN, throw below error:
1490
-
1491
- // 6.1 If Status = "Locked" and config.AutoReleaseYN = "Y", throws below error:
1492
- if (this.Status === UserStatus.LOCKED && autoReleaseYN === 'Y') {
1493
- throw new ClassError(
1494
- 'LoginUser',
1495
- 'LoginUserErrMsg0X',
1496
- 'Your account has been temporarily locked due to too many failed login attempts, please try again later.',
1497
- );
1498
- }
1499
-
1500
- // 6.2 If Status = "Locked" and config.AutoReleaseYN = "N", throws below error:
1501
- if (this.Status === UserStatus.LOCKED && autoReleaseYN === 'N') {
1502
- throw new ClassError(
1503
- 'LoginUser',
1504
- 'LoginUserErrMsg0X',
1505
- 'Your account has been locked due to too many failed login attempts, please contact IT Support for instructions on how to unlock your account',
1506
- );
1507
- }
1508
-
1509
- // 6.2 If Status = "Locked" and config.AutoReleaseYN = "N", throws below error:
1510
- if (this.Status == UserStatus.LOCKED) {
1511
- throw new ClassError(
1512
- 'LoginUser',
1513
- 'LoginUserErrMsg0X',
1514
- 'Invalid credentials.',
1515
- );
1516
- }
1517
- }
1518
-
1519
- public static shouldReleaseLock(LastFailedLoginAt) {
1520
- // This method is used to check whether the account is eligible to be unlocked.
1521
-
1522
- // 1. Retrieve maxFailedLoginAttempts and autoReleaseYN from component config, call ComponentConfig.getComponentConfigValue
1523
- const minuteToAutoRelease = ComponentConfig.getComponentConfigValue(
1524
- '@tomei/sso',
1525
- 'minuteToAutoRelease',
1526
- );
1527
-
1528
- const autoReleaseYN = ComponentConfig.getComponentConfigValue(
1529
- '@tomei/sso',
1530
- 'autoReleaseYN',
1531
- );
1532
-
1533
- // 2. Make sure all maxFailedLoginAttempts keys got values
1534
- if (!minuteToAutoRelease || !autoReleaseYN) {
1535
- throw new ClassError(
1536
- 'LoginUser',
1537
- 'LoginUserErrMsg0X',
1538
- 'Missing minuteToAutoRelease and or autoReleaseYN. Please set in config file.',
1539
- );
1540
- }
1541
-
1542
- // 6. Depending on the config.AutoReleaseYN, do the following :
1543
-
1544
- // 6.1 If config.AutoReleaseYN = "Y":
1545
- if (autoReleaseYN === 'Y') {
1546
- const lastFailedDate = new Date(LastFailedLoginAt);
1547
- const currentDate = new Date();
1548
- const timeDifferenceInMillis =
1549
- currentDate.getTime() - lastFailedDate.getTime();
1550
- const timeDifferenceInMinutes: number =
1551
- timeDifferenceInMillis / (1000 * 60);
1552
-
1553
- if (timeDifferenceInMinutes > +minuteToAutoRelease) {
1554
- return true;
1555
- } else {
1556
- return false;
1557
- }
1558
- // 6.2 If config.AutoReleaseYN = "N":
1559
- } else if (autoReleaseYN === 'N') {
1560
- return false;
1561
- }
1562
- }
1563
-
1564
- private static releaseLock(UserId: number, dbTransaction: any) {
1565
- // This method is used to process all the necessary step after login failed by invalid credential
1566
-
1567
- // 1. Update User Status and FailedLoginAttemptCount
1568
- this._Repository.update(
1569
- {
1570
- FailedLoginAttemptCount: 0,
1571
- Status: UserStatus.ACTIVE,
1572
- },
1573
- {
1574
- where: {
1575
- UserId: UserId,
1576
- },
1577
- transaction: dbTransaction,
1578
- },
1579
- );
1580
- }
1581
-
1582
- public static async getGroups(loginUser: LoginUser, dbTransaction: any) {
1583
- // This method will retrieve all user groups.
1584
-
1585
- // Part 1: Privilege Checking
1586
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
1587
- const isPrivileged = await loginUser.checkPrivileges(
1588
- systemCode,
1589
- 'UserGroup - List Own',
1590
- );
1591
- if (!isPrivileged) {
1592
- throw new Error('You do not have permission to list UserGroup.');
1593
- }
1594
-
1595
- // Part 2: Retrieve User Groups & Returns
1596
- const userGroups = await LoginUser._UserGroupRepo.findAll({
1597
- where: {
1598
- UserId: loginUser.ObjectId,
1599
- Status: 'Active',
1600
- },
1601
- include: [{ model: User, as: 'User' }, { model: GroupModel }],
1602
- transaction: dbTransaction,
1603
- });
1604
-
1605
- return userGroups;
1606
- }
1607
-
1608
- private static async getInheritedSystemAccess(
1609
- dbTransaction: any,
1610
- group: GroupModel,
1611
- ) {
1612
- // This method is a recursive method that will returns group system access with its parent group system access if applicable.
1613
- // Part 1: Retrieve Group System Access
1614
- const dataSystemAccesses = await LoginUser._GroupSystemAccessRepo.findAll({
1615
- where: {
1616
- GroupCode: group.GroupCode,
1617
- Status: 'Active',
1618
- },
1619
- include: [{ model: SystemModel }],
1620
- transaction: dbTransaction,
1621
- });
1622
- let systemAccesses = dataSystemAccesses;
1623
-
1624
- // Part 2: Retrieve Parent Group System Access If Applicable
1625
- // 2.1 Check if Params.group.InheritParentSystemAccessYN is "Y" and Params.group.ParentGroupCode is not empty
1626
- if (group.InheritParentPrivilegeYN === 'Y' && group.ParentGroupCode) {
1627
- const GroupCode = group.ParentGroupCode;
1628
- const parentGroup = await LoginUser._GroupRepo.findByPk(
1629
- GroupCode,
1630
- dbTransaction,
1631
- );
1632
- const dataParentSystemAccesses = await LoginUser.getInheritedSystemAccess(
1633
- dbTransaction,
1634
- parentGroup,
1635
- );
1636
-
1637
- const parentSystemAccesses = dataParentSystemAccesses;
1638
-
1639
- systemAccesses = systemAccesses.concat(parentSystemAccesses);
1640
- }
1641
- // Part 3: Return Array
1642
- return systemAccesses;
1643
- }
1644
-
1645
- private static async combineSystemAccess(
1646
- loginUser: LoginUser,
1647
- dbTransaction: any,
1648
- groups: any,
1649
- ) {
1650
- // Part 1: Retrieve User System Access
1651
- const userAccess = await LoginUser._UserSystemAccessRepo.findAll({
1652
- where: {
1653
- UserId: loginUser.ObjectId,
1654
- Status: 'Active',
1655
- },
1656
- include: [{ model: SystemModel }],
1657
- transaction: dbTransaction,
1658
- });
1659
-
1660
- // Part 2: Create Group System Access Promises
1661
- const groupAccessPromises = groups.map(async (e) => {
1662
- if (e.InheritParentSystemAccessYN) {
1663
- return await this.getInheritedSystemAccess(dbTransaction, e);
1664
- } else {
1665
- return [];
1666
- }
1667
- });
1668
-
1669
- // Part 3: Resolve Promises and Flatten the Results
1670
- const groupAccess = (await Promise.all(groupAccessPromises)).flat(); // Combine all group
1671
-
1672
- // Part 4: Combine, Remove Duplicates & Returns
1673
- const allAccess = userAccess.concat(groupAccess);
1674
- const uniqueAccess = new Set(
1675
- allAccess.filter((value, index, self) => {
1676
- // Check for duplicates based on object properties (replace with your actual comparison logic)
1677
- return self.some((prev) => prev.SystemCode === value.SystemCode);
1678
- }),
1679
- );
1680
- return Array.from(uniqueAccess) as ISystemAccess[];
1681
- }
1682
-
1683
- public static async getSystems(loginUser: LoginUser, dbTransaction: any) {
1684
- // This method will retrieve all system records which user has accessed to.
1685
-
1686
- // Part 1: Privilege Checking
1687
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
1688
- const isPrivileged = await loginUser.checkPrivileges(
1689
- systemCode,
1690
- 'System – List Own',
1691
- );
1692
- if (!isPrivileged) {
1693
- throw new Error('You do not have permission to list UserGroup.');
1694
- }
1695
-
1696
- // Part 2: Retrieve System Access
1697
- const groups = await LoginUser.getGroups(loginUser, dbTransaction);
1698
- const systemAccess = await LoginUser.combineSystemAccess(
1699
- loginUser,
1700
- dbTransaction,
1701
- groups,
1702
- );
1703
- const output = [];
1704
- if (systemAccess) {
1705
- for (let i = 0; i < systemAccess.length; i++) {
1706
- const system = await LoginUser._SystemRepository.findOne({
1707
- where: {
1708
- SystemCode: systemAccess[i].SystemCode,
1709
- Status: 'Active',
1710
- },
1711
- });
1712
- output.push({
1713
- UserSystemAccessId: systemAccess[i].UserSystemAccessId,
1714
- UserId: systemAccess[i].UserId,
1715
- SystemCode: systemAccess[i].SystemCode,
1716
- Status: systemAccess[i].Status,
1717
- CreatedById: systemAccess[i].CreatedById,
1718
- UpdatedById: systemAccess[i].UpdatedById,
1719
- CreatedAt: systemAccess[i].CreatedAt,
1720
- UpdatedAt: systemAccess[i].UpdatedAt,
1721
- System: system,
1722
- });
1723
- }
1724
- }
1725
-
1726
- // Part 3: Map Result to System Object
1727
- return output;
1728
- }
1729
-
1730
- //This method will check if user enable 2FA or not.
1731
- private static async check2FA(loginUser: LoginUser, dbTransaction: any) {
1732
- try {
1733
- //Use LoginUser._Repo findOne() method
1734
- const user = await LoginUser._Repository.findOne({
1735
- where: {
1736
- UserId: loginUser.UserId,
1737
- },
1738
- transaction: dbTransaction,
1739
- });
1740
-
1741
- //From the return record, if MFAEnabled value is 1 then return true else return false.
1742
- if (user.MFAEnabled === 1) {
1743
- return true;
1744
- }
1745
- return false;
1746
- } catch (error) {
1747
- throw error;
1748
- }
1749
- }
1750
-
1751
- //This method will set the 2FA setting for the login user
1752
- public static async setup2FA(userId: number, dbTransaction: any) {
1753
- // 1. Retrieve system code from app config using ApplicationConfig.
1754
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
1755
-
1756
- // 2. Retrieve user data by calling LoginUser._Repository.findOne with UserId
1757
- const user = await LoginUser._Repository.findOne({
1758
- where: {
1759
- UserId: userId,
1760
- },
1761
- });
1762
-
1763
- // 3. If user data not found then return throw Class Error
1764
- if (!user) {
1765
- throw new ClassError(
1766
- 'LoginUser',
1767
- 'LoginUserErrMsg0X',
1768
- 'Invalid Credentials',
1769
- );
1770
- }
1771
-
1772
- // 4. Generate the 2FA secret code by calling speakeasy.generateSecret with parameter
1773
- const secretCode = speakeasy.generateSecret({ name: systemCode });
1774
-
1775
- // parse MFA Config
1776
- let userMFAConfig = null;
1777
- if (user?.MFAConfig !== null && typeof user?.MFAConfig === 'string') {
1778
- try {
1779
- userMFAConfig = JSON.parse(user?.MFAConfig);
1780
- } catch (error) {
1781
- console.error('Invalid JSON string on MFAConfig:', error);
1782
- }
1783
- }
1784
-
1785
- // 5. Set MFAConfig
1786
- const MFAConfig = {
1787
- totp: {
1788
- enabled: true,
1789
- secret: secretCode.base32,
1790
- issuer: systemCode,
1791
- },
1792
- sms: {
1793
- enabled: userMFAConfig?.sms?.enable || false,
1794
- phoneNumber: userMFAConfig?.sms?.phoneNumber || '',
1795
- },
1796
- email: {
1797
- enabled: userMFAConfig?.email?.enable || false,
1798
- emailAddress: userMFAConfig?.email?.emailAddress || '',
1799
- },
1800
- };
1801
-
1802
- // 6. Set login user properties
1803
- user.MFAEnabled = 0;
1804
- user.MFAConfig = JSON.stringify(MFAConfig);
1805
-
1806
- // 7. Update the user data by calling LoginUser._Repository.Update
1807
- await LoginUser._Repository.update(
1808
- {
1809
- MFAEnabled: user.MFAEnabled,
1810
- MFAConfig: user.MFAConfig,
1811
- },
1812
- {
1813
- where: {
1814
- UserId: userId,
1815
- },
1816
- transaction: dbTransaction,
1817
- },
1818
- );
1819
-
1820
- // 8. return <result from step 2>.otpauth_url
1821
- return secretCode.otpauth_url;
1822
- }
1823
-
1824
- //This method will verify whether 2FA have been set correctly
1825
- public async verify2FASetup(
1826
- userId: number,
1827
- mfaToken: string,
1828
- dbTransaction: any,
1829
- ) {
1830
- // 1. Retrieve user data by calling LoginUser._Repository.findOne with UserId
1831
- const user = await LoginUser._Repository.findOne({
1832
- where: {
1833
- UserId: userId,
1834
- },
1835
- });
1836
-
1837
- // 2. If user data not found then return throw Class Error
1838
- if (!user) {
1839
- throw new ClassError(
1840
- 'LoginUser',
1841
- 'LoginUserErrMsg0X',
1842
- 'Invalid Credentials',
1843
- );
1844
- }
1845
-
1846
- // parse MFA Config
1847
- let userMFAConfig = null;
1848
- if (user?.MFAConfig !== null && typeof user?.MFAConfig === 'string') {
1849
- try {
1850
- userMFAConfig = JSON.parse(user?.MFAConfig);
1851
- } catch (error) {
1852
- console.error('Invalid JSON string on MFAConfig:', error);
1853
- }
1854
- }
1855
-
1856
- // 3. Verify the mfaToken by calling speakeasy.totp.verify
1857
- const isVerified = await speakeasy.totp.verify({
1858
- secret: userMFAConfig.totp.secret,
1859
- encoding: 'base32',
1860
- token: mfaToken,
1861
- });
1862
-
1863
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
1864
- if (!isVerified) {
1865
- return false;
1866
- }
1867
-
1868
- await LoginUser._Repository.update(
1869
- {
1870
- MFAEnabled: 1,
1871
- },
1872
- {
1873
- where: {
1874
- UserId: userId,
1875
- },
1876
- transaction: dbTransaction,
1877
- },
1878
- );
1879
-
1880
- // 5. Retrieve Session
1881
- const userSession = await this._SessionService.retrieveUserSession(
1882
- `${userId}`,
1883
- );
1884
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
1885
-
1886
- const systemLogin = userSession.systemLogins.find(
1887
- (e) => e.code === systemCode,
1888
- );
1889
- return `${userId}:${systemLogin.sessionId}`;
1890
- }
1891
-
1892
- // This method will verify 2FA codes
1893
- public async verify2FACode(
1894
- userId: number,
1895
- mfaToken: string,
1896
- dbTransaction: any,
1897
- ) {
1898
- // 1. Retrieve user data by calling LoginUser._Repository.findOne with UserId
1899
- const user = await LoginUser._Repository.findOne({
1900
- where: {
1901
- UserId: userId,
1902
- },
1903
- transaction: dbTransaction,
1904
- });
1905
-
1906
- // 2. If user data not found then return throw Class Error
1907
- if (!user) {
1908
- throw new ClassError(
1909
- 'LoginUser',
1910
- 'LoginUserErrMsg0X',
1911
- 'Invalid Credentials',
1912
- );
1913
- }
1914
-
1915
- // parse MFA Config
1916
- let userMFAConfig = null;
1917
- if (user?.MFAConfig !== null && typeof user?.MFAConfig === 'string') {
1918
- try {
1919
- userMFAConfig = JSON.parse(user?.MFAConfig);
1920
- } catch (error) {
1921
- console.error('Invalid JSON string on MFAConfig:', error);
1922
- }
1923
- }
1924
-
1925
- // 3. Verify the mfaToken by calling speakeasy.totp.verify
1926
- const isVerified = await speakeasy.totp.verify({
1927
- secret: userMFAConfig.totp.secret,
1928
- encoding: 'base32',
1929
- token: mfaToken,
1930
- });
1931
-
1932
- // 4. if not verified, then return false. if verified, Call LoginUser._Repo.update and update user data in database
1933
- if (!isVerified) {
1934
- return false;
1935
- }
1936
-
1937
- // 5. Retrieve Session
1938
- const userSession = await this._SessionService.retrieveUserSession(
1939
- `${userId}`,
1940
- );
1941
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
1942
-
1943
- const systemLogin = userSession.systemLogins.find(
1944
- (e) => e.code === systemCode,
1945
- );
1946
- return `${userId}:${systemLogin.sessionId}`;
1947
- }
1948
-
1949
- public async addUserGroup(
1950
- GroupCode: string,
1951
- loginUser: LoginUser,
1952
- dbTransaction: any,
1953
- ) {
1954
- // 1. Retrieve group data by calling LoginUser._GroupRepo.findOne with GroupCode
1955
- const group = await LoginUser._GroupRepo.findOne({
1956
- where: {
1957
- GroupCode,
1958
- },
1959
- transaction: dbTransaction,
1960
- });
1961
-
1962
- // 2. If group data not found then return throw Class Error
1963
- if (!group) {
1964
- throw new ClassError(
1965
- 'LoginUser',
1966
- 'LoginUserErrMsg0X',
1967
- 'Invalid Group Code',
1968
- );
1969
- }
1970
-
1971
- //3. Create new UserGroup record
1972
- const entityValueAfter = {
1973
- UserId: this.UserId,
1974
- GroupCode: group.GroupCode,
1975
- CreatedAt: new Date(),
1976
- CreatedById: loginUser.UserId,
1977
- UpdatedAt: new Date(),
1978
- UpdatedById: loginUser.UserId,
1979
- };
1980
- await LoginUser._UserGroupRepo.create(entityValueAfter, {
1981
- transaction: dbTransaction,
1982
- });
1983
-
1984
- //4. Record Create UserGroup Activity
1985
- const activity = new Activity();
1986
- activity.ActivityId = activity.createId();
1987
- activity.Action = ActionEnum.ADD;
1988
- activity.Description = 'Add User Group';
1989
- activity.EntityType = 'UserGroup';
1990
- activity.EntityId = group.GroupCode;
1991
- activity.EntityValueBefore = JSON.stringify({});
1992
- activity.EntityValueAfter = JSON.stringify(entityValueAfter);
1993
-
1994
- await activity.create(loginUser.ObjectId, dbTransaction);
1995
- }
1996
-
1997
- public async update(
1998
- data: {
1999
- UserName: string;
2000
- Email: string;
2001
- Status: UserStatus;
2002
- RecoveryEmail: string;
2003
- BuildingCode?: string;
2004
- CompanyCode?: string;
2005
- DepartmentCode?: string;
2006
- },
2007
- loginUser: LoginUser,
2008
- dbTransaction: any,
2009
- ) {
2010
- //Part 1: Privilege Checking
2011
- const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
2012
- const isPrivileged = await loginUser.checkPrivileges(
2013
- systemCode,
2014
- 'User - Update',
2015
- );
2016
-
2017
- //If user does not have privilege to update user, throw a ClassError
2018
- if (!isPrivileged) {
2019
- throw new ClassError(
2020
- 'LoginUser',
2021
- 'LoginUserErrMsg0X',
2022
- 'You do not have the privilege to update user',
2023
- );
2024
- }
2025
-
2026
- //Part 2: Validation
2027
- //Make sure UserId got values. If not, throw new ClassError
2028
- if (!this.UserId) {
2029
- throw new ClassError(
2030
- 'LoginUser',
2031
- 'LoginUserErrMsg0X',
2032
- 'UserId is required',
2033
- );
2034
- }
2035
-
2036
- //Make sure email is unique, call LoginUser.CheckUserInfoDuplicated method
2037
- if (data.Email !== this.Email || data.UserName !== this.UserName) {
2038
- await LoginUser.checkUserInfoDuplicated(dbTransaction, {
2039
- Email: data.Email,
2040
- UserName: data.UserName,
2041
- });
2042
- }
2043
-
2044
- //Part 3: Update Building, Company, Department
2045
- //If Params.BuildingCode is not null,
2046
- if (data.BuildingCode) {
2047
- //Check if BuildingCode is valid, call GroupModel.findOne method
2048
- const building = await GroupModel.findOne({
2049
- where: {
2050
- Type: 'Building',
2051
- GroupCode: data.BuildingCode,
2052
- },
2053
- transaction: dbTransaction,
2054
- });
2055
-
2056
- //If BuildingCode is invalid, throw new ClassError
2057
- if (!building) {
2058
- throw new ClassError(
2059
- 'LoginUser',
2060
- 'LoginUserErrMsg0X',
2061
- 'Invalid Building Code',
2062
- );
2063
- }
2064
-
2065
- //If BuildingCode is valid, call UserGroup.findOne method to find the user building record
2066
- const userBuilding = await LoginUser._UserGroupRepo.findOne({
2067
- where: {
2068
- UserId: this.UserId,
2069
- },
2070
- include: [
2071
- {
2072
- model: GroupModel,
2073
- where: {
2074
- Type: 'Building',
2075
- },
2076
- },
2077
- ],
2078
- transaction: dbTransaction,
2079
- });
2080
-
2081
- //If user building record found, call UserGroup.update method to update the record if not found, call UserGroup.create method to create new record
2082
- if (userBuilding) {
2083
- await LoginUser._UserGroupRepo.update(
2084
- {
2085
- GroupCode: data.BuildingCode,
2086
- UpdatedAt: new Date(),
2087
- UpdatedById: loginUser.UserId,
2088
- },
2089
- {
2090
- where: {
2091
- UserId: this.UserId,
2092
- GroupCode: userBuilding.GroupCode,
2093
- },
2094
- transaction: dbTransaction,
2095
- },
2096
- );
2097
- } else {
2098
- await LoginUser._UserGroupRepo.create(
2099
- {
2100
- UserId: this.UserId,
2101
- GroupCode: data.BuildingCode,
2102
- CreatedAt: new Date(),
2103
- CreatedById: loginUser.UserId,
2104
- UpdatedAt: new Date(),
2105
- UpdatedById: loginUser.UserId,
2106
- },
2107
- {
2108
- transaction: dbTransaction,
2109
- },
2110
- );
2111
- }
2112
- }
2113
-
2114
- //If Params.CompanyCode is not null,
2115
- if (data.CompanyCode) {
2116
- //Check if CompanyCode is valid, call GroupModel.findOne method
2117
- const company = await GroupModel.findOne({
2118
- where: {
2119
- Type: 'Company',
2120
- GroupCode: data.CompanyCode,
2121
- },
2122
- transaction: dbTransaction,
2123
- });
2124
-
2125
- //If CompanyCode is invalid, throw a ClassError
2126
- if (!company) {
2127
- throw new ClassError(
2128
- 'LoginUser',
2129
- 'LoginUserErrMsg0X',
2130
- 'Invalid Company Code',
2131
- );
2132
- }
2133
-
2134
- //If CompanyCode is valid, call UserGroup.findOne method to find the user company record
2135
- const userCompany = await LoginUser._UserGroupRepo.findOne({
2136
- where: {
2137
- UserId: this.UserId,
2138
- },
2139
- include: [
2140
- {
2141
- model: GroupModel,
2142
- where: {
2143
- Type: 'Company',
2144
- },
2145
- },
2146
- ],
2147
- transaction: dbTransaction,
2148
- });
2149
-
2150
- //If user company record found, call UserGroup.update method to update the record if not found, call UserGroup.create method to create new record
2151
- if (userCompany) {
2152
- await LoginUser._UserGroupRepo.update(
2153
- {
2154
- GroupCode: data.CompanyCode,
2155
- UpdatedAt: new Date(),
2156
- UpdatedById: loginUser.UserId,
2157
- },
2158
- {
2159
- where: {
2160
- UserId: this.UserId,
2161
- GroupCode: userCompany.GroupCode,
2162
- },
2163
- transaction: dbTransaction,
2164
- },
2165
- );
2166
- } else {
2167
- await LoginUser._UserGroupRepo.create(
2168
- {
2169
- UserId: this.UserId,
2170
- GroupCode: data.CompanyCode,
2171
- CreatedAt: new Date(),
2172
- CreatedById: loginUser.UserId,
2173
- UpdatedAt: new Date(),
2174
- UpdatedById: loginUser.UserId,
2175
- },
2176
- {
2177
- transaction: dbTransaction,
2178
- },
2179
- );
2180
- }
2181
- }
2182
-
2183
- //If Params.DepartmentCode is not null,
2184
- if (data.DepartmentCode) {
2185
- //Check if DepartmentCode is valid, call GroupModel.findOne method
2186
- const department = await GroupModel.findOne({
2187
- where: {
2188
- Type: 'Department',
2189
- GroupCode: data.DepartmentCode,
2190
- },
2191
- transaction: dbTransaction,
2192
- });
2193
-
2194
- //If DepartmentCode is invalid, throw a ClassError
2195
- if (!department) {
2196
- throw new ClassError(
2197
- 'LoginUser',
2198
- 'LoginUserErrMsg0X',
2199
- 'Invalid Department Code',
2200
- );
2201
- }
2202
-
2203
- //If DepartmentCode is valid, call UserGroup.findOne method to find the user department record
2204
- const userDepartment = await LoginUser._UserGroupRepo.findOne({
2205
- where: {
2206
- UserId: this.UserId,
2207
- },
2208
- include: [
2209
- {
2210
- model: GroupModel,
2211
- where: {
2212
- Type: 'Department',
2213
- },
2214
- },
2215
- ],
2216
- transaction: dbTransaction,
2217
- });
2218
-
2219
- //If user department record found, call UserGroup.update method to update the record if not found, call UserGroup.create method to create new record
2220
- if (userDepartment) {
2221
- await LoginUser._UserGroupRepo.update(
2222
- {
2223
- GroupCode: data.DepartmentCode,
2224
- UpdatedAt: new Date(),
2225
- UpdatedById: loginUser.UserId,
2226
- },
2227
- {
2228
- where: {
2229
- UserId: this.UserId,
2230
- GroupCode: userDepartment.GroupCode,
2231
- },
2232
- transaction: dbTransaction,
2233
- },
2234
- );
2235
- } else {
2236
- await LoginUser._UserGroupRepo.create(
2237
- {
2238
- UserId: this.UserId,
2239
- GroupCode: data.DepartmentCode,
2240
- CreatedAt: new Date(),
2241
- CreatedById: loginUser.UserId,
2242
- UpdatedAt: new Date(),
2243
- UpdatedById: loginUser.UserId,
2244
- },
2245
- {
2246
- transaction: dbTransaction,
2247
- },
2248
- );
2249
- }
2250
- }
2251
-
2252
- //Part 4: Update User Record
2253
- //Set EntityValueBefore
2254
- const entityValueBefore = {
2255
- UserId: this.UserId,
2256
- UserName: this.UserName,
2257
- Email: this.Email,
2258
- Password: this.Password,
2259
- Status: this.Status,
2260
- DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
2261
- FirstLoginAt: this.FirstLoginAt,
2262
- LastLoginAt: this.LastLoginAt,
2263
- MFAEnabled: this.MFAEnabled,
2264
- MFAConfig: this.MFAConfig,
2265
- RecoveryEmail: this.RecoveryEmail,
2266
- FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2267
- LastFailedLoginAt: this.LastFailedLoginAt,
2268
- LastPasswordChangedAt: this.LastPasswordChangedAt,
2269
- NeedToChangePasswordYN: this.NeedToChangePasswordYN,
2270
- CreatedById: this.CreatedById,
2271
- CreatedAt: this.CreatedAt,
2272
- UpdatedById: this.UpdatedById,
2273
- UpdatedAt: this.UpdatedAt,
2274
- };
2275
-
2276
- //Update user record
2277
- this.UserName = data.UserName;
2278
- this.Email = data.Email;
2279
- this.Status = data.Status;
2280
- this.RecoveryEmail = data.RecoveryEmail;
2281
- this.UpdatedAt = new Date();
2282
- this.UpdatedById = loginUser.UserId;
2283
- //Call LoginUser._Repo update method to update user record
2284
- await LoginUser._Repository.update(
2285
- {
2286
- UserName: this.UserName,
2287
- Email: this.Email,
2288
- Status: this.Status,
2289
- RecoveryEmail: this.RecoveryEmail,
2290
- UpdatedById: this.UpdatedById,
2291
- UpdatedAt: this.UpdatedAt,
2292
- },
2293
- {
2294
- where: {
2295
- UserId: this.UserId,
2296
- },
2297
- transaction: dbTransaction,
2298
- },
2299
- );
2300
-
2301
- //Part 5: Record Update User Activity
2302
- //Set EntityValueAfter
2303
- const entityValueAfter = {
2304
- UserId: this.UserId,
2305
- UserName: this.UserName,
2306
- Email: this.Email,
2307
- Password: this.Password,
2308
- Status: this.Status,
2309
- DefaultPasswordChangedYN: this.DefaultPasswordChangedYN,
2310
- FirstLoginAt: this.FirstLoginAt,
2311
- LastLoginAt: this.LastLoginAt,
2312
- MFAEnabled: this.MFAEnabled,
2313
- MFAConfig: this.MFAConfig,
2314
- RecoveryEmail: this.RecoveryEmail,
2315
- FailedLoginAttemptCount: this.FailedLoginAttemptCount,
2316
- LastFailedLoginAt: this.LastFailedLoginAt,
2317
- LastPasswordChangedAt: this.LastPasswordChangedAt,
2318
- NeedToChangePasswordYN: this.NeedToChangePasswordYN,
2319
- CreatedById: this.CreatedById,
2320
- CreatedAt: this.CreatedAt,
2321
- UpdatedById: this.UpdatedById,
2322
- UpdatedAt: this.UpdatedAt,
2323
- };
2324
-
2325
- //Call Activity.create method to create new activity record
2326
- const activity = new Activity();
2327
- activity.ActivityId = activity.createId();
2328
- activity.Action = ActionEnum.UPDATE;
2329
- activity.Description = 'Update User';
2330
- activity.EntityType = 'LoginUser';
2331
- activity.EntityId = this.UserId.toString();
2332
- activity.EntityValueBefore = JSON.stringify(entityValueBefore);
2333
- activity.EntityValueAfter = JSON.stringify(entityValueAfter);
2334
-
2335
- await activity.create(loginUser.ObjectId, dbTransaction);
2336
-
2337
- //Return Updated User Instance
2338
- return this;
2339
- }
2340
215
  }