@90soft/parse-server-kit 2.10.1 → 2.11.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/CHANGELOG.md CHANGED
@@ -8,6 +8,90 @@ All notable changes to `@90soft/parse-server-kit`.
8
8
 
9
9
  ---
10
10
 
11
+ ## [2.11.0]
12
+
13
+ ### Security
14
+
15
+ - **`rateLimit` could be bypassed completely, by anyone, without an account.**
16
+ The bucket key was `req.headers['x-parse-session-token'] || req.ip`. That
17
+ header is supplied by the caller and is **not validated at this point in the
18
+ pipeline** — the entity-route middleware runs before Parse Server resolves the
19
+ session — so sending a fresh random token on every request minted a fresh
20
+ bucket each time.
21
+
22
+ Measured against a limit of `max: 3`: ten requests with ten different random
23
+ tokens all succeeded. The limiter was decorative.
24
+
25
+ The key is now the **client address** (`req.ip`, falling back to the socket
26
+ address). An optional `keyBy` is available for apps that can derive a
27
+ trustworthy identity themselves.
28
+
29
+ ```ts
30
+ @CloudFunction({ rateLimit: { windowMs: 60_000, max: 5 } }) // keyed on address
31
+ @CloudFunction({ rateLimit: { windowMs: 60_000, max: 5, keyBy: r => r.apiKeyId } })
32
+ ```
33
+
34
+ **Two things to know when you adopt this:**
35
+
36
+ 1. **Configure Express `trust proxy` precisely** — a hop count, or the proxy's
37
+ address — if you run behind nginx or a load balancer. `req.ip` honours that
38
+ setting, so a permissive `true` lets a caller spoof the key with
39
+ `X-Forwarded-For`. This is the same bypass Parse Server fixed for its own
40
+ limiter in 9.10.1. Unset, Express ignores the header, which is safe.
41
+ 2. **Limits were previously per session token and are now per address**, so
42
+ several users behind one NAT now share a bucket. Raise `max` where that
43
+ matters, or supply `keyBy`.
44
+
45
+ - **The bucket store is now bounded** (10,000 clients per function, oldest
46
+ evicted). A caller rotating addresses could previously grow the map without
47
+ limit, turning the rate limiter into a memory-exhaustion vector.
48
+
49
+ ### Added
50
+
51
+ - `resetRateLimits()`, exported for tests.
52
+ - `test/rateLimit.test.ts` — eight tests, including the bypass above as a
53
+ regression. Rate limiting previously had **no test coverage at all**; the
54
+ regression test was verified to fail against the old implementation.
55
+
56
+ ### Changed
57
+
58
+ - **`engines.node` is now `>=20.19.0`** (was `>=18.0.0`). Parse Server 9
59
+ requires it, so the old floor advertised a combination that cannot work.
60
+ - **Peer floors raised** to `parse >= 8.0.0` and `parse-server >= 9.0.0` (were
61
+ `>= 5.0.0` and `>= 8.3.0`). Development and tests now run against
62
+ `parse@8.6.2` / `parse-server@9.10.1`; previously the kit was tested against
63
+ Parse JS SDK **5** while every consuming app ran 8.x. That gap matters:
64
+ SDK 6 made `Parse.Object.set`/`setACL`/`unset` and `Role.setName` **throw**
65
+ instead of taking an error callback, so such a failure escapes the
66
+ `catchError` convention entirely.
67
+
68
+ The floors stay permissive deliberately — nothing in a consuming app breaks on
69
+ install. **Parse Server ≥ 9.10.1 is strongly recommended**: the 9.5→9.10 line
70
+ is almost entirely security fixes, including account takeover through
71
+ auth-adapter identity spoofing, protected-field bypasses via logical
72
+ operators, dot-notation, relation counts and LiveQuery, and several stored-XSS
73
+ routes through file upload.
74
+
75
+ ### Notes
76
+
77
+ - Rate limiting remains **per process**: with N app instances the effective
78
+ limit is N × `max`. For cross-instance enforcement use Parse Server's own
79
+ `rateLimit` server option with `redisUrl`.
80
+ - A unique index with a **value** predicate (e.g. "one active row per pair")
81
+ needs `partialFilterExpression`, not `partialFilterNulls` — the latter only
82
+ emits `$exists: true`. Use the `options` escape hatch, which is passed
83
+ straight through to MongoDB:
84
+
85
+ ```ts
86
+ compoundIndexes: [{
87
+ fields: ['trainee', 'sport'],
88
+ unique: true,
89
+ options: { partialFilterExpression: { status: 'active' } },
90
+ }]
91
+ ```
92
+
93
+ ---
94
+
11
95
  ## [2.10.1]
