@90soft/parse-server-kit 2.9.0 → 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.
Files changed (47) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/CLAUDE.md +734 -698
  3. package/dist/config.d.ts +68 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +35 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/database/indexes.d.ts.map +1 -1
  8. package/dist/database/indexes.js +14 -1
  9. package/dist/database/indexes.js.map +1 -1
  10. package/dist/decorators/cloudDecorator.d.ts.map +1 -1
  11. package/dist/decorators/cloudDecorator.js +19 -0
  12. package/dist/decorators/cloudDecorator.js.map +1 -1
  13. package/dist/decorators/cloudRegistry.d.ts +13 -0
  14. package/dist/decorators/cloudRegistry.d.ts.map +1 -1
  15. package/dist/decorators/cloudRegistry.js +23 -1
  16. package/dist/decorators/cloudRegistry.js.map +1 -1
  17. package/dist/decorators/parseDecorators.d.ts +1 -1
  18. package/dist/decorators/parseDecorators.d.ts.map +1 -1
  19. package/dist/decorators/parseDecorators.js +131 -4
  20. package/dist/decorators/parseDecorators.js.map +1 -1
  21. package/dist/decorators/triggerDecorator.d.ts +14 -0
  22. package/dist/decorators/triggerDecorator.d.ts.map +1 -1
  23. package/dist/decorators/triggerDecorator.js +31 -2
  24. package/dist/decorators/triggerDecorator.js.map +1 -1
  25. package/dist/decorators/triggerRegistry.d.ts +8 -0
  26. package/dist/decorators/triggerRegistry.d.ts.map +1 -1
  27. package/dist/decorators/triggerRegistry.js +18 -0
  28. package/dist/decorators/triggerRegistry.js.map +1 -1
  29. package/dist/decorators/types/cloudTypes.d.ts +23 -0
  30. package/dist/decorators/types/cloudTypes.d.ts.map +1 -1
  31. package/dist/middleware/middleware.d.ts.map +1 -1
  32. package/dist/middleware/middleware.js +66 -5
  33. package/dist/middleware/middleware.js.map +1 -1
  34. package/dist/middleware/rateLimit.d.ts +25 -6
  35. package/dist/middleware/rateLimit.d.ts.map +1 -1
  36. package/dist/middleware/rateLimit.js +59 -13
  37. package/dist/middleware/rateLimit.js.map +1 -1
  38. package/dist/models/BaseModel.d.ts.map +1 -1
  39. package/dist/models/BaseModel.js +5 -2
  40. package/dist/models/BaseModel.js.map +1 -1
  41. package/dist/utils/dynamicImport.d.ts.map +1 -1
  42. package/dist/utils/dynamicImport.js +33 -11
  43. package/dist/utils/dynamicImport.js.map +1 -1
  44. package/dist/utils/roleCache.d.ts.map +1 -1
  45. package/dist/utils/roleCache.js +4 -1
  46. package/dist/utils/roleCache.js.map +1 -1
  47. package/package.json +8 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,183 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@90soft/parse-server-kit`.
4
+
5
+ > The public continuation of this library is published as **`parse-server-kit`**,
6
+ > starting at 3.0.0. Same source; the two are not interchangeable at matching
7
+ > version numbers.
8
+
9
+ ---
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
+
95
+ ## [2.10.1]
96
+
97
+ Twelve fixes found by a full read of the source, each verified against a running
98
+ server rather than by inspection. Two change behaviour; both are noted.
99
+
100
+ Checked against SMC before shipping: it uses neither `requiresAuth` nor
101
+ `rateLimit`, and has no intermediate model base classes — so nothing here
102
+ changes how it behaves.
103
+
104
+ ### Fixed
105
+
106
+ - **Field and trigger metadata leaked across an inheritance tree.**
107
+ `Reflect.getMetadata` walks the prototype chain, so a subclass with no
108
+ metadata of its own was handed the *parent's* object — which was then mutated
109
+ in place. Every class in the tree ended up sharing one record:
110
+
111
+ ```ts
112
+ class Auditable extends BaseModel { @ParseField() declare createdBy }
113
+ class Product extends Auditable { @ParseField() declare name }
114
+ class Order extends Auditable { @ParseField() declare total }
115
+ // Product gained `total`, Order gained `name`, Auditable gained both
116
+ ```
117
+
118
+ The result was wrong schemas, `fromParams` converting fields the model does
119
+ not have, `validateObject` demanding foreign required fields, and
120
+ `applyAllIndexes` indexing columns that do not exist — none of it reported.
121
+
122
+ Both `parse:fields` and `parse:pendingTriggers` now copy on first write.
123
+ **Identical for a class extending `BaseModel` directly**, which carries no
124
+ field metadata — so the common case is unchanged.
125
+
126
+ - **`requiresAuth` enforced nothing.** It was read only by the OpenAPI renderer,
127
+ so an endpoint declaring it was reachable with no session *while the docs drew
128
+ a padlock on it*. It is now checked in the handler wrapper before the body
129
+ runs; a call carrying the master key still passes.
130
+ **Behaviour change** — endpoints that were accidentally open are now closed.
131
+
132
+ - **`rateLimit` could be skipped entirely.** It was applied only in
133
+ `validateEntityRoutes`, which returns early for anything that is not a
134
+ registered entity prefix — so asking for `/functions/{name}` directly bypassed
135
+ it. `restrictRoutes` now applies it on that path too. **Behaviour change.**
136
+
137
+ The declared `methods` deliberately do **not** follow. `/functions/{name}` is
138
+ Parse's own protocol endpoint and every SDK's `Parse.Cloud.run` POSTs to it,
139
+ so enforcing them there would make any GET-declared function unreachable from
140
+ a Parse client.
141
+
142
+ - **Declaring a field `unique` dropped compound indexes containing it.** The
143
+ conflict check matched any non-unique index *containing* the field, so
144
+ `unique: true` on `status` alongside a compound index on
145
+ `['status', 'createdAt']` dropped the compound one on every boot. An index
146
+ created outside this library was dropped for good.
147
+
148
+ - **The master key was compared with `===`**, which short-circuits on the first
149
+ differing character and leaks the key a character at a time under timing
150
+ analysis. Now `crypto.timingSafeEqual`.
151
+
152
+ - **`restrictRoutes` matched `/functions` by prefix**, so `/functionsX` passed.
153
+ Now an exact segment match.
154
+
155
+ - **Malformed JSON mentioning a master key both answered 400 and threw**,
156
+ leaving Express reporting "Cannot set headers after they are sent" on top of
157
+ the real error. It throws only; body-parser answers 400. *The response body
158
+ for that narrow case is body-parser's rather than this library's.*
159
+
160
+ - **`readCachedRoles` returned the cache's own array.** A caller sorting or
161
+ filtering it in place rewrote what every later permission check saw.
162
+
163
+ - **`fromParams` wrote `className` into the caller's `req.params`.** It works on
164
+ a copy, so a retried transaction no longer sees an input the first attempt
165
+ altered.
166
+
167
+ - **`validateFunctionRoutes` 404'd everything** unless mounted at
168
+ `{mountPath}/functions`, because it read the path segment blindly. It now
169
+ works at either mount point.
170
+
171
+ ### Added
172
+
173
+ - `test/inheritance.test.ts` — pins the metadata fix, including the sibling-leak
174
+ case and version fields across inheritance.
175
+
176
+ ---
177
+
178
+ ## [2.10.0]
179
+
180
+ - Decorator order between `@CloudFunction` and `@Transactional` no longer
181
+ matters; the registry re-reads the method when it registers it.
182
+ - `@ParseField` properties are repaired when a class field shadows the prototype
183
+ accessor, so `title!: string` no longer silently drops writes.