implement account locking on too many attempts and unlocking after time window

This commit is contained in:
Elliot DeNolf
2020-09-25 14:22:13 -04:00
parent 57c6afa3a9
commit c19ccd5df4
5 changed files with 56 additions and 4 deletions

View File

@@ -35,9 +35,15 @@ async function login(args) {
if (!userDoc || (args.collection.config.auth.emailVerification && !userDoc._verified)) {
throw new AuthenticationError();
}
if (userDoc && userDoc.isLocked) {
throw new AuthenticationError();
}
const authResult = await userDoc.authenticate(password);
if (!authResult.user) {
if (authResult.user) {
await authResult.user.resetLoginAttempts();
} else {
await userDoc.incLoginAttempts();
throw new AuthenticationError();
}

View File

@@ -20,7 +20,46 @@ function registerCollections() {
const schema = buildSchema(formattedCollection, this.config);
if (collection.auth) {
schema.plugin(passportLocalMongoose, { usernameField: 'email' });
schema.plugin(passportLocalMongoose, {
usernameField: 'email',
});
// Check if collection is the admin user set in config
if (collection.slug === this.config.admin.user) {
schema.add({ loginAttempts: { type: Number, hide: true, default: 0 } });
schema.add({ lockUntil: { type: Date, hide: true } });
schema.virtual('isLocked').get(() => !!(this.lockUntil && this.lockUntil > Date.now()));
const { maxLoginAttempts, lockTime } = this.config.admin;
// eslint-disable-next-line func-names
schema.methods.incLoginAttempts = function (cb) {
// Expired lock, restart count at 1
if (this.lockUntil && this.lockUntil < Date.now()) {
return this.updateOne({
$set: { loginAttempts: 1 },
$unset: { lockUntil: 1 },
}, cb);
}
const updates = { $inc: { loginAttempts: 1 } };
// Lock the account if at max attempts and not already locked
if (this.loginAttempts + 1 >= maxLoginAttempts && !this.isLocked) {
updates.$set = { lockUntil: Date.now() + lockTime };
}
return this.updateOne(updates, cb);
};
// eslint-disable-next-line func-names
schema.methods.resetLoginAttempts = function (cb) {
return this.updateOne({
$set: { loginAttempts: 0 },
$unset: { lockUntil: 1 },
}, cb);
};
}
schema.path('hash').options.hide = true;
schema.path('salt').options.hide = true;
if (collection.auth.emailVerification) {

View File

@@ -29,6 +29,9 @@ const sanitizeConfig = (config) => {
sanitizedConfig.collections.push(defaultUser);
}
sanitizedConfig.maxLoginAttempts = sanitizedConfig.maxLoginAttempts || 3;
sanitizedConfig.lockTime = sanitizedConfig.lockTime || 600000; // 10 minutes
sanitizedConfig.email = config.email || {};
sanitizedConfig.email.fromName = sanitizedConfig.email.fromName || 'Payload';
sanitizedConfig.email.fromAddress = sanitizedConfig.email.fromName || 'hello@payloadcms.com';