From 03a4d883edaa35a55c449bbc16ec46705934347b Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Sun, 23 Nov 2025 09:42:58 +0100 Subject: [PATCH 1/6] feat: bump path-to-regexp to v8 --- package-lock.json | 32 ++++++++++++-------------------- package.json | 2 +- src/cloud-code/Parse.Cloud.js | 4 ++-- src/middlewares.js | 33 ++++++++++++++++++++++++++++----- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6019119b96..4f9f7ece7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "mustache": "4.2.0", "otpauth": "9.4.0", "parse": "7.0.1", - "path-to-regexp": "6.3.0", + "path-to-regexp": "8.3.0", "pg-monitor": "3.0.0", "pg-promise": "12.2.0", "pluralize": "8.0.0", @@ -18763,9 +18763,14 @@ } }, "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -20051,14 +20056,6 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "engines": { - "node": ">=16" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -35971,9 +35968,9 @@ } }, "path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" }, "path-type": { "version": "4.0.0", @@ -36889,11 +36886,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" - }, - "path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" } } }, diff --git a/package.json b/package.json index 4881e562a7..76b8e1518b 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "mustache": "4.2.0", "otpauth": "9.4.0", "parse": "7.0.1", - "path-to-regexp": "6.3.0", + "path-to-regexp": "8.3.0", "pg-monitor": "3.0.0", "pg-promise": "12.2.0", "pluralize": "8.0.0", diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index a057f8b3e6..3fc38c3aad 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -82,12 +82,12 @@ const getRoute = parseClass => { '@Config' : 'config', }[parseClass] || 'classes'; if (parseClass === '@File') { - return `/${route}/:id?(.*)`; + return `/${route}{/*id}`; } if (parseClass === '@Config') { return `/${route}`; } - return `/${route}/${parseClass}/:id?(.*)`; + return `/${route}/${parseClass}{/*id}`; }; /** @namespace * @name Parse diff --git a/src/middlewares.js b/src/middlewares.js index 93b16f3846..6b3812d975 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -321,7 +321,7 @@ const handleRateLimit = async (req, res, next) => { try { await Promise.all( rateLimits.map(async limit => { - const pathExp = new RegExp(limit.path); + const pathExp = limit.path.regexp || limit.path; if (pathExp.test(req.url)) { await limit.handler(req, res, err => { if (err) { @@ -561,10 +561,33 @@ export const addRateLimit = (route, config, cloud) => { }, }); } - let transformPath = route.requestPath.split('/*').join('/(.*)'); - if (transformPath === '*') { - transformPath = '(.*)'; - } + // Transform wildcards to named parameters for path-to-regexp v8 + // Only transform standalone * (not those already in named params like :id* or *id) + let transformPath = route.requestPath; + // Replace * that are not part of named parameter syntax + // Use a function to check if * is part of a named parameter + transformPath = transformPath.replace(/\*/g, (match, offset, string) => { + // Check if this * is part of a named parameter (e.g., :id*) + // Look backwards to find if there's a : before this * (without a / in between) + let isNamedParam = false; + for (let i = offset - 1; i >= 0; i--) { + if (string[i] === '/') { + break; // Found a /, so this * is not part of a named param + } + if (string[i] === ':') { + isNamedParam = true; // Found :, so this * is part of a named param + break; + } + } + // Check if * is followed by an identifier (like *id) - this is already a named wildcard + const nextChar = offset + 1 < string.length ? string[offset + 1] : null; + const isNamedWildcard = nextChar && /[a-zA-Z_$]/.test(nextChar); + // Only transform if it's not part of a named parameter and not already a named wildcard + if (!isNamedParam && !isNamedWildcard) { + return '*path'; + } + return match; // Keep * if it's part of :param* or *id + }); config.rateLimits.push({ path: pathToRegexp(transformPath), handler: rateLimit({ From 56800e5ee2a3b7a34c1975695ef48f634b32938f Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Sun, 23 Nov 2025 19:47:45 +0100 Subject: [PATCH 2/6] fix: feedbacks to have breaking changes --- spec/RateLimit.spec.js | 38 +++++++++++++++++++++++--------------- src/Config.js | 22 ++++++++++++++++++++++ src/Options/index.js | 2 +- src/middlewares.js | 29 +---------------------------- 4 files changed, 47 insertions(+), 44 deletions(-) diff --git a/spec/RateLimit.spec.js b/spec/RateLimit.spec.js index 3c57810702..aed9bd9abd 100644 --- a/spec/RateLimit.spec.js +++ b/spec/RateLimit.spec.js @@ -5,7 +5,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -26,7 +26,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -45,7 +45,7 @@ describe('rate limit', () => { Parse.Cloud.define('test', () => 'Abc'); await reconfigureServer({ rateLimit: { - requestPath: '*', + requestPath: '/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -83,7 +83,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -102,7 +102,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, includeMasterKey: true, @@ -122,7 +122,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/classes/*', + requestPath: '/classes/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -141,7 +141,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/classes/*', + requestPath: '/classes/*path', requestTimeWindow: 10000, requestCount: 1, requestMethods: 'POST', @@ -240,7 +240,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/classes/Test/*', + requestPath: '/classes/Test/*path', requestTimeWindow: 10000, requestCount: 1, requestMethods: 'DELETE', @@ -294,7 +294,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 100, errorResponseMessage: 'Too many requests', @@ -320,7 +320,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -340,7 +340,7 @@ describe('rate limit', () => { it('can use global zone', async () => { await reconfigureServer({ rateLimit: { - requestPath: '*', + requestPath: '*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -373,7 +373,7 @@ describe('rate limit', () => { }); fakeRes.json = jasmine.createSpy('json').and.callFake(resolvingPromise); middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - throw 'Should not call next'; + throw new Error('Should not call next'); }); await promise; expect(fakeRes.status).toHaveBeenCalledWith(429); @@ -386,7 +386,7 @@ describe('rate limit', () => { it('can use session zone', async () => { await reconfigureServer({ rateLimit: { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -407,7 +407,7 @@ describe('rate limit', () => { it('can use user zone', async () => { await reconfigureServer({ rateLimit: { - requestPath: '/functions/*', + requestPath: '/functions/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', @@ -481,6 +481,14 @@ describe('rate limit', () => { expect(() => validateRateLimit({ rateLimit: [{ requestTimeWindow: 3, requestCount: 'abc' }] }) ).toThrow('rateLimit.requestPath must be defined'); + expect(() => + validateRateLimit({ rateLimit: [{ requestPath: '/*' }] }) + ).toThrow(`rateLimit.requestPath "/*" uses deprecated wildcard syntax. ` + + `Please update to path-to-regexp v8 syntax. Examples:\n` + + ` Old: "/functions/*" → New: "/functions/*path"\n` + + ` Old: "/classes/*" → New: "/classes/*path"\n` + + ` Old: "*" → New: "*path"\n` + + `See: https://github.com/pillarjs/path-to-regexp#usage`); await expectAsync( reconfigureServer({ rateLimit: [{ requestTimeWindow: 3, requestCount: 1, path: 'abc', requestPath: 'a' }], @@ -494,7 +502,7 @@ describe('rate limit', () => { await reconfigureServer({ rateLimit: [ { - requestPath: '/classes/*', + requestPath: '/classes/*path', requestTimeWindow: 10000, requestCount: 1, errorResponseMessage: 'Too many requests', diff --git a/src/Config.js b/src/Config.js index 241edf9771..1951b902c7 100644 --- a/src/Config.js +++ b/src/Config.js @@ -3,6 +3,7 @@ // mount is the URL for the root of the API; includes http, domain, etc. import { isBoolean, isString } from 'lodash'; +import { pathToRegexp } from 'path-to-regexp'; import net from 'net'; import AppCache from './cache'; import DatabaseController from './Controllers/DatabaseController'; @@ -687,6 +688,27 @@ export class Config { if (typeof option.requestPath !== 'string') { throw `rateLimit.requestPath must be a string`; } + + // Validate path-to-regexp v8 syntax + // Check for common old syntax patterns + const oldWildcardPattern = /(?:^|\/)\*(?:\/|$)/; // Matches /* or * at start/end + const nakedWildcard = /^[\s]*\*[\s]*$/; // Matches bare * + if (oldWildcardPattern.test(option.requestPath) || nakedWildcard.test(option.requestPath)) { + throw `rateLimit.requestPath "${option.requestPath}" uses deprecated wildcard syntax. ` + + `Please update to path-to-regexp v8 syntax. Examples:\n` + + ` Old: "/functions/*" → New: "/functions/*path"\n` + + ` Old: "/classes/*" → New: "/classes/*path"\n` + + ` Old: "*" → New: "*path"\n` + + `See: https://github.com/pillarjs/path-to-regexp#usage`; + } + + // Validate that the path is valid path-to-regexp syntax + try { + pathToRegexp(option.requestPath); + } catch (error) { + throw `rateLimit.requestPath "${option.requestPath}" is not valid: ${error.message}`; + } + if (option.requestTimeWindow == null) { throw `rateLimit.requestTimeWindow must be defined`; } diff --git a/src/Options/index.js b/src/Options/index.js index 81dbc3c536..2d1e493474 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -350,7 +350,7 @@ export interface ParseServerOptions { } export interface RateLimitOptions { - /* 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 */ + /* 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 */ requestPath: string; /* The window of time in milliseconds within which the number of requests set in `requestCount` can be made before the rate limit is applied. */ requestTimeWindow: ?number; diff --git a/src/middlewares.js b/src/middlewares.js index 6b3812d975..e906852712 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -561,35 +561,8 @@ export const addRateLimit = (route, config, cloud) => { }, }); } - // Transform wildcards to named parameters for path-to-regexp v8 - // Only transform standalone * (not those already in named params like :id* or *id) - let transformPath = route.requestPath; - // Replace * that are not part of named parameter syntax - // Use a function to check if * is part of a named parameter - transformPath = transformPath.replace(/\*/g, (match, offset, string) => { - // Check if this * is part of a named parameter (e.g., :id*) - // Look backwards to find if there's a : before this * (without a / in between) - let isNamedParam = false; - for (let i = offset - 1; i >= 0; i--) { - if (string[i] === '/') { - break; // Found a /, so this * is not part of a named param - } - if (string[i] === ':') { - isNamedParam = true; // Found :, so this * is part of a named param - break; - } - } - // Check if * is followed by an identifier (like *id) - this is already a named wildcard - const nextChar = offset + 1 < string.length ? string[offset + 1] : null; - const isNamedWildcard = nextChar && /[a-zA-Z_$]/.test(nextChar); - // Only transform if it's not part of a named parameter and not already a named wildcard - if (!isNamedParam && !isNamedWildcard) { - return '*path'; - } - return match; // Keep * if it's part of :param* or *id - }); config.rateLimits.push({ - path: pathToRegexp(transformPath), + path: pathToRegexp(route.requestPath), handler: rateLimit({ windowMs: route.requestTimeWindow, max: route.requestCount, From 72af5725423b6aaa0eabb493d6aab04742295ad1 Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:08:01 +0100 Subject: [PATCH 3/6] fix: feedbacks --- 9.0.0.md | 24 ++++++++++++++++++++++++ spec/RateLimit.spec.js | 2 +- src/Config.js | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 9.0.0.md diff --git a/9.0.0.md b/9.0.0.md new file mode 100644 index 0000000000..78c84dc5a0 --- /dev/null +++ b/9.0.0.md @@ -0,0 +1,24 @@ +# Parse Server 9 Migration Guide + +This document only highlights specific changes that require a longer explanation. For a full list of changes in Parse Server 9 please refer to the [changelog](https://github.com/parse-community/parse-server/blob/alpha/CHANGELOG.md). + +--- +- [Route Path Syntax and Rate Limiting](#route-path-syntax-and-rate-limiting) +--- + +## Route Path Syntax and Rate Limiting +Parse Server 9 standardizes the route pattern syntax across cloud routes and rate-limiting to use the new **path-to-regexp v8** style. This update introduces validation and a clear deprecation error for the old wildcard route syntax. + +### Key Changes +- **Standardization**: All route paths now use the path-to-regexp v8 syntax, which provides better consistency and security. +- **Validation**: Added validation to ensure route paths conform to the new syntax. +- **Deprecation**: Old wildcard route syntax is deprecated and will trigger a clear error message. + +### Migration Steps +- Review your custom cloud routes and ensure they use the new path-to-regexp v8 syntax. +- Update any rate-limiting configurations to use the new route path format. +- Test your application to ensure all routes work as expected with the new syntax. +- **Consult the [Express 5 Migration Guide](https://expressjs.com/en/guide/migrating-5.html#path-syntax) for more details on the new path syntax.** + +### Related Pull Request +- [#9942: feat: Bump path-to-regexp to v8](https://github.com/parse-community/parse-server/pull/9942) diff --git a/spec/RateLimit.spec.js b/spec/RateLimit.spec.js index aed9bd9abd..0464ad141c 100644 --- a/spec/RateLimit.spec.js +++ b/spec/RateLimit.spec.js @@ -488,7 +488,7 @@ describe('rate limit', () => { ` Old: "/functions/*" → New: "/functions/*path"\n` + ` Old: "/classes/*" → New: "/classes/*path"\n` + ` Old: "*" → New: "*path"\n` + - `See: https://github.com/pillarjs/path-to-regexp#usage`); + `See parameter name on the express migration guide.`); await expectAsync( reconfigureServer({ rateLimit: [{ requestTimeWindow: 3, requestCount: 1, path: 'abc', requestPath: 'a' }], diff --git a/src/Config.js b/src/Config.js index 1951b902c7..f0ad410d18 100644 --- a/src/Config.js +++ b/src/Config.js @@ -699,7 +699,7 @@ export class Config { ` Old: "/functions/*" → New: "/functions/*path"\n` + ` Old: "/classes/*" → New: "/classes/*path"\n` + ` Old: "*" → New: "*path"\n` + - `See: https://github.com/pillarjs/path-to-regexp#usage`; + `See parameter name on the express migration guide.`; } // Validate that the path is valid path-to-regexp syntax From 91fb4c85c7d08ef65c81c4b088f32f012ca4a7bc Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Sun, 23 Nov 2025 22:53:00 +0100 Subject: [PATCH 4/6] fix: feedbacks --- 9.0.0.md | 30 ++++++++++++++++++++++++++++++ src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/9.0.0.md b/9.0.0.md index 78c84dc5a0..7fd2141ebc 100644 --- a/9.0.0.md +++ b/9.0.0.md @@ -15,6 +15,36 @@ Parse Server 9 standardizes the route pattern syntax across cloud routes and rat - **Deprecation**: Old wildcard route syntax is deprecated and will trigger a clear error message. ### Migration Steps + +#### Path Syntax Examples + +Update your rate limit configurations to use the new path-to-regexp v8 syntax: + +| Old Syntax (deprecated) | New Syntax (v8) | +|------------------------|-----------------| +| `/functions/*` | `/functions/*path` | +| `/classes/*` | `/classes/*path` | +| `/*` | `/*path` | +| `*` | `*path` | + +**Before:** +```javascript +rateLimit: { + requestPath: '/functions/*', + requestTimeWindow: 10000, + requestCount: 100 +} +``` + +**After:** +```javascript +rateLimit: { + requestPath: '/functions/*path', + requestTimeWindow: 10000, + requestCount: 100 +} +``` + - Review your custom cloud routes and ensure they use the new path-to-regexp v8 syntax. - Update any rate-limiting configurations to use the new route path format. - Test your application to ensure all routes work as expected with the new syntax. diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 1ae9512823..c406db453f 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -678,7 +678,7 @@ module.exports.RateLimitOptions = { requestPath: { env: 'PARSE_SERVER_RATE_LIMIT_REQUEST_PATH', help: - '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', + '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', required: true, }, requestTimeWindow: { diff --git a/src/Options/docs.js b/src/Options/docs.js index cdbd06de45..33631ac7fd 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -120,7 +120,7 @@ * @property {String} redisUrl Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests. * @property {Number} requestCount The number of requests that can be made per IP address within the time window set in `requestTimeWindow` before the rate limit is applied. * @property {String[]} requestMethods Optional, the HTTP request methods to which the rate limit should be applied, default is all methods. - * @property {String} requestPath 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 + * @property {String} requestPath 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 * @property {Number} requestTimeWindow The window of time in milliseconds within which the number of requests set in `requestCount` can be made before the rate limit is applied. * @property {String} zone The type of rate limit to apply. The following types are supported:

- `global`: rate limit based on the number of requests made by all users
- `ip`: rate limit based on the IP address of the request
- `user`: rate limit based on the user ID of the request
- `session`: rate limit based on the session token of the request


:default: 'ip' */ From 63b2b872f155a31eeca91253bfdd464a4f461658 Mon Sep 17 00:00:00 2001 From: coratgerl <73360179+coratgerl@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:45:30 +0100 Subject: [PATCH 5/6] fix: feedbacks --- 9.0.0.md | 6 ++++-- spec/RateLimit.spec.js | 8 -------- src/Config.js | 13 ------------- src/Options/index.js | 2 +- 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/9.0.0.md b/9.0.0.md index 7fd2141ebc..80ba6cf0a8 100644 --- a/9.0.0.md +++ b/9.0.0.md @@ -48,7 +48,9 @@ rateLimit: { - Review your custom cloud routes and ensure they use the new path-to-regexp v8 syntax. - Update any rate-limiting configurations to use the new route path format. - Test your application to ensure all routes work as expected with the new syntax. -- **Consult the [Express 5 Migration Guide](https://expressjs.com/en/guide/migrating-5.html#path-syntax) for more details on the new path syntax.** + +> [!Note] +> Consult the [path-to-regexp v8 docs](https://github.com/pillarjs/path-to-regexp) and the [Express 5 migration guide](https://expressjs.com/en/guide/migrating-5.html#path-syntax) for more details on the new path syntax. ### Related Pull Request -- [#9942: feat: Bump path-to-regexp to v8](https://github.com/parse-community/parse-server/pull/9942) +- [#9942](https://github.com/parse-community/parse-server/pull/9942) diff --git a/spec/RateLimit.spec.js b/spec/RateLimit.spec.js index 0464ad141c..429b7aaed2 100644 --- a/spec/RateLimit.spec.js +++ b/spec/RateLimit.spec.js @@ -481,14 +481,6 @@ describe('rate limit', () => { expect(() => validateRateLimit({ rateLimit: [{ requestTimeWindow: 3, requestCount: 'abc' }] }) ).toThrow('rateLimit.requestPath must be defined'); - expect(() => - validateRateLimit({ rateLimit: [{ requestPath: '/*' }] }) - ).toThrow(`rateLimit.requestPath "/*" uses deprecated wildcard syntax. ` + - `Please update to path-to-regexp v8 syntax. Examples:\n` + - ` Old: "/functions/*" → New: "/functions/*path"\n` + - ` Old: "/classes/*" → New: "/classes/*path"\n` + - ` Old: "*" → New: "*path"\n` + - `See parameter name on the express migration guide.`); await expectAsync( reconfigureServer({ rateLimit: [{ requestTimeWindow: 3, requestCount: 1, path: 'abc', requestPath: 'a' }], diff --git a/src/Config.js b/src/Config.js index f0ad410d18..879aebd77a 100644 --- a/src/Config.js +++ b/src/Config.js @@ -689,19 +689,6 @@ export class Config { throw `rateLimit.requestPath must be a string`; } - // Validate path-to-regexp v8 syntax - // Check for common old syntax patterns - const oldWildcardPattern = /(?:^|\/)\*(?:\/|$)/; // Matches /* or * at start/end - const nakedWildcard = /^[\s]*\*[\s]*$/; // Matches bare * - if (oldWildcardPattern.test(option.requestPath) || nakedWildcard.test(option.requestPath)) { - throw `rateLimit.requestPath "${option.requestPath}" uses deprecated wildcard syntax. ` + - `Please update to path-to-regexp v8 syntax. Examples:\n` + - ` Old: "/functions/*" → New: "/functions/*path"\n` + - ` Old: "/classes/*" → New: "/classes/*path"\n` + - ` Old: "*" → New: "*path"\n` + - `See parameter name on the express migration guide.`; - } - // Validate that the path is valid path-to-regexp syntax try { pathToRegexp(option.requestPath); diff --git a/src/Options/index.js b/src/Options/index.js index 1ba97e670b..45506fe679 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -350,7 +350,7 @@ export interface ParseServerOptions { } export interface RateLimitOptions { - /* 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 */ + /* 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. */ requestPath: string; /* The window of time in milliseconds within which the number of requests set in `requestCount` can be made before the rate limit is applied. */ requestTimeWindow: ?number; From 7951abc69d8711fd0d7dcef34421cb58147b42c0 Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:52:19 +0100 Subject: [PATCH 6/6] fix: definitions --- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 20a558df74..93c90505d6 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -678,7 +678,7 @@ module.exports.RateLimitOptions = { requestPath: { env: 'PARSE_SERVER_RATE_LIMIT_REQUEST_PATH', help: - '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', + '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.', required: true, }, requestTimeWindow: { diff --git a/src/Options/docs.js b/src/Options/docs.js index 25465ac136..d516ba8473 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -120,7 +120,7 @@ * @property {String} redisUrl Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests. * @property {Number} requestCount The number of requests that can be made per IP address within the time window set in `requestTimeWindow` before the rate limit is applied. * @property {String[]} requestMethods Optional, the HTTP request methods to which the rate limit should be applied, default is all methods. - * @property {String} requestPath 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 + * @property {String} requestPath 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. * @property {Number} requestTimeWindow The window of time in milliseconds within which the number of requests set in `requestCount` can be made before the rate limit is applied. * @property {String} zone The type of rate limit to apply. The following types are supported:Default is `ip`. */