Merge pull request #4048 from jsaalfeld/user_profile_enhancement
add change password field for users/admins
This commit is contained in:
commit
656753c2a7
@ -17,6 +17,7 @@ import { MainMenuService } from '../../../core/services/main-menu.service';
|
||||
* ```html
|
||||
* <os-head-bar
|
||||
* [nav]="false"
|
||||
* [goBack]="true"
|
||||
* [mainButton]="opCanEdit()"
|
||||
* [mainButtonIcon]="edit"
|
||||
* [editMode]="editMotion"
|
||||
|
@ -142,8 +142,12 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
public changePassword(): void {}
|
||||
/**
|
||||
* Redirects to the change password component
|
||||
*/
|
||||
public changePassword(): void {
|
||||
this.router.navigate([`./users/password`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to log out the current user
|
||||
|
@ -0,0 +1,94 @@
|
||||
<os-head-bar (mainEvent)="goBack()" [mainButton]="true" [nav]="false" [editMode]="true" (saveEvent)="save()">a
|
||||
<!-- Title -->
|
||||
<div class="title-slot"><h2 translate>Change Password</h2></div>
|
||||
</os-head-bar>
|
||||
<mat-card class="os-card">
|
||||
<div *ngIf="!this.canManage && !this.ownPage">
|
||||
<!-- no Admin, cannot Manage (a.k.a Attack Prevention) -->
|
||||
<span translate>You are not supposed to be here please leave...</span>
|
||||
</div>
|
||||
<div *ngIf="this.canManage && !this.ownPage">
|
||||
<!-- can Manage, but not own Page (a.k.a. Admin) -->
|
||||
<div *ngIf="user">
|
||||
<mat-icon>warning</mat-icon> <span translate>You are changing the password for the user:</span> {{user.full_name}}
|
||||
</div>
|
||||
<br>
|
||||
<form [formGroup]="adminPasswordForm" (keydown)="onKeyDown($event)">
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_admin_newPassword ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="admin_newPassword"
|
||||
placeholder="{{ 'New Password' | translate }}"
|
||||
/>
|
||||
<mat-icon
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
(click)="admin_generatePassword()">
|
||||
sync_problem
|
||||
</mat-icon>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<br>
|
||||
<div *ngIf="user">
|
||||
<span translate>Initial Password:</span> {{user.default_password}}
|
||||
</div>
|
||||
<br>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="hide_admin_newPassword = !hide_admin_newPassword">
|
||||
<span translate>Show Password</span>
|
||||
<mat-icon
|
||||
matSuffix
|
||||
mat-icon-button>
|
||||
{{hide_admin_newPassword ? 'visibility_off' : 'visibility'}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="this.ownPage">
|
||||
<!-- can not Manage, but own Page (a.k.a. User) -->
|
||||
<form [formGroup]="userPasswordForm" (keydown)="onKeyDown($event)">
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_user_password ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="user_oldPassword"
|
||||
placeholder="{{ 'Old Password' | translate }}"
|
||||
/>
|
||||
</mat-form-field><br>
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_user_password ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="user_newPassword1"
|
||||
placeholder="{{ 'New Password' | translate }}"
|
||||
/>
|
||||
<mat-icon
|
||||
mat-button matSuffix mat-icon-button
|
||||
(click)="user_generatePassword()">
|
||||
sync_problem
|
||||
</mat-icon>
|
||||
</mat-form-field><br>
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_user_password ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="user_newPassword2"
|
||||
placeholder="{{ 'Confirm New Password' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="hide_user_password = !hide_user_password">
|
||||
<span translate>Show Password</span>
|
||||
<mat-icon
|
||||
matSuffix
|
||||
mat-icon-button>
|
||||
{{ hide_user_password ? 'visibility_off' : 'visibility' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-card>
|
@ -0,0 +1,3 @@
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
@ -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<PasswordComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<void> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,10 @@
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="changePassword()">
|
||||
<mat-icon>security</mat-icon>
|
||||
<span translate>Change password</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</os-head-bar>
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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<ViewUser, User> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
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<void> {
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user