@qwickapps/server 1.3.1 → 1.4.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 +157 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +114 -0
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/types.d.ts +19 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
- package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.js +166 -0
- package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
- package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
- package/dist/plugins/auth/config-store.d.ts +11 -0
- package/dist/plugins/auth/config-store.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.js +232 -0
- package/dist/plugins/auth/config-store.js.map +1 -0
- package/dist/plugins/auth/config-store.test.d.ts +7 -0
- package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.test.js +299 -0
- package/dist/plugins/auth/config-store.test.js.map +1 -0
- package/dist/plugins/auth/env-config.d.ts +51 -1
- package/dist/plugins/auth/env-config.d.ts.map +1 -1
- package/dist/plugins/auth/env-config.js +640 -7
- package/dist/plugins/auth/env-config.js.map +1 -1
- package/dist/plugins/auth/index.d.ts +6 -2
- package/dist/plugins/auth/index.d.ts.map +1 -1
- package/dist/plugins/auth/index.js +5 -1
- package/dist/plugins/auth/index.js.map +1 -1
- package/dist/plugins/auth/types.d.ts +106 -0
- package/dist/plugins/auth/types.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +4 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +3 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
- package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
- package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
- package/dist/plugins/rate-limit/cleanup.js +72 -0
- package/dist/plugins/rate-limit/cleanup.js.map +1 -0
- package/dist/plugins/rate-limit/env-config.d.ts +91 -0
- package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
- package/dist/plugins/rate-limit/env-config.js +318 -0
- package/dist/plugins/rate-limit/env-config.js.map +1 -0
- package/dist/plugins/rate-limit/index.d.ts +76 -0
- package/dist/plugins/rate-limit/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/index.js +79 -0
- package/dist/plugins/rate-limit/index.js.map +1 -0
- package/dist/plugins/rate-limit/middleware.d.ts +40 -0
- package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
- package/dist/plugins/rate-limit/middleware.js +169 -0
- package/dist/plugins/rate-limit/middleware.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
- package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
- package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
- package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
- package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/index.js +8 -0
- package/dist/plugins/rate-limit/stores/index.js.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.js +27 -0
- package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
- package/dist/plugins/rate-limit/types.d.ts +265 -0
- package/dist/plugins/rate-limit/types.d.ts.map +1 -0
- package/dist/plugins/rate-limit/types.js +9 -0
- package/dist/plugins/rate-limit/types.js.map +1 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +478 -0
- package/dist-ui/assets/index-D7DoZ9rL.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +141 -0
- package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +2 -0
- package/dist-ui-lib/index.js +3332 -2343
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
- package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
- package/package.json +1 -1
- package/src/core/control-panel.ts +128 -0
- package/src/core/types.ts +17 -0
- package/src/index.ts +38 -0
- package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
- package/src/plugins/auth/adapter-wrapper.ts +205 -0
- package/src/plugins/auth/config-store.test.ts +417 -0
- package/src/plugins/auth/config-store.ts +305 -0
- package/src/plugins/auth/env-config.ts +714 -7
- package/src/plugins/auth/index.ts +22 -1
- package/src/plugins/auth/types.ts +138 -0
- package/src/plugins/index.ts +49 -0
- package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
- package/src/plugins/rate-limit/cleanup.ts +117 -0
- package/src/plugins/rate-limit/env-config.ts +400 -0
- package/src/plugins/rate-limit/index.ts +128 -0
- package/src/plugins/rate-limit/middleware.ts +212 -0
- package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
- package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
- package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
- package/src/plugins/rate-limit/stores/index.ts +8 -0
- package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
- package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
- package/src/plugins/rate-limit/strategies/index.ts +30 -0
- package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
- package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
- package/src/plugins/rate-limit/types.ts +338 -0
- package/ui/src/App.tsx +32 -14
- package/ui/src/api/controlPanelApi.ts +226 -0
- package/ui/src/dashboard/builtInWidgets.tsx +5 -1
- package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
- package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
- package/ui/src/dashboard/widgets/index.ts +2 -0
- package/ui/src/pages/AuthPage.tsx +986 -142
- package/ui/src/pages/IntegrationsPage.tsx +288 -0
- package/ui/src/pages/RateLimitPage.tsx +292 -0
- package/dist-ui/assets/index-BY8OxNgO.js +0 -465
- package/dist-ui/assets/index-BY8OxNgO.js.map +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Middleware
|
|
3
|
+
*
|
|
4
|
+
* Express middleware for automatic rate limiting.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
import type { RequestHandler } from 'express';
|
|
9
|
+
import type { RateLimitMiddlewareOptions } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create rate limit middleware
|
|
12
|
+
*
|
|
13
|
+
* @param options Middleware configuration
|
|
14
|
+
* @returns Express middleware function
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // Basic usage
|
|
19
|
+
* app.use('/api', rateLimitMiddleware());
|
|
20
|
+
*
|
|
21
|
+
* // Custom configuration
|
|
22
|
+
* app.post('/api/chat', rateLimitMiddleware({
|
|
23
|
+
* windowMs: 60000,
|
|
24
|
+
* max: 50,
|
|
25
|
+
* keyGenerator: (req) => `chat:${req.user.id}`,
|
|
26
|
+
* }));
|
|
27
|
+
*
|
|
28
|
+
* // Tiered limits
|
|
29
|
+
* app.use(rateLimitMiddleware({
|
|
30
|
+
* max: (req) => req.user?.tier === 'premium' ? 1000 : 50,
|
|
31
|
+
* }));
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function rateLimitMiddleware(options?: RateLimitMiddlewareOptions): RequestHandler;
|
|
35
|
+
/**
|
|
36
|
+
* Create a rate limit middleware that only checks without incrementing
|
|
37
|
+
* Useful for displaying rate limit status without counting the request
|
|
38
|
+
*/
|
|
39
|
+
export declare function rateLimitStatusMiddleware(options?: RateLimitMiddlewareOptions): RequestHandler;
|
|
40
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/plugins/rate-limit/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,0BAA0B,EAAe,MAAM,YAAY,CAAC;AAwD1E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,cAAc,CAkE5F;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,cAAc,CAkDlG"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Middleware
|
|
3
|
+
*
|
|
4
|
+
* Express middleware for automatic rate limiting.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
import { getRateLimitService } from './rate-limit-service.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default key generator
|
|
11
|
+
* Uses user ID if authenticated, otherwise IP address
|
|
12
|
+
*/
|
|
13
|
+
function defaultKeyGenerator(req) {
|
|
14
|
+
const authReq = req;
|
|
15
|
+
const userId = authReq.auth?.user?.id;
|
|
16
|
+
if (userId) {
|
|
17
|
+
return `user:${userId}`;
|
|
18
|
+
}
|
|
19
|
+
// Fall back to IP address
|
|
20
|
+
const ip = req.ip ||
|
|
21
|
+
req.headers['x-forwarded-for']?.toString().split(',')[0].trim() ||
|
|
22
|
+
req.socket.remoteAddress ||
|
|
23
|
+
'unknown';
|
|
24
|
+
return `ip:${ip}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default handler when rate limit is exceeded
|
|
28
|
+
*/
|
|
29
|
+
function defaultHandler(_req, res, _next, status) {
|
|
30
|
+
res.status(429).json({
|
|
31
|
+
error: 'Too Many Requests',
|
|
32
|
+
message: `Rate limit exceeded. Try again in ${status.retryAfter} seconds.`,
|
|
33
|
+
retryAfter: status.retryAfter,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Set standard rate limit headers on response
|
|
38
|
+
*/
|
|
39
|
+
function setRateLimitHeaders(res, status) {
|
|
40
|
+
// Standard rate limit headers (IETF draft)
|
|
41
|
+
res.setHeader('RateLimit-Limit', status.limit.toString());
|
|
42
|
+
res.setHeader('RateLimit-Remaining', status.remaining.toString());
|
|
43
|
+
res.setHeader('RateLimit-Reset', status.resetAt.toString());
|
|
44
|
+
// Retry-After header (RFC 7231)
|
|
45
|
+
if (status.limited) {
|
|
46
|
+
res.setHeader('Retry-After', status.retryAfter.toString());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create rate limit middleware
|
|
51
|
+
*
|
|
52
|
+
* @param options Middleware configuration
|
|
53
|
+
* @returns Express middleware function
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* // Basic usage
|
|
58
|
+
* app.use('/api', rateLimitMiddleware());
|
|
59
|
+
*
|
|
60
|
+
* // Custom configuration
|
|
61
|
+
* app.post('/api/chat', rateLimitMiddleware({
|
|
62
|
+
* windowMs: 60000,
|
|
63
|
+
* max: 50,
|
|
64
|
+
* keyGenerator: (req) => `chat:${req.user.id}`,
|
|
65
|
+
* }));
|
|
66
|
+
*
|
|
67
|
+
* // Tiered limits
|
|
68
|
+
* app.use(rateLimitMiddleware({
|
|
69
|
+
* max: (req) => req.user?.tier === 'premium' ? 1000 : 50,
|
|
70
|
+
* }));
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function rateLimitMiddleware(options = {}) {
|
|
74
|
+
const { windowMs, max, keyGenerator = defaultKeyGenerator, skip, handler = defaultHandler, strategy, headers = true, keyPrefix = '', } = options;
|
|
75
|
+
return async (req, res, next) => {
|
|
76
|
+
try {
|
|
77
|
+
// Check if should skip
|
|
78
|
+
if (skip) {
|
|
79
|
+
const shouldSkip = await skip(req);
|
|
80
|
+
if (shouldSkip) {
|
|
81
|
+
next();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Generate the rate limit key
|
|
86
|
+
let key = await keyGenerator(req);
|
|
87
|
+
if (keyPrefix) {
|
|
88
|
+
key = `${keyPrefix}:${key}`;
|
|
89
|
+
}
|
|
90
|
+
// Resolve max requests (can be dynamic)
|
|
91
|
+
let maxRequests;
|
|
92
|
+
if (typeof max === 'function') {
|
|
93
|
+
maxRequests = await max(req);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
maxRequests = max;
|
|
97
|
+
}
|
|
98
|
+
// Get the rate limit service
|
|
99
|
+
const service = getRateLimitService();
|
|
100
|
+
// Check and increment the rate limit
|
|
101
|
+
const status = await service.incrementLimit(key, {
|
|
102
|
+
windowMs,
|
|
103
|
+
maxRequests,
|
|
104
|
+
strategy,
|
|
105
|
+
});
|
|
106
|
+
// Set headers if enabled
|
|
107
|
+
if (headers) {
|
|
108
|
+
setRateLimitHeaders(res, status);
|
|
109
|
+
}
|
|
110
|
+
// If rate limited, call handler
|
|
111
|
+
if (status.limited) {
|
|
112
|
+
handler(req, res, next, status);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Continue to next middleware
|
|
116
|
+
next();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// On error, log and allow request (fail open)
|
|
120
|
+
console.error('[RateLimitMiddleware] Error:', error);
|
|
121
|
+
next();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a rate limit middleware that only checks without incrementing
|
|
127
|
+
* Useful for displaying rate limit status without counting the request
|
|
128
|
+
*/
|
|
129
|
+
export function rateLimitStatusMiddleware(options = {}) {
|
|
130
|
+
const { windowMs, max, keyGenerator = defaultKeyGenerator, strategy, headers = true, keyPrefix = '', } = options;
|
|
131
|
+
return async (req, res, next) => {
|
|
132
|
+
try {
|
|
133
|
+
// Generate the rate limit key
|
|
134
|
+
let key = await keyGenerator(req);
|
|
135
|
+
if (keyPrefix) {
|
|
136
|
+
key = `${keyPrefix}:${key}`;
|
|
137
|
+
}
|
|
138
|
+
// Resolve max requests (can be dynamic)
|
|
139
|
+
let maxRequests;
|
|
140
|
+
if (typeof max === 'function') {
|
|
141
|
+
maxRequests = await max(req);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
maxRequests = max;
|
|
145
|
+
}
|
|
146
|
+
// Get the rate limit service
|
|
147
|
+
const service = getRateLimitService();
|
|
148
|
+
// Check without incrementing
|
|
149
|
+
const status = await service.checkLimit(key, {
|
|
150
|
+
windowMs,
|
|
151
|
+
maxRequests,
|
|
152
|
+
strategy,
|
|
153
|
+
increment: false,
|
|
154
|
+
});
|
|
155
|
+
// Set headers if enabled
|
|
156
|
+
if (headers) {
|
|
157
|
+
setRateLimitHeaders(res, status);
|
|
158
|
+
}
|
|
159
|
+
// Always continue (this middleware doesn't block)
|
|
160
|
+
next();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// On error, log and continue
|
|
164
|
+
console.error('[RateLimitStatusMiddleware] Error:', error);
|
|
165
|
+
next();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/plugins/rate-limit/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides rate limiting capabilities for @qwickapps/server.
|
|
5
|
+
* Includes PostgreSQL persistence with RLS, caching, multiple strategies,
|
|
6
|
+
* and Express middleware.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
import type { Plugin } from '../../core/plugin-registry.js';
|
|
11
|
+
import type { RateLimitPluginConfig } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create the Rate Limit plugin
|
|
14
|
+
*
|
|
15
|
+
* @param config Plugin configuration
|
|
16
|
+
* @returns Plugin instance
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import {
|
|
21
|
+
* createGateway,
|
|
22
|
+
* createRateLimitPlugin,
|
|
23
|
+
* postgresRateLimitStore,
|
|
24
|
+
* getPostgres,
|
|
25
|
+
* } from '@qwickapps/server';
|
|
26
|
+
*
|
|
27
|
+
* const gateway = createGateway({
|
|
28
|
+
* plugins: [
|
|
29
|
+
* createRateLimitPlugin({
|
|
30
|
+
* store: postgresRateLimitStore({
|
|
31
|
+
* pool: () => getPostgres().getPool(),
|
|
32
|
+
* }),
|
|
33
|
+
* defaults: {
|
|
34
|
+
* windowMs: 60000,
|
|
35
|
+
* maxRequests: 100,
|
|
36
|
+
* strategy: 'sliding-window',
|
|
37
|
+
* },
|
|
38
|
+
* }),
|
|
39
|
+
* ],
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function createRateLimitPlugin(config: RateLimitPluginConfig): Plugin;
|
|
44
|
+
//# sourceMappingURL=rate-limit-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/rate-limit/rate-limit-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,+BAA+B,CAAC;AAC1F,OAAO,KAAK,EAAE,qBAAqB,EAAqB,MAAM,YAAY,CAAC;AAiB3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAmV3E"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides rate limiting capabilities for @qwickapps/server.
|
|
5
|
+
* Includes PostgreSQL persistence with RLS, caching, multiple strategies,
|
|
6
|
+
* and Express middleware.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
import { RateLimitService, setRateLimitService } from './rate-limit-service.js';
|
|
11
|
+
import { createRateLimitCache } from './stores/cache-store.js';
|
|
12
|
+
import { createCleanupJob } from './cleanup.js';
|
|
13
|
+
/**
|
|
14
|
+
* Create the Rate Limit plugin
|
|
15
|
+
*
|
|
16
|
+
* @param config Plugin configuration
|
|
17
|
+
* @returns Plugin instance
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import {
|
|
22
|
+
* createGateway,
|
|
23
|
+
* createRateLimitPlugin,
|
|
24
|
+
* postgresRateLimitStore,
|
|
25
|
+
* getPostgres,
|
|
26
|
+
* } from '@qwickapps/server';
|
|
27
|
+
*
|
|
28
|
+
* const gateway = createGateway({
|
|
29
|
+
* plugins: [
|
|
30
|
+
* createRateLimitPlugin({
|
|
31
|
+
* store: postgresRateLimitStore({
|
|
32
|
+
* pool: () => getPostgres().getPool(),
|
|
33
|
+
* }),
|
|
34
|
+
* defaults: {
|
|
35
|
+
* windowMs: 60000,
|
|
36
|
+
* maxRequests: 100,
|
|
37
|
+
* strategy: 'sliding-window',
|
|
38
|
+
* },
|
|
39
|
+
* }),
|
|
40
|
+
* ],
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function createRateLimitPlugin(config) {
|
|
45
|
+
const debug = config.debug || false;
|
|
46
|
+
const apiPrefix = config.api?.prefix || '/rate-limit';
|
|
47
|
+
const apiEnabled = config.api?.enabled !== false;
|
|
48
|
+
const uiEnabled = config.ui?.enabled !== false;
|
|
49
|
+
const initialCleanupEnabled = config.cleanup?.enabled !== false;
|
|
50
|
+
const initialCleanupIntervalMs = config.cleanup?.intervalMs || 300000;
|
|
51
|
+
let service = null;
|
|
52
|
+
let cleanupJob = null;
|
|
53
|
+
let cache;
|
|
54
|
+
// Runtime config state (can be modified via API)
|
|
55
|
+
const runtimeConfig = {
|
|
56
|
+
windowMs: config.defaults?.windowMs || 60000,
|
|
57
|
+
maxRequests: config.defaults?.maxRequests || 100,
|
|
58
|
+
strategy: config.defaults?.strategy || 'sliding-window',
|
|
59
|
+
cleanupEnabled: initialCleanupEnabled,
|
|
60
|
+
cleanupIntervalMs: initialCleanupIntervalMs,
|
|
61
|
+
};
|
|
62
|
+
function log(message, data) {
|
|
63
|
+
if (debug) {
|
|
64
|
+
console.log(`[RateLimitPlugin] ${message}`, data || '');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
id: 'rate-limit',
|
|
69
|
+
name: 'Rate Limit',
|
|
70
|
+
version: '1.0.0',
|
|
71
|
+
async onStart(_pluginConfig, registry) {
|
|
72
|
+
log('Starting rate limit plugin');
|
|
73
|
+
// Initialize the store
|
|
74
|
+
await config.store.initialize();
|
|
75
|
+
log('Store initialized');
|
|
76
|
+
// Create cache
|
|
77
|
+
cache = createRateLimitCache(config.cache);
|
|
78
|
+
log('Cache created', { type: cache.name });
|
|
79
|
+
// Create service
|
|
80
|
+
service = new RateLimitService({
|
|
81
|
+
store: config.store,
|
|
82
|
+
cache,
|
|
83
|
+
defaults: config.defaults,
|
|
84
|
+
});
|
|
85
|
+
setRateLimitService(service);
|
|
86
|
+
log('Service created');
|
|
87
|
+
// Start cleanup job if enabled
|
|
88
|
+
if (runtimeConfig.cleanupEnabled) {
|
|
89
|
+
cleanupJob = createCleanupJob({
|
|
90
|
+
store: config.store,
|
|
91
|
+
intervalMs: runtimeConfig.cleanupIntervalMs,
|
|
92
|
+
debug,
|
|
93
|
+
});
|
|
94
|
+
cleanupJob.start();
|
|
95
|
+
log('Cleanup job started', { intervalMs: runtimeConfig.cleanupIntervalMs });
|
|
96
|
+
}
|
|
97
|
+
// Register health check
|
|
98
|
+
registry.registerHealthCheck({
|
|
99
|
+
name: 'rate-limit',
|
|
100
|
+
type: 'custom',
|
|
101
|
+
check: async () => {
|
|
102
|
+
try {
|
|
103
|
+
const defaults = service?.getDefaults();
|
|
104
|
+
return {
|
|
105
|
+
healthy: service !== null,
|
|
106
|
+
details: {
|
|
107
|
+
store: config.store.name,
|
|
108
|
+
cache: cache.name,
|
|
109
|
+
cacheAvailable: cache.isAvailable(),
|
|
110
|
+
defaults,
|
|
111
|
+
cleanupEnabled: runtimeConfig.cleanupEnabled,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { healthy: false };
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
// Register UI menu item if enabled
|
|
121
|
+
if (uiEnabled) {
|
|
122
|
+
registry.addMenuItem({
|
|
123
|
+
pluginId: 'rate-limit',
|
|
124
|
+
id: 'rate-limit:sidebar',
|
|
125
|
+
label: 'Rate Limits',
|
|
126
|
+
icon: 'speed',
|
|
127
|
+
route: '/rate-limits',
|
|
128
|
+
order: 40, // After Entitlements (35)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Add API routes if enabled
|
|
132
|
+
if (apiEnabled) {
|
|
133
|
+
// GET /rate-limit/config - Get current config
|
|
134
|
+
registry.addRoute({
|
|
135
|
+
method: 'get',
|
|
136
|
+
path: '/rate-limit/config',
|
|
137
|
+
pluginId: 'rate-limit',
|
|
138
|
+
handler: async (_req, res) => {
|
|
139
|
+
try {
|
|
140
|
+
const defaults = service?.getDefaults();
|
|
141
|
+
res.json({
|
|
142
|
+
...runtimeConfig,
|
|
143
|
+
...defaults,
|
|
144
|
+
store: config.store.name,
|
|
145
|
+
cache: cache.name,
|
|
146
|
+
cacheAvailable: cache.isAvailable(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('[RateLimitPlugin] Config error:', error);
|
|
151
|
+
res.status(500).json({ error: 'Failed to get config' });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
// PUT /rate-limit/config - Update config at runtime
|
|
156
|
+
registry.addRoute({
|
|
157
|
+
method: 'put',
|
|
158
|
+
path: '/rate-limit/config',
|
|
159
|
+
pluginId: 'rate-limit',
|
|
160
|
+
handler: async (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
const updates = req.body;
|
|
163
|
+
const validStrategies = ['sliding-window', 'fixed-window', 'token-bucket'];
|
|
164
|
+
// Validate and apply updates
|
|
165
|
+
if (updates.windowMs !== undefined) {
|
|
166
|
+
if (typeof updates.windowMs !== 'number' || updates.windowMs <= 0) {
|
|
167
|
+
return res.status(400).json({ error: 'windowMs must be a positive number' });
|
|
168
|
+
}
|
|
169
|
+
runtimeConfig.windowMs = updates.windowMs;
|
|
170
|
+
}
|
|
171
|
+
if (updates.maxRequests !== undefined) {
|
|
172
|
+
if (typeof updates.maxRequests !== 'number' || updates.maxRequests <= 0) {
|
|
173
|
+
return res.status(400).json({ error: 'maxRequests must be a positive number' });
|
|
174
|
+
}
|
|
175
|
+
runtimeConfig.maxRequests = updates.maxRequests;
|
|
176
|
+
}
|
|
177
|
+
if (updates.strategy !== undefined) {
|
|
178
|
+
if (!validStrategies.includes(updates.strategy)) {
|
|
179
|
+
return res.status(400).json({
|
|
180
|
+
error: `Invalid strategy. Must be one of: ${validStrategies.join(', ')}`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
runtimeConfig.strategy = updates.strategy;
|
|
184
|
+
}
|
|
185
|
+
// Update service defaults
|
|
186
|
+
if (service) {
|
|
187
|
+
service.setDefaults({
|
|
188
|
+
windowMs: runtimeConfig.windowMs,
|
|
189
|
+
maxRequests: runtimeConfig.maxRequests,
|
|
190
|
+
strategy: runtimeConfig.strategy,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// Handle cleanup config changes
|
|
194
|
+
if (updates.cleanupEnabled !== undefined) {
|
|
195
|
+
runtimeConfig.cleanupEnabled = updates.cleanupEnabled;
|
|
196
|
+
if (updates.cleanupEnabled && !cleanupJob) {
|
|
197
|
+
// Start cleanup job
|
|
198
|
+
cleanupJob = createCleanupJob({
|
|
199
|
+
store: config.store,
|
|
200
|
+
intervalMs: runtimeConfig.cleanupIntervalMs,
|
|
201
|
+
debug,
|
|
202
|
+
});
|
|
203
|
+
cleanupJob.start();
|
|
204
|
+
}
|
|
205
|
+
else if (!updates.cleanupEnabled && cleanupJob) {
|
|
206
|
+
// Stop cleanup job
|
|
207
|
+
cleanupJob.stop();
|
|
208
|
+
cleanupJob = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (updates.cleanupIntervalMs !== undefined) {
|
|
212
|
+
if (typeof updates.cleanupIntervalMs !== 'number' || updates.cleanupIntervalMs <= 0) {
|
|
213
|
+
return res.status(400).json({ error: 'cleanupIntervalMs must be a positive number' });
|
|
214
|
+
}
|
|
215
|
+
runtimeConfig.cleanupIntervalMs = updates.cleanupIntervalMs;
|
|
216
|
+
// Restart cleanup job with new interval if running
|
|
217
|
+
if (cleanupJob && runtimeConfig.cleanupEnabled) {
|
|
218
|
+
cleanupJob.stop();
|
|
219
|
+
cleanupJob = createCleanupJob({
|
|
220
|
+
store: config.store,
|
|
221
|
+
intervalMs: runtimeConfig.cleanupIntervalMs,
|
|
222
|
+
debug,
|
|
223
|
+
});
|
|
224
|
+
cleanupJob.start();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
log('Config updated', { ...runtimeConfig });
|
|
228
|
+
res.json({
|
|
229
|
+
success: true,
|
|
230
|
+
config: {
|
|
231
|
+
...runtimeConfig,
|
|
232
|
+
store: config.store.name,
|
|
233
|
+
cache: cache.name,
|
|
234
|
+
cacheAvailable: cache.isAvailable(),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error('[RateLimitPlugin] Config update error:', error);
|
|
240
|
+
res.status(500).json({ error: 'Failed to update config' });
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
// GET /rate-limit/status - Get rate limit status for current user
|
|
245
|
+
registry.addRoute({
|
|
246
|
+
method: 'get',
|
|
247
|
+
path: `${apiPrefix}/status`,
|
|
248
|
+
pluginId: 'rate-limit',
|
|
249
|
+
handler: async (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
const authReq = req;
|
|
252
|
+
const userId = authReq.auth?.user?.id;
|
|
253
|
+
// Generate key (same logic as middleware)
|
|
254
|
+
let key;
|
|
255
|
+
if (userId) {
|
|
256
|
+
key = `user:${userId}`;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const ip = req.ip ||
|
|
260
|
+
req.headers['x-forwarded-for']?.toString().split(',')[0].trim() ||
|
|
261
|
+
req.socket.remoteAddress ||
|
|
262
|
+
'unknown';
|
|
263
|
+
key = `ip:${ip}`;
|
|
264
|
+
}
|
|
265
|
+
// Get status without incrementing
|
|
266
|
+
const status = await service.checkLimit(key, {
|
|
267
|
+
userId,
|
|
268
|
+
increment: false,
|
|
269
|
+
});
|
|
270
|
+
res.json({
|
|
271
|
+
key,
|
|
272
|
+
...status,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error('[RateLimitPlugin] Status error:', error);
|
|
277
|
+
res.status(500).json({ error: 'Failed to get rate limit status' });
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
// GET /rate-limit/status/:key - Get rate limit status for a specific key
|
|
282
|
+
registry.addRoute({
|
|
283
|
+
method: 'get',
|
|
284
|
+
path: `${apiPrefix}/status/:key`,
|
|
285
|
+
pluginId: 'rate-limit',
|
|
286
|
+
handler: async (req, res) => {
|
|
287
|
+
try {
|
|
288
|
+
const authReq = req;
|
|
289
|
+
const userId = authReq.auth?.user?.id;
|
|
290
|
+
const key = req.params.key;
|
|
291
|
+
if (!key) {
|
|
292
|
+
return res.status(400).json({ error: 'Key is required' });
|
|
293
|
+
}
|
|
294
|
+
// Get status without incrementing
|
|
295
|
+
const status = await service.checkLimit(key, {
|
|
296
|
+
userId,
|
|
297
|
+
increment: false,
|
|
298
|
+
});
|
|
299
|
+
res.json({
|
|
300
|
+
key,
|
|
301
|
+
...status,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
console.error('[RateLimitPlugin] Status error:', error);
|
|
306
|
+
res.status(500).json({ error: 'Failed to get rate limit status' });
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
// DELETE /rate-limit/clear/:key - Clear a rate limit (requires auth)
|
|
311
|
+
registry.addRoute({
|
|
312
|
+
method: 'delete',
|
|
313
|
+
path: `${apiPrefix}/clear/:key`,
|
|
314
|
+
pluginId: 'rate-limit',
|
|
315
|
+
handler: async (req, res) => {
|
|
316
|
+
try {
|
|
317
|
+
const authReq = req;
|
|
318
|
+
const userId = authReq.auth?.user?.id;
|
|
319
|
+
if (!userId) {
|
|
320
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
321
|
+
}
|
|
322
|
+
const key = req.params.key;
|
|
323
|
+
if (!key) {
|
|
324
|
+
return res.status(400).json({ error: 'Key is required' });
|
|
325
|
+
}
|
|
326
|
+
await service.clearLimit(key, userId);
|
|
327
|
+
res.status(204).send();
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error('[RateLimitPlugin] Clear error:', error);
|
|
331
|
+
res.status(500).json({ error: 'Failed to clear rate limit' });
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
log('Rate limit plugin started');
|
|
337
|
+
},
|
|
338
|
+
async onStop() {
|
|
339
|
+
log('Stopping rate limit plugin');
|
|
340
|
+
// Stop cleanup job
|
|
341
|
+
if (cleanupJob) {
|
|
342
|
+
cleanupJob.stop();
|
|
343
|
+
cleanupJob = null;
|
|
344
|
+
}
|
|
345
|
+
// Clear service reference
|
|
346
|
+
setRateLimitService(null);
|
|
347
|
+
service = null;
|
|
348
|
+
// Shutdown store
|
|
349
|
+
await config.store.shutdown();
|
|
350
|
+
log('Rate limit plugin stopped');
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=rate-limit-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-plugin.js","sourceRoot":"","sources":["../../../src/plugins/rate-limit/rate-limit-plugin.ts"],"names":[],"mappings":"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"}
|