Files
kami-parse-server/src/AccountLockout.js

181 lines
4.5 KiB
JavaScript

// This class handles the Account Lockout Policy settings.
import Parse from 'parse/node';
export class AccountLockout {
constructor(user, config) {
this._user = user;
this._config = config;
}
/**
* set _failed_login_count to value
*/
_setFailedLoginCount(value) {
const query = {
username: this._user.username,
};
const updateFields = {
_failed_login_count: value,
};
return this._config.database.update('_User', query, updateFields);
}
/**
* check if the _failed_login_count field has been set
*/
_isFailedLoginCountSet() {
const query = {
username: this._user.username,
_failed_login_count: { $exists: true },
};
return this._config.database.find('_User', query).then(users => {
if (Array.isArray(users) && users.length > 0) {
return true;
} else {
return false;
}
});
}
/**
* if _failed_login_count is NOT set then set it to 0
* else do nothing
*/
_initFailedLoginCount() {
return this._isFailedLoginCountSet().then(failedLoginCountIsSet => {
if (!failedLoginCountIsSet) {
return this._setFailedLoginCount(0);
}
});
}
/**
* increment _failed_login_count by 1
*/
_incrementFailedLoginCount() {
const query = {
username: this._user.username,
};
const updateFields = {
_failed_login_count: { __op: 'Increment', amount: 1 },
};
return this._config.database.update('_User', query, updateFields);
}
/**
* if the failed login count is greater than the threshold
* then sets lockout expiration to 'currenttime + accountPolicy.duration', i.e., account is locked out for the next 'accountPolicy.duration' minutes
* else do nothing
*/
_setLockoutExpiration() {
const query = {
username: this._user.username,
_failed_login_count: { $gte: this._config.accountLockout.threshold },
};
const now = new Date();
const updateFields = {
_account_lockout_expires_at: Parse._encode(
new Date(now.getTime() + this._config.accountLockout.duration * 60 * 1000)
),
};
return this._config.database.update('_User', query, updateFields).catch(err => {
if (
err &&
err.code &&
err.message &&
err.code === Parse.Error.OBJECT_NOT_FOUND &&
err.message === 'Object not found.'
) {
return; // nothing to update so we are good
} else {
throw err; // unknown error
}
});
}
/**
* if _account_lockout_expires_at > current_time and _failed_login_count > threshold
* reject with account locked error
* else
* resolve
*/
_notLocked() {
const query = {
username: this._user.username,
_account_lockout_expires_at: { $gt: Parse._encode(new Date()) },
_failed_login_count: { $gte: this._config.accountLockout.threshold },
};
return this._config.database.find('_User', query).then(users => {
if (Array.isArray(users) && users.length > 0) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Your account is locked due to multiple failed login attempts. Please try again after ' +
this._config.accountLockout.duration +
' minute(s)'
);
}
});
}
/**
* set and/or increment _failed_login_count
* if _failed_login_count > threshold
* set the _account_lockout_expires_at to current_time + accountPolicy.duration
* else
* do nothing
*/
_handleFailedLoginAttempt() {
return this._initFailedLoginCount()
.then(() => {
return this._incrementFailedLoginCount();
})
.then(() => {
return this._setLockoutExpiration();
});
}
/**
* handle login attempt if the Account Lockout Policy is enabled
*/
handleLoginAttempt(loginSuccessful) {
if (!this._config.accountLockout) {
return Promise.resolve();
}
return this._notLocked().then(() => {
if (loginSuccessful) {
return this._setFailedLoginCount(0);
} else {
return this._handleFailedLoginAttempt();
}
});
}
/**
* 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;