From 73cbd8f687d99e9bcd4e82c82d8d1f0d233bde27 Mon Sep 17 00:00:00 2001 From: Jochen Saalfeld Date: Tue, 4 Dec 2018 11:31:14 +0100 Subject: [PATCH] add change password field for users/admins --- .../components/head-bar/head-bar.component.ts | 1 + client/src/app/site/site.component.ts | 8 +- .../password/password.component.html | 94 +++++++ .../password/password.component.scss | 3 + .../password/password.component.spec.ts | 26 ++ .../components/password/password.component.ts | 265 ++++++++++++++++++ .../user-detail/user-detail.component.html | 4 + .../user-detail/user-detail.component.ts | 14 +- .../users/services/user-repository.service.ts | 17 +- .../app/site/users/users-routing.module.ts | 9 + client/src/app/site/users/users.module.ts | 3 +- 11 files changed, 438 insertions(+), 6 deletions(-) create mode 100644 client/src/app/site/users/components/password/password.component.html create mode 100644 client/src/app/site/users/components/password/password.component.scss create mode 100644 client/src/app/site/users/components/password/password.component.spec.ts create mode 100644 client/src/app/site/users/components/password/password.component.ts diff --git a/client/src/app/shared/components/head-bar/head-bar.component.ts b/client/src/app/shared/components/head-bar/head-bar.component.ts index 3cda0c940..1a2c05a2e 100644 --- a/client/src/app/shared/components/head-bar/head-bar.component.ts +++ b/client/src/app/shared/components/head-bar/head-bar.component.ts @@ -17,6 +17,7 @@ import { MainMenuService } from '../../../core/services/main-menu.service'; * ```html * a + +

Change Password

+
+ +
+ + You are not supposed to be here please leave... +
+
+ +
+ warning You are changing the password for the user: {{user.full_name}} +
+
+
+ + + + sync_problem + + +
+
+
+ Initial Password: {{user.default_password}} +
+
+ +
+
+ +
+ + +
+ + + + sync_problem + +
+ + + +
+ +
+
diff --git a/client/src/app/site/users/components/password/password.component.scss b/client/src/app/site/users/components/password/password.component.scss new file mode 100644 index 000000000..bc76a27e2 --- /dev/null +++ b/client/src/app/site/users/components/password/password.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} diff --git a/client/src/app/site/users/components/password/password.component.spec.ts b/client/src/app/site/users/components/password/password.component.spec.ts new file mode 100644 index 000000000..ff7e23a58 --- /dev/null +++ b/client/src/app/site/users/components/password/password.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PasswordComponent } from './password.component'; +import { E2EImportsModule } from 'e2e-imports.module'; + +describe('PasswordComponent', () => { + let component: PasswordComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [PasswordComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/users/components/password/password.component.ts b/client/src/app/site/users/components/password/password.component.ts new file mode 100644 index 000000000..601121bd4 --- /dev/null +++ b/client/src/app/site/users/components/password/password.component.ts @@ -0,0 +1,265 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { MatSnackBar } from '@angular/material'; +import { Title } from '@angular/platform-browser'; + +import { ViewUser } from '../../models/view-user'; +import { UserRepositoryService } from '../../services/user-repository.service'; +import { OperatorService } from '../../../../core/services/operator.service'; +import { BaseViewComponent } from '../../../../site/base/base-view'; + +/** + * Component for the Password-Reset Handling + */ +@Component({ + selector: 'os-password', + templateUrl: './password.component.html', + styleUrls: ['./password.component.scss'] +}) +export class PasswordComponent extends BaseViewComponent implements OnInit { + /** + * the user that is currently worked own + */ + public user: ViewUser; + + /** + * if this pw-page is for your own user + */ + public ownPage: boolean; + + /** + * user id from url parameter + */ + public userId: number; + + /** + * if current user has the "can_manage" permission + */ + public canManage: boolean; + + /** + * formGroup for the admin user + */ + public adminPasswordForm: FormGroup; + + /** + * formGroup for the normal user + */ + public userPasswordForm: FormGroup; + + /** + * if the new password in userform is hidden + */ + public hide_user_password = true; + + /** + * If the new Password in the adminform is hidden + */ + public hide_admin_newPassword = true; + + /** + * Constructor + * + * @param title the title + * @param translate translation server + * @param matSnackBar snack bar for errors + * @param route current route + * @param router router service + * @param repo user repository + * @param operator the operatorservice + * @param formBuilder formbuilder for the two forms + */ + public constructor( + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, + private route: ActivatedRoute, + private router: Router, + private repo: UserRepositoryService, + private operator: OperatorService, + private formBuilder: FormBuilder + ) { + super(title, translate, matSnackBar); + this.route.params.subscribe(params => { + if (params.id) { + this.userId = params.id; + } + }); + if (this.userId === undefined) { + this.operator.getObservable().subscribe(users => { + if (users) { + this.userId = users.id; + this.router.navigate([`./users/password/${this.userId}`]); + } + }); + } + } + + /** + * Initializes the forms and some of the frontend options + */ + public ngOnInit(): void { + this.setViewUser(this.userId); + this.setOpOwnsPage(this.userId); + + this.adminPasswordForm = this.formBuilder.group({ + admin_newPassword: ['', Validators.required] + }); + + this.userPasswordForm = this.formBuilder.group({ + user_newPassword1: ['', Validators.required], + user_newPassword2: ['', Validators.required], + user_oldPassword: ['', Validators.required] + }); + } + + /** + * Triggered by the "x" Button of the Form + */ + public goBack(): void { + if (!this.ownPage) { + this.router.navigate([`./users/${this.user.id}`]); + } else { + this.router.navigate(['./']); + } + } + + /** + * sets the current user that should be worked on + * + * @param userId user id from the route + */ + private setViewUser(userId: number): void { + this.repo.getViewModelObservable(userId).subscribe(newViewUser => { + if (newViewUser) { + this.user = newViewUser; + } + }); + } + + /** + * sets the parameters if the pw-page is our own and if the current + * user has the can_manage permission + * + * @param userId user id from the route + */ + private setOpOwnsPage(userId: number): void { + this.operator.getObservable().subscribe(users => { + if (users) { + this.ownPage = +userId === +users.id; + this.canManage = this.operator.hasPerms('users.can_manage'); + } + }); + } + + /** + * Handles the whole save routine for every possible event + */ + public async save(): Promise { + // can Manage, but not own Page (a.k.a. Admin) + try { + if (this.canManage && !this.ownPage) { + const pw = this.adminPasswordForm.get('admin_newPassword').value; + this.adminNewPassword(pw); + this.router.navigate([`./users/${this.user.id}`]); + } + // can not Manage, but own Page (a.k.a. User) + if (this.ownPage) { + const oldPw = this.userPasswordForm.get('user_oldPassword').value; + const newPw1 = this.userPasswordForm.get('user_newPassword1').value; + const newPw2 = this.userPasswordForm.get('user_newPassword2').value; + await this.userNewPassword(newPw1, newPw2, oldPw); + this.router.navigate(['./']); + } + } catch (e) { + this.raiseError(e); + } + } + + /** + * Sends new Password entered in the new password field to server + * + * @param password the password that should be set + */ + private adminNewPassword(password: string): void { + this.repo.resetPassword(this.user, password).catch(this.raiseError); + } + + /** + * sets the new password for a user and sends it to the server + * + * @param newPassword1 the new password + * @param newPassword2 confirmation of the new password + * @param oldPassword the old password + */ + private userNewPassword(newPassword1: string, newPassword2: string, oldPassword: string): void { + if (newPassword1 !== newPassword2) { + this.raiseError(this.translate.instant('Passwords do not match')); + } + this.repo.setNewPassword(oldPassword, newPassword1).catch(this.raiseError); + } + + /** + * clicking Shift and Enter will save automatically + * + * @param event has the code + */ + public onKeyDown(event: KeyboardEvent): void { + if (event.key === 'Enter' && event.shiftKey) { + this.save(); + } + } + + /** + * Takes generated password and puts it into admin PW field + * and displays it + */ + public admin_generatePassword(): void { + this.adminPasswordForm.patchValue({ + admin_newPassword: this.repo.getRandomPassword() + }); + this.admin_hidePassword(false); + } + + /** + * Takes generated password and puts it into user PW fields + * and displays them + */ + public user_generatePassword(): void { + const newPW = this.repo.getRandomPassword(); + this.userPasswordForm.patchValue({ + user_newPassword1: newPW, + user_newPassword2: newPW + }); + this.user_hidePassword(false); + } + + /** + * Helper function to hide or display the pw in cleartext for admin form + * + * @param hide optional - states if it should be shown or not + */ + public admin_hidePassword(hide?: boolean): void { + if (hide !== null) { + this.hide_admin_newPassword = hide; + } else { + this.hide_admin_newPassword = !this.hide_admin_newPassword; + } + } + + /** + * Helper function to hide or display new pw in clearext for user form + * + * @param hide optional - states if it should be shown or not + */ + public user_hidePassword(hide?: boolean): void { + if (hide !== null) { + this.hide_user_password = hide; + } else { + this.hide_user_password = !this.hide_user_password; + } + } + +} diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.html b/client/src/app/site/users/components/user-detail/user-detail.component.html index ed6e9f205..294db7884 100644 --- a/client/src/app/site/users/components/user-detail/user-detail.component.html +++ b/client/src/app/site/users/components/user-detail/user-detail.component.html @@ -25,6 +25,10 @@ delete Delete + diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.ts b/client/src/app/site/users/components/user-detail/user-detail.component.ts index c8461b731..d95dcfbb5 100644 --- a/client/src/app/site/users/components/user-detail/user-detail.component.ts +++ b/client/src/app/site/users/components/user-detail/user-detail.component.ts @@ -37,6 +37,11 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { */ public editUser = false; + /** + * Set new Password + */ + public newPassword = false; + /** * True if a new user is created */ @@ -140,7 +145,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { case 'seePersonal': return this.operator.hasPerms('users.can_see_extra_data', 'users.can_manage') || this.ownPage; case 'changePersonal': - return this.operator.hasPerms('user.cans_manage') || this.ownPage; + return this.operator.hasPerms('users.can_manage') || this.ownPage; default: return false; } @@ -322,6 +327,13 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit { } } + /** + * navigate to the change Password site + */ + public changePassword(): void { + this.router.navigate([`./users/password/${this.user.id}`]); + } + /** * Init function. */ diff --git a/client/src/app/site/users/services/user-repository.service.ts b/client/src/app/site/users/services/user-repository.service.ts index ab309b5d6..4abec8fd4 100644 --- a/client/src/app/site/users/services/user-repository.service.ts +++ b/client/src/app/site/users/services/user-repository.service.ts @@ -10,6 +10,7 @@ import { CollectionStringModelMapperService } from '../../../core/services/colle import { ConfigService } from 'app/core/services/config.service'; import { HttpService } from 'app/core/services/http.service'; import { TranslateService } from '@ngx-translate/core'; +import { environment } from '../../../../environments/environment'; /** * Repository service for users @@ -110,17 +111,29 @@ export class UserRepositoryService extends BaseRepository { } /** - * Updates the default password and sets the real password. + * Updates the password and sets the password without checking for the old one * * @param user The user to update * @param password The password to set */ public async resetPassword(user: ViewUser, password: string): Promise { - await this.update({ default_password: password }, user); const path = `/rest/users/user/${user.id}/reset_password/`; await this.httpService.post(path, { password: password }); } + /** + * Updates the password and sets a new one, if the old one was correct. + * + * @param oldPassword the old password + * @param newPassword the new password + */ + public async setNewPassword(oldPassword: string, newPassword: string): Promise { + await this.httpService.post(`${environment.urlPrefix}/users/setpassword/`, { + old_password: oldPassword, + new_password: newPassword + }); + } + /** * Sends invitation emails to all given users. Returns a prepared string to show the user. * This string should always be shown, becuase even in success cases, some users may not get diff --git a/client/src/app/site/users/users-routing.module.ts b/client/src/app/site/users/users-routing.module.ts index 879acea2e..aaabd3056 100644 --- a/client/src/app/site/users/users-routing.module.ts +++ b/client/src/app/site/users/users-routing.module.ts @@ -3,12 +3,21 @@ import { Routes, RouterModule } from '@angular/router'; import { UserListComponent } from './components/user-list/user-list.component'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { GroupListComponent } from './components/group-list/group-list.component'; +import { PasswordComponent } from './components/password/password.component'; const routes: Routes = [ { path: '', component: UserListComponent }, + { + path: 'password', + component: PasswordComponent + }, + { + path: 'password/:id', + component: PasswordComponent + }, { path: 'new', component: UserDetailComponent diff --git a/client/src/app/site/users/users.module.ts b/client/src/app/site/users/users.module.ts index 9306149c0..f65a41af9 100644 --- a/client/src/app/site/users/users.module.ts +++ b/client/src/app/site/users/users.module.ts @@ -6,9 +6,10 @@ import { SharedModule } from '../../shared/shared.module'; import { UserListComponent } from './components/user-list/user-list.component'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { GroupListComponent } from './components/group-list/group-list.component'; +import { PasswordComponent } from './components/password/password.component'; @NgModule({ imports: [CommonModule, UsersRoutingModule, SharedModule], - declarations: [UserListComponent, UserDetailComponent, GroupListComponent] + declarations: [UserListComponent, UserDetailComponent, GroupListComponent, PasswordComponent] }) export class UsersModule {}