Skip to content

Instantly share code, notes, and snippets.

@OnnoGabriel
Last active April 30, 2021 06:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save OnnoGabriel/c6935c40f458a498df42ef1fb257b173 to your computer and use it in GitHub Desktop.
Save OnnoGabriel/c6935c40f458a498df42ef1fb257b173 to your computer and use it in GitHub Desktop.
Login Attempt Limiter for Feathers
/**
* 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