@antelopejs/interface-api-util 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/rate-limit.d.ts +91 -0
- package/dist/rate-limit.js +158 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/tests/rate-limit.test.d.ts +1 -0
- package/dist/tests/rate-limit.test.js +202 -0
- package/dist/tests/rate-limit.test.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="./docs/1.introduction.md"><img src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000" alt="Documentation"></a>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
Utility
|
|
10
|
+
Utility helpers for API request validation, rate limiting, and error handling in AntelopeJS. This package provides the `assert` and `assertValidation` helpers that throw structured HTTP error responses when conditions fail or input validation does not pass, plus the `@RateLimit` decorator that caps how often a handler can be called.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -23,4 +23,4 @@ Detailed documentation is available in the `docs` directory:
|
|
|
23
23
|
|
|
24
24
|
## License
|
|
25
25
|
|
|
26
|
-
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
26
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
17
|
exports.assert = assert;
|
|
4
18
|
exports.assertValidation = assertValidation;
|
|
5
19
|
const interface_api_1 = require("@antelopejs/interface-api");
|
|
20
|
+
__exportStar(require("./rate-limit"), exports);
|
|
6
21
|
/**
|
|
7
22
|
* Assert a condition is truthy, throwing an HTTPResult error if false.
|
|
8
23
|
*
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA0BA,wBAQC;AA0BD,4CAYC;AAxED,6DAAuD;AAEvD,+CAA6B;AAE7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,MAAM,CACpB,SAAY,EACZ,IAAY,EACZ,OAAe;IAEf,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,0BAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,gBAAgB,CAC9B,IAAa,EACb,SAA+B,EAC/B,SAAqC,EACrC,IAAI,GAAG,GAAG;IAEV,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,IAAI,0BAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type RequestContext } from "@antelopejs/interface-api";
|
|
2
|
+
interface RateLimitWindow {
|
|
3
|
+
count: number;
|
|
4
|
+
windowStart: number;
|
|
5
|
+
}
|
|
6
|
+
interface RateLimitDecision {
|
|
7
|
+
allowed: boolean;
|
|
8
|
+
remaining: number;
|
|
9
|
+
resetMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Options for the {@link RateLimit} decorator.
|
|
13
|
+
*/
|
|
14
|
+
export interface RateLimitOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Derive the rate-limit key from the request context. When omitted, or when
|
|
17
|
+
* it returns a falsy value, the client IP
|
|
18
|
+
* (`context.rawRequest.socket.remoteAddress`) is used. Provide a custom
|
|
19
|
+
* function to key by user id, API key, or a forwarded header. The return
|
|
20
|
+
* type allows `null`/`undefined` so optional sources (a missing route
|
|
21
|
+
* parameter or header) can be returned directly without a cast.
|
|
22
|
+
*/
|
|
23
|
+
key?: (context: RequestContext) => string | null | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Body sent with the `429` response. Defaults to `"Too many requests"`.
|
|
26
|
+
*/
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply the fixed-window algorithm to a single key.
|
|
31
|
+
*
|
|
32
|
+
* The window resets lazily on access once `windowMs` has elapsed since it
|
|
33
|
+
* started. `now` is passed in so callers (and tests) control the clock.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare function hitWindow(store: Map<string, RateLimitWindow>, key: string, limit: number, windowMs: number, now: number): RateLimitDecision;
|
|
38
|
+
/**
|
|
39
|
+
* Drop windows that have fully elapsed, bounding the store to the keys seen
|
|
40
|
+
* within the current window rather than letting it grow forever.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export declare function pruneExpired(store: Map<string, RateLimitWindow>, windowMs: number, now: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the rate-limit key for a request, falling back to the client IP when
|
|
47
|
+
* no custom key is provided or the custom key yields a falsy value.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveKey(context: RequestContext, options: RateLimitOptions): string;
|
|
52
|
+
/**
|
|
53
|
+
* Rate-limit a route handler using a fixed window, counted per client.
|
|
54
|
+
*
|
|
55
|
+
* Each decorated handler keeps an in-memory, per-process count of requests for
|
|
56
|
+
* every key within a `windowMs` window. Successful responses carry the
|
|
57
|
+
* `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`
|
|
58
|
+
* (epoch seconds) headers so clients can self-pace. Once the count exceeds
|
|
59
|
+
* `limit`, the handler instead throws an `HTTPResult` with status `429` (adding
|
|
60
|
+
* a `Retry-After` header) without running, exactly like `assert`. Elapsed
|
|
61
|
+
* windows are pruned periodically, so the store is bounded by the keys seen
|
|
62
|
+
* within a single window (counts are not shared across processes).
|
|
63
|
+
*
|
|
64
|
+
* Place `@RateLimit` **directly beneath** the route decorator. The route
|
|
65
|
+
* decorator captures the handler's parameters when it is applied, so a
|
|
66
|
+
* `@RateLimit` placed above it would be ignored — this is detected at startup
|
|
67
|
+
* and throws rather than silently leaving the route unprotected.
|
|
68
|
+
*
|
|
69
|
+
* @param limit Maximum number of requests allowed per window
|
|
70
|
+
* @param windowMs Window duration in milliseconds
|
|
71
|
+
* @param options Optional custom key function and `429` message
|
|
72
|
+
* @throws {HTTPResult} Status `429` when the limit is exceeded
|
|
73
|
+
* @throws {Error} At decoration time if placed above its route decorator
|
|
74
|
+
*
|
|
75
|
+
* Example:
|
|
76
|
+
* ```ts
|
|
77
|
+
* @Get("users")
|
|
78
|
+
* @RateLimit(100, 60_000)
|
|
79
|
+
* getUsers() {
|
|
80
|
+
* return new HTTPResult(200, { users: [] });
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* @Post("login")
|
|
84
|
+
* @RateLimit(5, 60_000, { key: (ctx) => ctx.routeParameters.tenant })
|
|
85
|
+
* login() {
|
|
86
|
+
* return new HTTPResult(200, { ok: true });
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare const RateLimit: (limit: number, windowMs: number, options?: RateLimitOptions | undefined) => import("@antelopejs/interface-core/decorators").MethodDecorator;
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimit = void 0;
|
|
4
|
+
exports.hitWindow = hitWindow;
|
|
5
|
+
exports.pruneExpired = pruneExpired;
|
|
6
|
+
exports.resolveKey = resolveKey;
|
|
7
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
8
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
9
|
+
const decorators_1 = require("@antelopejs/interface-core/decorators");
|
|
10
|
+
const TOO_MANY_REQUESTS = 429;
|
|
11
|
+
const DEFAULT_MESSAGE = "Too many requests";
|
|
12
|
+
const UNKNOWN_KEY = "unknown";
|
|
13
|
+
const MILLISECONDS_PER_SECOND = 1000;
|
|
14
|
+
const RETRY_AFTER_HEADER = "Retry-After";
|
|
15
|
+
const RATELIMIT_LIMIT_HEADER = "X-RateLimit-Limit";
|
|
16
|
+
const RATELIMIT_REMAINING_HEADER = "X-RateLimit-Remaining";
|
|
17
|
+
const RATELIMIT_RESET_HEADER = "X-RateLimit-Reset";
|
|
18
|
+
const ORDERING_ERROR = "@RateLimit must be placed directly beneath its route decorator (@Get, @Post, ...). " +
|
|
19
|
+
"A route for this handler is already registered, so the rate-limit guard would be ignored.";
|
|
20
|
+
/**
|
|
21
|
+
* Apply the fixed-window algorithm to a single key.
|
|
22
|
+
*
|
|
23
|
+
* The window resets lazily on access once `windowMs` has elapsed since it
|
|
24
|
+
* started. `now` is passed in so callers (and tests) control the clock.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
function hitWindow(store, key, limit, windowMs, now) {
|
|
29
|
+
const existing = store.get(key);
|
|
30
|
+
const isActive = existing !== undefined && now - existing.windowStart < windowMs;
|
|
31
|
+
const window = isActive ? existing : { count: 0, windowStart: now };
|
|
32
|
+
window.count += 1;
|
|
33
|
+
store.set(key, window);
|
|
34
|
+
return {
|
|
35
|
+
allowed: window.count <= limit,
|
|
36
|
+
remaining: Math.max(0, limit - window.count),
|
|
37
|
+
resetMs: window.windowStart + windowMs - now,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Drop windows that have fully elapsed, bounding the store to the keys seen
|
|
42
|
+
* within the current window rather than letting it grow forever.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
function pruneExpired(store, windowMs, now) {
|
|
47
|
+
for (const [key, window] of store) {
|
|
48
|
+
if (now - window.windowStart >= windowMs) {
|
|
49
|
+
store.delete(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the rate-limit key for a request, falling back to the client IP when
|
|
55
|
+
* no custom key is provided or the custom key yields a falsy value.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
function resolveKey(context, options) {
|
|
60
|
+
const custom = options.key?.(context);
|
|
61
|
+
if (custom) {
|
|
62
|
+
return custom;
|
|
63
|
+
}
|
|
64
|
+
return context.rawRequest.socket.remoteAddress ?? UNKNOWN_KEY;
|
|
65
|
+
}
|
|
66
|
+
function applyRateLimitHeaders(target, limit, decision, now) {
|
|
67
|
+
const resetEpochSeconds = Math.ceil((now + decision.resetMs) / MILLISECONDS_PER_SECOND);
|
|
68
|
+
target.addHeader(RATELIMIT_LIMIT_HEADER, String(limit));
|
|
69
|
+
target.addHeader(RATELIMIT_REMAINING_HEADER, String(decision.remaining));
|
|
70
|
+
target.addHeader(RATELIMIT_RESET_HEADER, String(resetEpochSeconds));
|
|
71
|
+
}
|
|
72
|
+
function buildLimitResult(limit, decision, now, options) {
|
|
73
|
+
const result = new interface_api_1.HTTPResult(TOO_MANY_REQUESTS, options.message ?? DEFAULT_MESSAGE);
|
|
74
|
+
const retryAfterSeconds = Math.ceil(decision.resetMs / MILLISECONDS_PER_SECOND);
|
|
75
|
+
result.addHeader(RETRY_AFTER_HEADER, String(retryAfterSeconds));
|
|
76
|
+
applyRateLimitHeaders(result, limit, decision, now);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
function getControllerMeta(target) {
|
|
80
|
+
return (0, interface_core_1.GetMetadata)(target.constructor, interface_api_1.ControllerMeta);
|
|
81
|
+
}
|
|
82
|
+
function assertGuardWiringOrder(meta, key) {
|
|
83
|
+
const name = String(key);
|
|
84
|
+
const alreadyRouted = (0, interface_api_1.getRegisteredRoutes)().some((route) => route.properties === meta.computed_props && route.callbackName === name);
|
|
85
|
+
if (alreadyRouted) {
|
|
86
|
+
throw new Error(ORDERING_ERROR);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function nextFreeIndex(meta, key) {
|
|
90
|
+
const params = meta.computed_params[key];
|
|
91
|
+
if (!params) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const indices = Object.keys(params).map((value) => Number.parseInt(value, 10));
|
|
95
|
+
return indices.length === 0 ? 0 : Math.max(...indices) + 1;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Rate-limit a route handler using a fixed window, counted per client.
|
|
99
|
+
*
|
|
100
|
+
* Each decorated handler keeps an in-memory, per-process count of requests for
|
|
101
|
+
* every key within a `windowMs` window. Successful responses carry the
|
|
102
|
+
* `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`
|
|
103
|
+
* (epoch seconds) headers so clients can self-pace. Once the count exceeds
|
|
104
|
+
* `limit`, the handler instead throws an `HTTPResult` with status `429` (adding
|
|
105
|
+
* a `Retry-After` header) without running, exactly like `assert`. Elapsed
|
|
106
|
+
* windows are pruned periodically, so the store is bounded by the keys seen
|
|
107
|
+
* within a single window (counts are not shared across processes).
|
|
108
|
+
*
|
|
109
|
+
* Place `@RateLimit` **directly beneath** the route decorator. The route
|
|
110
|
+
* decorator captures the handler's parameters when it is applied, so a
|
|
111
|
+
* `@RateLimit` placed above it would be ignored — this is detected at startup
|
|
112
|
+
* and throws rather than silently leaving the route unprotected.
|
|
113
|
+
*
|
|
114
|
+
* @param limit Maximum number of requests allowed per window
|
|
115
|
+
* @param windowMs Window duration in milliseconds
|
|
116
|
+
* @param options Optional custom key function and `429` message
|
|
117
|
+
* @throws {HTTPResult} Status `429` when the limit is exceeded
|
|
118
|
+
* @throws {Error} At decoration time if placed above its route decorator
|
|
119
|
+
*
|
|
120
|
+
* Example:
|
|
121
|
+
* ```ts
|
|
122
|
+
* @Get("users")
|
|
123
|
+
* @RateLimit(100, 60_000)
|
|
124
|
+
* getUsers() {
|
|
125
|
+
* return new HTTPResult(200, { users: [] });
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* @Post("login")
|
|
129
|
+
* @RateLimit(5, 60_000, { key: (ctx) => ctx.routeParameters.tenant })
|
|
130
|
+
* login() {
|
|
131
|
+
* return new HTTPResult(200, { ok: true });
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
exports.RateLimit = (0, decorators_1.MakeMethodDecorator)((target, key, _descriptor, limit, windowMs, options) => {
|
|
136
|
+
const resolvedOptions = options ?? {};
|
|
137
|
+
const meta = getControllerMeta(target);
|
|
138
|
+
assertGuardWiringOrder(meta, key);
|
|
139
|
+
const store = new Map();
|
|
140
|
+
let lastSweep = 0;
|
|
141
|
+
const index = nextFreeIndex(meta, key);
|
|
142
|
+
(0, interface_api_1.SetParameterProvider)(target, key, index, (context) => {
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
if (now - lastSweep >= windowMs) {
|
|
145
|
+
pruneExpired(store, windowMs, now);
|
|
146
|
+
lastSweep = now;
|
|
147
|
+
}
|
|
148
|
+
const decision = hitWindow(store, resolveKey(context, resolvedOptions), limit, windowMs, now);
|
|
149
|
+
if (!decision.allowed) {
|
|
150
|
+
throw buildLimitResult(limit, decision, now, resolvedOptions);
|
|
151
|
+
}
|
|
152
|
+
if (context.response) {
|
|
153
|
+
applyRateLimitHeaders(context.response, limit, decision, now);
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
16
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
17
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
18
|
+
const rate_limit_1 = require("../rate-limit");
|
|
19
|
+
const TOO_MANY_REQUESTS = 429;
|
|
20
|
+
const LIMIT = 2;
|
|
21
|
+
const WINDOW_MS = 60_000;
|
|
22
|
+
const RETRY_AFTER_HEADER = "Retry-After";
|
|
23
|
+
const RATELIMIT_LIMIT_HEADER = "X-RateLimit-Limit";
|
|
24
|
+
const RATELIMIT_REMAINING_HEADER = "X-RateLimit-Remaining";
|
|
25
|
+
const RATELIMIT_RESET_HEADER = "X-RateLimit-Reset";
|
|
26
|
+
function toHTTPResult(error) {
|
|
27
|
+
if (!(error instanceof interface_api_1.HTTPResult)) {
|
|
28
|
+
throw new Error("Expected an HTTPResult error");
|
|
29
|
+
}
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
function makeContext(remoteAddress) {
|
|
33
|
+
return {
|
|
34
|
+
rawRequest: { socket: { remoteAddress } },
|
|
35
|
+
response: new interface_api_1.HTTPResult(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function captureThrow(action) {
|
|
39
|
+
try {
|
|
40
|
+
action();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return error;
|
|
44
|
+
}
|
|
45
|
+
throw new Error("Expected the action to throw");
|
|
46
|
+
}
|
|
47
|
+
function guardProvider(controller) {
|
|
48
|
+
const meta = (0, interface_core_1.GetMetadata)(controller, interface_api_1.ControllerMeta);
|
|
49
|
+
const provider = meta.computed_params.handler[0]?.provider;
|
|
50
|
+
if (!provider) {
|
|
51
|
+
throw new Error("Expected a rate-limit provider to be registered");
|
|
52
|
+
}
|
|
53
|
+
return provider;
|
|
54
|
+
}
|
|
55
|
+
describe("RateLimit", () => {
|
|
56
|
+
describe("hitWindow", () => {
|
|
57
|
+
it("allows up to the limit then denies within the window", () => {
|
|
58
|
+
const store = new Map();
|
|
59
|
+
const first = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 0);
|
|
60
|
+
const second = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 10);
|
|
61
|
+
const third = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 20);
|
|
62
|
+
node_assert_1.default.equal(first.allowed, true);
|
|
63
|
+
node_assert_1.default.equal(first.remaining, 1);
|
|
64
|
+
node_assert_1.default.equal(second.allowed, true);
|
|
65
|
+
node_assert_1.default.equal(second.remaining, 0);
|
|
66
|
+
node_assert_1.default.equal(third.allowed, false);
|
|
67
|
+
node_assert_1.default.equal(third.remaining, 0);
|
|
68
|
+
});
|
|
69
|
+
it("reports the time remaining until the window resets", () => {
|
|
70
|
+
const store = new Map();
|
|
71
|
+
(0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 1000);
|
|
72
|
+
const decision = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 1500);
|
|
73
|
+
node_assert_1.default.equal(decision.resetMs, WINDOW_MS - 500);
|
|
74
|
+
});
|
|
75
|
+
it("resets the window once it has elapsed", () => {
|
|
76
|
+
const store = new Map();
|
|
77
|
+
(0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 0);
|
|
78
|
+
(0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 1);
|
|
79
|
+
const denied = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 2);
|
|
80
|
+
const afterReset = (0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, WINDOW_MS + 5);
|
|
81
|
+
node_assert_1.default.equal(denied.allowed, false);
|
|
82
|
+
node_assert_1.default.equal(afterReset.allowed, true);
|
|
83
|
+
node_assert_1.default.equal(afterReset.remaining, 1);
|
|
84
|
+
});
|
|
85
|
+
it("tracks each key independently", () => {
|
|
86
|
+
const store = new Map();
|
|
87
|
+
(0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 0);
|
|
88
|
+
(0, rate_limit_1.hitWindow)(store, "a", LIMIT, WINDOW_MS, 1);
|
|
89
|
+
const otherKey = (0, rate_limit_1.hitWindow)(store, "b", LIMIT, WINDOW_MS, 2);
|
|
90
|
+
node_assert_1.default.equal(otherKey.allowed, true);
|
|
91
|
+
node_assert_1.default.equal(otherKey.remaining, 1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("pruneExpired", () => {
|
|
95
|
+
it("removes only windows whose duration has fully elapsed", () => {
|
|
96
|
+
const store = new Map();
|
|
97
|
+
(0, rate_limit_1.hitWindow)(store, "old", LIMIT, WINDOW_MS, 0);
|
|
98
|
+
(0, rate_limit_1.hitWindow)(store, "fresh", LIMIT, WINDOW_MS, WINDOW_MS);
|
|
99
|
+
(0, rate_limit_1.pruneExpired)(store, WINDOW_MS, WINDOW_MS + 1);
|
|
100
|
+
node_assert_1.default.equal(store.has("old"), false);
|
|
101
|
+
node_assert_1.default.equal(store.has("fresh"), true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe("resolveKey", () => {
|
|
105
|
+
it("uses the custom key function when provided", () => {
|
|
106
|
+
const options = { key: () => "custom" };
|
|
107
|
+
node_assert_1.default.equal((0, rate_limit_1.resolveKey)(makeContext("1.2.3.4"), options), "custom");
|
|
108
|
+
});
|
|
109
|
+
it("falls back to the client IP", () => {
|
|
110
|
+
node_assert_1.default.equal((0, rate_limit_1.resolveKey)(makeContext("1.2.3.4"), {}), "1.2.3.4");
|
|
111
|
+
});
|
|
112
|
+
it("uses a fallback key when the IP is unknown", () => {
|
|
113
|
+
node_assert_1.default.equal((0, rate_limit_1.resolveKey)(makeContext(undefined), {}), "unknown");
|
|
114
|
+
});
|
|
115
|
+
it("falls back to the client IP when the custom key is falsy", () => {
|
|
116
|
+
const options = { key: () => undefined };
|
|
117
|
+
node_assert_1.default.equal((0, rate_limit_1.resolveKey)(makeContext("1.2.3.4"), options), "1.2.3.4");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("decorator", () => {
|
|
121
|
+
it("registers a guard provider at the trailing parameter index", () => {
|
|
122
|
+
class Indexed extends (0, interface_api_1.Controller)("rate-limit-index") {
|
|
123
|
+
handler() { }
|
|
124
|
+
}
|
|
125
|
+
const proto = Indexed.prototype;
|
|
126
|
+
(0, interface_api_1.SetParameterProvider)(proto, "handler", 0, () => "existing");
|
|
127
|
+
(0, rate_limit_1.RateLimit)(LIMIT, WINDOW_MS)(proto, "handler", {
|
|
128
|
+
value: proto.handler,
|
|
129
|
+
});
|
|
130
|
+
const meta = (0, interface_core_1.GetMetadata)(Indexed, interface_api_1.ControllerMeta);
|
|
131
|
+
node_assert_1.default.ok(meta.computed_params.handler[1]?.provider);
|
|
132
|
+
});
|
|
133
|
+
it("passes while under the limit and throws 429 once exceeded", () => {
|
|
134
|
+
class Limited extends (0, interface_api_1.Controller)("rate-limit-behavior") {
|
|
135
|
+
handler() {
|
|
136
|
+
return new interface_api_1.HTTPResult(200, "ok");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
__decorate([
|
|
140
|
+
(0, rate_limit_1.RateLimit)(LIMIT, WINDOW_MS),
|
|
141
|
+
__metadata("design:type", Function),
|
|
142
|
+
__metadata("design:paramtypes", []),
|
|
143
|
+
__metadata("design:returntype", void 0)
|
|
144
|
+
], Limited.prototype, "handler", null);
|
|
145
|
+
const provider = guardProvider(Limited);
|
|
146
|
+
const context = makeContext("9.9.9.9");
|
|
147
|
+
node_assert_1.default.equal(provider(context), undefined);
|
|
148
|
+
node_assert_1.default.equal(provider(context), undefined);
|
|
149
|
+
const error = toHTTPResult(captureThrow(() => provider(context)));
|
|
150
|
+
const headers = error.getHeaders();
|
|
151
|
+
const retryAfter = Number(headers[RETRY_AFTER_HEADER]);
|
|
152
|
+
node_assert_1.default.equal(error.getStatus(), TOO_MANY_REQUESTS);
|
|
153
|
+
node_assert_1.default.equal(headers[RATELIMIT_LIMIT_HEADER], String(LIMIT));
|
|
154
|
+
node_assert_1.default.equal(headers[RATELIMIT_REMAINING_HEADER], "0");
|
|
155
|
+
node_assert_1.default.ok(retryAfter > 0);
|
|
156
|
+
node_assert_1.default.ok(Number(headers[RATELIMIT_RESET_HEADER]) > retryAfter);
|
|
157
|
+
});
|
|
158
|
+
it("emits rate-limit headers on the successful response", () => {
|
|
159
|
+
class Counted extends (0, interface_api_1.Controller)("rate-limit-headers") {
|
|
160
|
+
handler() { }
|
|
161
|
+
}
|
|
162
|
+
__decorate([
|
|
163
|
+
(0, rate_limit_1.RateLimit)(LIMIT, WINDOW_MS),
|
|
164
|
+
__metadata("design:type", Function),
|
|
165
|
+
__metadata("design:paramtypes", []),
|
|
166
|
+
__metadata("design:returntype", void 0)
|
|
167
|
+
], Counted.prototype, "handler", null);
|
|
168
|
+
const provider = guardProvider(Counted);
|
|
169
|
+
const context = makeContext("5.5.5.5");
|
|
170
|
+
node_assert_1.default.equal(provider(context), undefined);
|
|
171
|
+
const headers = context.response.getHeaders();
|
|
172
|
+
node_assert_1.default.equal(headers[RATELIMIT_LIMIT_HEADER], String(LIMIT));
|
|
173
|
+
node_assert_1.default.equal(headers[RATELIMIT_REMAINING_HEADER], String(LIMIT - 1));
|
|
174
|
+
node_assert_1.default.ok(Number(headers[RATELIMIT_RESET_HEADER]) > 0);
|
|
175
|
+
});
|
|
176
|
+
it("keys requests separately so other clients are unaffected", () => {
|
|
177
|
+
class Scoped extends (0, interface_api_1.Controller)("rate-limit-scope") {
|
|
178
|
+
handler() { }
|
|
179
|
+
}
|
|
180
|
+
__decorate([
|
|
181
|
+
(0, rate_limit_1.RateLimit)(LIMIT, WINDOW_MS),
|
|
182
|
+
__metadata("design:type", Function),
|
|
183
|
+
__metadata("design:paramtypes", []),
|
|
184
|
+
__metadata("design:returntype", void 0)
|
|
185
|
+
], Scoped.prototype, "handler", null);
|
|
186
|
+
const provider = guardProvider(Scoped);
|
|
187
|
+
provider(makeContext("1.1.1.1"));
|
|
188
|
+
provider(makeContext("1.1.1.1"));
|
|
189
|
+
node_assert_1.default.equal(provider(makeContext("2.2.2.2")), undefined);
|
|
190
|
+
});
|
|
191
|
+
it("throws at decoration time when placed above its route decorator", () => {
|
|
192
|
+
class Misordered extends (0, interface_api_1.Controller)("rate-limit-misorder") {
|
|
193
|
+
handler() { }
|
|
194
|
+
}
|
|
195
|
+
const proto = Misordered.prototype;
|
|
196
|
+
const descriptor = { value: proto.handler };
|
|
197
|
+
(0, interface_api_1.Get)()(proto, "handler", descriptor);
|
|
198
|
+
node_assert_1.default.throws(() => (0, rate_limit_1.RateLimit)(LIMIT, WINDOW_MS)(proto, "handler", descriptor), /directly beneath/);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
//# sourceMappingURL=rate-limit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../src/tests/rate-limit.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-api-util",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "AntelopeJS utility functions for API request validation and error handling",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"test": "src/antelope.test.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@antelopejs/interface-api": "^0.0.
|
|
42
|
+
"@antelopejs/interface-api": "^0.0.3",
|
|
43
43
|
"@antelopejs/interface-core": "^0.0.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|