RED-6003: Changing e-mail requires password challenge

This commit is contained in:
Adina Țeudan 2023-01-17 07:55:02 +02:00
parent d474a6a057
commit 28fedb4f00
11 changed files with 212 additions and 45 deletions

View File

@ -18,7 +18,7 @@
</div>
</a>
<div class="actions flex-2">
<div class="actions flex-2 ml-20">
<div class="buttons">
<redaction-spotlight-search
*allow="roles.search; if: (isSearchScreen$ | async) === false && (currentUser.isUser || currentUser.isManager)"
@ -32,7 +32,7 @@
<redaction-notifications [iqserHelpMode]="'open_notifications'"></redaction-notifications>
</div>
<iqser-user-button [matMenuTriggerFor]="userMenu" id="userMenu" [iqserHelpMode]="'open_usermenu'"></iqser-user-button>
<iqser-user-button [iqserHelpMode]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
<mat-menu #userMenu="matMenu" xPosition="before">
<div id="user-menu-items">

View File

@ -0,0 +1,26 @@
<section class="dialog">
<div class="dialog-header heading-l">{{ 'user-profile-screen.confirm-password.header' | translate }}</div>
<form [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required">
<div class="iqser-input-group required w-300">
<label [translate]="'user-profile-screen.confirm-password.form.password.label'"></label>
<input formControlName="password" name="password" type="password" />
</div>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
(click)="save()"
[disabled]="disabled"
[label]="'user-profile-screen.confirm-password.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,30 @@
import { Component } from '@angular/core';
import { BaseDialogComponent } from '@iqser/common-ui';
import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog';
import { AbstractControl, FormGroup, Validators } from '@angular/forms';
interface FormType {
password: AbstractControl<string>;
}
@Component({
templateUrl: './confirm-password-dialog.component.html',
})
export class ConfirmPasswordDialogComponent extends BaseDialogComponent {
constructor(protected readonly _dialogRef: MatDialogRef<ConfirmPasswordDialogComponent>) {
super(_dialogRef);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
}
save(): void {
this._dialogRef.close(this.form.get('password').value);
}
private _getForm(): FormGroup<FormType> {
return this._formBuilder.group({
password: [null, Validators.required],
});
}
}

View File

@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { DialogConfig, DialogService } from '@iqser/common-ui';
import { ConfirmPasswordDialogComponent } from '../confirm-password-dialog/confirm-password-dialog.component';
type DialogType = 'confirmPassword';
@Injectable()
export class UserProfileDialogService extends DialogService<DialogType> {
protected readonly _config: DialogConfig<DialogType> = {
confirmPassword: {
component: ConfirmPasswordDialogComponent,
},
};
constructor(protected readonly _dialog: MatDialog) {
super(_dialog);
}
}

View File

@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService } from '@iqser/common-ui';
import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
import { IProfile } from '@red/domain';
import { languagesTranslations } from '@translations/languages-translations';
import { UserService } from '@users/user.service';
@ -10,6 +10,8 @@ import { ConfigService } from '@services/config.service';
import { firstValueFrom } from 'rxjs';
import { UserPreferenceService } from '@users/user-preference.service';
import { ROLES } from '@users/roles';
import { UserProfileDialogService } from '../services/user-profile-dialog.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
selector: 'redaction-user-profile-screen',
@ -30,11 +32,14 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
private readonly _userService: UserService,
readonly userPreferences: UserPreferenceService,
private readonly _loadingService: LoadingService,
private readonly _dialogService: UserProfileDialogService,
private readonly _formBuilder: UntypedFormBuilder,
private readonly _languageService: LanguageService,
protected readonly _translateService: TranslateService,
private readonly _permissionsService: IqserPermissionsService,
protected readonly _userPreferenceService: UserPreferenceService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _toaster: Toaster,
) {
super();
this._loadingService.start();
@ -49,6 +54,10 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value;
}
get emailChanged(): boolean {
return this.#profileModel['email'] !== this.form.get('email').value;
}
get profileChanged(): boolean {
const keys = Object.keys(this.form.getRawValue());
keys.splice(keys.indexOf('language'), 1);
@ -72,31 +81,42 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
}
async save(): Promise<void> {
this._loadingService.start();
try {
if (this.profileChanged) {
const value = this.form.getRawValue() as IProfile;
// delete value.language;
// delete value.darkTheme;
if (this.languageChanged) {
await this._languageService.change(this.form.get('language').value);
if (this.emailChanged) {
const dialogRef = this._dialogService.openDialog('confirmPassword', null, null);
const password = await firstValueFrom(dialogRef.afterClosed());
if (!password) {
return;
}
value['password'] = password;
}
this._loadingService.start();
await firstValueFrom(this._userService.updateMyProfile(value));
await this._userService.loadCurrentUser();
await firstValueFrom(this._userService.loadAll());
}
if (this.languageChanged) {
await this._languageService.change(this.form.get('language').value);
}
if (this.themeChanged) {
await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light');
}
this._initializeForm();
this._toaster.success(_('user-profile-screen.update.success'));
} catch (e) {
this._loadingService.stop();
}
if (this.profileChanged) {
const value = this.form.getRawValue() as IProfile;
delete value.language;
await firstValueFrom(
this._userService.updateMyProfile({
...value,
}),
);
await this._userService.loadCurrentUser();
await firstValueFrom(this._userService.loadAll());
}
if (this.themeChanged) {
await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light');
}
this._initializeForm();
}
private _getForm(): UntypedFormGroup {
@ -122,15 +142,12 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
language: this._languageService.currentLanguage ?? '',
darkTheme: this._userPreferenceService.getTheme() === 'dark',
};
if (this._userService.currentUser.email) {
// disable email if it's already set
this.form.get('email').disable();
}
this.form.patchValue(this.#profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {
this._loadingService.stop();
this._changeRef.markForCheck();
}
}
}

