Skip to content

Commit 0e23e85

Browse files
authored
Merge pull request DSpace#1804 from 4Science/CST-6153
Ask current password to user when setting a new password
2 parents 064dae2 + 9de6f38 commit 0e23e85

8 files changed

Lines changed: 85 additions & 11 deletions

src/app/core/eperson/eperson-data.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ describe('EPersonDataService', () => {
307307
it('should sent a patch request with an uuid, token and new password to the epersons endpoint', () => {
308308
service.patchPasswordWithToken('test-uuid', 'test-token', 'test-password');
309309

310-
const operation = Object.assign({ op: 'add', path: '/password', value: 'test-password' });
310+
const operation = Object.assign({ op: 'add', path: '/password', value: { new_password: 'test-password' } });
311311
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/test-uuid?token=test-token', [operation]);
312312

313313
expect(requestService.send).toHaveBeenCalledWith(expected);

src/app/core/eperson/eperson-data.service.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { createSelector, select, Store } from '@ngrx/store';
33
import { Operation } from 'fast-json-patch';
44
import { Observable } from 'rxjs';
55
import { find, map, take } from 'rxjs/operators';
6-
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../access-control/epeople-registry/epeople-registry.actions';
6+
import {
7+
EPeopleRegistryCancelEPersonAction,
8+
EPeopleRegistryEditEPersonAction
9+
} from '../../access-control/epeople-registry/epeople-registry.actions';
710
import { EPeopleRegistryState } from '../../access-control/epeople-registry/epeople-registry.reducers';
811
import { AppState } from '../../app.reducer';
912
import { hasNoValue, hasValue } from '../../shared/empty.util';
@@ -318,7 +321,7 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
318321
patchPasswordWithToken(uuid: string, token: string, password: string): Observable<RemoteData<EPerson>> {
319322
const requestId = this.requestService.generateRequestId();
320323

321-
const operation = Object.assign({ op: 'add', path: '/password', value: password });
324+
const operation = Object.assign({ op: 'add', path: '/password', value: { 'new_password': password } });
322325

323326
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
324327
map((endpoint: string) => this.getIDHref(endpoint, uuid)),

src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ describe('ProfilePageSecurityFormComponent', () => {
7474

7575
expect(component.passwordValue.emit).toHaveBeenCalledWith('new-password');
7676
}));
77+
78+
it('should emit the value on password change with current password for profile-page', fakeAsync(() => {
79+
spyOn(component.passwordValue, 'emit');
80+
spyOn(component.currentPasswordValue, 'emit');
81+
component.FORM_PREFIX = 'profile.security.form.';
82+
component.ngOnInit();
83+
component.formGroup.patchValue({password: 'new-password'});
84+
component.formGroup.patchValue({'current-password': 'current-password'});
85+
tick(300);
86+
87+
expect(component.passwordValue.emit).toHaveBeenCalledWith('new-password');
88+
expect(component.currentPasswordValue.emit).toHaveBeenCalledWith('current-password');
89+
}));
7790
});
7891
});
7992
});

