Giter VIP home page Giter VIP logo

express-slow-down's Introduction

Express Slow Down

tests npm version npm downloads

Basic rate-limiting middleware for Express that slows down responses rather than blocking them outright. Use to slow repeated requests to public APIs and/or endpoints such as password reset.

Plays nice with (and built on top of) Express Rate Limit

Stores

The default memory store does not share state with any other processes or servers. It's sufficient for basic abuse prevention, but an external store will provide more consistency.

express-slow-down uses express-rate-limit's stores

Note: when using express-slow-down and express-rate-limit with an external store, you'll need to create two instances of the store and provide different prefixes so that they don't double-count requests.

Installation

From the npm registry:

# Using npm
> npm install express-slow-down
# Using yarn or pnpm
> yarn/pnpm add express-slow-down

From Github Releases:

# Using npm
> npm install https://github.com/express-rate-limit/express-slow-down/releases/download/v{version}/express-slow-down.tgz
# Using yarn or pnpm
> yarn/pnpm add https://github.com/express-rate-limit/express-slow-down/releases/download/v{version}/express-slow-down.tgz

Replace {version} with the version of the package that you want to your, e.g.: 2.0.0.

Usage

Importing

This library is provided in ESM as well as CJS forms, and works with both Javascript and Typescript projects.

This package requires you to use Node 16 or above.

Import it in a CommonJS project (type: commonjs or no type field in package.json) as follows:

const { slowDown } = require('express-slow-down')

Import it in a ESM project (type: module in package.json) as follows:

import { slowDown } from 'express-slow-down'

Examples

To use it in an API-only server where the speed-limiter should be applied to all requests:

import { slowDown } from 'express-slow-down'

const limiter = slowDown({
	windowMs: 15 * 60 * 1000, // 15 minutes
	delayAfter: 5, // Allow 5 requests per 15 minutes.
	delayMs: (hits) => hits * 100, // Add 100 ms of delay to every request after the 5th one.

	/**
	 * So:
	 *
	 * - requests 1-5 are not delayed.
	 * - request 6 is delayed by 600ms
	 * - request 7 is delayed by 700ms
	 * - request 8 is delayed by 800ms
	 *
	 * and so on. After 15 minutes, the delay is reset to 0.
	 */
})

// Apply the delay middleware to all requests.
app.use(limiter)

To use it in a 'regular' web server (e.g. anything that uses express.static()), where the rate-limiter should only apply to certain requests:

import { slowDown } from 'express-slow-down'

const apiLimiter = slowDown({
	windowMs: 15 * 60 * 1000, // 15 minutes
	delayAfter: 1, // Allow only one request to go at full-speed.
	delayMs: (hits) => hits * hits * 1000, // 2nd request has a 4 second delay, 3rd is 9 seconds, 4th is 16, etc.
})

// Apply the delay middleware to API calls only.
app.use('/api', apiLimiter)

To use a custom store:

import { slowDown } from 'express-slow-down'
import { MemcachedStore } from 'rate-limit-memcached'

const speedLimiter = slowDown({
	windowMs: 15 * 60 * 1000, // 15 minutes
	delayAfter: 1, // Allow only one request to go at full-speed.
	delayMs: (hits) => hits * hits * 1000, // Add exponential delay after 1 request.
	store: new MemcachedStore({
		/* ... */
	}), // Use the external store
})

// Apply the rate limiting middleware to all requests.
app.use(speedLimiter)

Note: most stores will require additional configuration, such as custom prefixes, when using multiple instances. The default built-in memory store is an exception to this rule.

Configuration

number

Time frame for which requests are checked/remembered.

Note that some stores have to be passed the value manually, while others infer it from the options passed to this middleware.

Defaults to 60000 ms (= 1 minute).

delayAfter

number | function

The max number of requests allowed during windowMs before the middleware starts delaying responses. Can be the limit itself as a number or a (sync/async) function that accepts the Express req and res objects and then returns a number.

Defaults to 1.

An example of using a function:

const isPremium = async (user) => {
	// ...
}

const limiter = slowDown({
	// ...
	delayAfter: async (req, res) => {
		if (await isPremium(req.user)) return 10
		else return 1
	},
})

delayMs

number | function

The delay to apply to each request once the limit is reached. Can be the delay itself (in milliseconds) as a number or a (sync/async) function that accepts a number (number of requests in the current window), the Express req and res objects and then returns a number.

By default, it increases the delay by 1 second for every request over the limit:

