Rework the reset password component (fixes #4079)
This commit is contained in:
parent
76210e807f
commit
6a33b68a41
@ -132,11 +132,15 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
||||
*
|
||||
* @param user The user to update
|
||||
* @param password The password to set
|
||||
* @param updateDefaultPassword Control, if the default password should be updated.
|
||||
*/
|
||||
public async resetPassword(user: ViewUser, password: string): Promise<void> {
|
||||
public async resetPassword(
|
||||
user: ViewUser,
|
||||
password: string,
|
||||
updateDefaultPassword: boolean = false
|
||||
): Promise<void> {
|
||||
const path = `/rest/users/user/${user.id}/reset_password/`;
|
||||
await this.httpService.post(path, { password: password });
|
||||
await this.update({ default_password: password }, user);
|
||||
await this.httpService.post(path, { password: password, update_default_password: updateDefaultPassword });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,24 +11,37 @@
|
||||
<!-- can Manage, but not own Page (a.k.a. Admin) -->
|
||||
<div *ngIf="user">
|
||||
<h1><span translate>Change password for</span> {{ user.full_name }}</h1>
|
||||
<mat-icon>warning</mat-icon> <span translate>You override the personally set password!</span>
|
||||
<mat-icon>warning</mat-icon>
|
||||
<span translate>You override the personally set password!</span>
|
||||
</div>
|
||||
<br>
|
||||
<form [formGroup]="adminPasswordForm" (keydown)="onKeyDown($event)">
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_admin_newPassword ? 'password' : 'text'"
|
||||
[type]="hidePassword ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="admin_newPassword"
|
||||
formControlName="newPassword"
|
||||
placeholder="{{ 'New password' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-icon
|
||||
class="pointer"
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
(click)="admin_generatePassword()">
|
||||
sync_problem
|
||||
matTooltip="{{ hidePassword ? ('Show password' | translate) : ('Hide password' | translate) }}"
|
||||
(click)="hidePassword = !hidePassword">
|
||||
{{ hidePassword ? 'visibility' : 'visibility_off' }}
|
||||
</mat-icon>
|
||||
<mat-icon
|
||||
class="pointer spacer-left-10"
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Generate password' | translate }}"
|
||||
(click)="generatePassword()">
|
||||
settings
|
||||
</mat-icon>
|
||||
|
||||
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<br>
|
||||
@ -36,62 +49,45 @@
|
||||
<span translate>Initial password</span>: {{ user.default_password }}<br>
|
||||
<span translate>Username</span>: {{ user.username }}
|
||||
</div>
|
||||
<br>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="hide_admin_newPassword = !hide_admin_newPassword">
|
||||
<mat-icon
|
||||
matSuffix
|
||||
mat-icon-button>
|
||||
{{hide_admin_newPassword ? 'visibility_off' : 'visibility'}}
|
||||
</mat-icon>
|
||||
<span translate>Show password</span>
|
||||
</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"
|
||||
formControlName="oldPassword"
|
||||
placeholder="{{ 'Old password' | translate }}"
|
||||
required
|
||||
/>
|
||||
</mat-form-field><br>
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_user_password ? 'password' : 'text'"
|
||||
[type]="hidePassword ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="user_newPassword1"
|
||||
formControlName="newPassword1"
|
||||
placeholder="{{ 'New password' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-icon
|
||||
mat-button matSuffix mat-icon-button
|
||||
(click)="user_generatePassword()">
|
||||
sync_problem
|
||||
(click)="hidePassword = !hidePassword">
|
||||
{{ hidePassword ? 'visibility' : 'visibility_off' }}
|
||||
</mat-icon>
|
||||
</mat-form-field><br>
|
||||
<mat-form-field>
|
||||
<input
|
||||
[type]="hide_user_password ? 'password' : 'text'"
|
||||
[type]="hidePassword ? 'password' : 'text'"
|
||||
matInput
|
||||
formControlName="user_newPassword2"
|
||||
formControlName="newPassword2"
|
||||
placeholder="{{ 'Confirm new password' | translate }}"
|
||||
required
|
||||
/>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="hide_user_password = !hide_user_password">
|
||||
<mat-icon
|
||||
matSuffix
|
||||
mat-icon-button>
|
||||
{{ hide_user_password ? 'visibility_off' : 'visibility' }}
|
||||
</mat-icon>
|
||||
<span translate>Show password</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</mat-card>
|
||||
|
@ -1,10 +1,11 @@
|
||||
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 { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ViewUser } from '../../models/view-user';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
@ -29,11 +30,6 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
public ownPage: boolean;
|
||||
|
||||
/**
|
||||
* user id from url parameter
|
||||
*/
|
||||
public userId: number;
|
||||
|
||||
/**
|
||||
* if current user has the "can_manage" permission
|
||||
*/
|
||||
@ -50,14 +46,11 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
public userPasswordForm: FormGroup;
|
||||
|
||||
/**
|
||||
* if the new password in userform is hidden
|
||||
* if all password inputs is hidden
|
||||
*/
|
||||
public hide_user_password = true;
|
||||
public hidePassword = true;
|
||||
|
||||
/**
|
||||
* If the new Password in the adminform is hidden
|
||||
*/
|
||||
public hide_admin_newPassword = true;
|
||||
private urlUserId: number | null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -82,39 +75,48 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
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.getUserObservable().subscribe(user => {
|
||||
if (user) {
|
||||
this.userId = user.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.route.params.subscribe(params => {
|
||||
if (params.id) {
|
||||
this.urlUserId = +params.id;
|
||||
this.repo.getViewModelObservable(this.urlUserId).subscribe(() => {
|
||||
this.updateUser();
|
||||
});
|
||||
}
|
||||
this.updateUser();
|
||||
});
|
||||
|
||||
this.operator.getUserObservable().subscribe(() => {
|
||||
this.updateUser();
|
||||
});
|
||||
|
||||
this.adminPasswordForm = this.formBuilder.group({
|
||||
admin_newPassword: ['', Validators.required]
|
||||
newPassword: ['', Validators.required]
|
||||
});
|
||||
|
||||
this.userPasswordForm = this.formBuilder.group({
|
||||
user_newPassword1: ['', Validators.required],
|
||||
user_newPassword2: ['', Validators.required],
|
||||
user_oldPassword: ['', Validators.required]
|
||||
newPassword1: ['', Validators.required],
|
||||
newPassword2: ['', Validators.required],
|
||||
oldPassword: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
private updateUser(): void {
|
||||
const operator = this.operator.user;
|
||||
this.ownPage = this.urlUserId ? operator.id === this.urlUserId : true;
|
||||
if (this.ownPage) {
|
||||
this.user = this.operator.viewUser;
|
||||
} else {
|
||||
this.user = this.repo.getViewModel(this.urlUserId);
|
||||
}
|
||||
this.canManage = this.operator.hasPerms('users.can_manage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered by the "x" Button of the Form
|
||||
*/
|
||||
@ -126,34 +128,6 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getUserObservable().subscribe(user => {
|
||||
if (user) {
|
||||
this.ownPage = +userId === +user.id;
|
||||
this.canManage = this.operator.hasPerms('users.can_manage');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the whole save routine for every possible event
|
||||
*/
|
||||
@ -161,46 +135,32 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
// 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}`]);
|
||||
if (!this.adminPasswordForm.valid) {
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
const password = this.adminPasswordForm.value.newPassword;
|
||||
await this.repo.resetPassword(this.user, password);
|
||||
this.router.navigate([`./users/${this.user.id}`]);
|
||||
} else if (this.ownPage) {
|
||||
if (!this.userPasswordForm.valid) {
|
||||
return;
|
||||
}
|
||||
const oldPassword = this.userPasswordForm.value.oldPassword;
|
||||
const newPassword1 = this.userPasswordForm.value.newPassword1;
|
||||
const newPassword2 = this.userPasswordForm.value.newPassword2;
|
||||
|
||||
if (newPassword1 !== newPassword2) {
|
||||
this.raiseError(this.translate.instant('New passwords do not match'));
|
||||
} else {
|
||||
await this.repo.setNewPassword(oldPassword, newPassword1);
|
||||
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
|
||||
*
|
||||
@ -216,49 +176,15 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
|
||||
* Takes generated password and puts it into admin PW field
|
||||
* and displays it
|
||||
*/
|
||||
public admin_generatePassword(): void {
|
||||
public generatePassword(): void {
|
||||
const randomPassword = this.repo.getRandomPassword();
|
||||
this.adminPasswordForm.patchValue({
|
||||
admin_newPassword: this.repo.getRandomPassword()
|
||||
newPassword: randomPassword
|
||||
});
|
||||
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
|
||||
newPassword1: randomPassword,
|
||||
newPassword2: randomPassword
|
||||
});
|
||||
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;
|
||||
}
|
||||
this.hidePassword = false;
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
public async resetPasswordsSelected(): Promise<void> {
|
||||
for (const user of this.selectedRows) {
|
||||
const password = this.repo.getRandomPassword();
|
||||
this.repo.resetPassword(user, password);
|
||||
this.repo.resetPassword(user, password, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,6 +398,11 @@ button.mat-menu-item.selected {
|
||||
.spacer-bottom-20 {
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
.spacer-left-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
.button24 {
|
||||
background-color: white;
|
||||
width: 24px !important;
|
||||
|
@ -141,19 +141,30 @@ class UserViewSet(ModelViewSet):
|
||||
def reset_password(self, request, pk=None):
|
||||
"""
|
||||
View to reset the password using the requested password.
|
||||
If update_defualt_password=True is given, the new password will also be set
|
||||
as the default_password.
|
||||
"""
|
||||
user = self.get_object()
|
||||
if isinstance(request.data.get("password"), str):
|
||||
password = request.data.get("password")
|
||||
if not isinstance(password, str):
|
||||
raise ValidationError({"detail": "Password has to be a string."})
|
||||
|
||||
update_default_password = request.data.get("update_default_password", False)
|
||||
if not isinstance(update_default_password, bool):
|
||||
raise ValidationError(
|
||||
{"detail": "update_default_password has to be a boolean."}
|
||||
)
|
||||
|
||||
try:
|
||||
validate_password(request.data.get("password"), user=request.user)
|
||||
validate_password(password, user=request.user)
|
||||
except DjangoValidationError as errors:
|
||||
raise ValidationError({"detail": " ".join(errors)})
|
||||
user.set_password(request.data.get("password"))
|
||||
user.set_password(password)
|
||||
if update_default_password:
|
||||
user.default_password = password
|
||||
user.save()
|
||||
return Response({"detail": "Password successfully reset."})
|
||||
|
||||
raise ValidationError({"detail": "Password has to be a string."})
|
||||
|
||||
@list_route(methods=["post"])
|
||||
@transaction.atomic
|
||||
def mass_import(self, request):
|
||||
|
Loading…
Reference in New Issue
Block a user