Add account unlock on password reset (#7146)

* added account unlock on password reset

* added account policy option

* added changelog entry

* Added docs entry

* moved changelog entry to correct position

* improved tests to ensure requesting password reset email does not unlock account

* run prettier
This commit is contained in:
Manuel
2021-02-01 01:07:04 +01:00
committed by GitHub
parent 25fb576776
commit 08b2ea45b0
9 changed files with 171 additions and 4 deletions

View File

@@ -158,6 +158,23 @@ export class AccountLockout {
}
});
}
/**
* Removes the account lockout.
*/
unlockAccount() {
if (!this._config.accountLockout || !this._config.accountLockout.unlockOnPasswordReset) {
return Promise.resolve();
}
return this._config.database.update(
'_User',
{ username: this._user.username },
{
_failed_login_count: { __op: 'Delete' },
_account_lockout_expires_at: { __op: 'Delete' },
}
);
}
}
export default AccountLockout;

View File

@@ -9,7 +9,9 @@ import net from 'net';
import {
IdempotencyOptions,
FileUploadOptions,
AccountLockoutOptions,
} from './Options/Definitions';
import { isBoolean } from 'lodash';
function removeTrailingSlash(str) {
if (!str) {
@@ -146,6 +148,12 @@ export class Config {
) {
throw 'Account lockout threshold should be an integer greater than 0 and less than 1000';
}
if (accountLockout.unlockOnPasswordReset === undefined) {
accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default;
} else if (!isBoolean(accountLockout.unlockOnPasswordReset)) {
throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.';
}
}
}

View File

@@ -4,6 +4,7 @@ import AdaptableController from './AdaptableController';
import MailAdapter from '../Adapters/Email/MailAdapter';
import rest from '../rest';
import Parse from 'parse/node';
import AccountLockout from '../AccountLockout';
var RestQuery = require('../RestQuery');
var Auth = require('../Auth');
@@ -258,7 +259,11 @@ export class UserController extends AdaptableController {
updatePassword(username, token, password) {
return this.checkResetTokenValidity(username, token)
.then(user => updateUserPassword(user.objectId, password, this.config))
.then(user => updateUserPassword(user, password, this.config))
.then(user => {
const accountLockoutPolicy = new AccountLockout(user, this.config);
return accountLockoutPolicy.unlockAccount();
})
.catch(error => {
if (error && error.message) {
// in case of Parse.Error, fail with the error message only
@@ -302,16 +307,16 @@ export class UserController extends AdaptableController {
}
// Mark this private
function updateUserPassword(userId, password, config) {
function updateUserPassword(user, password, config) {
return rest.update(
config,
Auth.master(config),
'_User',
{ objectId: userId },
{ objectId: user.objectId },
{
password: password,
}
);
).then(() => user);
}
function buildEmailLink(destination, username, token, config) {

View File

@@ -570,6 +570,12 @@ module.exports.AccountLockoutOptions = {
help: 'number of failed sign-in attempts that will cause a user account to be locked',
action: parsers.numberParser('threshold'),
},
unlockOnPasswordReset: {
env: 'PARSE_SERVER_ACCOUNT_LOCKOUT_UNLOCK_ON_PASSWORD_RESET',
help: 'Is true if the account lock should be removed after a successful password reset.',
action: parsers.booleanParser,
default: false,
},
};
module.exports.PasswordPolicyOptions = {
doNotAllowUsername: {

View File

@@ -126,6 +126,7 @@
* @interface AccountLockoutOptions
* @property {Number} duration number of minutes that a locked-out account remains locked out before automatically becoming unlocked.
* @property {Number} threshold number of failed sign-in attempts that will cause a user account to be locked
* @property {Boolean} unlockOnPasswordReset Is true if the account lock should be removed after a successful password reset.
*/
/**

View File

@@ -301,6 +301,9 @@ export interface AccountLockoutOptions {
duration: ?number;
/* number of failed sign-in attempts that will cause a user account to be locked */
threshold: ?number;
/* Is true if the account lock should be removed after a successful password reset.
:DEFAULT: false */
unlockOnPasswordReset: ?boolean;
}
export interface PasswordPolicyOptions {