const limiter = slowDown({
	// ...
	delayMs: (used) => (used - delayAfter) * 1000,
})

maxDelayMs

number | function

The absolute maximum value for delayMs. After many consecutive attempts, the delay will always be this value. This option should be used especially when your application is running behind a load balancer or reverse proxy that has a request timeout. Can be the number itself (in milliseconds) or a (sync/async) function that accepts the Express req and res objects and then returns a number.

Defaults to Infinity.

For example, for the following configuration:

const limiter = slowDown({
	// ...
	delayAfter: 1,
	delayMs: (hits) => hits * 1000,
	maxDelayMs: 4000,
})

The first request will have no delay. The second will have a 2 second delay, the 3rd will have a 3 second delay, but the fourth, fifth, sixth, seventh and so on requests will all have a 4 second delay.

Options from express-rate-limit

Because express-rate-limit is used internally, additional options that it supports may be passed in. Some of them are listed below; see express-rate-limit's documentation for the complete list.

Note: The limit (max) option is not supported (use delayAfter instead), nor are handler or the various headers options.

Request API

A req.slowDown property is added to all requests with the limit, used, and remaining number of requests and, if the store provides it, a resetTime Date object. It also has the delay property, which is the amount of delay imposed on current request (milliseconds). These may be used in your application code to take additional actions or inform the user of their status.

Note that used includes the current request, so it should always be > 0.

The property name can be configured with the configuration option requestPropertyName.

Issues and Contributing

If you encounter a bug or want to see something added/changed, please go ahead and open an issue! If you need help with something, feel free to start a discussion!

If you wish to contribute to the library, thanks! First, please read the contributing guide. Then you can pick up any issue and fix/implement it!

License

MIT © Nathan Friedly, Vedant K

express-slow-down's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

express-slow-down's Issues

Release v1.4.1 fails test

Having upgraded from using version 1.4.0 in our code I'm now seeing tests timing out.

I then installed/run the code using node v14.18.3 and am seeing one of your test cases fail too:

  • Test failure : should decrement hits with closed response and skipFailedRequests

image

The problem appears to have been caused by a https://github.com/nfriedly/express-slow-down/blob/459f2d532246c263356137dde6b840f522b6bf1d/lib/express-slow-down.js#L123 which is the only significant change since the previous revision at 1.4.0. I have no idea what fix is needed.

Add maximum value to slow down delay

It would be really nice to have a way to set a maximum delay for slow down (e.g.: a maxDelayMs config property).

If such property is set, the delay would increase as per delayMs config up to maxDelayMs. After reaching it, every subsequent request would be delayed exactly by maxDelayMs.

Rationale

  1. Most HTTP client libraries come with a built-in timeout configuration that will simply drop a request after a certain period of time.
  2. When the application is behind a reverse proxy, usually there's also a timeout that will force the connection dropping for such requests.

By boundlessly increasing the response delay, we could probably end up with many dangling requests, that will never see the light of day, consuming resources for nothing at all.

delayAfter = 0 does not disable rate slowdown

I am trying to dynamically turn on and off the rate slowdown. Whenever I set the delayAfter = 0 it defaults to 1 and immediately starts to slow down the requests. Since this is the only option that supports using a function handler for returning the option value this is the only option we can use to handle this use case.

Allow failed requests to count twice

Essentially, the current options skipSuccessfulRequests and skipFailedRequests take boolean values which modify the counting behavior by either adding 0 or 1 to the counter. This could be taken a step further:

Introduce two new options:

successfulRequestWeight: number 
failedRequestWeight: number

Deprecate the existing options:

skipSuccessfulRequests = true -> successfulRequestWeight = 0
skipSuccessfulRequests = false -> successfulRequestWeight = 1
skipFailedRequests = true -> failedRequestWeight = 0
skipFailedRequests = false -> failedRequestWeight = 1

Update the stores (in a backward-compatible way):

increment: (key: any, cb: (err: Error | null, current: number, resetTime: number) => unknown, amount: number = 1)
decrement: (key: any, amount: number = 1)

This would allow anyone to configure just how much successful or failed requests contribute towards the limit, while still keeping the options simple.

If you'd like this (or something similar), I'd be happy to provide a pull request for express-slow-down and/or express-rate-limit

req.ip is undefiend on production

i was using onLimitReached to log the possible attacker's ip, but figured out that req.ip is undefiend.
in addition, the limit is applying to all computers, because everyone's req.ip is undefiend!

Question: Is delay per IP or for all IPs?