src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export class ProfilePageSecurityFormComponent implements OnInit {
2727
* Emits the value of the password
2828
*/
2929
@Output() passwordValue = new EventEmitter<string>();
30+
/**
31+
* Emits the value of the current-password
32+
*/
33+
@Output() currentPasswordValue = new EventEmitter<string>();
3034

3135
/**
3236
* The form's input models
@@ -70,6 +74,14 @@ export class ProfilePageSecurityFormComponent implements OnInit {
7074
}
7175

7276
ngOnInit(): void {
77+
if (this.FORM_PREFIX === 'profile.security.form.') {
78+
this.formModel.unshift(new DynamicInputModel({
79+
id: 'current-password',
80+
name: 'current-password',
81+
inputType: 'password',
82+
required: true
83+
}));
84+
}
7385
if (this.passwordCanBeEmpty) {
7486
this.formGroup = this.formService.createFormGroup(this.formModel,
7587
{ validators: [this.checkPasswordsEqual] });
@@ -94,6 +106,9 @@ export class ProfilePageSecurityFormComponent implements OnInit {
94106
debounceTime(300),
95107
).subscribe((valueChange) => {
96108
this.passwordValue.emit(valueChange.password);
109+
if (this.FORM_PREFIX === 'profile.security.form.') {
110+
this.currentPasswordValue.emit(valueChange['current-password']);
111+
}
97112
}));
98113
}
99114

src/app/profile-page/profile-page.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ <h3 class="mb-4">{{'profile.head' | translate}}</h3>
2424
[FORM_PREFIX]="'profile.security.form.'"
2525
(isInvalid)="setInvalid($event)"
2626
(passwordValue)="setPasswordValue($event)"
27+
(currentPasswordValue)="setCurrentPasswordValue($event)"
2728
></ds-profile-page-security-form>
2829
</div>
2930
</div>

src/app/profile-page/profile-page.component.spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ describe('ProfilePageComponent', () => {
180180

181181
beforeEach(() => {
182182
component.setPasswordValue('');
183-
183+
component.setCurrentPasswordValue('current-password');
184184
result = component.updateSecurity();
185185
});
186186

@@ -199,6 +199,7 @@ describe('ProfilePageComponent', () => {
199199
beforeEach(() => {
200200
component.setPasswordValue('test');
201201
component.setInvalid(true);
202+
component.setCurrentPasswordValue('current-password');
202203
result = component.updateSecurity();
203204
});
204205

@@ -215,8 +216,11 @@ describe('ProfilePageComponent', () => {
215216
beforeEach(() => {
216217
component.setPasswordValue('testest');
217218
component.setInvalid(false);
219+
component.setCurrentPasswordValue('current-password');
218220

219-
operations = [{ op: 'add', path: '/password', value: 'testest' }];
221+
operations = [
222+
{ 'op': 'add', 'path': '/password', 'value': { 'new_password': 'testest', 'current_password': 'current-password' } }
223+
];
220224
result = component.updateSecurity();
221225
});
222226

@@ -228,6 +232,28 @@ describe('ProfilePageComponent', () => {
228232
expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
229233
});
230234
});
235+
236+
describe('when password is filled in, and is valid but return 403', () => {
237+
let result;
238+
let operations;
239+
240+
it('should return call epersonService.patch', (done) => {
241+
epersonService.patch.and.returnValue(observableOf(Object.assign(new RestResponse(false, 403, 'Error'))));
242+
component.setPasswordValue('testest');
243+
component.setInvalid(false);
244+
component.setCurrentPasswordValue('current-password');
245+
operations = [
246+
{ 'op': 'add', 'path': '/password', 'value': {'new_password': 'testest', 'current_password': 'current-password' }}
247+
];
248+
result = component.updateSecurity();
249+
epersonService.patch(user, operations).subscribe((response) => {
250+
expect(response.statusCode).toEqual(403);
251+
done();
252+
});
253+
expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
254+
expect(result).toEqual(true);
255+
});
256+
});
231257
});
232258

233259
describe('canChangePassword$', () => {

src/app/profile-page/profile-page.component.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class ProfilePageComponent implements OnInit {
6767
* The password filled in, in the security form
6868
*/
6969
private password: string;
70+
/**
71+
* The current-password filled in, in the security form
72+
*/
73+
private currentPassword: string;
7074

7175
/**
7276
* The authenticated user
@@ -138,23 +142,23 @@ export class ProfilePageComponent implements OnInit {
138142
*/
139143
updateSecurity() {
140144
const passEntered = isNotEmpty(this.password);
141-
142145
if (this.invalidSecurity) {
143146
this.notificationsService.error(this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.general'));
144147
}
145148
if (!this.invalidSecurity && passEntered) {
146-
const operation = {op: 'add', path: '/password', value: this.password} as Operation;
147-
this.epersonService.patch(this.currentUser, [operation]).pipe(
148-
getFirstCompletedRemoteData()
149-
).subscribe((response: RemoteData<EPerson>) => {
149+
const operations = [
150+
{ 'op': 'add', 'path': '/password', 'value': { 'new_password': this.password, 'current_password': this.currentPassword } }
151+
] as Operation[];
152+
this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData<EPerson>) => {
150153
if (response.hasSucceeded) {
151154
this.notificationsService.success(
152155
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'success.title'),
153156
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'success.content')
154157
);
155158
} else {
156159
this.notificationsService.error(
157-
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'), response.errorMessage
160+
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'),
161+
this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.change-failed')
158162
);
159163
}
160164
});
@@ -170,6 +174,14 @@ export class ProfilePageComponent implements OnInit {
170174
this.password = $event;
171175
}
172176

177+
/**
178+
* Set the current-password value based on the value emitted from the security form
179+
* @param $event
180+
*/
181+
setCurrentPasswordValue($event: string) {
182+
this.currentPassword = $event;
183+
}
184+
173185
/**
174186
* Submit of the security form that triggers the updateProfile method
175187
*/

src/assets/i18n/en.json5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,12 +3081,16 @@
30813081

30823082
"profile.security.form.label.passwordrepeat": "Retype to confirm",
30833083

3084+
"profile.security.form.label.current-password": "Current password",
3085+
30843086
"profile.security.form.notifications.success.content": "Your changes to the password were saved.",
30853087

30863088
"profile.security.form.notifications.success.title": "Password saved",
30873089

30883090
"profile.security.form.notifications.error.title": "Error changing passwords",
30893091

3092+
"profile.security.form.notifications.error.change-failed": "An error occurred while trying to change the password. Please check if the current password is correct.",
3093+
30903094
"profile.security.form.notifications.error.not-same": "The provided passwords are not the same.",
30913095

30923096
"profile.security.form.notifications.error.general": "Please fill required fields of security form.",

0 commit comments

Comments
 (0)