Skip to content

Commit 56800e5

Browse files
committed
fix: feedbacks to have breaking changes
1 parent 03a4d88 commit 56800e5

File tree

4 files changed

+47
-44
lines changed

4 files changed

+47
-44
lines changed

spec/RateLimit.spec.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('rate limit', () => {
55
await reconfigureServer({
66
rateLimit: [
77
{
8-
requestPath: '/functions/*',
8+
requestPath: '/functions/*path',
99
requestTimeWindow: 10000,
1010
requestCount: 1,
1111
errorResponseMessage: 'Too many requests',
@@ -26,7 +26,7 @@ describe('rate limit', () => {
2626
await reconfigureServer({
2727
rateLimit: [
2828
{
29-
requestPath: '/functions/*',
29+
requestPath: '/functions/*path',
3030
requestTimeWindow: 10000,
3131
requestCount: 1,
3232
errorResponseMessage: 'Too many requests',
@@ -45,7 +45,7 @@ describe('rate limit', () => {
4545
Parse.Cloud.define('test', () => 'Abc');
4646
await reconfigureServer({
4747
rateLimit: {
48-
requestPath: '*',
48+
requestPath: '/*path',
4949
requestTimeWindow: 10000,
5050
requestCount: 1,
5151
errorResponseMessage: 'Too many requests',
@@ -83,7 +83,7 @@ describe('rate limit', () => {
8383
await reconfigureServer({
8484
rateLimit: [
8585
{
86-
requestPath: '/functions/*',
86+
requestPath: '/functions/*path',
8787
requestTimeWindow: 10000,
8888
requestCount: 1,
8989
errorResponseMessage: 'Too many requests',
@@ -102,7 +102,7 @@ describe('rate limit', () => {
102102
await reconfigureServer({
103103
rateLimit: [
104104
{
105-
requestPath: '/functions/*',
105+
requestPath: '/functions/*path',
106106
requestTimeWindow: 10000,
107107
requestCount: 1,
108108
includeMasterKey: true,
@@ -122,7 +122,7 @@ describe('rate limit', () => {
122122
await reconfigureServer({
123123
rateLimit: [
124124
{
125-
requestPath: '/classes/*',
125+
requestPath: '/classes/*path',
126126
requestTimeWindow: 10000,
127127
requestCount: 1,
128128
errorResponseMessage: 'Too many requests',
@@ -141,7 +141,7 @@ describe('rate limit', () => {
141141
await reconfigureServer({
142142
rateLimit: [
143143
{
144-
requestPath: '/classes/*',
144+
requestPath: '/classes/*path',
145145
requestTimeWindow: 10000,
146146
requestCount: 1,
147147
requestMethods: 'POST',
@@ -240,7 +240,7 @@ describe('rate limit', () => {
240240
await reconfigureServer({
241241
rateLimit: [
242242
{
243-
requestPath: '/classes/Test/*',
243+
requestPath: '/classes/Test/*path',
244244
requestTimeWindow: 10000,
245245
requestCount: 1,
246246
requestMethods: 'DELETE',
@@ -294,7 +294,7 @@ describe('rate limit', () => {
294294
await reconfigureServer({
295295
rateLimit: [
296296
{
297-
requestPath: '/functions/*',
297+
requestPath: '/functions/*path',
298298
requestTimeWindow: 10000,
299299
requestCount: 100,
300300
errorResponseMessage: 'Too many requests',
@@ -320,7 +320,7 @@ describe('rate limit', () => {
320320
await reconfigureServer({
321321
rateLimit: [
322322
{
323-
requestPath: '/functions/*',
323+
requestPath: '/functions/*path',
324324
requestTimeWindow: 10000,
325325
requestCount: 1,
326326
errorResponseMessage: 'Too many requests',
@@ -340,7 +340,7 @@ describe('rate limit', () => {
340340
it('can use global zone', async () => {
341341
await reconfigureServer({
342342
rateLimit: {
343-
requestPath: '*',
343+
requestPath: '*path',
344344
requestTimeWindow: 10000,
345345
requestCount: 1,
346346
errorResponseMessage: 'Too many requests',
@@ -373,7 +373,7 @@ describe('rate limit', () => {
373373
});
374374
fakeRes.json = jasmine.createSpy('json').and.callFake(resolvingPromise);
375375
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
376-
throw 'Should not call next';
376+
throw new Error('Should not call next');
377377
});
378378
await promise;
379379
expect(fakeRes.status).toHaveBeenCalledWith(429);
@@ -386,7 +386,7 @@ describe('rate limit', () => {
386386
it('can use session zone', async () => {
387387
await reconfigureServer({
388388
rateLimit: {
389-
requestPath: '/functions/*',
389+
requestPath: '/functions/*path',
390390
requestTimeWindow: 10000,
391391
requestCount: 1,
392392
errorResponseMessage: 'Too many requests',
@@ -407,7 +407,7 @@ describe('rate limit', () => {
407407
it('can use user zone', async () => {
408408
await reconfigureServer({
409409
rateLimit: {
410-
requestPath: '/functions/*',
410+
requestPath: '/functions/*path',
411411
requestTimeWindow: 10000,
412412
requestCount: 1,
413413
errorResponseMessage: 'Too many requests',
@@ -481,6 +481,14 @@ describe('rate limit', () => {
481481
expect(() =>
482482
validateRateLimit({ rateLimit: [{ requestTimeWindow: 3, requestCount: 'abc' }] })
483483
).toThrow('rateLimit.requestPath must be defined');
484+
expect(() =>
485+
validateRateLimit({ rateLimit: [{ requestPath: '/*' }] })
486+
).toThrow(`rateLimit.requestPath "/*" uses deprecated wildcard syntax. ` +
487+
`Please update to path-to-regexp v8 syntax. Examples:\n` +
488+
` Old: "/functions/*" → New: "/functions/*path"\n` +
489+
` Old: "/classes/*" → New: "/classes/*path"\n` +
490+
` Old: "*" → New: "*path"\n` +
491+
`See: https://github.com/pillarjs/path-to-regexp#usage`);
484492
await expectAsync(
485493
reconfigureServer({
486494
rateLimit: [{ requestTimeWindow: 3, requestCount: 1, path: 'abc', requestPath: 'a' }],
@@ -494,7 +502,7 @@ describe('rate limit', () => {
494502
await reconfigureServer({
495503
rateLimit: [
496504
{
497-
requestPath: '/classes/*',
505+
requestPath: '/classes/*path',
498506
requestTimeWindow: 10000,
499507
requestCount: 1,
500508
errorResponseMessage: 'Too many requests',

src/Config.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// mount is the URL for the root of the API; includes http, domain, etc.
44

55
import { isBoolean, isString } from 'lodash';
6+
import { pathToRegexp } from 'path-to-regexp';
67
import net from 'net';
78
import AppCache from './cache';
89
import DatabaseController from './Controllers/DatabaseController';
@@ -687,6 +688,27 @@ export class Config {
687688
if (typeof option.requestPath !== 'string') {
688689
throw `rateLimit.requestPath must be a string`;
689690
}
691+
692+
// Validate path-to-regexp v8 syntax
693+
// Check for common old syntax patterns
694+
const oldWildcardPattern = /(?:^|\/)\*(?:\/|$)/; // Matches /* or * at start/end
695+
const nakedWildcard = /^[\s]*\*[\s]*$/; // Matches bare *
696+
if (oldWildcardPattern.test(option.requestPath) || nakedWildcard.test(option.requestPath)) {
697+
throw `rateLimit.requestPath "${option.requestPath}" uses deprecated wildcard syntax. ` +
698+
`Please update to path-to-regexp v8 syntax. Examples:\n` +
699+
` Old: "/functions/*" → New: "/functions/*path"\n` +
700+
` Old: "/classes/*" → New: "/classes/*path"\n` +
701+
` Old: "*" → New: "*path"\n` +
702+
`See: https://github.com/pillarjs/path-to-regexp#usage`;
703+
}
704+
705+
// Validate that the path is valid path-to-regexp syntax
706+
try {
707+
pathToRegexp(option.requestPath);
708+
} catch (error) {
709+
throw `rateLimit.requestPath "${option.requestPath}" is not valid: ${error.message}`;
710+
}
711+
690712
if (option.requestTimeWindow == null) {
691713
throw `rateLimit.requestTimeWindow must be defined`;
692714
}

src/Options/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ export interface ParseServerOptions {
350350
}
351351

352352
export interface RateLimitOptions {
353-
/* The path of the API route to be rate limited. Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expression. See: https://expressjs.com/en/guide/routing.html */
353+
/* The path of the API route to be rate limited. Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings or string patterns following path-to-regexp v8 syntax. Wildcards must be named (e.g., `/*path` instead of `/*`). Examples: `/functions/*path`, `/classes/MyClass/*path`, `/*path`. See: https://github.com/pillarjs/path-to-regexp */
354354
requestPath: string;
355355
/* The window of time in milliseconds within which the number of requests set in `requestCount` can be made before the rate limit is applied. */
356356
requestTimeWindow: ?number;

src/middlewares.js

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -561,35 +561,8 @@ export const addRateLimit = (route, config, cloud) => {
561561
},
562562
});
563563
}
564-
// Transform wildcards to named parameters for path-to-regexp v8
565-
// Only transform standalone * (not those already in named params like :id* or *id)
566-
let transformPath = route.requestPath;
567-
// Replace * that are not part of named parameter syntax
568-
// Use a function to check if * is part of a named parameter
569-
transformPath = transformPath.replace(/\*/g, (match, offset, string) => {
570-
// Check if this * is part of a named parameter (e.g., :id*)
571-
// Look backwards to find if there's a : before this * (without a / in between)
572-
let isNamedParam = false;
573-
for (let i = offset - 1; i >= 0; i--) {
574-
if (string[i] === '/') {
575-
break; // Found a /, so this * is not part of a named param
576-
}
577-
if (string[i] === ':') {
578-
isNamedParam = true; // Found :, so this * is part of a named param
579-
break;
580-
}
581-
}
582-
// Check if * is followed by an identifier (like *id) - this is already a named wildcard
583-
const nextChar = offset + 1 < string.length ? string[offset + 1] : null;
584-
const isNamedWildcard = nextChar && /[a-zA-Z_$]/.test(nextChar);
585-
// Only transform if it's not part of a named parameter and not already a named wildcard
586-
if (!isNamedParam && !isNamedWildcard) {
587-
return '*path';
588-
}
589-
return match; // Keep * if it's part of :param* or *id
590-
});
591564
config.rateLimits.push({
592-
path: pathToRegexp(transformPath),
565+
path: pathToRegexp(route.requestPath),
593566
handler: rateLimit({
594567
windowMs: route.requestTimeWindow,
595568
max: route.requestCount,

0 commit comments

Comments
 (0)