sveltekit-rate-limiter
Advanced tools
Comparing version 0.3.1 to 0.3.2
@@ -5,3 +5,2 @@ import type { RequestEvent } from '@sveltejs/kit'; | ||
export interface RateLimiterStore { | ||
check: (hash: string, unit: RateUnit) => Promise<number>; | ||
add: (hash: string, unit: RateUnit) => Promise<number>; | ||
@@ -11,3 +10,3 @@ clear: () => Promise<void>; | ||
export interface RateLimiterPlugin { | ||
hash: (event: RequestEvent) => Promise<string | boolean>; | ||
hash: (event: RequestEvent) => Promise<string | boolean | null>; | ||
get rate(): Rate; | ||
@@ -33,8 +32,8 @@ } | ||
} | ||
export type RateLimiterOptions = { | ||
plugins?: RateLimiterPlugin[]; | ||
store?: RateLimiterStore; | ||
maxItems?: number; | ||
onLimited?: (event: RequestEvent, reason: 'rate' | 'rejected') => Promise<void | boolean> | void | boolean; | ||
rates?: { | ||
export type RateLimiterOptions = Partial<{ | ||
plugins: RateLimiterPlugin[]; | ||
store: RateLimiterStore; | ||
maxItems: number; | ||
onLimited: (event: RequestEvent, reason: 'rate' | 'rejected') => Promise<void | boolean> | void | boolean; | ||
rates: { | ||
IP?: Rate; | ||
@@ -44,3 +43,3 @@ IPUA?: Rate; | ||
}; | ||
}; | ||
}>; | ||
export declare class RateLimiter { | ||
@@ -47,0 +46,0 @@ private readonly store; |
@@ -19,16 +19,13 @@ import crypto from 'crypto'; | ||
} | ||
set(hash, rate, unit) { | ||
this.cache.set(hash, rate, { ttl: RateLimiter.TTLTime(unit) }); | ||
return rate; | ||
} | ||
async clear() { | ||
return this.cache.clear(); | ||
} | ||
async check(hash) { | ||
return this.cache.get(hash) ?? 0; | ||
} | ||
async add(hash, unit) { | ||
const currentRate = await this.check(hash); | ||
const currentRate = this.cache.get(hash) ?? 0; | ||
return this.set(hash, currentRate + 1, unit); | ||
} | ||
set(hash, rate, unit) { | ||
this.cache.set(hash, rate, { ttl: RateLimiter.TTLTime(unit) }); | ||
return rate; | ||
} | ||
} | ||
@@ -158,2 +155,5 @@ ///// Plugins ///////////////////////////////////////////////////////////////// | ||
} | ||
else if (id === null) { | ||
continue; | ||
} | ||
if (!id) { | ||
@@ -176,3 +176,3 @@ throw new Error('Empty hash returned from rate limiter ' + plugin.constructor.name); | ||
constructor(options = {}) { | ||
this.plugins = options.plugins ?? []; | ||
this.plugins = [...(options.plugins ?? [])]; | ||
this.onLimited = options.onLimited; | ||
@@ -179,0 +179,0 @@ if (options.rates?.IP) |
{ | ||
"name": "sveltekit-rate-limiter", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"author": "Andreas Söderlund <ciscoheat@gmail.com> (https://blog.encodeart.dev)", | ||
@@ -5,0 +5,0 @@ "description": "A modular rate limiter for SvelteKit. Use in password resets, account registration, etc.", |
@@ -5,8 +5,12 @@ # sveltekit-rate-limiter | ||
Uses an in-memory cache, but can be swapped for something else. Same for limiters, which are plugins. See the [source file](https://github.com/ciscoheat/sveltekit-rate-limiter/blob/main/src/lib/rateLimiter.ts) for interfaces. | ||
Uses an in-memory cache ([@isaacs/ttlcache](https://www.npmjs.com/package/@isaacs/ttlcache)), but can be swapped for something else. Same for limiters, which are plugins. The [source file](https://github.com/ciscoheat/sveltekit-rate-limiter/blob/main/src/lib/server/index.ts#L24-L32) lists both interfaces. | ||
## How to use | ||
```ts | ||
import { error } from '@sveltejs/kit'; | ||
import { RateLimiter } from 'sveltekit-rate-limiter/server'; | ||
const limiter = new RateLimiter({ | ||
// A rate is defined as [number, unit] | ||
rates: { | ||
@@ -18,3 +22,3 @@ IP: [10, 'h'], // IP address limiter | ||
name: 'limiterid', | ||
secret: 'SECRETKEY-SERVER-ONLY', | ||
secret: 'SECRETKEY-SERVER-ONLY', // Use $env/static/private | ||
rate: [2, 'm'], | ||
@@ -27,2 +31,3 @@ preflight: true // Require preflight call (see load) | ||
export const load = async (event) => { | ||
// Preflight: If not called before posting, request will be limited. | ||
limiter.cookieLimiter?.preflight(event); | ||
@@ -33,3 +38,3 @@ }; | ||
default: async (event) => { | ||
if (await limiter.isLimited(event)) return fail(429); | ||
if (await limiter.isLimited(event)) throw error(429); | ||
} | ||
@@ -39,2 +44,14 @@ }; | ||
The limiters will be called in smallest unit and rate order, so in the example above: | ||
``` | ||
cookie(2/min) → IPUA(5/min) → IP(10/hour) | ||
``` | ||
Valid units are, from smallest to largest: | ||
``` | ||
'ms' | 's' | '15s' | '30s' | 'm' | '15m' | '30m' | 'h' | '2h' | '6h' | '12h' | 'd' | ||
``` | ||
## Creating a custom limiter | ||
@@ -46,3 +63,3 @@ | ||
interface RateLimiterPlugin { | ||
hash: (event: RequestEvent) => Promise<string | boolean>; | ||
hash: (event: RequestEvent) => Promise<string | boolean | null>; | ||
get rate(): Rate; | ||
@@ -52,6 +69,17 @@ } | ||
In `hash`, return a string based on a `RequestEvent`, which will be counted and checked against the rate, or a boolean to make the request fail (`false`) or succeed (`true`) no matter the current rate. The string will be hashed later. | ||
In `hash`, return one of the following: | ||
Here's the source for the IP + User Agent limiter, as an example: | ||
- A `string` based on a [RequestEvent](https://kit.svelte.dev/docs/types#public-types-requestevent), which will be counted and checked against the rate. | ||
- A `boolean`, to short-circuit the plugin chain and make the request fail (`false`) or succeed (`true`) no matter the current rate. | ||
- Or `null`, to signify an indeterminate result and move to the next plugin in the chain, or fail the request if it's the last one. | ||
### String hash rules | ||
- The string will be hashed later, so you don't need to use a hash function. | ||
- The string cannot be empty, in which case an exception will be thrown. | ||
### Example | ||
Here's the source for the IP + User Agent limiter: | ||
```ts | ||
@@ -76,3 +104,3 @@ import type { RequestEvent } from '@sveltejs/kit'; | ||
Add the limiter to `options.plugins` to use it. | ||
Add your limiter to `options.plugins` to use it. | ||
@@ -84,3 +112,4 @@ ```ts | ||
plugins: [new CustomLimiter([5, 'm'])] | ||
// The built-in limiters can be added as well. | ||
}); | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14953
107
253
1