Last active
April 30, 2021 06:13
-
-
Save OnnoGabriel/c6935c40f458a498df42ef1fb257b173 to your computer and use it in GitHub Desktop.
Login Attempt Limiter for Feathers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Login Attempt Limiter Hook for Feathers (https://feathersjs.com/) | |
* | |
* Limits the login attempts by recording the number of failed logins | |
* and the datetime of the last login attempt to the user data. | |
* | |
* 1. Extend your user model by two fields: | |
* loginAttempts: { | |
* type: DataTypes.INTEGER, | |
* defaultValue: 0 | |
* }, | |
* lastLoginAttempt: { | |
* type: DataTypes.DATE, | |
* allowNull: true, | |
* defaultValue: null | |
* } | |
* | |
* 2. Store this hook to /hooks/login-attempt-limiter.js | |
* | |
* 3. Add this hook to the before and after hooks in authentication.js: | |
* | |
* const loginAttemptLimiter = require('./hooks/login-attempt-limiter') | |
* ... | |
* before: { | |
* create: [ | |
* loginAttemptLimiter(), | |
* ... | |
* ] | |
* } | |
* after: { | |
* create: [ | |
* ... | |
* loginAttemptLimiter() | |
* ] | |
* } | |
**/ | |
const errors = require('@feathersjs/errors') | |
module.exports = function () { | |
return async context => { | |
// Runs only on authentication service | |
if (context.path !== 'authentication') { | |
return context | |
} | |
// Runs only for local auth strategy with given email address | |
if (context.data.strategy !== 'local' || !context.data.email) { | |
return context | |
} | |
// In before hook | |
if (context.type == 'before') { | |
// Get user data | |
const result = await context.app.service('users').find({ | |
query: { | |
email: context.data.email, | |
} | |
}) | |
// User not found? | |
if (result.total !== 1) { | |
return context | |
} | |
const user = result.data[0] | |
// Limit Login Attempt | |
if (user.loginAttempts > 3) { | |
const diffSeconds = Math.round( | |
(new Date().getTime() - new Date(user.lastLoginAttempt).getTime()) / 1000 | |
) | |
// Next login attempt possible in loginAttempts^2 seconds | |
const nextSeconds = (user.loginAttempts) * (user.loginAttempts) | |
// Next login to early? | |
if (diffSeconds < nextSeconds) { | |
// Return number of seconds until next possible login | |
throw new errors.BadRequest('Next login in ' + (nextSeconds - diffSeconds) ) | |
} | |
} | |
// Increase loginAttempt and set lastLoginAttempt to current datetime | |
await context.app.service('users').patch( | |
user.id, | |
{ | |
loginAttempts: user.loginAttempts + 1, | |
lastLoginAttempt: new Date() | |
} | |
) | |
} | |
// In after hook | |
if (context.type == 'after') { | |
// Login successful => Reset login attempts | |
await context.app.service('users').patch( | |
context.result.user.id, | |
{ | |
loginAttempts: 0, | |
lastLoginAttempt: new Date() | |
} | |
) | |
} | |
return context | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment