Skip to content

Commit fa8723b

Browse files
authored
feat: Update route patterns to use path-to-regexp v8 syntax (#9942)
BREAKING CHANGE: Route pattern syntax across cloud routes and rate-limiting now use the new path-to-regexp v8 syntax; see the [migration guide](https://github.com/parse-community/parse-server/blob/alpha/9.0.0.md) for more details.
1 parent 5a61993 commit fa8723b

File tree

10 files changed

+100
-105
lines changed

10 files changed

+100
-105
lines changed

9.0.0.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Parse Server 9 Migration Guide <!-- omit in toc -->
2+
3+
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).
4+
5+
---
6+
- [Route Path Syntax and Rate Limiting](#route-path-syntax-and-rate-limiting)
7+
---
8+
9+
## Route Path Syntax and Rate Limiting
10+
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.
11+
12+
### Key Changes
13+
- **Standardization**: All route paths now use the path-to-regexp v8 syntax, which provides better consistency and security.
14+
- **Validation**: Added validation to ensure route paths conform to the new syntax.
15+
- **Deprecation**: Old wildcard route syntax is deprecated and will trigger a clear error message.
16+
17+
### Migration Steps
18+
19+
#### Path Syntax Examples
20+
21+
Update your rate limit configurations to use the new path-to-regexp v8 syntax:
22+
23+
| Old Syntax (deprecated) | New Syntax (v8) |
24+
|------------------------|-----------------|
25+
| `/functions/*` | `/functions/*path` |
26+
| `/classes/*` | `/classes/*path` |
27+
| `/*` | `/*path` |
28+
| `*` | `*path` |
29+
30+
**Before:**
31+
```javascript
32+
rateLimit: {
33+
requestPath: '/functions/*',
34+
requestTimeWindow: 10000,
35+
requestCount: 100
36+
}
37+
```
38+
39+
**After:**
40+
```javascript
41+
rateLimit: {
42+
requestPath: '/functions/*path',
43+
requestTimeWindow: 10000,
44+
requestCount: 100
45+
}
46+
```
47+
48+
- Review your custom cloud routes and ensure they use the new path-to-regexp v8 syntax.
49+
- Update any rate-limiting configurations to use the new route path format.
50+
- Test your application to ensure all routes work as expected with the new syntax.
51+
52+
> [!Note]
53+
> 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.
54+
55+
### Related Pull Request
56+
- [#9942](https://github.com/parse-community/parse-server/pull/9942)

package-lock.json

Lines changed: 12 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
"mongodb": "6.20.0",
4949
"mustache": "4.2.0",
5050
"otpauth": "9.4.0",
51+
"path-to-regexp": "8.3.0",
5152
"parse": "8.0.0",
52-
"path-to-regexp": "6.3.0",
5353
"pg-monitor": "3.0.0",
5454
"pg-promise": "12.2.0",
5555
"pluralize": "8.0.0",

spec/RateLimit.spec.js

Lines changed: 15 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',
@@ -494,7 +494,7 @@ describe('rate limit', () => {
494494
await reconfigureServer({
495495
rateLimit: [
496496
{
497-
requestPath: '/classes/*',
497+
requestPath: '/classes/*path',
498498
requestTimeWindow: 10000,
499499
requestCount: 1,
500500
errorResponseMessage: 'Too many requests',

src/Config.js

Lines changed: 9 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,14 @@ export class Config {
687688
if (typeof option.requestPath !== 'string') {
688689
throw `rateLimit.requestPath must be a string`;
689690
}
691+
692+
// Validate that the path is valid path-to-regexp syntax
693+
try {
694+
pathToRegexp(option.requestPath);
695+
} catch (error) {
696+
throw `rateLimit.requestPath "${option.requestPath}" is not valid: ${error.message}`;
697+
}
698+
690699
if (option.requestTimeWindow == null) {
691700
throw `rateLimit.requestTimeWindow must be defined`;
692701
}

src/Options/Definitions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ module.exports.RateLimitOptions = {
686686
requestPath: {
687687
env: 'PARSE_SERVER_RATE_LIMIT_REQUEST_PATH',
688688
help:
689-
'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',
689+
'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 <a href="https://github.com/pillarjs/path-to-regexp">path-to-regexp v8</a> syntax.',
690690
required: true,
691691
},
692692
requestTimeWindow: {

src/Options/docs.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)