@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.
- package/CHANGELOG.md +183 -0
- package/CLAUDE.md +734 -698
- package/dist/config.d.ts +68 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/database/indexes.d.ts.map +1 -1
- package/dist/database/indexes.js +14 -1
- package/dist/database/indexes.js.map +1 -1
- package/dist/decorators/cloudDecorator.d.ts.map +1 -1
- package/dist/decorators/cloudDecorator.js +19 -0
- package/dist/decorators/cloudDecorator.js.map +1 -1
- package/dist/decorators/cloudRegistry.d.ts +13 -0
- package/dist/decorators/cloudRegistry.d.ts.map +1 -1
- package/dist/decorators/cloudRegistry.js +23 -1
- package/dist/decorators/cloudRegistry.js.map +1 -1
- package/dist/decorators/parseDecorators.d.ts +1 -1
- package/dist/decorators/parseDecorators.d.ts.map +1 -1
- package/dist/decorators/parseDecorators.js +131 -4
- package/dist/decorators/parseDecorators.js.map +1 -1
- package/dist/decorators/triggerDecorator.d.ts +14 -0
- package/dist/decorators/triggerDecorator.d.ts.map +1 -1
- package/dist/decorators/triggerDecorator.js +31 -2
- package/dist/decorators/triggerDecorator.js.map +1 -1
- package/dist/decorators/triggerRegistry.d.ts +8 -0
- package/dist/decorators/triggerRegistry.d.ts.map +1 -1
- package/dist/decorators/triggerRegistry.js +18 -0
- package/dist/decorators/triggerRegistry.js.map +1 -1
- package/dist/decorators/types/cloudTypes.d.ts +23 -0
- package/dist/decorators/types/cloudTypes.d.ts.map +1 -1
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +66 -5
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/rateLimit.d.ts +25 -6
- package/dist/middleware/rateLimit.d.ts.map +1 -1
- package/dist/middleware/rateLimit.js +59 -13
- package/dist/middleware/rateLimit.js.map +1 -1
- package/dist/models/BaseModel.d.ts.map +1 -1
- package/dist/models/BaseModel.js +5 -2
- package/dist/models/BaseModel.js.map +1 -1
- package/dist/utils/dynamicImport.d.ts.map +1 -1
- package/dist/utils/dynamicImport.js +33 -11
- package/dist/utils/dynamicImport.js.map +1 -1
- package/dist/utils/roleCache.d.ts.map +1 -1
- package/dist/utils/roleCache.js +4 -1
- package/dist/utils/roleCache.js.map +1 -1
- 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.
|