Skip to content

Commit

Permalink
feat: update throttler for v5
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcdo29 committed Sep 5, 2023
1 parent 4c648e3 commit 790feb7
Showing 1 changed file with 98 additions and 17 deletions.
115 changes: 98 additions & 17 deletions content/security/rate-limiting.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ Once the installation is complete, the `ThrottlerModule` can be configured as an
@@filename(app.module)
@Module({
imports: [
ThrottlerModule.forRoot({
ttl: 60,
ThrottlerModule.forRoot([{
ttl: 60000,
limit: 10,
}),
}]),
],
})
export class AppModule {}
```

The above will set the global options for the `ttl`, the time to live, and the `limit`, the maximum number of requests within the ttl, for the routes of your application that are guarded.
The above will set the global options for the `ttl`, the time to live in milliseconds, and the `limit`, the maximum number of requests within the ttl, for the routes of your application that are guarded.

Once the module has been imported, you can then choose how you would like to bind the `ThrottlerGuard`. Any kind of binding as mentioned in the [guards](https://docs.nestjs.com/guards) section is fine. If you wanted to bind the guard globally, for example, you could do so by adding this provider to any module:

Expand All @@ -32,9 +32,39 @@ Once the module has been imported, you can then choose how you would like to bin
}
```

#### Multiple Throttler Definitions

There may come upon times where you want to set up multiple throttling definitions, like no more than 3 calls in a second, 20 calls in 10 seconds, and 100 calls in a minute. To do so, you can set up your definitions in the array with named options, that can later be referenced in the `@SkipThrottle()` and `@Throttle()` decorators to change the options again.

```typescript
@@filename(app.module)
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'short'
ttl: 1000,
limit: 3,
},
{
name: 'medium',
ttl: 10000,
limit: 20
},
{
name: 'long',
ttl: 60000,
limit: 100
}
]),
],
})
export class AppModule {}
```

#### Customization

There may be a time where you want to bind the guard to a controller or globally, but want to disable rate limiting for one or more of your endpoints. For that, you can use the `@SkipThrottle()` decorator, to negate the throttler for an entire class or a single route. The `@SkipThrottle()` decorator can also take in a boolean for if there is a case where you want to exclude _most_ of a controller, but not every route.
There may be a time where you want to bind the guard to a controller or globally, but want to disable rate limiting for one or more of your endpoints. For that, you can use the `@SkipThrottle()` decorator, to negate the throttler for an entire class or a single route. The `@SkipThrottle()` decorator can also take in an object of string keys with boolean values for if there is a case where you want to exclude _most_ of a controller, but not every route, and configure it per throttler set if you have more than one. If you do not pass an object, the default is to use `{{ '{' }} default: true {{ '}' }}`

```typescript
@SkipThrottle()
Expand All @@ -49,22 +79,22 @@ This `@SkipThrottle()` decorator can be used to skip a route or a class or to ne
@Controller('users')
export class UsersController {
// Rate limiting is applied to this route.
@SkipThrottle(false)
@SkipThrottle({ default: false })
dontSkip() {
return "List users work with Rate limiting.";
return 'List users work with Rate limiting.';
}
// This route will skip rate limiting.
doSkip() {
return "List users work without Rate limiting.";
return 'List users work without Rate limiting.';
}
}
```

There is also the `@Throttle()` decorator which can be used to override the `limit` and `ttl` set in the global module, to give tighter or looser security options. This decorator can be used on a class or a function as well. The order for this decorator does matter, as the arguments are in the order of `limit, ttl`. You have to configure it like this:
There is also the `@Throttle()` decorator which can be used to override the `limit` and `ttl` set in the global module, to give tighter or looser security options. This decorator can be used on a class or a function as well. With version 5 and onwards, the decorator takes in an object with the string relating to the name of the throttler set, and an object with the limit and ttl keys and integer values, similar to the options passed to the root module. If you do not have a name set in your original options, use the string `default` You have to configure it like this:

```typescript
// Override default configuration for Rate limiting and duration.
@Throttle(3, 60)
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Get()
findAll() {
return "List users works with custom rate limiting.";
Expand All @@ -82,7 +112,7 @@ import { Injectable } from '@nestjs/common';

@Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
protected getTracker(req: Record<string, any>): string {
protected async getTracker(req: Record<string, any>): Promise<string> {
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
}
}
Expand All @@ -104,7 +134,7 @@ This module can work with websockets, but it requires some class extension. You
export class WsThrottlerGuard extends ThrottlerGuard {
async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
const client = context.switchToWs().getClient();
const ip = client._socket.remoteAddress
const ip = client._socket.remoteAddress;
const key = this.generateKey(context, ip);
const { totalHits } = await this.storageService.increment(key, ttl);

Expand All @@ -116,6 +146,7 @@ export class WsThrottlerGuard extends ThrottlerGuard {
}
}
```