View File

@ -5,11 +5,14 @@ import { SharedModule } from '@shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { TranslateModule } from '@ngx-translate/core';
import { ConfirmPasswordDialogComponent } from './confirm-password-dialog/confirm-password-dialog.component';
import { UserProfileDialogService } from './services/user-profile-dialog.service';
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [UserProfileScreenComponent],
declarations: [UserProfileScreenComponent, ConfirmPasswordDialogComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, TranslateModule],
providers: [UserProfileDialogService],
})
export class UserProfileModule {}

View File

@ -1428,11 +1428,11 @@
"processing": "Wird analysiert...",
"re-processing": "",
"reprocess": "Wird analysiert",
"table-parsing-analyzing": "Table Parsing",
"unassigned": "Nicht zugewiesen",
"under-approval": "In Genehmigung",
"under-review": "In Review",
"unprocessed": "Unbearbeitet",
"table-parsing-analyzing": "Table Parsing"
"unprocessed": "Unbearbeitet"
},
"file-upload": {
"type": {
@ -2111,6 +2111,12 @@
}
},
"unknown": "Unbekannt",
"update-profile": {
"errors": {
"bad-request": "",
"generic": ""
}
},
"upload-dictionary-dialog": {
"options": {
"cancel": "Abbrechen",
@ -2168,13 +2174,25 @@
"change-password": "Passwort ändern",
"save": "Änderungen speichern"
},
"confirm-password": {
"form": {
"password": {
"label": ""
}
},
"header": "",
"save": ""
},
"form": {
"dark-theme": "",
"email": "Email",
"first-name": "Vorname",
"last-name": "Nachname"
},
"title": "Profil bearbeiten"
"title": "Profil bearbeiten",
"update": {
"success": ""
}
},
"user-stats": {
"chart": {

View File

@ -1428,11 +1428,11 @@
"processing": "Processing...",
"re-processing": "Re-processing...",
"reprocess": "Processing",
"table-parsing-analyzing": "Table Parsing",
"unassigned": "Unassigned",
"under-approval": "Under Approval",
"under-review": "Under Review",
"unprocessed": "Unprocessed",
"table-parsing-analyzing": "Table Parsing"
"unprocessed": "Unprocessed"
},
"file-upload": {
"type": {
@ -2111,6 +2111,12 @@
}
},
"unknown": "Unknown",
"update-profile": {
"errors": {
"bad-request": "Error: {message}.",
"generic": "An error has occurred while updating the profile."
}
},
"upload-dictionary-dialog": {
"options": {
"cancel": "Cancel",
@ -2168,13 +2174,25 @@
"change-password": "Change Password",
"save": "Save Changes"
},
"confirm-password": {
"form": {
"password": {
"label": "Password"
}
},
"header": "Confirm your password",
"save": "Submit"
},
"form": {
"dark-theme": "Dark Theme",
"email": "Email",
"first-name": "First name",
"last-name": "Last name"
},
"title": "Edit Profile"
"title": "Edit Profile",
"update": {
"success": "Successfully updated profile!"
}
},
"user-stats": {
"chart": {

View File

@ -1428,11 +1428,11 @@
"processing": "Wird analysiert...",
"re-processing": "",
"reprocess": "Wird analysiert",
"table-parsing-analyzing": "Table Parsing",
"unassigned": "Nicht zugewiesen",
"under-approval": "In Genehmigung",
"under-review": "In Review",
"unprocessed": "Unbearbeitet",
"table-parsing-analyzing": "Table Parsing"
"unprocessed": "Unbearbeitet"
},
"file-upload": {
"type": {
@ -2111,6 +2111,12 @@
}
},
"unknown": "Unbekannt",
"update-profile": {
"errors": {
"bad-request": "",
"generic": ""
}
},
"upload-dictionary-dialog": {
"options": {
"cancel": "Abbrechen",
@ -2168,13 +2174,25 @@
"change-password": "Passwort ändern",
"save": "Änderungen speichern"
},
"confirm-password": {
"form": {
"password": {
"label": ""
}
},
"header": "",
"save": ""
},
"form": {
"dark-theme": "",
"email": "Email",
"first-name": "Vorname",
"last-name": "Nachname"
},
"title": "Profil bearbeiten"
"title": "Profil bearbeiten",
"update": {
"success": ""
}
},
"user-stats": {
"chart": {

View File

@ -1428,11 +1428,11 @@
"processing": "Processing...",
"re-processing": "Re-processing...",
"reprocess": "Processing",
"table-parsing-analyzing": "Table Parsing",
"unassigned": "Unassigned",
"under-approval": "Under Approval",
"under-review": "Under Review",
"unprocessed": "Unprocessed",
"table-parsing-analyzing": "Table Parsing"
"unprocessed": "Unprocessed"
},
"file-upload": {
"type": {
@ -2111,6 +2111,12 @@
}
},
"unknown": "Unknown",
"update-profile": {
"errors": {
"bad-request": "Error: {message}.",
"generic": "An error has occurred while updating the profile."
}
},
"upload-dictionary-dialog": {
"options": {
"cancel": "Cancel",
@ -2168,13 +2174,25 @@
"change-password": "Change Password",
"save": "Save Changes"
},
"confirm-password": {
"form": {
"password": {
"label": "Password"
}
},
"header": "Confirm your password",
"save": "Submit"
},
"form": {
"dark-theme": "Dark Theme",
"email": "Email",
"first-name": "First name",
"last-name": "Last name"
},
"title": "Edit Profile"
"title": "Edit Profile",
"update": {
"success": "Successfully updated profile!"
}
},
"user-stats": {
"chart": {

@ -1 +1 @@
Subproject commit f98109ca45147960805d1ddf28acc59fb50e4955
Subproject commit 36317aae757fa25229c0df83c78ef9900fd17800