@karbonjs/api 0.2.4 → 0.3.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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/proxy.d.ts +66 -0
- package/dist/server/proxy.d.ts.map +1 -0
- package/dist/server/proxy.js +170 -0
- package/dist/server/proxy.js.map +1 -0
- package/dist/server/rate-limiter.d.ts +43 -0
- package/dist/server/rate-limiter.d.ts.map +1 -0
- package/dist/server/rate-limiter.js +90 -0
- package/dist/server/rate-limiter.js.map +1 -0
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createClientApi } from './client/client.js';
|
|
2
2
|
export type { ClientApiConfig } from './client/client.js';
|
|
3
3
|
export { createServerApi } from './server/server.js';
|
|
4
|
+
export { createProxy, type ProxyConfig } from './server/proxy.js';
|
|
5
|
+
export { createRateLimiter, type RateLimitRule, type RateLimitRules, type RateLimitResult, type RateLimiter } from './server/rate-limiter.js';
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAmF,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createServerApi } from './server.js';
|
|
2
|
+
export { createProxy, type ProxyConfig } from './proxy.js';
|
|
3
|
+
export { createRateLimiter, type RateLimitRule, type RateLimitRules, type RateLimitResult, type RateLimiter } from './rate-limiter.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAmF,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable API proxy for SvelteKit catch-all routes.
|
|
3
|
+
*
|
|
4
|
+
* Generates GET/POST/PUT/PATCH/DELETE handlers that proxy
|
|
5
|
+
* requests to a backend server with security built-in:
|
|
6
|
+
* - Path sanitization (blocks `../`, `//`)
|
|
7
|
+
* - Blocked path prefixes (e.g. `internal`)
|
|
8
|
+
* - CSRF protection (Origin vs Host check)
|
|
9
|
+
* - Rate limiting (sliding window, per IP)
|
|
10
|
+
* - Body size limit
|
|
11
|
+
* - Request/response streaming (no memory buffering)
|
|
12
|
+
* - Cookie & auth header forwarding
|
|
13
|
+
* - X-Forwarded-For client IP forwarding
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // src/routes/api/[...path]/+server.ts
|
|
18
|
+
* import { createProxy } from '@karbonjs/api'
|
|
19
|
+
*
|
|
20
|
+
* export const { GET, POST, PUT, PATCH, DELETE } = createProxy({
|
|
21
|
+
* backend: 'http://localhost:8080',
|
|
22
|
+
* prefix: '/api',
|
|
23
|
+
* blockedPrefixes: ['internal'],
|
|
24
|
+
* csrf: true,
|
|
25
|
+
* rateLimit: {
|
|
26
|
+
* 'auth/login': { max: 20, windowSec: 60 },
|
|
27
|
+
* '*': { max: 200, windowSec: 60 },
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { type RateLimitRules } from './rate-limiter.js';
|
|
33
|
+
export interface ProxyConfig {
|
|
34
|
+
/** Backend URL (e.g. `http://localhost:8080`) */
|
|
35
|
+
backend: string;
|
|
36
|
+
/** URL prefix to prepend to proxied path (default: `/api`) */
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/** Max request body size in bytes (default: 10 MB) */
|
|
39
|
+
maxBodySize?: number;
|
|
40
|
+
/** Path prefixes to block — returns 404 (default: `['internal']`) */
|
|
41
|
+
blockedPrefixes?: string[];
|
|
42
|
+
/** Enable CSRF check on mutating requests (default: `true`) */
|
|
43
|
+
csrf?: boolean;
|
|
44
|
+
/** Rate limit rules per route pattern. Use `'*'` for default. Set `false` to disable. */
|
|
45
|
+
rateLimit?: RateLimitRules | false;
|
|
46
|
+
}
|
|
47
|
+
/** Minimal SvelteKit RequestEvent shape — keeps karbonjs framework-agnostic */
|
|
48
|
+
interface RequestEvent {
|
|
49
|
+
request: Request;
|
|
50
|
+
params: Record<string, string>;
|
|
51
|
+
url: URL;
|
|
52
|
+
getClientAddress: () => string;
|
|
53
|
+
}
|
|
54
|
+
type Handler = (event: RequestEvent) => Promise<Response>;
|
|
55
|
+
/**
|
|
56
|
+
* Create a proxy handler set for SvelteKit `+server.ts` catch-all routes.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createProxy(config: ProxyConfig): {
|
|
59
|
+
GET: Handler;
|
|
60
|
+
POST: Handler;
|
|
61
|
+
PUT: Handler;
|
|
62
|
+
PATCH: Handler;
|
|
63
|
+
DELETE: Handler;
|
|
64
|
+
};
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/server/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAqB,KAAK,cAAc,EAAoB,MAAM,mBAAmB,CAAA;AAE5F,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,yFAAyF;IACzF,SAAS,CAAC,EAAE,cAAc,GAAG,KAAK,CAAA;CACnC;AAED,+EAA+E;AAC/E,UAAU,YAAY;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,GAAG,EAAE,GAAG,CAAA;IACR,gBAAgB,EAAE,MAAM,MAAM,CAAA;CAC/B;AAED,KAAK,OAAO,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AASzD;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW;;;;;;EAiJ9C"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable API proxy for SvelteKit catch-all routes.
|
|
3
|
+
*
|
|
4
|
+
* Generates GET/POST/PUT/PATCH/DELETE handlers that proxy
|
|
5
|
+
* requests to a backend server with security built-in:
|
|
6
|
+
* - Path sanitization (blocks `../`, `//`)
|
|
7
|
+
* - Blocked path prefixes (e.g. `internal`)
|
|
8
|
+
* - CSRF protection (Origin vs Host check)
|
|
9
|
+
* - Rate limiting (sliding window, per IP)
|
|
10
|
+
* - Body size limit
|
|
11
|
+
* - Request/response streaming (no memory buffering)
|
|
12
|
+
* - Cookie & auth header forwarding
|
|
13
|
+
* - X-Forwarded-For client IP forwarding
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // src/routes/api/[...path]/+server.ts
|
|
18
|
+
* import { createProxy } from '@karbonjs/api'
|
|
19
|
+
*
|
|
20
|
+
* export const { GET, POST, PUT, PATCH, DELETE } = createProxy({
|
|
21
|
+
* backend: 'http://localhost:8080',
|
|
22
|
+
* prefix: '/api',
|
|
23
|
+
* blockedPrefixes: ['internal'],
|
|
24
|
+
* csrf: true,
|
|
25
|
+
* rateLimit: {
|
|
26
|
+
* 'auth/login': { max: 20, windowSec: 60 },
|
|
27
|
+
* '*': { max: 200, windowSec: 60 },
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { createRateLimiter } from './rate-limiter.js';
|
|
33
|
+
function jsonResponse(status, message, extra) {
|
|
34
|
+
return new Response(JSON.stringify({ success: false, message }), {
|
|
35
|
+
status,
|
|
36
|
+
headers: { 'content-type': 'application/json', ...extra },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a proxy handler set for SvelteKit `+server.ts` catch-all routes.
|
|
41
|
+
*/
|
|
42
|
+
export function createProxy(config) {
|
|
43
|
+
const { backend, prefix = '/api', maxBodySize = 10 * 1024 * 1024, blockedPrefixes = ['internal'], csrf = true, rateLimit: rateLimitConfig, } = config;
|
|
44
|
+
// Validate backend URL
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(backend);
|
|
47
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
48
|
+
throw new Error('Backend URL must use http or https');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`Invalid backend URL: ${backend}`);
|
|
53
|
+
}
|
|
54
|
+
// Init rate limiter (unless disabled)
|
|
55
|
+
let limiter = null;
|
|
56
|
+
if (rateLimitConfig !== false) {
|
|
57
|
+
limiter = createRateLimiter(rateLimitConfig ?? undefined);
|
|
58
|
+
}
|
|
59
|
+
const handler = async (event) => {
|
|
60
|
+
const { request, params, url } = event;
|
|
61
|
+
const rawPath = params.path ?? '';
|
|
62
|
+
// 1. Sanitize path
|
|
63
|
+
if (rawPath.includes('..') || rawPath.includes('//')) {
|
|
64
|
+
return jsonResponse(400, 'Invalid path');
|
|
65
|
+
}
|
|
66
|
+
// 2. Block forbidden prefixes
|
|
67
|
+
const firstSegment = rawPath.split('/')[0];
|
|
68
|
+
if (blockedPrefixes.includes(firstSegment)) {
|
|
69
|
+
return jsonResponse(404, 'Not found');
|
|
70
|
+
}
|
|
71
|
+
const path = `${prefix}/${rawPath}${url.search}`;
|
|
72
|
+
// 3. Client IP
|
|
73
|
+
let clientIp = '127.0.0.1';
|
|
74
|
+
try {
|
|
75
|
+
clientIp = event.getClientAddress();
|
|
76
|
+
}
|
|
77
|
+
catch { /* dev mode */ }
|
|
78
|
+
// 4. Rate limiting
|
|
79
|
+
if (limiter) {
|
|
80
|
+
const rl = limiter.check(clientIp, path);
|
|
81
|
+
if (!rl.allowed) {
|
|
82
|
+
return jsonResponse(429, 'Too many requests', {
|
|
83
|
+
'retry-after': String(rl.retryAfterSec ?? 60),
|
|
84
|
+
'x-ratelimit-limit': String(rl.limit),
|
|
85
|
+
'x-ratelimit-remaining': '0',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 5. CSRF — mutating requests must come from same origin
|
|
90
|
+
if (csrf && request.method !== 'GET' && request.method !== 'HEAD') {
|
|
91
|
+
const origin = request.headers.get('origin');
|
|
92
|
+
const host = request.headers.get('host');
|
|
93
|
+
if (origin && host) {
|
|
94
|
+
try {
|
|
95
|
+
const originHost = new URL(origin).host;
|
|
96
|
+
if (originHost !== host) {
|
|
97
|
+
return jsonResponse(403, 'Forbidden');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return jsonResponse(403, 'Forbidden');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// 6. Build headers
|
|
106
|
+
const headers = new Headers();
|
|
107
|
+
headers.set('content-type', request.headers.get('content-type') || 'application/json');
|
|
108
|
+
const auth = request.headers.get('authorization');
|
|
109
|
+
if (auth)
|
|
110
|
+
headers.set('authorization', auth);
|
|
111
|
+
const cookies = request.headers.get('cookie');
|
|
112
|
+
if (cookies)
|
|
113
|
+
headers.set('cookie', cookies);
|
|
114
|
+
headers.set('x-forwarded-for', clientIp);
|
|
115
|
+
// 7. Stream body with size check
|
|
116
|
+
let body = null;
|
|
117
|
+
if (request.method !== 'GET' && request.method !== 'HEAD' && request.body) {
|
|
118
|
+
const contentLength = request.headers.get('content-length');
|
|
119
|
+
if (contentLength && parseInt(contentLength) > maxBodySize) {
|
|
120
|
+
return jsonResponse(413, 'Request too large');
|
|
121
|
+
}
|
|
122
|
+
body = request.body;
|
|
123
|
+
}
|
|
124
|
+
// 8. Proxy to backend
|
|
125
|
+
let apiRes;
|
|
126
|
+
try {
|
|
127
|
+
apiRes = await fetch(`${backend}${path}`, {
|
|
128
|
+
method: request.method,
|
|
129
|
+
headers,
|
|
130
|
+
body,
|
|
131
|
+
// @ts-expect-error — duplex required for streaming body in Node
|
|
132
|
+
duplex: body ? 'half' : undefined,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return jsonResponse(502, 'Backend unreachable');
|
|
137
|
+
}
|
|
138
|
+
// 9. Build response — stream back
|
|
139
|
+
const responseHeaders = new Headers();
|
|
140
|
+
responseHeaders.set('content-type', apiRes.headers.get('content-type') || 'application/json');
|
|
141
|
+
// Rate limit info
|
|
142
|
+
if (limiter) {
|
|
143
|
+
const rl = limiter.check(clientIp, path);
|
|
144
|
+
responseHeaders.set('x-ratelimit-limit', String(rl.limit));
|
|
145
|
+
responseHeaders.set('x-ratelimit-remaining', String(rl.remaining));
|
|
146
|
+
}
|
|
147
|
+
// Forward Set-Cookie
|
|
148
|
+
const setCookies = apiRes.headers.getSetCookie?.() ?? [];
|
|
149
|
+
for (const cookie of setCookies) {
|
|
150
|
+
responseHeaders.append('set-cookie', cookie);
|
|
151
|
+
}
|
|
152
|
+
if (setCookies.length === 0) {
|
|
153
|
+
const raw = apiRes.headers.get('set-cookie');
|
|
154
|
+
if (raw)
|
|
155
|
+
responseHeaders.set('set-cookie', raw);
|
|
156
|
+
}
|
|
157
|
+
return new Response(apiRes.body, {
|
|
158
|
+
status: apiRes.status,
|
|
159
|
+
headers: responseHeaders,
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
GET: handler,
|
|
164
|
+
POST: handler,
|
|
165
|
+
PUT: handler,
|
|
166
|
+
PATCH: handler,
|
|
167
|
+
DELETE: handler,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/server/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory sliding window rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* Tracks request timestamps per IP + route pattern.
|
|
5
|
+
* Auto-purges expired entries every 5 minutes.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const limiter = createRateLimiter({
|
|
10
|
+
* 'auth/login': { max: 20, windowSec: 60 },
|
|
11
|
+
* '*': { max: 200, windowSec: 60 },
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* const result = limiter.check('192.168.1.1', '/api/auth/login')
|
|
15
|
+
* if (!result.allowed) // return 429
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface RateLimitRule {
|
|
19
|
+
max: number;
|
|
20
|
+
windowSec: number;
|
|
21
|
+
}
|
|
22
|
+
export type RateLimitRules = Record<string, RateLimitRule>;
|
|
23
|
+
export interface RateLimitResult {
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
remaining: number;
|
|
26
|
+
limit: number;
|
|
27
|
+
retryAfterSec?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface RateLimiter {
|
|
30
|
+
/** Check if request is allowed. Records the timestamp if allowed. */
|
|
31
|
+
check(ip: string, path: string): RateLimitResult;
|
|
32
|
+
/** Reset all entries (useful for testing) */
|
|
33
|
+
reset(): void;
|
|
34
|
+
/** Stop the cleanup interval */
|
|
35
|
+
destroy(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a rate limiter with custom rules.
|
|
39
|
+
* Rules are matched by path prefix (after stripping `/api/`).
|
|
40
|
+
* Use `'*'` as the fallback rule.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createRateLimiter(rules?: RateLimitRules): RateLimiter;
|
|
43
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/server/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;AAE1D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAUD,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAAA;IAChD,6CAA6C;IAC7C,KAAK,IAAI,IAAI,CAAA;IACb,gCAAgC;IAChC,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,cAA8B,GAAG,WAAW,CAqEpF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory sliding window rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* Tracks request timestamps per IP + route pattern.
|
|
5
|
+
* Auto-purges expired entries every 5 minutes.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const limiter = createRateLimiter({
|
|
10
|
+
* 'auth/login': { max: 20, windowSec: 60 },
|
|
11
|
+
* '*': { max: 200, windowSec: 60 },
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* const result = limiter.check('192.168.1.1', '/api/auth/login')
|
|
15
|
+
* if (!result.allowed) // return 429
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_RULES = {
|
|
19
|
+
'*': { max: 200, windowSec: 60 },
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Create a rate limiter with custom rules.
|
|
23
|
+
* Rules are matched by path prefix (after stripping `/api/`).
|
|
24
|
+
* Use `'*'` as the fallback rule.
|
|
25
|
+
*/
|
|
26
|
+
export function createRateLimiter(rules = DEFAULT_RULES) {
|
|
27
|
+
const mergedRules = { ...DEFAULT_RULES, ...rules };
|
|
28
|
+
const store = new Map();
|
|
29
|
+
// Cleanup expired entries every 5 min
|
|
30
|
+
const cleanupTimer = setInterval(() => {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
for (const [ip, routes] of store) {
|
|
33
|
+
for (const [route, entry] of routes) {
|
|
34
|
+
const rule = getRule(route);
|
|
35
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < rule.windowSec * 1000);
|
|
36
|
+
if (entry.timestamps.length === 0)
|
|
37
|
+
routes.delete(route);
|
|
38
|
+
}
|
|
39
|
+
if (routes.size === 0)
|
|
40
|
+
store.delete(ip);
|
|
41
|
+
}
|
|
42
|
+
}, 5 * 60 * 1000);
|
|
43
|
+
// Don't block process exit
|
|
44
|
+
if (cleanupTimer && typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
|
|
45
|
+
cleanupTimer.unref();
|
|
46
|
+
}
|
|
47
|
+
function matchRoute(path) {
|
|
48
|
+
const route = path.replace(/^\/api\//, '');
|
|
49
|
+
for (const pattern of Object.keys(mergedRules)) {
|
|
50
|
+
if (pattern === '*')
|
|
51
|
+
continue;
|
|
52
|
+
if (route.startsWith(pattern))
|
|
53
|
+
return pattern;
|
|
54
|
+
}
|
|
55
|
+
return '*';
|
|
56
|
+
}
|
|
57
|
+
function getRule(routeKey) {
|
|
58
|
+
return mergedRules[routeKey] ?? mergedRules['*'] ?? DEFAULT_RULES['*'];
|
|
59
|
+
}
|
|
60
|
+
function check(ip, path) {
|
|
61
|
+
const routeKey = matchRoute(path);
|
|
62
|
+
const rule = getRule(routeKey);
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const windowMs = rule.windowSec * 1000;
|
|
65
|
+
if (!store.has(ip))
|
|
66
|
+
store.set(ip, new Map());
|
|
67
|
+
const ipMap = store.get(ip);
|
|
68
|
+
if (!ipMap.has(routeKey))
|
|
69
|
+
ipMap.set(routeKey, { timestamps: [] });
|
|
70
|
+
const entry = ipMap.get(routeKey);
|
|
71
|
+
// Sliding window
|
|
72
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < windowMs);
|
|
73
|
+
if (entry.timestamps.length >= rule.max) {
|
|
74
|
+
const oldest = entry.timestamps[0];
|
|
75
|
+
const retryAfterSec = Math.ceil((oldest + windowMs - now) / 1000);
|
|
76
|
+
return { allowed: false, remaining: 0, limit: rule.max, retryAfterSec };
|
|
77
|
+
}
|
|
78
|
+
entry.timestamps.push(now);
|
|
79
|
+
return { allowed: true, remaining: rule.max - entry.timestamps.length, limit: rule.max };
|
|
80
|
+
}
|
|
81
|
+
function reset() {
|
|
82
|
+
store.clear();
|
|
83
|
+
}
|
|
84
|
+
function destroy() {
|
|
85
|
+
clearInterval(cleanupTimer);
|
|
86
|
+
store.clear();
|
|
87
|
+
}
|
|
88
|
+
return { check, reset, destroy };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/server/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karbonjs/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Type-safe API client for Karbon backends with SSR and client support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./server": {
|
|
14
|
+
"types": "./dist/server/index.d.ts",
|
|
15
|
+
"import": "./dist/server/index.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"dist"
|
|
16
20
|
],
|
|
17
21
|
"dependencies": {
|
|
18
|
-
"@karbonjs/types": "0.
|
|
22
|
+
"@karbonjs/types": "0.3.0"
|
|
19
23
|
},
|
|
20
24
|
"publishConfig": {
|
|
21
25
|
"access": "public"
|