@creator.co/wapi 1.10.0 → 1.10.1

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@creator.co/wapi",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@creator.co/wapi",
9
- "version": "1.10.0",
9
+ "version": "1.10.1",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@aws-sdk/client-dynamodb": "^3.730.0",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creator.co/wapi",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,6 +30,12 @@ export default class Proxy {
30
30
  * @returns None
31
31
  */
32
32
  private readonly serverlessHandler;
33
+ /**
34
+ * Route resolver used to identify per-route rate limit configuration.
35
+ * @private
36
+ * @readonly
37
+ */
38
+ private readonly routeResolver;
33
39
  /**
34
40
  * Represents a listener for an HTTP server.
35
41
  * @private
@@ -70,6 +76,15 @@ export default class Proxy {
70
76
  * @returns None
71
77
  */
72
78
  private installRoutes;
79
+ /**
80
+ * Creates rate limiting middleware from a per-route {@link RateLimitConfig},
81
+ * inheriting the global Redis store configuration when available so all
82
+ * rate-limit counters share the same Redis connection.
83
+ * @param {RateLimitConfig} config - The per-route rate limit configuration
84
+ * @returns {express.RequestHandler} Express middleware for rate limiting
85
+ * @private
86
+ */
87
+ private createRouteRateLimitMiddleware;
73
88
  /**
74
89
  * Creates rate limiting middleware based on the provided configuration.
75
90
  * @param {GlobalRateLimitConfig} config - The rate limit configuration
@@ -18,6 +18,7 @@ import HealthHandler from './HealthHandler.js';
18
18
  import Globals from '../../../Globals.js';
19
19
  import Logger from '../../../Logger/Logger.js';
20
20
  import Utils from '../../../Util/Utils.js';
21
+ import RouteResolver from '../../RouteResolver.js';
21
22
  /* Get package.json version from Wapi on ESM */
22
23
  const { version: appVersion } = JSON.parse(fs.readFileSync('package.json').toString());
23
24
  /**
@@ -34,6 +35,7 @@ export default class Proxy {
34
35
  this.stopping = false;
35
36
  this.config = config;
36
37
  this.serverlessHandler = serverlessHandler;
38
+ this.routeResolver = new RouteResolver(this.config);
37
39
  this.logger = new Logger({ logLevel: 'INFO' }, 'proxy-container');
38
40
  this.app = express();
39
41
  // Trust the first proxy hop so req.ip resolves to the real client IP
@@ -57,7 +59,16 @@ export default class Proxy {
57
59
  // Apply global rate limiting if configured
58
60
  if (this.config.rateLimit && this.config.rateLimit.enabled !== false) {
59
61
  this.logger.info('[Proxy] - [RATE-LIMIT] - Global rate limiting enabled');
60
- const rateLimitMiddleware = this.createRateLimitMiddleware(this.config.rateLimit);
62
+ // Augment the skip function to bypass the global limit for routes that
63
+ // have their own rateLimit config (false = disable, or a RateLimitConfig).
64
+ const globalConfig = Object.assign(Object.assign({}, this.config.rateLimit), { skip: (req) => {
65
+ var _a, _b, _c;
66
+ const route = this.routeResolver.resolveRoute(req.method, req.path);
67
+ if (route && route.rateLimit !== undefined)
68
+ return true;
69
+ return (_c = (_b = (_a = this.config.rateLimit).skip) === null || _b === void 0 ? void 0 : _b.call(_a, req)) !== null && _c !== void 0 ? _c : false;
70
+ } });
71
+ const rateLimitMiddleware = this.createRateLimitMiddleware(globalConfig);
61
72
  this.app.use(rateLimitMiddleware);
62
73
  }
63
74
  // //This supposedly fix some 502 codes where nodejs socket would hang during
@@ -151,11 +162,79 @@ export default class Proxy {
151
162
  this.app
152
163
  .route(this.config.healthCheckRoute || Globals.Listener_HTTP_DefaultHealthCheckRoute)
153
164
  .get(HealthHandler);
165
+ // Register individual routes that declare their own rateLimit config.
166
+ // These are installed BEFORE the wildcard so Express matches them first,
167
+ // giving each route an independent rate limit bucket.
168
+ for (const route of this.config.routes) {
169
+ if (!route.rateLimit)
170
+ continue;
171
+ const rlMiddleware = this.createRouteRateLimitMiddleware(route.rateLimit);
172
+ const paths = Array.isArray(route.path) ? route.path : [route.path];
173
+ for (const path of paths) {
174
+ ;
175
+ this.app.route(path)[route.method.toLowerCase()](rlMiddleware, GenericHandler(this.serverlessHandler));
176
+ }
177
+ }
154
178
  //Main route -- We use a wildcard route because is not the job of the runtime and neither
155
179
  //the task to deny/constrain routes that invoked this task; all the job is done by the
156
180
  //load balancer and we just foward everything we have to the function.
157
181
  this.app.route(Globals.Listener_HTTP_ProxyRoute).all(GenericHandler(this.serverlessHandler));
158
182
  }
183
+ /**
184
+ * Creates rate limiting middleware from a per-route {@link RateLimitConfig},
185
+ * inheriting the global Redis store configuration when available so all
186
+ * rate-limit counters share the same Redis connection.
187
+ * @param {RateLimitConfig} config - The per-route rate limit configuration
188
+ * @returns {express.RequestHandler} Express middleware for rate limiting
189
+ * @private
190
+ */
191
+ createRouteRateLimitMiddleware(config) {
192
+ // Resolve keyGenerator string shorthands to concrete functions.
193
+ let keyGenerator;
194
+ if (config.keyGenerator === 'ip') {
195
+ keyGenerator = (req) => req.ip || req.socket.remoteAddress || 'unknown';
196
+ }
197
+ else if (config.keyGenerator === 'userId') {
198
+ keyGenerator = (req) => {
199
+ const authHeader = req.headers['authorization'];
200
+ if (authHeader === null || authHeader === void 0 ? void 0 : authHeader.startsWith('Bearer ')) {
201
+ const parts = authHeader.slice(7).split('.');
202
+ if (parts.length === 3) {
203
+ try {
204
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
205
+ const userId = payload.sub || payload.id || payload.userId;
206
+ if (userId)
207
+ return String(userId);
208
+ }
209
+ catch (_a) {
210
+ // malformed token — fall through to IP
211
+ }
212
+ }
213
+ }
214
+ return req.ip || req.socket.remoteAddress || 'unknown';
215
+ };
216
+ }
217
+ else {
218
+ keyGenerator = config.keyGenerator;
219
+ }
220
+ // Inherit the global Redis store (if configured) so per-route limiters
221
+ // share the same connection; distinguish them with a unique key prefix.
222
+ const globalRl = this.config.rateLimit;
223
+ const globalConfig = {
224
+ windowMs: config.windowMs,
225
+ limit: config.limit,
226
+ keyGenerator,
227
+ skip: config.skip,
228
+ store: globalRl === null || globalRl === void 0 ? void 0 : globalRl.store,
229
+ redis: (globalRl === null || globalRl === void 0 ? void 0 : globalRl.redis)
230
+ ? {
231
+ client: globalRl.redis.client,
232
+ prefix: `${globalRl.redis.prefix || 'wapi:rl:'}route:`,
233
+ }
234
+ : undefined,
235
+ };
236
+ return this.createRateLimitMiddleware(globalConfig);
237
+ }
159
238
  /**
160
239
  * Creates rate limiting middleware based on the provided configuration.
161
240
  * @param {GlobalRateLimitConfig} config - The rate limit configuration
@@ -1 +1 @@
1
- {"version":3,"file":"Proxy.js","sourceRoot":"","sources":["../../../../../src/Server/lib/container/Proxy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Proxy.js","sourceRoot":"","sources":["../../../../../src/Server/lib/container/Proxy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creator.co/wapi",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,10 +9,12 @@ import { RedisStore } from 'rate-limit-redis'
9
9
  import Server from './../Server.js'
10
10
  import GenericHandler from './GenericHandler.js'
11
11
  import HealthHandler from './HealthHandler.js'
12
+ import { HttpMethod } from '../../../API/Request.js'
12
13
  import Globals from '../../../Globals.js'
13
14
  import Logger from '../../../Logger/Logger.js'
14
15
  import Utils from '../../../Util/Utils.js'
15
- import { GlobalRateLimitConfig, RouterConfig } from '../../Router.js'
16
+ import { GlobalRateLimitConfig, RateLimitConfig, RouterConfig } from '../../Router.js'
17
+ import RouteResolver from '../../RouteResolver.js'
16
18
 
17
19
  /* Get package.json version from Wapi on ESM */
18
20
  const { version: appVersion } = JSON.parse(fs.readFileSync('package.json').toString())
@@ -47,6 +49,12 @@ export default class Proxy {
47
49
  * @returns None
48
50
  */
49
51
  private readonly serverlessHandler: Server['handleServerlessEvent']
52
+ /**
53
+ * Route resolver used to identify per-route rate limit configuration.
54
+ * @private
55
+ * @readonly
56
+ */
57
+ private readonly routeResolver: RouteResolver
50
58
  /**
51
59
  * Represents a listener for an HTTP server.
52
60
  * @private
@@ -64,6 +72,7 @@ export default class Proxy {
64
72
  this.stopping = false
65
73
  this.config = config
66
74
  this.serverlessHandler = serverlessHandler
75
+ this.routeResolver = new RouteResolver(this.config)
67
76
  this.logger = new Logger({ logLevel: 'INFO' }, 'proxy-container')
68
77
  this.app = express()
69
78
  // Trust the first proxy hop so req.ip resolves to the real client IP
@@ -94,7 +103,17 @@ export default class Proxy {
94
103
  // Apply global rate limiting if configured
95
104
  if (this.config.rateLimit && this.config.rateLimit.enabled !== false) {
96
105
  this.logger.info('[Proxy] - [RATE-LIMIT] - Global rate limiting enabled')
97
- const rateLimitMiddleware = this.createRateLimitMiddleware(this.config.rateLimit)
106
+ // Augment the skip function to bypass the global limit for routes that
107
+ // have their own rateLimit config (false = disable, or a RateLimitConfig).
108
+ const globalConfig: GlobalRateLimitConfig = {
109
+ ...this.config.rateLimit,
110
+ skip: (req: express.Request) => {
111
+ const route = this.routeResolver.resolveRoute(req.method as HttpMethod, req.path)
112
+ if (route && route.rateLimit !== undefined) return true
113
+ return this.config.rateLimit!.skip?.(req) ?? false
114
+ },
115
+ }
116
+ const rateLimitMiddleware = this.createRateLimitMiddleware(globalConfig)
98
117
  this.app.use(rateLimitMiddleware)
99
118
  }
100
119
 
@@ -189,12 +208,80 @@ export default class Proxy {
189
208
  this.app
190
209
  .route(this.config.healthCheckRoute || Globals.Listener_HTTP_DefaultHealthCheckRoute)
191
210
  .get(HealthHandler)
211
+ // Register individual routes that declare their own rateLimit config.
212
+ // These are installed BEFORE the wildcard so Express matches them first,
213
+ // giving each route an independent rate limit bucket.
214
+ for (const route of this.config.routes) {
215
+ if (!route.rateLimit) continue
216
+ const rlMiddleware = this.createRouteRateLimitMiddleware(route.rateLimit)
217
+ const paths = Array.isArray(route.path) ? route.path : [route.path]
218
+ for (const path of paths) {
219
+ ;(this.app.route(path) as any)[route.method.toLowerCase()](
220
+ rlMiddleware,
221
+ GenericHandler(this.serverlessHandler)
222
+ )
223
+ }
224
+ }
192
225
  //Main route -- We use a wildcard route because is not the job of the runtime and neither
193
226
  //the task to deny/constrain routes that invoked this task; all the job is done by the
194
227
  //load balancer and we just foward everything we have to the function.
195
228
  this.app.route(Globals.Listener_HTTP_ProxyRoute).all(GenericHandler(this.serverlessHandler))
196
229
  }
197
230
 
231
+ /**
232
+ * Creates rate limiting middleware from a per-route {@link RateLimitConfig},
233
+ * inheriting the global Redis store configuration when available so all
234
+ * rate-limit counters share the same Redis connection.
235
+ * @param {RateLimitConfig} config - The per-route rate limit configuration
236
+ * @returns {express.RequestHandler} Express middleware for rate limiting
237
+ * @private
238
+ */
239
+ private createRouteRateLimitMiddleware(config: RateLimitConfig): express.RequestHandler {
240
+ // Resolve keyGenerator string shorthands to concrete functions.
241
+ let keyGenerator: GlobalRateLimitConfig['keyGenerator']
242
+ if (config.keyGenerator === 'ip') {
243
+ keyGenerator = (req: express.Request) => req.ip || req.socket.remoteAddress || 'unknown'
244
+ } else if (config.keyGenerator === 'userId') {
245
+ keyGenerator = (req: express.Request) => {
246
+ const authHeader = req.headers['authorization']
247
+ if (authHeader?.startsWith('Bearer ')) {
248
+ const parts = authHeader.slice(7).split('.')
249
+ if (parts.length === 3) {
250
+ try {
251
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'))
252
+ const userId = payload.sub || payload.id || payload.userId
253
+ if (userId) return String(userId)
254
+ } catch {
255
+ // malformed token — fall through to IP
256
+ }
257
+ }
258
+ }
259
+ return req.ip || req.socket.remoteAddress || 'unknown'
260
+ }
261
+ } else {
262
+ keyGenerator = config.keyGenerator
263
+ }
264
+
265
+ // Inherit the global Redis store (if configured) so per-route limiters
266
+ // share the same connection; distinguish them with a unique key prefix.
267
+ const globalRl = this.config.rateLimit
268
+ const globalConfig: GlobalRateLimitConfig = {
269
+ windowMs: config.windowMs,
270
+ limit: config.limit,
271
+ keyGenerator,
272
+ skip: config.skip,
273
+ store: globalRl?.store,
274
+ redis: globalRl?.redis
275
+ ? {
276
+ client: globalRl.redis.client,
277
+ prefix: `${globalRl.redis.prefix || 'wapi:rl:'}route:`,
278
+ }
279
+ : undefined,
280
+ }
281
+
282
+ return this.createRateLimitMiddleware(globalConfig)
283
+ }
284
+
198
285
  /**
199
286
  * Creates rate limiting middleware based on the provided configuration.
200
287
  * @param {GlobalRateLimitConfig} config - The rate limit configuration
@@ -3,9 +3,10 @@ import { APIGatewayProxyEvent, Context } from 'aws-lambda'
3
3
  import { expect as c_expect } from 'chai'
4
4
  import request from 'supertest'
5
5
 
6
+ import { HttpMethod } from '../../../../src/API/Request.js'
6
7
  import Globals from '../../../../src/Globals.js'
7
8
  import Proxy from '../../../../src/Server/lib/container/Proxy.js'
8
- import { GlobalRateLimitConfig } from '../../../../src/Server/Router.js'
9
+ import { GlobalRateLimitConfig, RateLimitConfig } from '../../../../src/Server/Router.js'
9
10
  import { defaultUrl } from '../../../Test.utils.js'
10
11
 
11
12
  describe('Rate Limiting', () => {
@@ -829,4 +830,144 @@ describe('Rate Limiting', () => {
829
830
  }, 10000)
830
831
  })
831
832
 
833
+ describe('Per-route rate limiting', () => {
834
+ // @ts-ignore
835
+ let mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {})
836
+ let proxy: Proxy | null = null
837
+
838
+ beforeAll(() => {
839
+ // @ts-ignore
840
+ mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {})
841
+ })
842
+
843
+ afterAll(() => {
844
+ mockExit.mockRestore()
845
+ })
846
+
847
+ beforeEach(() => {
848
+ mockExit.mockReset()
849
+ })
850
+
851
+ afterEach(async () => {
852
+ if (proxy) {
853
+ await proxy.unload()
854
+ proxy = null
855
+ }
856
+ await new Promise(resolve => setTimeout(resolve, 100))
857
+ })
858
+
859
+ test('Per-route rate limit is applied independently of global', async () => {
860
+ const port = 57020
861
+ const url = defaultUrl.replace(String(Globals.Listener_HTTP_DefaultPort), String(port))
862
+
863
+ const routeRateLimit: RateLimitConfig = { limit: 2, windowMs: 1000 }
864
+
865
+ proxy = new Proxy(
866
+ {
867
+ routes: [
868
+ {
869
+ path: '/api/limited',
870
+ method: HttpMethod.GET,
871
+ rateLimit: routeRateLimit,
872
+ handler: async () => ({}) as any,
873
+ },
874
+ ],
875
+ port,
876
+ // No global rate limit — only per-route
877
+ },
878
+ async (event: APIGatewayProxyEvent, context: Context) => {
879
+ context.succeed({ body: JSON.stringify({ success: true }), statusCode: 200 })
880
+ }
881
+ )
882
+
883
+ await proxy.load()
884
+
885
+ // First 2 requests succeed
886
+ await request(url).get('/api/limited').expect(200)
887
+ await request(url).get('/api/limited').expect(200)
888
+
889
+ // 3rd request is blocked by the per-route rate limit
890
+ const res = await request(url).get('/api/limited').expect(429)
891
+ c_expect(res.body).to.have.property('error', 'rate_limit_exceeded')
892
+
893
+ // Wait for the window to reset and verify recovery
894
+ await new Promise(resolve => setTimeout(resolve, 1100))
895
+ await request(url).get('/api/limited').expect(200)
896
+ }, 10000)
897
+
898
+ test('rateLimit: false exempts a route from the global rate limit', async () => {
899
+ const port = 57021
900
+ const url = defaultUrl.replace(String(Globals.Listener_HTTP_DefaultPort), String(port))
901
+
902
+ proxy = new Proxy(
903
+ {
904
+ routes: [
905
+ {
906
+ path: '/api/exempt',
907
+ method: HttpMethod.GET,
908
+ rateLimit: false,
909
+ handler: async () => ({}) as any,
910
+ },
911
+ ],
912
+ port,
913
+ rateLimit: { enabled: true, limit: 2, windowMs: 5000 },
914
+ },
915
+ async (event: APIGatewayProxyEvent, context: Context) => {
916
+ context.succeed({ body: JSON.stringify({ success: true }), statusCode: 200 })
917
+ }
918
+ )
919
+
920
+ await proxy.load()
921
+
922
+ // Global limit is 2, but /api/exempt bypasses it — all 5 requests should succeed
923
+ for (let i = 0; i < 5; i++) {
924
+ const res = await request(url).get('/api/exempt').expect(200)
925
+ c_expect(res.body).to.deep.equal({ success: true })
926
+ }
927
+
928
+ // A non-exempt path still hits the global limit
929
+ await request(url).get('/other-path').expect(200)
930
+ await request(url).get('/other-path').expect(200)
931
+ await request(url).get('/other-path').expect(429)
932
+ }, 10000)
933
+
934
+ test('Per-route and global limits operate independently', async () => {
935
+ const port = 57022
936
+ const url = defaultUrl.replace(String(Globals.Listener_HTTP_DefaultPort), String(port))
937
+
938
+ proxy = new Proxy(
939
+ {
940
+ routes: [
941
+ {
942
+ path: '/api/strict',
943
+ method: HttpMethod.GET,
944
+ rateLimit: { limit: 2, windowMs: 5000 },
945
+ handler: async () => ({}) as any,
946
+ },
947
+ ],
948
+ port,
949
+ rateLimit: { enabled: true, limit: 5, windowMs: 5000 },
950
+ },
951
+ async (event: APIGatewayProxyEvent, context: Context) => {
952
+ context.succeed({ body: JSON.stringify({ success: true }), statusCode: 200 })
953
+ }
954
+ )
955
+
956
+ await proxy.load()
957
+
958
+ // /api/strict has a tighter per-route limit (2); hits 429 on 3rd request
959
+ await request(url).get('/api/strict').expect(200)
960
+ await request(url).get('/api/strict').expect(200)
961
+ await request(url).get('/api/strict').expect(429)
962
+
963
+ // /api/strict requests do NOT consume global tokens — /other-path still has its full budget
964
+ for (let i = 0; i < 5; i++) {
965
+ const res = await request(url).get('/other-path').expect(200)
966
+ c_expect(res.body).to.deep.equal({ success: true })
967
+ }
968
+ // 6th request to /other-path exhausts the global limit
969
+ await request(url).get('/other-path').expect(429)
970
+ }, 15000)
971
+ })
972
+
832
973
  export {}