RED-6003: Changing e-mail requires password challenge
This commit is contained in:
parent
d474a6a057
commit
28fedb4f00
@ -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">
|
||||
|
||||
@ -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>
|
||||
@ -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],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user