From 28fedb4f00ba2ddf2fb9b9eb8fb83228cf88bf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 17 Jan 2023 07:55:02 +0200 Subject: [PATCH] RED-6003: Changing e-mail requires password challenge --- .../base-screen/base-screen.component.html | 4 +- .../confirm-password-dialog.component.html | 26 +++++++ .../confirm-password-dialog.component.ts | 30 ++++++++ .../services/user-profile-dialog.service.ts | 19 +++++ .../user-profile-screen.component.ts | 75 ++++++++++++------- .../user-profile/user-profile.module.ts | 5 +- apps/red-ui/src/assets/i18n/redact/de.json | 24 +++++- apps/red-ui/src/assets/i18n/redact/en.json | 24 +++++- apps/red-ui/src/assets/i18n/scm/de.json | 24 +++++- apps/red-ui/src/assets/i18n/scm/en.json | 24 +++++- libs/common-ui | 2 +- 11 files changed, 212 insertions(+), 45 deletions(-) create mode 100644 apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.ts create mode 100644 apps/red-ui/src/app/modules/account/screens/user-profile/services/user-profile-dialog.service.ts diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html index 1295beef8..b35ce4371 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html @@ -18,7 +18,7 @@ -
+
- +
diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.html b/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.html new file mode 100644 index 000000000..8b83ec69a --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.html @@ -0,0 +1,26 @@ +
+
{{ 'user-profile-screen.confirm-password.header' | translate }}
+ +
+
+
+
+ + +
+
+
+ +
+ +
+
+ + +
diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.ts new file mode 100644 index 000000000..ed8defd8c --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/confirm-password-dialog/confirm-password-dialog.component.ts @@ -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; +} + +@Component({ + templateUrl: './confirm-password-dialog.component.html', +}) +export class ConfirmPasswordDialogComponent extends BaseDialogComponent { + constructor(protected readonly _dialogRef: MatDialogRef) { + super(_dialogRef); + + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); + } + + save(): void { + this._dialogRef.close(this.form.get('password').value); + } + + private _getForm(): FormGroup { + return this._formBuilder.group({ + password: [null, Validators.required], + }); + } +} diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/services/user-profile-dialog.service.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/services/user-profile-dialog.service.ts new file mode 100644 index 000000000..a220b2ee1 --- /dev/null +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/services/user-profile-dialog.service.ts @@ -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 { + protected readonly _config: DialogConfig = { + confirmPassword: { + component: ConfirmPasswordDialogComponent, + }, + }; + + constructor(protected readonly _dialog: MatDialog) { + super(_dialog); + } +} diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts index 9953c42eb..fc4aae6cd 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts @@ -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 { - 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(); } } } diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts index 504248661..6ef42fa66 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts @@ -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 {} diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 2404e99dd..98edda09a 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 52f5b3425..58423e52c 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 5d20b9cff..f3124d89e 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index b597dd174..8595d6e30 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -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": { diff --git a/libs/common-ui b/libs/common-ui index f98109ca4..36317aae7 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit f98109ca45147960805d1ddf28acc59fb50e4955 +Subproject commit 36317aae757fa25229c0df83c78ef9900fd17800