Files
dspace-angular/src/app/profile-page/profile-page.component.ts
VictorDuranEscire 090dda28ad Changing metadata in a user profile without specifying a password brings up a success and an error panel (#3818)
* Agregar validador para mostrar mensaje de error solo si se escribe la contraseña actual

* Agregar validador para mostrar mensaje de error solo si se escribe la contraseña actual

* Set constant variable for valid current password in profile page
2025-01-28 17:22:31 -06:00

286 lines
10 KiB
TypeScript

import {
AsyncPipe,
NgForOf,
NgIf,
NgTemplateOutlet,
} from '@angular/common';
import {
Component,
OnInit,
ViewChild,
} from '@angular/core';
import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { Operation } from 'fast-json-patch';
import {
BehaviorSubject,
Observable,
} from 'rxjs';
import {
filter,
switchMap,
tap,
} from 'rxjs/operators';
import { AuthService } from '../core/auth/auth.service';
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { PaginatedList } from '../core/data/paginated-list.model';
import { RemoteData } from '../core/data/remote-data';
import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { EPerson } from '../core/eperson/models/eperson.model';
import { Group } from '../core/eperson/models/group.model';
import { PaginationService } from '../core/pagination/pagination.service';
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
import {
getAllCompletedRemoteData,
getAllSucceededRemoteData,
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../core/shared/operators';
import { SuggestionsNotificationComponent } from '../notifications/suggestions-notification/suggestions-notification.component';
import {
hasValue,
isNotEmpty,
} from '../shared/empty.util';
import { ErrorComponent } from '../shared/error/error.component';
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { PaginationComponent } from '../shared/pagination/pagination.component';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { followLink } from '../shared/utils/follow-link-config.model';
import { VarDirective } from '../shared/utils/var.directive';
import { ThemedProfilePageMetadataFormComponent } from './profile-page-metadata-form/themed-profile-page-metadata-form.component';
import { ProfilePageResearcherFormComponent } from './profile-page-researcher-form/profile-page-researcher-form.component';
import { ProfilePageSecurityFormComponent } from './profile-page-security-form/profile-page-security-form.component';
@Component({
selector: 'ds-base-profile-page',
styleUrls: ['./profile-page.component.scss'],
templateUrl: './profile-page.component.html',
imports: [
ThemedProfilePageMetadataFormComponent,
ProfilePageSecurityFormComponent,
AsyncPipe,
TranslateModule,
ProfilePageResearcherFormComponent,
VarDirective,
NgIf,
NgForOf,
SuggestionsNotificationComponent,
NgTemplateOutlet,
PaginationComponent,
ThemedLoadingComponent,
ErrorComponent,
],
standalone: true,
})
/**
* Component for a user to edit their profile information
*/
export class ProfilePageComponent implements OnInit {
/**
* A reference to the metadata form component
*/
@ViewChild(ThemedProfilePageMetadataFormComponent) metadataForm: ThemedProfilePageMetadataFormComponent;
/**
* The authenticated user as observable
*/
user$: Observable<EPerson>;
/**
* The groups the user belongs to
*/
groupsRD$: Observable<RemoteData<PaginatedList<Group>>>;
/**
* The special groups the user belongs to
*/
specialGroupsRD$: Observable<RemoteData<PaginatedList<Group>>>;
/**
* Prefix for the notification messages of this component
*/
NOTIFICATIONS_PREFIX = 'profile.notifications.';
/**
* Prefix for the notification messages of this security form
*/
PASSWORD_NOTIFICATIONS_PREFIX = 'profile.security.form.notifications.';
/**
* The validity of the password filled in, in the security form
*/
private invalidSecurity: boolean;
/**
* The password filled in, in the security form
*/
private password: string;
/**
* The current-password filled in, in the security form
*/
private currentPassword: string;
/**
* The authenticated user
*/
private currentUser: EPerson;
canChangePassword$: Observable<boolean>;
/**
* Default configuration for group pagination
**/
optionsGroupsPagination = Object.assign(new PaginationComponentOptions(),{
id: 'page_groups',
currentPage: 1,
pageSize: 20,
});
isResearcherProfileEnabled$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor(private authService: AuthService,
private notificationsService: NotificationsService,
private translate: TranslateService,
private epersonService: EPersonDataService,
private authorizationService: AuthorizationDataService,
private configurationService: ConfigurationDataService,
public dsoNameService: DSONameService,
private paginationService: PaginationService,
) {
}
ngOnInit(): void {
this.user$ = this.authService.getAuthenticatedUserFromStore().pipe(
filter((user: EPerson) => hasValue(user.id)),
switchMap((user: EPerson) => this.epersonService.findById(user.id, true, true, followLink('groups'))),
getAllSucceededRemoteData(),
getRemoteDataPayload(),
tap((user: EPerson) => this.currentUser = user),
);
this.groupsRD$ = this.paginationService.getCurrentPagination(this.optionsGroupsPagination.id, this.optionsGroupsPagination).pipe(
switchMap((pageOptions: PaginationComponentOptions) => {
return this.epersonService.findById(this.currentUser.id, true, true, followLink('groups',{
findListOptions: {
elementsPerPage: pageOptions.pageSize,
currentPage: pageOptions.currentPage,
} }));
}),
getAllCompletedRemoteData(),
getRemoteDataPayload(),
switchMap((user: EPerson) => user?.groups),
);
this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href)));
this.specialGroupsRD$ = this.authService.getSpecialGroupsFromAuthStatus();
this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe(
getFirstCompletedRemoteData(),
).subscribe((configRD: RemoteData<ConfigurationProperty>) => {
this.isResearcherProfileEnabled$.next(configRD.hasSucceeded && configRD.payload.values.length > 0);
});
}
/**
* Fire an update on both the metadata and security forms
* Show a warning notification when no changes were made in both forms
*/
updateProfile(): void {
const metadataChanged = this.metadataForm.compRef.instance.updateProfile();
const securityChanged = this.updateSecurity();
if (!metadataChanged && !securityChanged) {
this.notificationsService.warning(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'warning.no-changes.title'),
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'warning.no-changes.content'),
);
}
}
/**
* Sets the validity of the password based on an emitted of the form
* @param $event
*/
setInvalid($event: boolean) {
this.invalidSecurity = $event;
}
/**
* Update the user's security details
*
* Sends a patch request for changing the user's password when a new password is present and the password confirmation
* matches the new password.
* Nothing happens when no passwords are filled in.
* An error notification is displayed when the password confirmation does not match the new password.
*
* Returns false when the password was empty
*/
updateSecurity() {
const passEntered = isNotEmpty(this.password);
const validCurrentPassword = isNotEmpty(this.currentPassword);
if (validCurrentPassword && !passEntered) {
this.notificationsService.error(this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.general'));
}
if (!this.invalidSecurity && passEntered) {
const operations = [
{ 'op': 'add', 'path': '/password', 'value': { 'new_password': this.password, 'current_password': this.currentPassword } },
] as Operation[];
this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData<EPerson>) => {
if (response.hasSucceeded) {
this.notificationsService.success(
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'success.title'),
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'success.content'),
);
} else {
this.notificationsService.error(
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'),
this.getPasswordErrorMessage(response),
);
}
});
}
return passEntered;
}
/**
* Set the password value based on the value emitted from the security form
* @param $event
*/
setPasswordValue($event: string) {
this.password = $event;
}
/**
* Set the current-password value based on the value emitted from the security form
* @param $event
*/
setCurrentPasswordValue($event: string) {
this.currentPassword = $event;
}
/**
* Submit of the security form that triggers the updateProfile method
*/
submit() {
this.updateProfile();
}
/**
* Returns an error message from a password validation request with a specific reason or
* a default message without specific reason.
* @param response from the validation password patch request.
*/
getPasswordErrorMessage(response) {
if (response.hasFailed && isNotEmpty(response.errorMessage)) {
// Response has a specific error message. Show this message in the error notification.
return this.translate.instant(response.errorMessage);
}
// Show default error message notification.
return this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.change-failed');
}
}