I want the delay to be for each IP accessing the JSON API separately.
For example:
Client 1 --> Server (100 Requests in under 1 Minute)
Client 2 --> Server (less than 100 Requests in under 1 Minute)

Is the slow down for all Clients or only for Client 1?
(I have already configured slowDown to match my little Demo above:

const speedLimiter = slowDown({
    windowMs: 1 * 60 * 1000, // 1 minute
    delayAfter: 100, // allow 100 requests per minute, then...
    delayMs: 500 // add delay to every request after 100 requests in 1 minute
});

Thanks for any answers,
Alex

onLimitReached , or running custom code

Description

In v1.5.0 we used onLimitReached to log and record metrics of our limiting. onLimitReached is no more, as its not in express-rate-limit, however in express-rate-limit we can use a handler to perform those actions. As described in your readme.md, handler is not supported in slow-down. Is there an alternative way to run custom code, or are there plans to?

Why

Logging and metrics

Alternatives

Giving up on logging and recording these events

On v1.4.1 when slow delay is triggered the http request end up hanging up indefinitly and never being resolved

On v1.4.1 when slow delay is triggered the http request end up hanging up indefinitly and never being resolved

I'm on Windows with NodeJS v16.15.0

From what i saw the issue is that on this line we are sending the req object and with this object being sent the on-finished library trigger immediatly the onFinished event because the req.complete is already set (complete on req mean that the parsing of the request is done but not the http context being resolved source )

There are 2 possible solutions :

  • Using the res object instead of the req one on the line linked above
  • Not using on-finished library at all and replacing line linked above with req.on('close', () => {

I personnaly prefer the second solution because it means One less dependency to have so much cleaner to me ...

maxDelayMs missing from @types/express-slow-down

Hello,

I m using @types/express-slow-down for my typescript expressjs project. And I noticed that the option maxDelayMs is missing in the typescript adapter. It's a shame because without this option, the delay applied to my requests keeps increasing. Is this on purpose ?

Thanks

Rationale behind linear backoff

Can one explain why linear backoff (additive) is appropriate here? Rather than exponential backoff (multiplicative) for instance.

Logging when threshold met

I think it would be cool if you could enable logging of the request / slow down details when a slow down occurs.

TypeError: Cannot read properties of undefined (reading 'toString')

I'm getting this error when trying to set up a simple example with express-slowdown and rate-limit-redis. Here's the full example:

import express from 'express';
import RedisStore from 'rate-limit-redis';
import slowDown from 'express-slow-down';
import { createClient } from 'redis';

(async () => {
  const client = createClient();

  await client.connect();

  const app = express();

  const speedLimiter = slowDown({
    windowMs: 30 * 1000,
    delayAfter: 1,
    delayMs: 500,
    store: new RedisStore({
      sendCommand: (...args) => client.sendCommand(args),
    }),
  });

  app.use(speedLimiter);

  app.all('*', (req, res) => {
    res.send('Hello');
  });

  app.listen(3000, () => {
    console.log('Server listening on port 3000');
  });
})();

Versions of relevant packages:

[email protected]
[email protected]
[email protected]
[email protected]

I'm assuming the error is on my end somewhere, but any help would be greatly appreciated. Thanks!

CVE-2024-29041 vulnerability of Express.js

Description

Are you aware of the express.js malformed URLs vulnerability?

Ref: GHSA-rv95-896h-c2vc
Ref: https://nvd.nist.gov/vuln/detail/CVE-2024-29041

"Open Redirect: express is vulnerable to Open Redirect. The vulnerability is due to improper handling of user-provided URLs during redirection in Express.js, which performs encoding using the encodeurl library before passing it to the 'location' header. It allows bypass of properly implemented allow lists and leading to an Open Redirect vulnerability"

We are using express-rate-limit in its latest version, and Veracode reports this vulnerability.
Is there going to be a fix for this?

Library version

2.0.1

Node version

= 16

Typescript version (if you are using it)

No response

Module system

ESM

user passed in validate overwrites `limit: false`

Description

I get lots of warnings like:

ChangeWarning: Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7 See https://express-rate-limit.github.io/WRN_ERL_MAX_ZERO/ for more information.

looking at the code I can see you try to take care of this:

But I'm passing in my own validate to suppress delayMs warnings. This is then inside notUndefinedOptions and so its spreading overrides your addition on line 143

Library version

2.0.0

Node version

v18.17.1

Typescript version (if you are using it)

No response

Module system

CommonJS

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.