> info **Hint** If you are using ws, it is necessary to replace the `_socket` with `conn`
There's a few things to keep in mind when working with WebSockets:
Expand All @@ -142,12 +173,16 @@ export class GqlThrottlerGuard extends ThrottlerGuard {

#### Configuration

The following options are valid for the `ThrottlerModule`:
The following options are valid for the object passed to the array of the `ThrottlerModule`'s options:

<table>
<tr>
<td><code>name</code></td>
<td>the name for internal tracking of which throttler set is being used. Defaults to `default` if not passed</td>
</tr>
<tr>
<td><code>ttl</code></td>
<td>the number of seconds that each request will last in storage</td>
<td>the number of milliseconds that each request will last in storage</td>
</tr>
<tr>
<td><code>limit</code></td>
Expand All @@ -157,9 +192,30 @@ The following options are valid for the `ThrottlerModule`:
<td><code>ignoreUserAgents</code></td>
<td>an array of regular expressions of user-agents to ignore when it comes to throttling requests</td>
</tr>
<tr>
<td><code>skipIf</code></td>
<td>a function that takes in the <code>ExecutionContext</code> and returns a <code>boolean</code> to short circuit the throttler logic. Like <code>@SkipThrottler()</code>, but based on the request</td>
</tr>
</table>

If you need to set up storages instead, or want to use a some of the above options in a more global sense, applying to each throttler set, you can pass the options above via the `throttlers` option key and use the below table

<table>
<tr>
<td><code>storage</code></td>
<td> the storage setting for how to keep track of the requests</td>
<td>a custom storage service for where the throttling should be kept track. <a href="/security/rate-limiting#storages">See here.</a></td>
</tr>
<tr>
<td><code>ignoreUserAgents</code></td>
<td>an array of regular expressions of user-agents to ignore when it comes to throttling requests</td>
</tr>
<tr>
<td><code>skipIf</code></td>
<td>a function that takes in the <code>ExecutionContext</code> and returns a <code>boolean</code> to short circuit the throttler logic. Like <code>@SkipThrottler()</code>, but based on the request</td>
</tr>
<tr>
<td><code>throttlers</code></td>
<td>an array of throttler sets, defined using the table above</td>
</tr>
</table>

Expand All @@ -175,10 +231,10 @@ One approach would be to use a factory function:
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
useFactory: (config: ConfigService) => ([{
ttl: config.get('THROTTLE_TTL'),
limit: config.get('THROTTLE_LIMIT'),
}),
}]),
}),
],
})
Expand Down Expand Up @@ -208,3 +264,28 @@ The built in storage is an in memory cache that keeps track of the requests made
For distributed servers you could use the community storage provider for [Redis](https://github.com/kkoomen/nestjs-throttler-storage-redis) to have a single source of truth.

> info **Note** `ThrottlerStorage` can be imported from `@nestjs/throttler`.
#### Time Helpers

There are a couple of helper methods to make the timings more readable if you prefer to use them over the direct definition. `@nestjs/throttler` exports five different helpers, `seconds`, `minutes`, `hours`, `days`, and `weeks`. To use them, simply call `seconds(5)` or any of the other helpers, and the correct number of milliseconds will be returned.

#### Migration Guide

For most people, wrapping your options in an array will be enough.

If you are using a custom storage, you should wrap you `ttl` and `limit` in an
array and assign it to the `throttlers` property of the options object.

Any `@ThrottleSkip()` should now take in an object with `string: boolean` props.
The strings are the names of the throttlers. If you do not have a name, pass the
string `'default'`, as this is what will be used under the hood otherwise.

Any `@Throttle()` decorators should also now take in an object with string keys,
relating to the names of the throttler contexts (again, `'default'` if no name)
and values of objects that have `limit` and `ttl` keys.

> Warning **Important** The `ttl` is now in **milliseconds**. If you want to keep your ttl
> in seconds for readability, use the `seconds` helper from this package. It just
> multiplies the ttl by 1000 to make it in milliseconds.
For more info, see the [Changelog](https://github.com/nestjs/throttler/blob/master/CHANGELOG.md#500)

0 comments on commit 790feb7

Please sign in to comment.