Feature 10914: Enforce Password change on First login 75/11875/1
authorSANDHYA.JS <sandhya.j@tataelxsi.co.in>
Tue, 12 Apr 2022 03:37:08 +0000 (09:07 +0530)
committerSANDHYA.JS <sandhya.j@tataelxsi.co.in>
Tue, 12 Apr 2022 03:37:08 +0000 (09:07 +0530)
* Added NG-UI support to Enforce Password change on First login
* A popup will be opened on First login with current password, new password and confirm password fields
* Once new password is entered, Click apply button
* The popup is closed & redirected to Login page.
* Sign in using the new password.

Change-Id: I9ee6bf923e897b40d06a1781cdd7d044b171c825
Signed-off-by: SANDHYA.JS <sandhya.j@tataelxsi.co.in>
20 files changed:
src/app/AppModule.ts
src/app/approutes.module.ts
src/app/login/LoginComponent.ts
src/app/users/UsersModule.ts
src/app/users/add-user/AddEditUserComponent.html
src/app/users/add-user/AddEditUserComponent.scss
src/app/users/add-user/AddEditUserComponent.ts
src/app/utilities/change-password/ChangePasswordComponent.html [new file with mode: 0644]
src/app/utilities/change-password/ChangePasswordComponent.scss [new file with mode: 0644]
src/app/utilities/change-password/ChangePasswordComponent.ts [new file with mode: 0644]
src/app/utilities/change-password/ChangePasswordModule.ts [new file with mode: 0644]
src/assets/i18n/de.json
src/assets/i18n/en.json
src/assets/i18n/es.json
src/assets/i18n/pt.json
src/models/CommonModel.ts
src/models/VNFDModel.ts
src/services/AuthGuardService.ts
src/services/AuthenticationService.ts
tsconfig.json

index f36b8bf..451e0f1 100644 (file)
@@ -50,6 +50,8 @@ import { NgIdleKeepaliveModule } from '@ng-idle/keepalive';
 import { AuthenticationService } from 'AuthenticationService';
 import { AuthGuardService } from 'AuthGuardService';
 import { BreadcrumbComponent } from 'BreadCrumb';
+import { ChangePasswordComponent } from 'ChangePasswordComponent';
+import { ChangePasswordModule } from 'ChangePasswordModule';
 import { ComposePackages } from 'ComposePackages';
 import { ConfirmationTopologyComponent } from 'ConfirmationTopology';
 import { DeleteComponent } from 'DeleteComponent';
