Skip to content

Commit 969c6cd

Browse files
committed
fix: option to skip current password check when changing password
1 parent 05f5fa2 commit 969c6cd

File tree

4 files changed

+46
-9
lines changed

4 files changed

+46
-9
lines changed

schemas/updatePassword.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
"remoteip": {
3939
"type": "string",
4040
"format": "ipv4"
41+
},
42+
"noCurrentPasswordCheck": {
43+
"type": "boolean",
44+
"default": false
4145
}
4246
}
4347
}

src/actions/updatePassword.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ async function usernamePasswordReset(service, username, currentPassword) {
3737
]);
3838

3939
// if no password is not allowed - it will throw on hasPassword above
40-
if (internalData.password) {
40+
// @TODO tests
41+
if (internalData.password && currentPassword !== false) {
4142
await scrypt.verify(internalData.password, currentPassword);
4243
}
4344

@@ -71,14 +72,15 @@ async function setPassword(service, userId, password) {
7172
* @apiParam (Payload) {String} [resetToken] - must be present if `username` or `currentPassword` is not
7273
* @apiParam (Payload) {String} newPassword - password will be changed to this
7374
* @apiParam (Payload) {Boolean} [invalidateTokens=false] - if set to `true` will invalidate issued tokens
75+
* @apiParam (Payload) {Boolean} [noCurrentPasswordCheck=false] - disable checking of current password
7476
* @apiParam (Payload) {String} [remoteip] - will be used for rate limiting if supplied
7577
*/
7678
async function updatePassword(request) {
7779
const { config, redis, tokenManager } = this;
7880
if (!config.noPasswordCheck && !request.params.currentPassword) {
7981
throw new HttpStatusError(400, 'must have required property currentPassword');
8082
}
81-
const { newPassword: password, remoteip = false } = request.params;
83+
const { newPassword: password, noCurrentPasswordCheck, remoteip = false } = request.params;
8284
const invalidateTokens = !!request.params.invalidateTokens;
8385
const loginRateLimiter = new UserLoginRateLimiter(redis, config.rateLimiters.userLogin);
8486

@@ -98,7 +100,7 @@ async function updatePassword(request) {
98100
throw Forbidden;
99101
}
100102
} else {
101-
userId = await usernamePasswordReset(this, request.params.username, request.params.currentPassword);
103+
userId = await usernamePasswordReset(this, request.params.username, noCurrentPasswordCheck ? false : request.params.currentPassword);
102104
}
103105

104106
// update password

src/custom/phone-rate-limiter.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,29 @@ const ErrorCaptchaConfigInvalid = new HttpStatusError(500, 'Invalid reCAPTCHA co
1010
const ErrorCaptchaInvalid = new HttpStatusError(400, 'Captcha invalid');
1111
const ErrorCaptchaRequired = new HttpStatusError(412, 'Captcha required');
1212
const ErrorIpRequired = new HttpStatusError(400, 'IP address required');
13-
const ErrorLockLimit = new HttpStatusError(403, 'Lock limit');
14-
const ErrorTotalLimit = new HttpStatusError(403, 'Total limit');
13+
14+
const ErrorLockLimit = (reset) => {
15+
const error = new HttpStatusError(403, 'Lock limit');
16+
17+
error.code = 'E_LOCK_LIMIT';
18+
error.detail = { reset };
19+
20+
return error;
21+
};
22+
const ErrorTotalLimit = (reset) => {
23+
const error = new HttpStatusError(403, 'Total limit');
24+
25+
error.code = 'E_TOTAL_LIMIT';
26+
error.detail = { reset };
27+
28+
return error;
29+
};
1530

1631
const ErrorCaptchaError = (error) => new HttpStatusError(500, `Captcha error: ${error.message}`);
1732

1833
ErrorCaptchaInvalid.code = 'E_CAPTCHA_INVALID';
1934
ErrorCaptchaRequired.code = 'E_CAPTCHA_REQUIRED';
2035
ErrorIpRequired.code = 'E_IP_REQUIRED';
21-
ErrorLockLimit.code = 'E_LOCK_LIMIT';
22-
ErrorTotalLimit.code = 'E_TOTAL_LIMIT';
2336

2437
const actionUsernameParamNameMap = {
2538
'disposable-password': ['id', 'challengeType', 'remoteip'],
@@ -92,7 +105,7 @@ const checkTotalLimit = async (service) => {
92105
await createTotalLimiter(service).check(totalRateLimiterKey);
93106
} catch (error) {
94107
if (error instanceof KeyIpRateLimiter.RateLimitError) {
95-
throw ErrorTotalLimit;
108+
throw ErrorTotalLimit(error.reset);
96109
}
97110

98111
throw error;
@@ -104,7 +117,7 @@ const checkLockLimit = async (service, username, remoteIp) => {
104117
await createLockLimiter(service).check(username, remoteIp);
105118
} catch (error) {
106119
if (error instanceof KeyIpRateLimiter.RateLimitError) {
107-
throw ErrorLockLimit;
120+
throw ErrorLockLimit(error.reset);
108121
}
109122

110123
throw error;

test/suites/actions/update-password.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ describe('#updatePassword', function updatePasswordSuite() {
7474
});
7575
});
7676

77+
it('should not be able to update password without current password', async function test() {
78+
const params = { username, newPassword: 'zzz' };
79+
80+
await assert.rejects(
81+
() => this.users.dispatch('updatePassword', { params }),
82+
/password arg must be present/
83+
);
84+
});
85+
86+
it('should be able to update password without current password with noCurrentPasswordCheck', async function test() {
87+
const params = { username, newPassword: 'zzz', noCurrentPasswordCheck: true };
88+
89+
return this.users.dispatch('updatePassword', { params })
90+
.then((updatePassword) => {
91+
assert.deepEqual(updatePassword, { success: true });
92+
});
93+
});
94+
7795
describe('token', function tokenSuite() {
7896
beforeEach(async function pretest() {
7997
const data = await challenge.call(this.users, 'email', {

0 commit comments

Comments
 (0)