implement account locking on too many attempts and unlocking after time window
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user