@@ -124,7 +126,8 @@ const customNotifierOptions: NotifierOptions = {
         SDNControllerActionComponent,
         SwitchProjectComponent,
         GoToTopDirective,
-        ScalingComponent
+        ScalingComponent,
+        ChangePasswordComponent
     ],
     imports: [
         NotifierModule.withConfig(customNotifierOptions),
@@ -149,7 +152,8 @@ const customNotifierOptions: NotifierOptions = {
         RouterModule.forRoot(appRoutes, { useHash: false, relativeLinkResolution: 'legacy' }),
         NgIdleKeepaliveModule.forRoot(),
         LoaderModule,
-        SharedModule
+        SharedModule,
+        ChangePasswordModule
     ],
     providers: [
         {
@@ -196,7 +200,8 @@ const customNotifierOptions: NotifierOptions = {
         PDUInstancesActionComponent,
         SDNControllerActionComponent,
         SwitchProjectComponent,
-        ScalingComponent
+        ScalingComponent,
+        ChangePasswordComponent
     ]
 })
 
@@ -225,7 +230,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
         translate.setDefaultLang('en');
         const languageCode: string = localStorage.getItem('languageCode');
         if (languageCode !== null && languageCode !== undefined && languageCode !== '') {
-            await translate.use(languageCode).toPromise().catch(() => {
+            await translate.use(languageCode).toPromise().catch((): void => {
                 translate.setDefaultLang('en');
             });
         } else {
index 6145aae..e75416e 100644 (file)
@@ -20,6 +20,7 @@
  */
 import { Routes } from '@angular/router';
 import { AuthGuardService } from 'AuthGuardService';
+import { ChangePasswordComponent } from 'ChangePasswordComponent';
 import { LayoutComponent } from 'LayoutComponent';
 import { LoginComponent } from 'LoginComponent';
 import { PageNotFoundComponent } from 'PageNotFound';
@@ -114,6 +115,11 @@ export const appRoutes: Routes = [
             }
         ]
     },
+    {
+        path: 'changepassword',
+        component: ChangePasswordComponent,
+        canActivate: [AuthGuardService]
+    },
     {
         path: '**',
         component: PageNotFoundComponent
index 2f4f67e..8c6f5a3 100644 (file)
@@ -55,6 +55,9 @@ export class LoginComponent implements OnInit {
     /** Observable Hold the value of subscription  @public */
     public isLoggedIn$: Observable<boolean>;
 
+    /** Observable Hold the value of subscription  @public */
+    public isChangePassword$: Observable<boolean>;
+
     /** contains access token information @public */
     public accessToken: string;
 
@@ -70,6 +73,12 @@ export class LoginComponent implements OnInit {
     /** Contains all methods related to shared @public */
     public sharedService: SharedService;
 
+    /** contains the loggedIn observable value @public */
+    public loggedIn: boolean;
+
+    /** contains the passwordIn observable value @public */
+    public changePassword: boolean;
+
     /** Utilizes auth service for any auth operations @private */
     private authService: AuthenticationService;
 
@@ -94,11 +103,24 @@ export class LoginComponent implements OnInit {
      */
     public ngOnInit(): void {
         this.isLoggedIn$ = this.authService.isLoggedIn;
-        if (this.isLoggedIn$) {
-            this.router.navigate(['/']).catch(() => {
+        this.isLoggedIn$.subscribe((res: boolean): void => {
+            this.loggedIn = res;
+        });
+        if (this.loggedIn === true) {
+            this.router.navigate(['/']).catch((): void => {
                 // Catch Navigation Error
             });
         }
+        this.isChangePassword$ = this.authService.isChangePassword;
+        this.isChangePassword$.subscribe((res: boolean): void => {
+            this.changePassword = res;
+        });
+        if (this.changePassword === true) {
+            this.router.navigate(['changepassword']).catch((): void => {
+                // Catch Navigation Error
+            });
+        }
+
         this.loginForm = this.formBuilder.group({
             userName: ['', [Validators.required]],
             password: ['', [Validators.required]]
@@ -117,13 +139,19 @@ export class LoginComponent implements OnInit {
         this.isLoadingResults = true;
         this.sharedService.cleanForm(this.loginForm);
         this.authService.login(this.loginForm.value.userName, this.loginForm.value.password).subscribe(
-            (data: {}) => {
+            (data: {}): void => {
                 this.isLoadingResults = false;
-                this.router.navigate([this.returnUrl]).catch(() => {
-                    // Catch Navigation Error
-                });
+                if (this.changePassword === true && this.loggedIn === false) {
+                    this.router.navigate(['/changepassword']).catch((): void => {
+                        // Catch Navigation Error
+                    });
+                } else {
+                    this.router.navigate([this.returnUrl]).catch((): void => {
+                        // Catch Navigation Error
+                    });
+                }
                 localStorage.removeItem('returnUrl');
-            }, (err: HttpErrorResponse) => {
+            }, (err: HttpErrorResponse): void => {
                 this.isLoadingResults = false;
                 this.restService.handleError(err, 'post');
             });
index 2014c48..8b5f316 100644 (file)
@@ -28,7 +28,7 @@ import { RouterModule, Routes } from '@angular/router';
 import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgSelectModule } from '@ng-select/ng-select';
 import { TranslateModule } from '@ngx-translate/core';
-import { AddEditUserComponent } from 'AddEditUserComponent';
+import { ChangePasswordModule } from 'ChangePasswordModule';
 import { DataService } from 'DataService';
 import { LoaderModule } from 'LoaderModule';
 import { Ng2SmartTableModule } from 'ng2-smart-table';
@@ -60,10 +60,11 @@ const routes: Routes = [
  */
 @NgModule({
     imports: [ReactiveFormsModule, FormsModule, CommonModule, HttpClientModule, Ng2SmartTableModule, TranslateModule,
-        FlexLayoutModule, NgSelectModule, NgbModule, RouterModule.forChild(routes), PagePerRowModule, LoaderModule, PageReloadModule],
-    declarations: [UsersComponent, UserDetailsComponent, AddEditUserComponent, ProjectRoleComponent],
+        FlexLayoutModule, NgSelectModule, NgbModule, RouterModule.forChild(routes), PagePerRowModule, LoaderModule,
+        PageReloadModule, ChangePasswordModule],
+    declarations: [UsersComponent, UserDetailsComponent, ProjectRoleComponent],
     providers: [DataService],
-    entryComponents: [AddEditUserComponent, ProjectRoleComponent]
+    entryComponents: [ProjectRoleComponent]
 })
 /** Exporting a class @exports UsersModule */
 export class UsersModule {
index 8c496cc..d3a0b08 100644 (file)
@@ -15,75 +15,105 @@ limitations under the License.
 
 Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
 -->
-<form [formGroup]="userForm" (ngSubmit)="userAction(userType)" autocomplete="off">
-  <div class="modal-header">
-    <h4 class="modal-title" id="modal-basic-title">{{userTitle}}</h4>
-    <button class="button-xs" type="button" class="close" aria-label="Close" (click)="activeModal.close()">
-      <i class="fas fa-times-circle text-danger"></i>
-    </button>
-  </div>
-  <div class="modal-body">
-    <label class="col-sm-12 col-form-label mandatory-label"
-      [ngClass]="{'text-danger': userForm.invalid === true && submitted === true}">{{'MANDATORYCHECK' | translate}}</label>
-    <div class="row form-group" *ngIf="userType === 'add' || userType === 'editUserName'">
-      <div class="col-sm-4">
-        <label for="userName">{{'PAGE.USERS.USERNAME' | translate}} *</label>
-      </div>
-      <div class="col-sm-8">
-        <input class="form-control" placeholder="{{'PAGE.USERS.USERNAME' | translate}}" type="text"
-          formControlName="userName" id="userName" [ngClass]="{ 'is-invalid': submitted && f.userName.errors }"
-          required>
-      </div>
-      <div *ngIf="submitted && f.userName.errors" class="input-validation-msg">
-        <div *ngIf="f.userName.errors.minlength">
-          {{'PAGE.LOGIN.USERNAMEMINLENGTHVALIDMESSAGE' | translate}} </div>
-      </div>
+<div class="wrap-user" [ngClass]="{'change-password': isPassword}">
+  <form [formGroup]="userForm" (ngSubmit)="userAction(userType)" autocomplete="off">
+    <div class="modal-header">
+      <h4 class="modal-title" id="modal-basic-title">{{userTitle}}</h4>
+      <button *ngIf="!isFirstLogin" class="button-xs" type="button" class="close" aria-label="Close"
+        (click)="activeModal.close()">
+        <i class="fas fa-times-circle text-danger"></i>
+      </button>
     </div>
-    <ng-container *ngIf="userType === 'add' || userType === 'editPassword'">
-      <div class="row form-group">
+    <div class="modal-body">
+      <label class="col-sm-12 col-form-label mandatory-label"
+        [ngClass]="{'text-danger': userForm.invalid === true && submitted === true,'message': isPassword && userForm.invalid === true && submitted  }">{{'MANDATORYCHECK'|
+        translate}}</label>
+      <div class="row form-group" *ngIf="userType === 'add' || userType === 'editUserName'">
         <div class="col-sm-4">
-          <label for="password">{{'PAGE.USERS.PASSWORD' | translate}} *</label>
+          <label for="userName">{{'PAGE.USERS.USERNAME' | translate}} *</label>
         </div>
         <div class="col-sm-8">
-          <input class="form-control" placeholder="{{'PAGE.USERS.PASSWORD' | translate}}" minlength="8" maxlength="50"
-            type="password" formControlName="password" id="password" autocomplete="new-password"
-            [ngClass]="{ 'is-invalid': submitted && f.password.errors }" required>
+          <input class="form-control" placeholder="{{'PAGE.USERS.USERNAME' | translate}}" type="text"
+            formControlName="userName" id="userName" [ngClass]="{ 'is-invalid': submitted && f.userName.errors }"
+            required>
         </div>
-        <div class="input-validation-msg">
-          <div *ngIf="userForm?.controls.password.hasError('minlength') || userForm?.controls.password.errors?.pattern">
-            {{'PAGE.LOGIN.PASSWORDMINLENGTHVALIDMESSAGE' | translate}} </div>
+        <div *ngIf="submitted && f.userName.errors" class="input-validation-msg">
+          <div *ngIf="f.userName.errors.minlength">
+            {{'PAGE.LOGIN.USERNAMEMINLENGTHVALIDMESSAGE' | translate}} </div>
         </div>
       </div>
-      <div class="row form-group">
-        <div class="col-sm-4">
-          <label for="password2">{{'PAGE.USERS.CONFPASSWORD' | translate}} *</label>
+      <ng-container *ngIf="userType === 'add' || userType === 'editPassword' || userType === 'changePassword'">
+        <div class="row form-group" *ngIf=" userType === 'changePassword'">
+          <div class="col-sm-4">
+            <label for="oldpassword">{{'PAGE.USERS.OLDPASSWORD' | translate}} *</label>
+          </div>
+          <div class="col-sm-8">
+            <input class="form-control" placeholder="{{'PAGE.USERS.OLDPASSWORD' | translate}}" type="password"
+              formControlName="old_password" id="old_password" autocomplete="old-password"
+              [ngClass]="{ 'is-invalid': submitted && f.old_password.errors }" required>
+          </div>
         </div>
-        <div class="col-sm-8">
-          <input class="form-control" placeholder="{{'PAGE.USERS.CONFPASSWORD' | translate}}" type="password"
-            formControlName="password2" id="password2" autocomplete="new-password"
-            [ngClass]="{ 'is-invalid': submitted && f.password2.errors }" required>
-          <div class="mr-top-5" *ngIf="userForm?.controls.password.value && userForm?.controls.password2.value">
-            <i class="far"
-              [ngClass]="{'fa-times-circle text-danger':userForm?.controls.password.value !== userForm?.controls.password2.value,
+        <div class="row form-group" *ngIf="userType === 'add' || userType === 'editPassword'">
+          <div class="col-sm-4">
+            <label for="password">{{'PAGE.USERS.PASSWORD' | translate}} *</label>
+          </div>
+          <div class="col-sm-8">
+            <input class="form-control" placeholder="{{'PAGE.USERS.PASSWORD' | translate}}" minlength="8" maxlength="50"
+              type="password" formControlName="password" id="password" autocomplete="new-password"
+              [ngClass]="{ 'is-invalid': submitted && f.password.errors }" required>
+          </div>
+          <div class="input-validation-msg" [ngClass]="{'message': isPassword}">
+            <div
+              *ngIf="userForm?.controls.password.hasError('minlength') || userForm?.controls.password.errors?.pattern">
+              {{'PAGE.LOGIN.PASSWORDMINLENGTHVALIDMESSAGE' | translate}} </div>
+          </div>
+        </div>
+        <div class="row form-group" *ngIf="userType === 'changePassword'">
+          <div class="col-sm-4">
+            <label for="password">{{'PAGE.USERS.NEWPASSWORD' | translate}} *</label>
+          </div>
+          <div class="col-sm-8">
+            <input class="form-control" placeholder="{{'PAGE.USERS.NEWPASSWORD' | translate}}" minlength="8"
+              maxlength="50" type="password" formControlName="password" id="password" autocomplete="new-password"
+              [ngClass]="{ 'is-invalid': submitted && f.password.errors }" required>
+          </div>
+          <div class="input-validation-msg" [ngClass]="{'message': isPassword}">
+            <div
+              *ngIf="userForm?.controls.password.hasError('minlength') || userForm?.controls.password.errors?.pattern">
+              {{'PAGE.LOGIN.PASSWORDMINLENGTHVALIDMESSAGE' | translate}} </div>
+          </div>
+        </div>
+        <div class="row form-group">
+          <div class="col-sm-4">
+            <label for="password2">{{'PAGE.USERS.CONFPASSWORD' | translate}} *</label>
+          </div>
+          <div class="col-sm-8">
+            <input class="form-control" placeholder="{{'PAGE.USERS.CONFPASSWORD' | translate}}" type="password"
+              formControlName="password2" id="password2" autocomplete="new-password"
+              [ngClass]="{ 'is-invalid': submitted && f.password2.errors }" required>
+            <div class="mr-top-5" *ngIf="userForm?.controls.password.value && userForm?.controls.password2.value">
+              <i class="far"
+                [ngClass]="{'fa-times-circle text-danger':userForm?.controls.password.value !== userForm?.controls.password2.value,
             'fa-check-circle text-success':userForm?.controls.password.value === userForm?.controls.password2.value}"></i>
-            {{'PAGE.USERS.PASSWORDMATCH' | translate}}
+              {{'PAGE.USERS.PASSWORDMATCH' | translate}}
+            </div>
           </div>
         </div>
+      </ng-container>
+      <div class="form-group row" *ngIf="userType === 'add'">
+        <label class="col-sm-4 col-form-label">{{'DOMAIN' | translate}} {{'NAME' | translate}}</label>
+        <div class="col-sm-8">
+          <ng-select [clearable]="false" placeholder="{{'SELECT' | translate}}" [items]="domains" bindLabel="title"
+            bindValue="value" formControlName="domain_name" id="domain_name"
+            [ngClass]="{ 'is-invalid': submitted && f.domain_name.errors }"></ng-select>
+        </div>
       </div>
-    </ng-container>
-    <div class="form-group row" *ngIf="userType === 'add'">
-      <label class="col-sm-4 col-form-label">{{'DOMAIN' | translate}} {{'NAME' | translate}}</label>
-      <div class="col-sm-8">
-        <ng-select [clearable]="false" placeholder="{{'SELECT' | translate}}" [items]="domains" bindLabel="title"
-          bindValue="value" formControlName="domain_name" id="domain_name"
-          [ngClass]="{ 'is-invalid': submitted && f.domain_name.errors }"></ng-select>
-      </div>
     </div>
-  </div>
-  <div class="modal-footer">
-    <button type="button" class="btn btn-danger" (click)="activeModal.close()">{{'CANCEL' | translate}}</button>
-    <button *ngIf="userType==='add'" type="submit" class="btn btn-primary">{{'CREATE' | translate}}</button>
-    <button *ngIf="userType!=='add'" type="submit" class="btn btn-primary">{{'APPLY' | translate}}</button>
-  </div>
-</form>
+    <div class="modal-footer">
+      <button type="button" class="btn btn-danger" (click)="close()">{{'CANCEL' | translate}}</button>
+      <button *ngIf="userType==='add'" type="submit" class="btn btn-primary">{{'CREATE' | translate}}</button>
+      <button *ngIf="userType!=='add'" type="submit" class="btn btn-primary">{{'APPLY' | translate}}</button>
+    </div>
+  </form>
+</div>
 <app-loader [waitingMessage]="message" *ngIf="isLoadingResults"></app-loader>
\ No newline at end of file
index 05f2819..3520b4e 100644 (file)
 
  Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
 */
-@import '../../../assets/scss/mixins/mixin';
-@import '../../../assets/scss/variable';
-.input-validation-msg{
-    color:$red;
-    text-align:left;
+@import "../../../assets/scss/mixins/mixin";
+@import "../../../assets/scss/variable";
+.input-validation-msg {
+    color: $red;
+    text-align: left;
     @include padding-value(0, 0, 0, 10);
     @include font(null, 11px, null);
+}
+.change-password {
+    @include background(
+        linear-gradient(
+            to left bottom,
+            #00c0ef,
+            #00b3f9,
+            #3ea3fd,
+            #7190f8,
+            #9c78e8,
+            #a86cdd,
+            #b25fd1,
+            #bb51c3,
+            #b151c4,
+            #a652c6,
+            #9b53c6,
+            #9053c7
+        ),
+        null,
+        null,
+        null,
+        null
+    );
+    color: $white;
+    overflow: visible;
+    @include box-shadow(0px, 3px, 10px, 0px, rgba($black, 0.5));
+}
+.message {
+    @include roundedCorners(25);
+    @include background(null, $cerise-pink, null, null, null);
+    color: $white !important;
+    text-align: left;
+    @include padding-value(0, 0, 0, 10);
+    @include margin-value(0, 0, 10, 0);
+    @include font(null, 11px, null);
 }
\ No newline at end of file
index d988548..0e9456a 100644 (file)
@@ -36,6 +36,7 @@ import { isNullOrUndefined } from 'util';
  * @Component takes AddEditUserComponent.html as template url
  */
 @Component({
+    selector: 'app-add-edit-user',
     templateUrl: './AddEditUserComponent.html',
     styleUrls: ['./AddEditUserComponent.scss']
 })
@@ -74,6 +75,12 @@ export class AddEditUserComponent implements OnInit {
     /** Holds list of domains @public */
     public domains: TYPESECTION[] = [];
 
+    /** Variable contains type is changepassword or not @public */
+    public isPassword: boolean;
+
+    /** Variable holds value for first login user @public */
+    public isFirstLogin: boolean = Boolean(localStorage.getItem('firstLogin') === 'true');
+
     /** Instance of the rest service @private */
     private restService: RestService;
 
@@ -113,6 +120,7 @@ export class AddEditUserComponent implements OnInit {
             userName: ['', Validators.required],
             password: [null, [Validators.required, Validators.pattern(this.sharedService.REGX_PASSWORD_PATTERN)]],
             password2: [null, Validators.required],
+            old_password: [null, Validators.required],
             domain_name: [null]
         });
     }
@@ -131,6 +139,8 @@ export class AddEditUserComponent implements OnInit {
             this.getDomainList();
         } else if (this.userType === 'editUserName') {
             this.userForm.patchValue({ userName: this.userName });
+        } else if (this.isFirstLogin) {
+            this.isPassword = true;
         }
     }
 
@@ -139,11 +149,21 @@ export class AddEditUserComponent implements OnInit {
         if (userType === 'editPassword') {
             this.getFormControl('userName').setValidators([]);
             this.getFormControl('userName').updateValueAndValidity();
+            this.getFormControl('old_password').setValidators([]);
+            this.getFormControl('old_password').updateValueAndValidity();
         } else if (userType === 'editUserName') {
             this.getFormControl('password').setValidators([]);
             this.getFormControl('password').updateValueAndValidity();
             this.getFormControl('password2').setValidators([]);
             this.getFormControl('password2').updateValueAndValidity();
+            this.getFormControl('old_password').setValidators([]);
+            this.getFormControl('old_password').updateValueAndValidity();
+        } else if (userType === 'changePassword') {
+            this.getFormControl('userName').setValidators([]);
+            this.getFormControl('userName').updateValueAndValidity();
+        } else if (userType === 'add') {
+            this.getFormControl('old_password').setValidators([]);
+            this.getFormControl('old_password').updateValueAndValidity();
         }
         this.submitted = true;
         this.modalData = {
@@ -157,7 +177,7 @@ export class AddEditUserComponent implements OnInit {
             }
             if (userType === 'add') {
                 this.addUser();
-            } else if (userType === 'editUserName' || userType === 'editPassword') {
+            } else {
                 this.editUser();
             }
         }
@@ -191,6 +211,9 @@ export class AddEditUserComponent implements OnInit {
         const payLoad: LOGINPARAMS = {};
         if (this.userType === 'editPassword') {
             payLoad.password = (this.userForm.value.password);
+        } else if (this.userType === 'changePassword') {
+            payLoad.password = (this.userForm.value.password);
+            payLoad.old_password = (this.userForm.value.old_password);
         } else {
             payLoad.username = this.userForm.value.userName.toLowerCase();
         }
@@ -201,13 +224,33 @@ export class AddEditUserComponent implements OnInit {
         this.restService.patchResource(apiURLHeader, payLoad).subscribe((result: {}): void => {
             this.checkUsername(payLoad);
             this.activeModal.close(this.modalData);
+            if (this.isFirstLogin) {
+                this.notifierService.notify('success', this.translateService.instant('PAGE.USERS.CHANGEPASSWORD'));
+                this.authService.destoryToken();
+            } else {
+                this.notifierService.notify('success', this.translateService.instant('PAGE.USERS.EDITEDSUCCESSFULLY'));
+            }
             this.isLoadingResults = false;
-            this.notifierService.notify('success', this.translateService.instant('PAGE.USERS.EDITEDSUCCESSFULLY'));
         }, (error: ERRORDATA): void => {
-            this.restService.handleError(error, 'put');
+            if (this.isFirstLogin) {
+                this.notifierService.notify('error', error.error.detail);
+                this.activeModal.close(this.modalData);
+                this.authService.destoryToken();
+            } else {
+                this.restService.handleError(error, 'put');
+            }
             this.isLoadingResults = false;
         });
     }
+    /** Close the modal and destroy subscribe @public */
+    public close(): void {
+        if (this.isFirstLogin) {
+            this.activeModal.close(this.modalData);
+            this.authService.destoryToken();
+        } else {
+            this.activeModal.close(this.modalData);
+        }
+    }
     /** Get domain name list @private */
     private getDomainList(): void {
         this.isLoadingResults = true;
diff --git a/src/app/utilities/change-password/ChangePasswordComponent.html b/src/app/utilities/change-password/ChangePasswordComponent.html
new file mode 100644 (file)
index 0000000..8248b74
--- /dev/null
@@ -0,0 +1,18 @@
+<!--
+Copyright 2020 TATA ELXSI
+
+Licensed under the Apache License, Version 2.0 (the 'License');
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: SANDHYA JS (sandhya.j@tataelxsi.co.in)
+-->
+<div class="password-container"></div>
\ No newline at end of file
diff --git a/src/app/utilities/change-password/ChangePasswordComponent.scss b/src/app/utilities/change-password/ChangePasswordComponent.scss
new file mode 100644 (file)
index 0000000..4b15236
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ Copyright 2020 TATA ELXSI
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Author: SANDHYA JS (sandhya.j@tataelxsi.co.in)
+*/
+@import "../../../assets/scss/mixins/mixin";
+@import "../../../assets/scss/variable";
+.password-container {
+   @include wh-value(100%, 100vh);
+   @include flexbox(flex, center, null, null, center, null);
+   @include background(url("../../../assets/images/login_background.jpg"), null, cover, no-repeat, center);
+   background-attachment: fixed;
+}
\ No newline at end of file
diff --git a/src/app/utilities/change-password/ChangePasswordComponent.ts b/src/app/utilities/change-password/ChangePasswordComponent.ts
new file mode 100644 (file)
index 0000000..a2036ff
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ Copyright 2020 TATA ELXSI
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Author: SANDHYA JS (sandhya.j@tataelxsi.co.in)
+*/
+/**
+ * @file change password component
+ */
+import { Component, Injector, OnInit } from '@angular/core';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { TranslateService } from '@ngx-translate/core';
+import { AddEditUserComponent } from 'AddEditUserComponent';
+import { MODALCLOSERESPONSEDATA } from 'CommonModel';
+import { SharedService } from 'SharedService';
+
+/**
+ * Creating component
+ * @Component takes ChangePasswordComponent.html as template url
+ */
+@Component({
+    templateUrl: './ChangePasswordComponent.html',
+    styleUrls: ['./ChangePasswordComponent.scss']
+})
+/** Exporting a class @exports ChangePasswordComponent */
+export class ChangePasswordComponent implements OnInit {
+    /** To inject services @public */
+    public injector: Injector;
+
+    /** handle translate @public */
+    public translateService: TranslateService;
+
+    /** Contains edit type data @public */
+    public editType: string = 'changePassword';
+
+    /** Contains all methods related to shared @private */
+    private sharedService: SharedService;
+
+    /** Instance of the modal service @private */
+    private modalService: NgbModal;
+
+    constructor(injector: Injector) {
+        this.injector = injector;
+        this.translateService = this.injector.get(TranslateService);
+        this.sharedService = this.injector.get(SharedService);
+        this.modalService = this.injector.get(NgbModal);
+    }
+
+    /** Lifecyle Hooks the trigger before component is instantiate @public */
+    public ngOnInit(): void {
+        const modalRef: NgbModalRef = this.modalService.open(AddEditUserComponent, { backdrop: 'static', keyboard: false });
+        modalRef.componentInstance.userID = localStorage.getItem('user_id');
+        if (this.editType === 'changePassword') {
+            modalRef.componentInstance.userTitle = this.translateService.instant('PAGE.USERS.EDITCREDENTIALS');
+        }
+        modalRef.componentInstance.userType = this.editType;
+        modalRef.result.then((result: MODALCLOSERESPONSEDATA): void => {
+            if (result) {
+                this.sharedService.callData();
+            }
+        }).catch((err: Error): void => { // catch error
+        });
+    }
+
+}
diff --git a/src/app/utilities/change-password/ChangePasswordModule.ts b/src/app/utilities/change-password/ChangePasswordModule.ts
new file mode 100644 (file)
index 0000000..bd64f9e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ Copyright 2020 TATA ELXSI
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Author: SANDHYA JS (sandhya.j@tataelxsi.co.in)
+*/
+/**
+ * @file ChangePassword Module.
+ */
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { ReactiveFormsModule } from '@angular/forms';
+import { NgSelectModule } from '@ng-select/ng-select';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddEditUserComponent } from 'AddEditUserComponent';
+import { LoaderModule } from 'LoaderModule';
+/**
+ * Creating @NgModule component for Modules
+ */
+@NgModule({
+    imports: [CommonModule, TranslateModule, FormsModule, ReactiveFormsModule, NgSelectModule, LoaderModule],
+    declarations: [AddEditUserComponent],
+    exports: [AddEditUserComponent],
+    entryComponents: [AddEditUserComponent]
+})
+/** Exporting a class @exports ChangePasswordModule */
+export class ChangePasswordModule {
+    /** Variables declared to avoid state-less class */
+    private changepwdModule: string;
+}
index 54dfe73..2447cb9 100644 (file)
             "NEWUSER": "Neuer Benutzer",
             "USERNAME": "Nutzername",
             "PASSWORD": "Passwort",
+            "OLDPASSWORD": "Jetziges Passwort",
             "CONFPASSWORD": "Passwort bestätigen",
             "EDITUSER": "Benutzer bearbeiten",
             "NEWPASSWORD": "Neues Kennwort",
             "PROJECTSROLES": "Projekte Rollen",
             "EDITPROJECTROLEMAPPING": "Projektrollenzuordnung bearbeiten",
             "ADDMAPPINGS": "Mappings hinzufügen",
-            "EDITPROJECTROLEERROR": "Bitte geben Sie mindestens eine Projektrollenzuordnung an, um fortzufahren"
+            "EDITPROJECTROLEERROR": "Bitte geben Sie mindestens eine Projektrollenzuordnung an, um fortzufahren",
+            "CHANGEPASSWORD": "Passwort wurde geändert. Melden Sie sich an, um Ihre Sitzung zu starten"
         },
         "TOPOLOGY": {
             "SELECTELEMENT": "Element auswählen",
index 200e1b9..45313c1 100644 (file)
             "NEWUSER": "New User",
             "USERNAME": "User Name",
             "PASSWORD": "Password",
+            "OLDPASSWORD": "Current Password",
             "CONFPASSWORD": "Confirm Password",
             "EDITUSER": "Edit User",
             "NEWPASSWORD": "New Password",
             "PROJECTSROLES": "Projects Roles",
             "EDITPROJECTROLEMAPPING": "Edit Project Role Mapping",
             "ADDMAPPINGS": "Add Mappings",
-            "EDITPROJECTROLEERROR": "Please provide at least one project role mapping to continue"
+            "EDITPROJECTROLEERROR": "Please provide at least one project role mapping to continue",
+            "CHANGEPASSWORD": "Password is changed, Sign in to start your session"
         },
         "TOPOLOGY": {
             "SELECTELEMENT": "Select Element",
index 3f6092c..7e2df4d 100644 (file)
             "NEWUSER": "Nuevo usuario",
             "USERNAME": "Nombre de usuario",
             "PASSWORD": "Contraseña",
+            "OLDPASSWORD": "Contraseña actual",
             "CONFPASSWORD": "Confirmar contraseña",
             "EDITUSER": "Editar usuario",
             "NEWPASSWORD": "Nueva contraseña",
             "PROJECTSROLES": "Roles de proyectos",
             "EDITPROJECTROLEMAPPING": "Editar asignación de roles de proyecto",
             "ADDMAPPINGS": "Agregar asignaciones",
-            "EDITPROJECTROLEERROR": "Proporcione al menos un mapeo de roles del proyecto para continuar"
+            "EDITPROJECTROLEERROR": "Proporcione al menos un mapeo de roles del proyecto para continuar",
+            "CHANGEPASSWORD": "Se cambió la contraseña, inicie sesión para iniciar su sesión"
         },
         "TOPOLOGY": {
             "SELECTELEMENT": "Seleccionar elemento",
index 64a5c13..ee257f9 100644 (file)
             "NEWUSER": "Novo usuário",
             "USERNAME": "Nome de Usuário",
             "PASSWORD": "Senha",
+            "OLDPASSWORD": "Senha atual",
             "CONFPASSWORD": "Confirme a Senha",
             "EDITUSER": "Editar usuário",
             "NEWPASSWORD": "Nova senha",
             "PROJECTSROLES": "Funções dos Projetos",
             "EDITPROJECTROLEMAPPING": "Editar mapeamento de função do projeto",
             "ADDMAPPINGS": "Adicionar mapeamentos",
-            "EDITPROJECTROLEERROR": "Forneça pelo menos um mapeamento de função do projeto para continuar"
+            "EDITPROJECTROLEERROR": "Forneça pelo menos um mapeamento de função do projeto para continuar",
+            "CHANGEPASSWORD": "A senha foi alterada, faça login para iniciar sua sessão"
         },
         "TOPOLOGY": {
             "SELECTELEMENT": "Selecionar elemento",
index 96d926d..eb2bec3 100644 (file)
@@ -333,6 +333,7 @@ export interface DOMAINS {
 export interface LOGINPARAMS {
     username?: string;
     password?: string;
+    old_password?: string;
 }
 /** Interface for the LABELVALUE */
 export interface LABELVALUE {
index 7029ace..5f2cc05 100644 (file)
@@ -32,6 +32,8 @@ export interface ProjectModel {
     username: string;
     remote_host: string;
     admin: boolean;
+    message?: string;
+    user_id?: string;
 }
 
 /** Interface for ProjectDetails */
index 0c8d1e4..2effeca 100644 (file)
@@ -22,8 +22,8 @@
 import { Injectable } from '@angular/core';
 import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
 import { AuthenticationService } from 'AuthenticationService';
-import { Observable } from 'rxjs';
-import { map, take } from 'rxjs/operators';
+import { combineLatest, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
 
 /**
  * An Injectable is a class adorned with the @Injectable decorator function.
@@ -45,17 +45,19 @@ export class AuthGuardService implements CanActivate {
      * Returns Observable<boolean> if authorized @public
      */
     public canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
-        return this.authService.isLoggedIn
-            .pipe(
-                take(1),
-                map((isLoggedIn: boolean) => {
-                    if (!isLoggedIn) {
-                        this.router.navigate(['/login']).catch(() => {
-                            //TODO: Handle error notification
-                        });
-                    }
+        return combineLatest(
+            this.authService.isLoggedIn,
+            this.authService.isChangePassword
+        ).pipe(
+            map(([isLoggedIn, changePassword]: [boolean, boolean]): boolean => {
+                if (changePassword || isLoggedIn) {
                     return true;
-                })
-            );
+                } else {
+                    this.router.navigate(['/login']).catch();
+                    this.authService.destoryToken();
+                    return false;
+                }
+            })
+        );
     }
 }
index 0621763..0399c59 100644 (file)
@@ -37,24 +37,6 @@ import { RestService } from './RestService';
  */
 @Injectable()
 export class AuthenticationService {
-    /**
-     * Get method for  Observable loggedIn
-     */
-    get isLoggedIn(): Observable<boolean> {
-        return this.loggedIn.asObservable();
-    }
-
-    /**
-     * Get method for Observable Username
-     */
-    get username(): Observable<string> {
-        return this.userName.asObservable();
-    }
-
-    /** Get method for project name */
-    get ProjectName(): Observable<string> {
-        return this.projectName$.asObservable();
-    }
     /** To inject services @public */
     public injector: Injector;
 
@@ -79,6 +61,9 @@ export class AuthenticationService {
     /** Holds the logged in condition of type BehaviorSubject<boolean> @private */
     private loggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
 
+    /** Holds the change password in condition of type BehaviorSubject<boolean> @private */
+    private changePassword: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
+
     /** Hold Rest Service Objects */
     private restService: RestService;
 
@@ -98,12 +83,43 @@ export class AuthenticationService {
         this.restService = this.injector.get(RestService);
         this.modalService = this.injector.get(NgbModal);
         this.idle = this.injector.get(Idle);
-        if (localStorage.getItem('id_token') !== null) {
+        if (localStorage.getItem('username') !== null) {
             this.loggedIn.next(true);
+            this.changePassword.next(false);
+        } else if (localStorage.getItem('firstLogin') !== null) {
+            this.changePassword.next(true);
+            this.loggedIn.next(false);
         } else {
             this.loggedIn.next(false);
         }
         this.userName.next(localStorage.getItem('username'));
+        this.redirectToPage();
+    }
+
+    /**
+     * Get method for  Observable loggedIn
+     */
+    get isLoggedIn(): Observable<boolean> {
+        return this.loggedIn.asObservable();
+    }
+
+    /**
+     * Get method for  Observable changepassword
+     */
+    get isChangePassword(): Observable<boolean> {
+        return this.changePassword.asObservable();
+    }
+
+    /**
+     * Get method for Observable Username
+     */
+    get username(): Observable<string> {
+        return this.userName.asObservable();
+    }
+
+    /** Get method for project name */
+    get ProjectName(): Observable<string> {
+        return this.projectName$.asObservable();
     }
 
     /**
@@ -118,8 +134,16 @@ export class AuthenticationService {
             httpOptions: { headers: this.httpOptions }
         };
         return this.restService.postResource(apiURLHeader, this.payLoad)
-            .pipe(map((data: ProjectModel) => {
-                if (data) {
+            .pipe(map((data: ProjectModel): BehaviorSubject<boolean> => {
+                if (data.message === 'change_password') {
+                    localStorage.setItem('firstLogin', 'true');
+                    localStorage.setItem('id_token', data.id);
+                    localStorage.setItem('user_id', data.user_id);
+                    this.idle.watch(true);
+                    this.changePassword.next(true);
+                    this.loggedIn.next(false);
+                    return this.changePassword;
+                } else {
                     this.setLocalStorage(data);
                     this.idle.watch(true);
                     this.loggedIn.next(true);
@@ -127,7 +151,7 @@ export class AuthenticationService {
                     this.userName.next(data.username);
                     return this.loggedIn;
                 }
-            }, (error: ERRORDATA) => { this.restService.handleError(error, 'post'); }
+            }, (error: ERRORDATA): void => { this.restService.handleError(error, 'post'); }
             ));
     }
 
@@ -162,6 +186,7 @@ export class AuthenticationService {
     /** Destory tokens API response handling @public */
     public logoutResponse(): void {
         this.loggedIn.next(false);
+        this.changePassword.next(false);
         const langCode: string = localStorage.getItem('languageCode');
         const redirecturl: string = isNullOrUndefined(localStorage.getItem('returnUrl')) ? '/' : localStorage.getItem('returnUrl');
         const osmVersion: string = isNullOrUndefined(localStorage.getItem('osmVersion')) ? '' : localStorage.getItem('osmVersion');
@@ -182,16 +207,25 @@ export class AuthenticationService {
         this.modalService.dismissAll();
         this.destoryToken();
     }
-    /** Destory tokens on logout @private */
-    private destoryToken(): void {
+    /** Destory tokens on logout @public */
+    public destoryToken(): void {
         const tokenID: string = localStorage.getItem('id_token');
         if (tokenID !== null) {
             const deletingURl: string = environment.GENERATETOKEN_URL + '/' + tokenID;
-            this.restService.deleteResource(deletingURl).subscribe((res: {}) => {
+            this.restService.deleteResource(deletingURl).subscribe((res: {}): void => {
                 this.logoutResponse();
-            }, (error: ERRORDATA) => {
+            }, (error: ERRORDATA): void => {
                 this.restService.handleError(error, 'delete');
             });
         }
     }
+
+    /** Return to previous page deny access to changepassword */
+    public redirectToPage(): void {
+        if (window.location.pathname === '/changepassword' && localStorage.getItem('username') !== null) {
+            window.history.back();
+        } else if (window.location.pathname === '/' && localStorage.getItem('firstLogin') === 'true') {
+            this.router.navigate(['/login']).catch();
+        }
+    }
 }
index d31012b..abeb17a 100644 (file)
             "OperationalAppActionsComponent": ["src/app/operational-view/operational-view-app-actions/OperationalViewAppActionsComponent"],
             "OperationalAppExecutedActionsComponent" : ["src/app/operational-view/operational-view-app-executed-actions/OperationalViewAppExecutedActionsComponent"],
             "ResourcesOverviewComponent": ["src/app/vim-accounts/Resources-Overview/ResourcesOverviewComponent"],
-            "SharedModule": ["src/app/vim-accounts/Resources-Overview/SharedModule"]
+            "SharedModule": ["src/app/vim-accounts/Resources-Overview/SharedModule"],
+            "ChangePasswordComponent": ["src/app/utilities/change-password/ChangePasswordComponent"],
+            "ChangePasswordModule": ["src/app/utilities/change-password/ChangePasswordModule"]
         }
     }
 }
\ No newline at end of file