12
96
 
13
97
  Twelve fixes found by a full read of the source, each verified against a running
package/CLAUDE.md CHANGED
@@ -187,7 +187,7 @@ Static-method decorator. Registers the function and wraps it with role checking.
187
187
  interface RouteConfig {
188
188
  methods: HttpMethod[]; // 'GET'|'POST'|'PUT'|'DELETE'|'PATCH'
189
189
  requiresAuth?: boolean;
190
- rateLimit?: {windowMs: number; max: number};
190
+ rateLimit?: {windowMs: number; max: number; keyBy?: (req) => string}; // keyed on client ADDRESS
191
191
  validation?: Parse.Cloud.Validator;
192
192
  description?: string;
193
193
  requireRoles?: string[];
@@ -454,11 +454,35 @@ parent's final ACL and **before** saving. For status-derived visibility set in a
454
454
  | `removeResultMiddleware` | Unwraps Parse's `{result: …}` response |
455
455
  | `conditionalJsonMiddleware` | `express.json({limit:'10mb', type:['text/plain']})`, skipping `{mountPath}/files` |
456
456
  | `validateFunctionRoutes` | Legacy `/functions/*` validation |
457
- | `checkRateLimit(req,res,name,cfg)` | In-process token bucket; returns `false` after sending 429 |
457
+ | `checkRateLimit(req,res,name,cfg)` | In-process token bucket keyed on client address; returns `false` after sending 429 |
458
458
 
459
459
  **Master key** is read from the `X-Parse-Master-Key` header (preferred) or the
460
- request body (deprecated, warns once). Rate limiting is **per process** — no
461
- enforcement across instances.
460
+ request body (deprecated, warns once).
461
+
462
+ ### Rate limiting — read before relying on it
463
+
464
+ The bucket key is the **client address** (`req.ip`, falling back to the socket
465
+ address). Pass `keyBy` only if the app can derive an identity it actually trusts.
466
+
467
+ > Before 2.11.0 the key was `x-parse-session-token || req.ip`. That header is
468
+ > caller-supplied and unvalidated here — this middleware runs **before** Parse
469
+ > resolves the session — so a rotating random token minted a fresh bucket per
470
+ > request and the limit did nothing. Ten requests passed a limit of three.
471
+
472
+ Three constraints:
473
+
474
+ 1. **`trust proxy` must be exact** — a hop count or the proxy's address — when
475
+ behind nginx or a load balancer. `req.ip` honours it, so a permissive `true`
476
+ lets a caller spoof the key via `X-Forwarded-For` (the bypass Parse Server
477
+ fixed for its own limiter in 9.10.1). Unset is safe: Express ignores the
478
+ header.
479
+ 2. **Per process.** With N instances the effective limit is N × `max`. For
480
+ cross-instance enforcement use Parse Server's `rateLimit` option with
481
+ `redisUrl`.
482
+ 3. **Per address, not per user.** Users behind one NAT share a bucket.
483
+
484
+ The store is bounded at 10,000 clients per function, oldest evicted, so a caller
485
+ rotating addresses cannot grow it without limit.
462
486
 
463
487
  ---
464
488
 
@@ -488,7 +512,9 @@ interface CompoundIndex {
488
512
  unique?: boolean;
489
513
  name?: string;
490
514
  sparse?: boolean;
491
- partialFilterNulls?: boolean; // only index docs where all fields exist
515
+ partialFilterNulls?: boolean; // only index docs where all fields EXIST
516
+ // For a VALUE predicate (e.g. one active row per pair) use the options escape hatch:
517
+ // options: {partialFilterExpression: {status: 'active'}} -> passed straight to MongoDB
492
518
  fieldTypes?: Record<string, 1|-1|'text'|'2dsphere'|'hashed'>;
493
519
  options?: Record<string, unknown>; // e.g. default_language
494
520
  }
@@ -688,11 +714,21 @@ output for `[Versioning]`.
688
714
 
689
715
  | | Requirement |
690
716
  |---|---|
691
- | Node | ≥ 18 for this package; **≥ 20.19** with parse-server 9 |
692
- | parse-server | ≥ 8.3 (optional peer) — `keepUnknownIndexes`, adapter internals |
717
+ | Node | **≥ 20.19** |
718
+ | parse-server | ≥ 9.0 (optional peer); **≥ 9.10.1 strongly recommended** — see below. ≥ 9.10.0 is required on MongoDB 8.3+ (GeoPoint distance queries 500 otherwise) |
693
719
  | MongoDB | ≥ 7.0.16 for parse-server 9; **replica set** for transactions |
694
- | Parse SDK | ≥ 5; verified on 5.3.0 and 8.6.0 |
720
+ | Parse SDK | ≥ 8; developed and tested on 8.6.2 |
695
721
  | TypeScript | any, with `experimentalDecorators: true` |
696
722
 
723
+ **Why ≥ 9.10.1 rather than merely 9.x:** the 9.5→9.10 releases are almost
724
+ entirely security fixes — account takeover through auth-adapter identity
725
+ spoofing, protected-field bypasses via logical operators, dot-notation, relation
726
+ counts and LiveQuery, multiple rate-limit bypasses, and several stored-XSS routes
727
+ through file upload. An older 9.x is not "stable", it is unpatched.
728
+
729
+ **`@types/parse` is far behind the runtime** (3.0.x against an 8.x SDK), which is
730
+ why reaching newer Parse APIs sometimes needs an `as unknown as` cast. That is
731
+ the types package, not this kit.
732
+
697
733
  **Transactions and `@ParseVersionField` are MongoDB-only.** On Postgres the
698
734
  version field is declared but never enforced.
@@ -12,9 +12,21 @@ export interface SwaggerDocConfig {
12
12
  export interface RouteConfig {
13
13
  methods: HttpMethod[];
14
14
  requiresAuth?: boolean;
15
+ /**
16
+ * Per-function rate limit, keyed on the CLIENT ADDRESS.
17
+ *
18
+ * Enforced per process: with N app instances the effective limit is N x `max`.
19
+ * For cross-instance enforcement use Parse Server's own `rateLimit` server
20
+ * option with `redisUrl`.
21
+ *
22
+ * Requires Express `trust proxy` to be configured precisely (a hop count or
23
+ * the proxy's address) when running behind a reverse proxy. A permissive
24
+ * value lets a caller spoof the key with `X-Forwarded-For`.
25
+ */
15
26
  rateLimit?: {
16
27
  windowMs: number;
17
28
  max: number;
29
+ keyBy?: (req: any) => string;
18
30
  };
19
31
  validation?: Parse.Cloud.Validator;
20
32
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cloudTypes.d.ts","sourceRoot":"","sources":["../../../src/decorators/types/cloudTypes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,UAAU,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,eAAe,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACtE,2EAA2E;IAC3E,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"cloudTypes.d.ts","sourceRoot":"","sources":["../../../src/decorators/types/cloudTypes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAA;KAAE,CAAC;IAC5E,UAAU,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,eAAe,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACtE,2EAA2E;IAC3E,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -1,10 +1,29 @@
1
1
  import { Request, Response } from 'express';
2
- /**
3
- * Check rate limit for a function + client.
4
- * Returns null if allowed, or an error response object if exceeded.
5
- */
6
- export declare function checkRateLimit(req: Request, res: Response, functionName: string, config: {
2
+ /** Per-function configuration, as declared on `@CloudFunction({rateLimit})`. */
3
+ export interface RateLimitConfig {
7
4
  windowMs: number;
8
5
  max: number;
9
- }): boolean;
6
+ /**
7
+ * Optional custom bucket key.
8
+ *
9
+ * This middleware runs BEFORE Parse Server validates the session, so there is
10
+ * no authenticated user here. Supply this only if the app can derive a
11
+ * trustworthy identity on its own (e.g. a validated API key). Returning
12
+ * anything the caller controls freely re-opens the bypass described below.
13
+ */
14
+ keyBy?: (req: Request) => string;
15
+ }
16
+ /**
17
+ * Check the rate limit for a function + client.
18
+ *
19
+ * Returns true when the request may proceed. Returns false after sending a 429,
20
+ * in which case the caller must not continue the chain.
21
+ *
22
+ * NOTE — the store is in-memory and therefore PER PROCESS. With N app instances
23
+ * the effective limit is N x `max`. For cross-instance enforcement use Parse
24
+ * Server's own `rateLimit` server option with `redisUrl`.
25
+ */
26
+ export declare function checkRateLimit(req: Request, res: Response, functionName: string, config: RateLimitConfig): boolean;
27
+ /** Clear all buckets. Intended for tests. */
28
+ export declare function resetRateLimits(): void;
10
29
  //# sourceMappingURL=rateLimit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAuC1C;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAiCT"}
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAO1C,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;CAClC;AA8DD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,eAAe,GACtB,OAAO,CAwCT;AAED,6CAA6C;AAC7C,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -1,8 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkRateLimit = checkRateLimit;
4
+ exports.resetRateLimits = resetRateLimits;
4
5
  // Map<functionName, Map<clientKey, RateLimitEntry>>
5
6
  const store = new Map();
7
+ /**
8
+ * Hard ceiling on tracked clients per function. A caller rotating addresses
9
+ * would otherwise grow this map without bound — turning the rate limiter into
10
+ * a memory-exhaustion vector. When the ceiling is hit we drop the oldest
11
+ * entries rather than stop limiting.
12
+ */
13
+ const MAX_CLIENTS_PER_FUNCTION = 10000;
6
14
  // Cleanup old entries every 5 minutes
7
15
  const cleanupTimer = setInterval(() => {
8
16
  const now = Date.now();
@@ -24,34 +32,68 @@ const cleanupTimer = setInterval(() => {
24
32
  // which is the only time it has anything to do.
25
33
  cleanupTimer.unref();
26
34
  /**
27
- * Get client key — session token if available, fallback to IP.
35
+ * Resolve the bucket key for a request.
36
+ *
37
+ * SECURITY — why this is the client address and NOT the session token:
38
+ *
39
+ * Until v2.11.0 the key was `req.headers['x-parse-session-token'] || req.ip`.
40
+ * That header is supplied by the caller and is NOT validated at this point in
41
+ * the pipeline, so sending a fresh random token on every request minted a fresh
42
+ * bucket each time and the limit did nothing at all. No account was needed.
43
+ *
44
+ * The address is the only identifier available here that the caller cannot
45
+ * multiply at will.
46
+ *
47
+ * `req.ip` honours Express's `trust proxy` setting. The app MUST configure that
48
+ * precisely — a hop count, or the proxy's address. With a permissive value
49
+ * (`true`) a caller sets `X-Forwarded-For: 127.0.0.1` and spoofs the key, which
50
+ * is the same bypass Parse Server fixed in 9.10.1 for its own limiter. When
51
+ * `trust proxy` is unset, Express already ignores the header and `req.ip` is
52
+ * the socket address, which is what we want.
28
53
  */
29
- function getClientKey(req) {
30
- return req.headers['x-parse-session-token'] || req.ip || 'unknown';
54
+ function getClientKey(req, config) {
55
+ if (config.keyBy) {
56
+ return config.keyBy(req);
57
+ }
58
+ return req.ip || req.socket?.remoteAddress || 'unknown';
31
59
  }
32
60
  /**
33
- * Check rate limit for a function + client.
34
- * Returns null if allowed, or an error response object if exceeded.
61
+ * Check the rate limit for a function + client.
62
+ *
63
+ * Returns true when the request may proceed. Returns false after sending a 429,
64
+ * in which case the caller must not continue the chain.
65
+ *
66
+ * NOTE — the store is in-memory and therefore PER PROCESS. With N app instances
67
+ * the effective limit is N x `max`. For cross-instance enforcement use Parse
68
+ * Server's own `rateLimit` server option with `redisUrl`.
35
69
  */
36
70
  function checkRateLimit(req, res, functionName, config) {
37
- const clientKey = getClientKey(req);
71
+ const clientKey = getClientKey(req, config);
38
72
  const now = Date.now();
39
- // Get or create function map
40
73
  if (!store.has(functionName)) {
41
74
  store.set(functionName, new Map());
42
75
  }
43
76
  const clients = store.get(functionName);
44
- // Get or create client entry
45
77
  const entry = clients.get(clientKey);
46
78
  if (!entry || now > entry.resetAt) {
47
- // First request or window expired — start fresh
79
+ // First request in this window — start fresh.
80
+ if (!entry && clients.size >= MAX_CLIENTS_PER_FUNCTION) {
81
+ // Evict the oldest-expiring entries to make room. Map preserves insertion
82
+ // order, so the head is the least recently added.
83
+ const toEvict = Math.ceil(MAX_CLIENTS_PER_FUNCTION * 0.1);
84
+ let evicted = 0;
85
+ for (const key of clients.keys()) {
86
+ clients.delete(key);
87
+ if (++evicted >= toEvict)
88
+ break;
89
+ }
90
+ }
48
91
  clients.set(clientKey, { count: 1, resetAt: now + config.windowMs });
49
- return true; // allowed
92
+ return true;
50
93
  }
51
94
  if (entry.count < config.max) {
52
- // Under limit
53
95
  entry.count++;
54
- return true; // allowed
96
+ return true;
55
97
  }
56
98
  // Exceeded — send 429
57
99
  const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
@@ -60,6 +102,10 @@ function checkRateLimit(req, res, functionName, config) {
60
102
  message: `Too many requests. Try again in ${retryAfter} seconds.`,
61
103
  retryAfter,
62
104
  });
63
- return false; // blocked
105
+ return false;
106
+ }
107
+ /** Clear all buckets. Intended for tests. */
108
+ function resetRateLimits() {
109
+ store.clear();
64
110
  }
65
111
  //# sourceMappingURL=rateLimit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":";;AA2CA,wCAsCC;AA1ED,oDAAoD;AACpD,MAAM,KAAK,GAA6C,IAAI,GAAG,EAAE,CAAC;AAElE,sCAAsC;AACtC,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;gBACxB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;AACH,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAElB,0EAA0E;AAC1E,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,gDAAgD;AAChD,YAAY,CAAC,KAAK,EAAE,CAAC;AAErB;;GAEG;AACH,SAAS,YAAY,CAAC,GAAY;IAChC,OAAQ,GAAG,CAAC,OAAO,CAAC,uBAAuB,CAAY,IAAI,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC;AACjF,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAC5B,GAAY,EACZ,GAAa,EACb,YAAoB,EACpB,MAAyC;IAEzC,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,6BAA6B;IAC7B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAE,CAAC;IAEzC,6BAA6B;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAErC,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAClC,gDAAgD;QAChD,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,CAAC,UAAU;IACzB,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;QAC7B,cAAc;QACd,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,CAAC,UAAU;IACzB,CAAC;IAED,sBAAsB;IACtB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,OAAO,EAAE,mCAAmC,UAAU,WAAW;QACjE,UAAU;KACX,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,CAAC,UAAU;AAC1B,CAAC"}
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@90soft/parse-server-kit",
3
- "version": "2.10.1",
3
+ "version": "2.11.0",
4
4
  "description": "Complete decorator-driven development kit for Parse Server — models, cloud functions, triggers, cron, routing, ACL, Swagger, and more.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,14 +34,14 @@
34
34
  "url": "https://git.90-soft.com/90_soft/parse-server-kit.git"
35
35
  },
36
36
  "engines": {
37
- "node": ">=18.0.0"
37
+ "node": ">=20.19.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@types/parse": ">=3.0.0",
41
41
  "express": ">=4.0.0",
42
42
  "node-cron": ">=3.0.0",
43
- "parse": ">=5.0.0",
44
- "parse-server": ">=8.3.0",
43
+ "parse": ">=8.0.0",
44
+ "parse-server": ">=9.0.0",
45
45
  "reflect-metadata": ">=0.1.13",
46
46
  "swagger-ui-express": ">=4.0.0"
47
47
  },
@@ -67,8 +67,8 @@
67
67
  "jest": "^29.7.0",
68
68
  "mongodb-memory-server": "^11.2.0",
69
69
  "node-cron": "^4.0.0",
70
- "parse": "^5.3.0",
71
- "parse-server": "^9.10.0",
70
+ "parse": "^8.6.2",
71
+ "parse-server": "^9.10.1",
72
72
  "reflect-metadata": "^0.2.2",
73
73
  "swagger-ui-express": "^5.0.1",
74
74
  "ts-jest": "^29.2.0",