@90soft/parse-server-kit 2.11.0 → 2.12.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,88 @@ All notable changes to `@90soft/parse-server-kit`.
8
8
 
9
9
  ---
10
10
 
11
+ ## [2.12.0]
12
+
13
+ ### Added
14
+
15
+ - **File access rules — a file's PATH can now carry a permission.**
16
+
17
+ Parse has no per-file permission and cannot have one: the file router is
18
+ handed a name, and a name does not know which row references it. So a file
19
+ url is a bearer capability — anyone holding the string gets the bytes, with
20
+ no session, no ACL check and no expiry. Fine for published pictures; not fine
21
+ for a document, where a url that leaks once works forever, for anyone.
22
+
23
+ Parse does give one hook, `beforeFind` on `Parse.File`. This release owns it
24
+ and makes it usable:
25
+
26
+ ```ts
27
+ import {registerFileAccessRule, privateFileRule, attachPrivateFile} from '@90soft/parse-server-kit';
28
+
29
+ registerFileAccessRule(privateFileRule()); // once, in cloud code
30
+ await attachPrivateFile(row, 'document', upload); // stores under private/<Class>/<objectId>/
31
+ ```
32
+
33
+ A file stored under `private/<ClassName>/<objectId>/` is then served only to
34
+ a caller who can read THAT row, loaded **as the caller** so Parse evaluates
35
+ the row's ACL and the class's CLP exactly as it would for a query. The
36
+ permission is not a copy of the row's ACL; it IS the row's ACL — nothing to
37
+ stamp, nothing to sync, no second source of truth.
38
+
39
+ Why not signed urls: a signed url is the same bearer token on a timer. It
40
+ leaks the same ways, forces a refresh dance and cache invalidation on every
41
+ client, and cannot express "this account was banned thirty seconds ago". The
42
+ rule re-checks every request.
43
+
44
+ **Nothing is registered until you add a rule**, so upgrading changes no
45
+ behaviour in a project that does not opt in, and a project that already
46
+ registers its own file trigger is not quietly overridden.
47
+
48
+ **Five traps this exists to absorb**, each of which cost real debugging:
49
+
50
+ 1. **There is exactly ONE slot.** File triggers are keyed on the pseudo-class
51
+ `@File`, so a second `beforeFind(Parse.File)` anywhere REPLACES the first
52
+ — Parse logs "Only the last one will be used" and keeps serving. Two
53
+ features each registering a rule would silently disable one. Hence a
54
+ registry: the kit holds the slot, rules compose in registration order.
55
+ 2. **Response headers come from `afterFind`.** `beforeFind`'s
56
+ `responseHeaders` are discarded. A rule's `headers` are applied in the
57
+ right place, including on a refusal, so a denial is not cached either.
58
+ 3. **Only a header authenticates.** The file GET route reads the session from
59
+ `X-Parse-Session-Token` and nowhere else — no query parameter, no cookie.
60
+ A browser `<img src>` therefore cannot fetch a governed file; fetch it and
61
+ bind a blob. The upside: nobody can "simplify" this by putting a session
62
+ token in a url, because Parse will not read one there.
63
+ 4. **A throw is global.** This runs on every file download in the app, so a
64
+ mistake does not break documents — it blanks every image at once. The
65
+ public path is one string comparison before any `await`, a rule whose
66
+ predicate throws is skipped rather than fatal, and the behaviour is
67
+ tested.
68
+ 5. **Fail-open is the invisible failure.** "I cannot tell who owns this" must
69
+ mean refuse. `isPrivatePath` (is it governed?) and `privateOwnerOf` (who
70
+ owns it?) are deliberately separate functions, because a single predicate
71
+ returning `owner | null` conflates them: it answers `null` both for an
72
+ ordinary image and for `private/<Class>/<id>/extra/doc.jpg`, one directory
73
+ deeper than expected — and would serve that document to anybody, with the
74
+ symptom that it loads perfectly.
75
+
76
+ `Parse.File.setDirectory` / `.name()` / `.directory()` are now declared, and
77
+ `Parse.Cloud.beforeFind` / `afterFind` accept a class as well as a class name.
78
+ `@types/parse` is a hand-written stub that predates file directories (and is
79
+ empty from 6.x), so these are declared once here rather than cast at every
80
+ call site.
81
+
82
+ Requires Parse Server **>= 9.5.0** for file directories; verified against
83
+ 9.10.4. 20 tests.
84
+
85
+ ### Changed
86
+
87
+ - Development now runs against `parse-server` **9.10.4** (was 9.10.1). The peer
88
+ range is unchanged at `>=9.0.0` — nothing here needs the newer server except
89
+ the file directories above, which are a 9.5.0 feature.
90
+
91
+ ---
92
+
11
93
  ## [2.11.0]
12
94
 
13
95
  ### Security
@@ -0,0 +1,224 @@
1
+ /**
2
+ * File access rules — turn a file's PATH into a permission.
3
+ *
4
+ * Parse has no per-file permission and cannot have one: the file router is
5
+ * handed a name, and a name does not know which row references it. So a file
6
+ * url is a bearer capability — anyone holding the string gets the bytes, with
7
+ * no session, no ACL check and no expiry. For published pictures that is
8
+ * correct and cheap. For a document (a certificate, an ID photo, an invoice) it
9
+ * is not: a url that leaks once works forever, for anyone, which is a
10
+ * REVOCATION problem that signed urls do not solve — a signed url is the same
11
+ * bearer token on a timer, and it cannot express "this account was banned
12
+ * thirty seconds ago".
13
+ *
14
+ * Parse does give us one hook: `beforeFind` on `Parse.File`, which runs on
15
+ * every `GET /files/...` and can throw to refuse. This module owns that hook
16
+ * and makes it usable, because using it directly has five traps:
17
+ *
18
+ * 1. **There is exactly ONE slot.** File triggers are keyed on the
19
+ * pseudo-class `@File`, not on a class, so a second
20
+ * `beforeFind(Parse.File)` anywhere REPLACES the first — Parse logs
21
+ * "Only the last one will be used" and carries on serving. Two features
22
+ * each registering their own rule would silently disable one of them.
23
+ * Hence a registry: this module holds the slot, rules compose.
24
+ * 2. **The headers come from `afterFind`.** `beforeFind`'s `responseHeaders`
25
+ * are discarded; only the `afterFind` request's are written to the
26
+ * response. A rule that marks a response uncacheable has to act there.
27
+ * 3. **Only a header authenticates.** The file GET route reads the session
28
+ * from `X-Parse-Session-Token` and nowhere else — no query parameter, no
29
+ * cookie. A browser `<img src>` therefore cannot fetch a governed file;
30
+ * the client must fetch it and bind a blob. The upside is that nobody can
31
+ * "simplify" this by putting a session token in a url.
32
+ * 4. **A throw is global.** This runs on every file download in the app, so
33
+ * a mistake does not break documents, it blanks every image at once.
34
+ * Everything on the public path here is a string comparison, before any
35
+ * `await`, and cannot throw.
36
+ * 5. **Fail-open is the invisible failure.** If "I cannot tell who owns
37
+ * this" is treated as "public", the document is served and the symptom is
38
+ * that it loads perfectly. So governed-but-unidentifiable must REFUSE.
39
+ *
40
+ * **Nothing is registered until a rule is added.** Upgrading the kit therefore
41
+ * changes no behaviour in a project that does not opt in, and a project that
42
+ * already registers its own file trigger is not quietly overridden.
43
+ *
44
+ * The usual setup, in cloud code:
45
+ *
46
+ * ```ts
47
+ * import {registerFileAccessRule, privateFileRule} from '@90soft/parse-server-kit';
48
+ * registerFileAccessRule(privateFileRule());
49
+ * ```
50
+ *
51
+ * [privateFileRule] is the general case: a file stored under
52
+ * `private/<ClassName>/<objectId>/` is served only to a caller who can read
53
+ * THAT row, judged by Parse's own ACL and CLP. The permission is not a copy of
54
+ * the row's ACL; it IS the row's ACL — nothing to stamp, nothing to sync.
55
+ */
56
+ /**
57
+ * What a FILE trigger is handed, which is not what a class trigger gets: there
58
+ * is no `object` and no `query`, only the file.
59
+ *
60
+ * Declared HERE rather than taken from the ambient Parse types on purpose.
61
+ * `@types/parse` does not describe file triggers at all, so a consumer relying
62
+ * on it could not name the argument of [evaluateFileAccess] — a published
63
+ * library should not require its callers to declare types for its own API.
64
+ */
65
+ export interface FileTriggerRequest {
66
+ file?: Parse.File;
67
+ fileSize?: number;
68
+ master?: boolean;
69
+ isReadOnly?: boolean;
70
+ user?: Parse.User;
71
+ ip?: string;
72
+ forceDownload?: boolean;
73
+ responseHeaders?: Record<string, string>;
74
+ }
75
+ /** The decision a rule is asked to make. */
76
+ export interface FileAccessContext {
77
+ /** The stored path, including any directory prefix. */
78
+ path: string;
79
+ /** The caller, when they sent a session token. */
80
+ user?: Parse.User;
81
+ /** True for a master-key request. */
82
+ master: boolean;
83
+ }
84
+ /** One rule, governing some subset of stored files. */
85
+ export interface FileAccessRule {
86
+ /**
87
+ * Identifies the rule in diagnostics, and makes replacing one deliberate:
88
+ * registering the same name twice warns rather than stacking silently.
89
+ */
90
+ name: string;
91
+ /**
92
+ * Does this rule govern that path?
93
+ *
94
+ * **Must not throw and should not await.** It is consulted for every file
95
+ * download in the application, including every public image.
96
+ */
97
+ governs(path: string): boolean;
98
+ /**
99
+ * Decide. Throw a `Parse.Error` to refuse.
100
+ *
101
+ * Returning normally allows the download. Only called when [governs] said
102
+ * yes and the request is not master-key.
103
+ */
104
+ authorize(context: FileAccessContext): Promise<void> | void;
105
+ /**
106
+ * Headers to add to a governed response, e.g. `Cache-Control`.
107
+ *
108
+ * Applied whenever [governs] matches — including on a refusal, so that a
109
+ * denial is not cached either.
110
+ */
111
+ headers?: Record<string, string>;
112
+ }
113
+ /**
114
+ * The gate. Public files leave before anything is awaited.
115
+ *
116
+ * Exported so a project can unit-test its rules without going through HTTP.
117
+ */
118
+ export declare function evaluateFileAccess(request: FileTriggerRequest): Promise<void>;
119
+ /**
120
+ * Add the governed response's headers.
121
+ *
122
+ * Separate from [evaluateFileAccess] because Parse reads response headers from
123
+ * the `afterFind` request and ignores `beforeFind`'s.
124
+ */
125
+ export declare function applyFileAccessHeaders(request: FileTriggerRequest): void;
126
+ /**
127
+ * Register a rule, attaching the Parse hooks on the first call.
128
+ *
129
+ * Order matters: rules are consulted in registration order and the first match
130
+ * decides, so register the most specific first.
131
+ */
132
+ export declare function registerFileAccessRule(rule: FileAccessRule): void;
133
+ /** Registered rules, in order. For diagnostics and tests. */
134
+ export declare function getFileAccessRules(): FileAccessRule[];
135
+ /**
136
+ * Drop every rule. For tests.
137
+ *
138
+ * The Parse hooks stay attached — they are harmless with no rules, since every
139
+ * path is then public — and re-attaching would hit Parse's duplicate warning.
140
+ */
141
+ export declare function clearFileAccessRules(): void;
142
+ /**
143
+ * The directory prefix marking a file as governed by [privateFileRule].
144
+ *
145
+ * Parse's own `validateDirectory` reserves `metadata` and requires a directory
146
+ * to match `^[a-zA-Z0-9][a-zA-Z0-9_\-/]*$`, so a single lowercase word is safe.
147
+ * It is baked into stored file names, so changing it would orphan every file
148
+ * already written under the old one.
149
+ */
150
+ export declare const PRIVATE_PREFIX = "private";
151
+ /** Who a private file's path says owns it. */
152
+ export interface PrivateFileOwner {
153
+ className: string;
154
+ objectId: string;
155
+ }
156
+ /**
157
+ * Is this path governed at all?
158
+ *
159
+ * Deliberately separate from [privateOwnerOf], and the separation is the
160
+ * safety property: "under the prefix" and "parses into an owner" are different
161
+ * questions, and a single predicate returning `owner | null` conflates them
162
+ * into a fail-OPEN. It would answer `null` both for an ordinary image and for
163
+ * `private/<Class>/<id>/extra/doc.jpg` — one directory deeper than expected —
164
+ * and so serve that document to anybody.
165
+ */
166
+ export declare function isPrivatePath(path: string): boolean;
167
+ /**
168
+ * Read the owner out of a governed path, or null if it is not well formed.
169
+ *
170
+ * `null` means "I cannot tell who owns this", which the rule treats as a
171
+ * refusal — never as permission.
172
+ */
173
+ export declare function privateOwnerOf(path: string): PrivateFileOwner | null;
174
+ /**
175
+ * The directory a row's private files live in.
176
+ *
177
+ * Throws rather than quietly producing something Parse will reject: a class
178
+ * whose name cannot be a path segment (`_User`, since a directory must start
179
+ * alphanumeric) surfaces here, at the one call site, instead of as a confusing
180
+ * upload failure later.
181
+ */
182
+ export declare function privateDirectoryFor(className: string, objectId: string): string;
183
+ /**
184
+ * Store a file so that the owning row's ACL governs it.
185
+ *
186
+ * The row must already be saved — its objectId is part of the path, which is
187
+ * what lets the rule find the owner with no index and no second source of
188
+ * truth. For a new record that means save, attach, save again: one extra write
189
+ * on a rare operation.
190
+ *
191
+ * **This should be the only way an application stores a private file.** The
192
+ * prefix is the entire boundary: a document saved without it is simply public,
193
+ * and nothing anywhere will complain.
194
+ *
195
+ * Private files deliberately get no derivatives — no converted copy, no
196
+ * thumbnail, and no blurhash, which is a small preview of the document and
197
+ * travels inside DTOs where the document itself does not.
198
+ */
199
+ export declare function attachPrivateFile(row: Parse.Object, field: string, upload: {
200
+ base64: string;
201
+ name?: string;
202
+ }): Promise<void>;
203
+ /** Options for [privateFileRule]. */
204
+ export interface PrivateFileRuleOptions {
205
+ /**
206
+ * Headers for a private response. Defaults to `private, no-store`.
207
+ *
208
+ * One cached private response served to the next caller undoes the whole
209
+ * check, and the symptom is that the document loads — so this defaults to
210
+ * the strict value rather than being left to the caller.
211
+ */
212
+ headers?: Record<string, string>;
213
+ }
214
+ /**
215
+ * Serve a private file only to a caller who can read its owning row.
216
+ *
217
+ * The caller is resolved from their session token, so Parse evaluates the
218
+ * row's ACL and the class's CLP exactly as it would for a query. With no
219
+ * session the row is loaded with no auth, which is right rather than a
220
+ * shortcut: if the owning row is publicly readable then its file is public
221
+ * too, because the row's ACL is the whole rule.
222
+ */
223
+ export declare function privateFileRule(options?: PrivateFileRuleOptions): FileAccessRule;
224
+ //# sourceMappingURL=fileAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileAccess.d.ts","sourceRoot":"","sources":["../../src/files/fileAccess.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;IAClB,qCAAqC;IACrC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE/B;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAiCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,GAC1B,IAAI,CAON;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAsBjE;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc/E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,KAAK,CAAC,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED,qCAAqC;AACrC,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,OAAO,GAAE,sBAA2B,GACnC,cAAc,CA8BhB"}
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ /**
3
+ * File access rules — turn a file's PATH into a permission.
4
+ *
5
+ * Parse has no per-file permission and cannot have one: the file router is
6
+ * handed a name, and a name does not know which row references it. So a file
7
+ * url is a bearer capability — anyone holding the string gets the bytes, with
8
+ * no session, no ACL check and no expiry. For published pictures that is
9
+ * correct and cheap. For a document (a certificate, an ID photo, an invoice) it
10
+ * is not: a url that leaks once works forever, for anyone, which is a
11
+ * REVOCATION problem that signed urls do not solve — a signed url is the same
12
+ * bearer token on a timer, and it cannot express "this account was banned
13
+ * thirty seconds ago".
14
+ *
15
+ * Parse does give us one hook: `beforeFind` on `Parse.File`, which runs on
16
+ * every `GET /files/...` and can throw to refuse. This module owns that hook
17
+ * and makes it usable, because using it directly has five traps:
18
+ *
19
+ * 1. **There is exactly ONE slot.** File triggers are keyed on the
20
+ * pseudo-class `@File`, not on a class, so a second
21
+ * `beforeFind(Parse.File)` anywhere REPLACES the first — Parse logs
22
+ * "Only the last one will be used" and carries on serving. Two features
23
+ * each registering their own rule would silently disable one of them.
24
+ * Hence a registry: this module holds the slot, rules compose.
25
+ * 2. **The headers come from `afterFind`.** `beforeFind`'s `responseHeaders`
26
+ * are discarded; only the `afterFind` request's are written to the
27
+ * response. A rule that marks a response uncacheable has to act there.
28
+ * 3. **Only a header authenticates.** The file GET route reads the session
29
+ * from `X-Parse-Session-Token` and nowhere else — no query parameter, no
30
+ * cookie. A browser `<img src>` therefore cannot fetch a governed file;
31
+ * the client must fetch it and bind a blob. The upside is that nobody can
32
+ * "simplify" this by putting a session token in a url.
33
+ * 4. **A throw is global.** This runs on every file download in the app, so
34
+ * a mistake does not break documents, it blanks every image at once.
35
+ * Everything on the public path here is a string comparison, before any
36
+ * `await`, and cannot throw.
37
+ * 5. **Fail-open is the invisible failure.** If "I cannot tell who owns
38
+ * this" is treated as "public", the document is served and the symptom is
39
+ * that it loads perfectly. So governed-but-unidentifiable must REFUSE.
40
+ *
41
+ * **Nothing is registered until a rule is added.** Upgrading the kit therefore
42
+ * changes no behaviour in a project that does not opt in, and a project that
43
+ * already registers its own file trigger is not quietly overridden.
44
+ *
45
+ * The usual setup, in cloud code:
46
+ *
47
+ * ```ts
48
+ * import {registerFileAccessRule, privateFileRule} from '@90soft/parse-server-kit';
49
+ * registerFileAccessRule(privateFileRule());
50
+ * ```
51
+ *
52
+ * [privateFileRule] is the general case: a file stored under
53
+ * `private/<ClassName>/<objectId>/` is served only to a caller who can read
54
+ * THAT row, judged by Parse's own ACL and CLP. The permission is not a copy of
55
+ * the row's ACL; it IS the row's ACL — nothing to stamp, nothing to sync.
56
+ */
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.PRIVATE_PREFIX = void 0;
59
+ exports.evaluateFileAccess = evaluateFileAccess;
60
+ exports.applyFileAccessHeaders = applyFileAccessHeaders;
61
+ exports.registerFileAccessRule = registerFileAccessRule;
62
+ exports.getFileAccessRules = getFileAccessRules;
63
+ exports.clearFileAccessRules = clearFileAccessRules;
64
+ exports.isPrivatePath = isPrivatePath;
65
+ exports.privateOwnerOf = privateOwnerOf;
66
+ exports.privateDirectoryFor = privateDirectoryFor;
67
+ exports.attachPrivateFile = attachPrivateFile;
68
+ exports.privateFileRule = privateFileRule;
69
+ /** Ordered; the first rule that governs a path decides it. */
70
+ const rules = [];
71
+ /** The hook is attached once, lazily, on the first registration. */
72
+ let attached = false;
73
+ /**
74
+ * 404, never 403.
75
+ *
76
+ * A 403 confirms the file is there and merely forbidden, which for a document
77
+ * is itself the leak — it tells the caller that a particular row has one.
78
+ * "Not found" is the same answer an invented name would get.
79
+ */
80
+ function notFound() {
81
+ return new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'File not found');
82
+ }
83
+ /** The first rule governing this path, or undefined when the file is public. */
84
+ function ruleFor(path) {
85
+ for (const rule of rules) {
86
+ // A rule that throws here would blank every image in the app, so one
87
+ // misbehaving rule must not take the public path down with it.
88
+ try {
89
+ if (rule.governs(path))
90
+ return rule;
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ /**
99
+ * The gate. Public files leave before anything is awaited.
100
+ *
101
+ * Exported so a project can unit-test its rules without going through HTTP.
102
+ */
103
+ async function evaluateFileAccess(request) {
104
+ const path = request.file?.name() ?? '';
105
+ const rule = ruleFor(path);
106
+ if (!rule)
107
+ return;
108
+ // Cloud code reading deliberately with the master key. Note this is NOT how
109
+ // a worker that fetches a url reaches a file: a plain GET carries no key.
110
+ if (request.master)
111
+ return;
112
+ await rule.authorize({
113
+ path,
114
+ user: request.user,
115
+ master: false,
116
+ });
117
+ }
118
+ /**
119
+ * Add the governed response's headers.
120
+ *
121
+ * Separate from [evaluateFileAccess] because Parse reads response headers from
122
+ * the `afterFind` request and ignores `beforeFind`'s.
123
+ */
124
+ function applyFileAccessHeaders(request) {
125
+ const rule = ruleFor(request.file?.name() ?? '');
126
+ if (!rule?.headers)
127
+ return;
128
+ // The incoming object already carries `X-Content-Type-Options: nosniff`, so
129
+ // extend it rather than replacing it.
130
+ request.responseHeaders = { ...(request.responseHeaders ?? {}), ...rule.headers };
131
+ }
132
+ /**
133
+ * Register a rule, attaching the Parse hooks on the first call.
134
+ *
135
+ * Order matters: rules are consulted in registration order and the first match
136
+ * decides, so register the most specific first.
137
+ */
138
+ function registerFileAccessRule(rule) {
139
+ const existing = rules.findIndex(r => r.name === rule.name);
140
+ if (existing >= 0) {
141
+ console.warn(`[FileAccess] Replacing existing rule "${rule.name}". Two rules with one name is almost always an accident.`);
142
+ rules.splice(existing, 1, rule);
143
+ }
144
+ else {
145
+ rules.push(rule);
146
+ }
147
+ if (attached)
148
+ return;
149
+ attached = true;
150
+ /*
151
+ * `Parse.File` is passed as the class, which Parse maps to `@File`. This is
152
+ * the only pair of triggers in a kit project that is not declared on a model
153
+ * with `@BeforeFind` / `@AfterFind`, because there is no model to declare it
154
+ * on.
155
+ */
156
+ Parse.Cloud.beforeFind(Parse.File, evaluateFileAccess);
157
+ Parse.Cloud.afterFind(Parse.File, applyFileAccessHeaders);
158
+ }
159
+ /** Registered rules, in order. For diagnostics and tests. */
160
+ function getFileAccessRules() {
161
+ return [...rules];
162
+ }
163
+ /**
164
+ * Drop every rule. For tests.
165
+ *
166
+ * The Parse hooks stay attached — they are harmless with no rules, since every
167
+ * path is then public — and re-attaching would hit Parse's duplicate warning.
168
+ */
169
+ function clearFileAccessRules() {
170
+ rules.length = 0;
171
+ }
172
+ // ── The private-file rule ───────────────────────────────────────────────────
173
+ /**
174
+ * The directory prefix marking a file as governed by [privateFileRule].
175
+ *
176
+ * Parse's own `validateDirectory` reserves `metadata` and requires a directory
177
+ * to match `^[a-zA-Z0-9][a-zA-Z0-9_\-/]*$`, so a single lowercase word is safe.
178
+ * It is baked into stored file names, so changing it would orphan every file
179
+ * already written under the old one.
180
+ */
181
+ exports.PRIVATE_PREFIX = 'private';
182
+ /**
183
+ * Is this path governed at all?
184
+ *
185
+ * Deliberately separate from [privateOwnerOf], and the separation is the
186
+ * safety property: "under the prefix" and "parses into an owner" are different
187
+ * questions, and a single predicate returning `owner | null` conflates them
188
+ * into a fail-OPEN. It would answer `null` both for an ordinary image and for
189
+ * `private/<Class>/<id>/extra/doc.jpg` — one directory deeper than expected —
190
+ * and so serve that document to anybody.
191
+ */
192
+ function isPrivatePath(path) {
193
+ return typeof path === 'string' && path.startsWith(`${exports.PRIVATE_PREFIX}/`);
194
+ }
195
+ /**
196
+ * Read the owner out of a governed path, or null if it is not well formed.
197
+ *
198
+ * `null` means "I cannot tell who owns this", which the rule treats as a
199
+ * refusal — never as permission.
200
+ */
201
+ function privateOwnerOf(path) {
202
+ if (!isPrivatePath(path))
203
+ return null;
204
+ // Exactly: private / ClassName / objectId / filename
205
+ const parts = path.split('/');
206
+ if (parts.length !== 4)
207
+ return null;
208
+ const [, className, objectId, filename] = parts;
209
+ if (!className || !objectId || !filename)
210
+ return null;
211
+ return { className, objectId };
212
+ }
213
+ /**
214
+ * The directory a row's private files live in.
215
+ *
216
+ * Throws rather than quietly producing something Parse will reject: a class
217
+ * whose name cannot be a path segment (`_User`, since a directory must start
218
+ * alphanumeric) surfaces here, at the one call site, instead of as a confusing
219
+ * upload failure later.
220
+ */
221
+ function privateDirectoryFor(className, objectId) {
222
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_]*$/.test(className)) {
223
+ throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${className} cannot own private files: Parse requires a directory segment to start with a letter or digit`);
224
+ }
225
+ if (!/^[a-zA-Z0-9]+$/.test(objectId)) {
226
+ throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'A private file needs the owning row saved first, so that it has an objectId');
227
+ }
228
+ return `${exports.PRIVATE_PREFIX}/${className}/${objectId}`;
229
+ }
230
+ /**
231
+ * Store a file so that the owning row's ACL governs it.
232
+ *
233
+ * The row must already be saved — its objectId is part of the path, which is
234
+ * what lets the rule find the owner with no index and no second source of
235
+ * truth. For a new record that means save, attach, save again: one extra write
236
+ * on a rare operation.
237
+ *
238
+ * **This should be the only way an application stores a private file.** The
239
+ * prefix is the entire boundary: a document saved without it is simply public,
240
+ * and nothing anywhere will complain.
241
+ *
242
+ * Private files deliberately get no derivatives — no converted copy, no
243
+ * thumbnail, and no blurhash, which is a small preview of the document and
244
+ * travels inside DTOs where the document itself does not.
245
+ */
246
+ async function attachPrivateFile(row, field, upload) {
247
+ if (!row.id) {
248
+ throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Save the row before attaching a private file — its objectId is part of the path');
249
+ }
250
+ const file = new Parse.File(encodeURIComponent(upload.name ?? 'document'), {
251
+ base64: upload.base64,
252
+ });
253
+ // Sent as `X-Parse-File-Directory`; honoured by Parse Server from 9.5.0.
254
+ file.setDirectory(privateDirectoryFor(row.className, row.id));
255
+ const saved = await file.save({ useMasterKey: true });
256
+ row.set(field, saved);
257
+ }
258
+ /**
259
+ * Serve a private file only to a caller who can read its owning row.
260
+ *
261
+ * The caller is resolved from their session token, so Parse evaluates the
262
+ * row's ACL and the class's CLP exactly as it would for a query. With no
263
+ * session the row is loaded with no auth, which is right rather than a
264
+ * shortcut: if the owning row is publicly readable then its file is public
265
+ * too, because the row's ACL is the whole rule.
266
+ */
267
+ function privateFileRule(options = {}) {
268
+ const headers = options.headers ?? { 'Cache-Control': 'private, no-store' };
269
+ return {
270
+ name: 'private-prefix',
271
+ headers,
272
+ governs: isPrivatePath,
273
+ async authorize({ path, user }) {
274
+ /*
275
+ * Under the prefix but not a shape we understand: REFUSE.
276
+ *
277
+ * Treating it as public because no owner could be found is the one
278
+ * failure mode of this design that nobody would notice, because the
279
+ * document would load perfectly.
280
+ */
281
+ const owner = privateOwnerOf(path);
282
+ if (!owner)
283
+ throw notFound();
284
+ const sessionToken = user?.getSessionToken();
285
+ const query = new Parse.Query(owner.className);
286
+ const row = await (sessionToken
287
+ ? query.get(owner.objectId, { sessionToken })
288
+ : query.get(owner.objectId)).catch(() => null);
289
+ if (!row)
290
+ throw notFound();
291
+ },
292
+ };
293
+ }
294
+ //# sourceMappingURL=fileAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileAccess.js","sourceRoot":"","sources":["../../src/files/fileAccess.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export { configureRoleCache, invalidateRoles, roleCacheEnabled, roleCacheStats }
24
24
  export type { RoleCacheOptions } from './utils/roleCache';
25
25
  export { validateFunctionRoutes, validateEntityRoutes, restrictRoutes, removeResultMiddleware, conditionalJsonMiddleware } from './middleware/middleware';
26
26
  export { checkRateLimit } from './middleware/rateLimit';
27
+ export { registerFileAccessRule, getFileAccessRules, clearFileAccessRules, evaluateFileAccess, applyFileAccessHeaders, privateFileRule, attachPrivateFile, isPrivatePath, privateOwnerOf, privateDirectoryFor, PRIVATE_PREFIX } from './files/fileAccess';
28
+ export type { FileAccessRule, FileAccessContext, FileTriggerRequest, PrivateFileOwner, PrivateFileRuleOptions } from './files/fileAccess';
27
29
  export { createSchemaConfig } from './database/schema';
28
30
  export type { SchemaConfigOptions } from './database/schema';
29
31
  export { applyAllIndexes, applyUniqueIndexes, getUniqueIndexes, getCompoundIndexes, getFieldIndexes } from './database/indexes';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  // @90soft/parse-server-kit — complete toolkit for Parse Server
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.getUserRoles = exports.catchError = exports.MAX_QUERY_LIMIT = exports.roleKey = exports.UserRoles = exports.cloneAcl = exports.syncImageAcl = exports.implementACL = exports.BaseModel = exports.applyMongoValidators = exports.getValidators = exports.validateOrThrow = exports.validateObject = exports.classNames = exports.RouteRegistry = exports.Route = exports.CronRegistry = exports.CronSchedule = exports.Cron = exports.TriggerRegistry = exports.AfterEvent = exports.BeforeSubscribe = exports.BeforeConnect = exports.AfterSaveConfig = exports.BeforeSaveConfig = exports.AfterFindFile = exports.BeforeFindFile = exports.AfterDeleteFile = exports.BeforeDeleteFile = exports.AfterSaveFile = exports.BeforeSaveFile = exports.BeforePasswordResetRequest = exports.AfterLogout = exports.AfterLogin = exports.BeforeLogin = exports.AfterFind = exports.BeforeFind = exports.AfterDelete = exports.BeforeDelete = exports.AfterSave = exports.BeforeSave = exports.CloudFunctionRegistry = exports.onFunctionRegistered = exports.ProtectedCloudFunction = exports.CloudFunction = exports.onFieldRegistered = exports.onClassRegistered = exports.getSchemaDefinition = exports.ParseField = exports.ParseClass = void 0;
5
- exports.setupSwagger = exports.getSwaggerJson = exports.generateSwaggerSpec = exports.SwaggerRegistry = exports.NO_AMBIENT_TRANSACTION = exports.withAmbientSession = exports.CONFLICT_MESSAGE = exports.CONFLICT = exports.useTransactionAdapter = exports.currentSession = exports.inTransaction = exports.withTransaction = exports.Transactional = exports.VERSION_CONFLICT_MESSAGE = exports.VERSION_CONFLICT = exports.createVersionedMongoAdapter = exports.VersionRegistry = exports.ParseVersionField = exports.getFieldIndexes = exports.getCompoundIndexes = exports.getUniqueIndexes = exports.applyUniqueIndexes = exports.applyAllIndexes = exports.createSchemaConfig = exports.checkRateLimit = exports.conditionalJsonMiddleware = exports.removeResultMiddleware = exports.restrictRoutes = exports.validateEntityRoutes = exports.validateFunctionRoutes = exports.roleCacheStats = exports.roleCacheEnabled = exports.invalidateRoles = exports.configureRoleCache = exports.importFiles = exports.sleep = exports.generateRandomString = exports.generateRandomInteger = exports.generateRandomPassword = exports.formatCount = exports.getUsersRoles = void 0;
5
+ exports.generateSwaggerSpec = exports.SwaggerRegistry = exports.NO_AMBIENT_TRANSACTION = exports.withAmbientSession = exports.CONFLICT_MESSAGE = exports.CONFLICT = exports.useTransactionAdapter = exports.currentSession = exports.inTransaction = exports.withTransaction = exports.Transactional = exports.VERSION_CONFLICT_MESSAGE = exports.VERSION_CONFLICT = exports.createVersionedMongoAdapter = exports.VersionRegistry = exports.ParseVersionField = exports.getFieldIndexes = exports.getCompoundIndexes = exports.getUniqueIndexes = exports.applyUniqueIndexes = exports.applyAllIndexes = exports.createSchemaConfig = exports.PRIVATE_PREFIX = exports.privateDirectoryFor = exports.privateOwnerOf = exports.isPrivatePath = exports.attachPrivateFile = exports.privateFileRule = exports.applyFileAccessHeaders = exports.evaluateFileAccess = exports.clearFileAccessRules = exports.getFileAccessRules = exports.registerFileAccessRule = exports.checkRateLimit = exports.conditionalJsonMiddleware = exports.removeResultMiddleware = exports.restrictRoutes = exports.validateEntityRoutes = exports.validateFunctionRoutes = exports.roleCacheStats = exports.roleCacheEnabled = exports.invalidateRoles = exports.configureRoleCache = exports.importFiles = exports.sleep = exports.generateRandomString = exports.generateRandomInteger = exports.generateRandomPassword = exports.formatCount = exports.getUsersRoles = void 0;
6
+ exports.setupSwagger = exports.getSwaggerJson = void 0;
6
7
  // ── Decorators ──
7
8
  var parseDecorators_1 = require("./decorators/parseDecorators");
8
9
  Object.defineProperty(exports, "ParseClass", { enumerable: true, get: function () { return parseDecorators_1.ParseClass; } });
@@ -96,6 +97,23 @@ Object.defineProperty(exports, "removeResultMiddleware", { enumerable: true, get
96
97
  Object.defineProperty(exports, "conditionalJsonMiddleware", { enumerable: true, get: function () { return middleware_1.conditionalJsonMiddleware; } });
97
98
  var rateLimit_1 = require("./middleware/rateLimit");
98
99
  Object.defineProperty(exports, "checkRateLimit", { enumerable: true, get: function () { return rateLimit_1.checkRateLimit; } });
100
+ // ── File access ──────────────────────────────────────────────────────────────
101
+ // Parse has no per-file permission; these turn a file's PATH into one. The kit
102
+ // owns the single `beforeFind(Parse.File)` slot so that rules compose instead
103
+ // of silently replacing each other, and nothing is registered until a rule is
104
+ // added — so upgrading changes no behaviour for a project that does not opt in.
105
+ var fileAccess_1 = require("./files/fileAccess");
106
+ Object.defineProperty(exports, "registerFileAccessRule", { enumerable: true, get: function () { return fileAccess_1.registerFileAccessRule; } });
107
+ Object.defineProperty(exports, "getFileAccessRules", { enumerable: true, get: function () { return fileAccess_1.getFileAccessRules; } });
108
+ Object.defineProperty(exports, "clearFileAccessRules", { enumerable: true, get: function () { return fileAccess_1.clearFileAccessRules; } });
109
+ Object.defineProperty(exports, "evaluateFileAccess", { enumerable: true, get: function () { return fileAccess_1.evaluateFileAccess; } });
110
+ Object.defineProperty(exports, "applyFileAccessHeaders", { enumerable: true, get: function () { return fileAccess_1.applyFileAccessHeaders; } });
111
+ Object.defineProperty(exports, "privateFileRule", { enumerable: true, get: function () { return fileAccess_1.privateFileRule; } });
112
+ Object.defineProperty(exports, "attachPrivateFile", { enumerable: true, get: function () { return fileAccess_1.attachPrivateFile; } });
113
+ Object.defineProperty(exports, "isPrivatePath", { enumerable: true, get: function () { return fileAccess_1.isPrivatePath; } });
114
+ Object.defineProperty(exports, "privateOwnerOf", { enumerable: true, get: function () { return fileAccess_1.privateOwnerOf; } });
115
+ Object.defineProperty(exports, "privateDirectoryFor", { enumerable: true, get: function () { return fileAccess_1.privateDirectoryFor; } });
116
+ Object.defineProperty(exports, "PRIVATE_PREFIX", { enumerable: true, get: function () { return fileAccess_1.PRIVATE_PREFIX; } });
99
117
  // ── Database ──
100
118
  var schema_1 = require("./database/schema");
101
119
  Object.defineProperty(exports, "createSchemaConfig", { enumerable: true, get: function () { return schema_1.createSchemaConfig; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,+DAA+D;;;;;AAE/D,mBAAmB;AACnB,gEAAiI;AAAxH,6GAAA,UAAU,OAAA;AAAE,6GAAA,UAAU,OAAA;AAAE,sHAAA,mBAAmB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAG1F,8DAA0G;AAAjG,+GAAA,aAAa,OAAA;AAAE,wHAAA,sBAAsB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AACpE,4DAAmE;AAA1D,sHAAA,qBAAqB,OAAA;AAE9B,kEAKoF;AAL3E,8GAAA,UAAU,OAAA;AAAE,6GAAA,SAAS,OAAA;AAAE,gHAAA,YAAY,OAAA;AAAE,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,6GAAA,SAAS,OAAA;AAC9E,+GAAA,WAAW,OAAA;AAAE,8GAAA,UAAU,OAAA;AAAE,+GAAA,WAAW,OAAA;AAAE,8HAAA,0BAA0B,OAAA;AAChE,kHAAA,cAAc,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAAE,mHAAA,eAAe,OAAA;AAChE,kHAAA,cAAc,OAAA;AAAE,iHAAA,aAAa,OAAA;AAC7B,oHAAA,gBAAgB,OAAA;AAAE,mHAAA,eAAe,OAAA;AACjC,iHAAA,aAAa,OAAA;AAAE,mHAAA,eAAe,OAAA;AAAE,8GAAA,UAAU,OAAA;AAC5C,gEAA+D;AAAtD,kHAAA,eAAe,OAAA;AAExB,4DAA8E;AAArE,qGAAA,IAAI,OAAA;AAAE,6GAAA,YAAY,OAAA;AAAE,6GAAA,YAAY,OAAA;AAEzC,8DAAmE;AAA1D,uGAAA,KAAK,OAAA;AAAE,+GAAA,aAAa,OAAA;AAE7B,cAAc;AACd,8DAA4D;AAAnD,yGAAA,UAAU,OAAA;AASnB,mBAAmB;AACnB,sEAA0H;AAAjH,iHAAA,cAAc,OAAA;AAAE,kHAAA,eAAe,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,uHAAA,oBAAoB,OAAA;AAE7E,eAAe;AACf,gDAA+C;AAAtC,sGAAA,SAAS,OAAA;AAElB,0BAA0B;AAC1B,mCAA2C;AAAlC,mGAAA,YAAY,OAAA;AACrB,6CAA0D;AAAjD,wGAAA,YAAY,OAAA;AAAE,oGAAA,QAAQ,OAAA;AAC/B,+CAAwE;AAA/D,sGAAA,SAAS,OAAA;AAAE,oGAAA,OAAO,OAAA;AAAE,4GAAA,eAAe,OAAA;AAG5C,kBAAkB;AAClB,yCACqG;AAD5F,oGAAA,UAAU,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,qGAAA,WAAW,OAAA;AAC3D,gHAAA,sBAAsB,OAAA;AAAE,+GAAA,qBAAqB,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,+FAAA,KAAK,OAAA;AAC5E,uDAAoD;AAA3C,4GAAA,WAAW,OAAA;AAEpB,4BAA4B;AAC5B,+CAA0G;AAAjG,+GAAA,kBAAkB,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAAE,2GAAA,cAAc,OAAA;AAG9E,mBAAmB;AACnB,sDACqF;AAD5E,oHAAA,sBAAsB,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AAAE,4GAAA,cAAc,OAAA;AACnE,oHAAA,sBAAsB,OAAA;AAAE,uHAAA,yBAAyB,OAAA;AACnD,oDAAwD;AAA/C,2GAAA,cAAc,OAAA;AAEvB,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,gFAAgF;AAChF,iDAGiG;AAHxF,oHAAA,sBAAsB,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AACvE,gHAAA,kBAAkB,OAAA;AAAE,oHAAA,sBAAsB,OAAA;AAC1C,6GAAA,eAAe,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAClC,2GAAA,aAAa,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,iHAAA,mBAAmB,OAAA;AAAE,4GAAA,cAAc,OAAA;AAIpE,iBAAiB;AACjB,4CAAuD;AAA9C,4GAAA,kBAAkB,OAAA;AAE3B,8CAAgI;AAAvH,0GAAA,eAAe,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,0GAAA,eAAe,OAAA;AAEnG,2BAA2B;AAC3B,8DAAgF;AAAvE,oHAAA,iBAAiB,OAAA;AAAE,kHAAA,eAAe,OAAA;AAC3C,0EAA2H;AAAlH,oIAAA,2BAA2B,OAAA;AAAE,yHAAA,gBAAgB,OAAA;AAAE,iIAAA,wBAAwB,OAAA;AAGhF,qBAAqB;AACrB,kDACoF;AAD3E,wGAAA,aAAa,OAAA;AAAE,0GAAA,eAAe,OAAA;AAAE,wGAAA,aAAa,OAAA;AAAE,yGAAA,cAAc,OAAA;AACpE,gHAAA,qBAAqB,OAAA;AAAE,mGAAA,QAAQ,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAEnD,gEAA2F;AAAlF,oHAAA,kBAAkB,OAAA;AAAE,wHAAA,sBAAsB,OAAA;AAEnD,gBAAgB;AAChB,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AAExB,qDAA4E;AAAnE,kHAAA,mBAAmB,OAAA;AAAE,6GAAA,cAAc,OAAA;AAC5C,yCAA+C;AAAtC,qGAAA,YAAY,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@90soft/parse-server-kit",
3
- "version": "2.11.0",
3
+ "version": "2.12.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",
@@ -68,7 +68,7 @@
68
68
  "mongodb-memory-server": "^11.2.0",
69
69
  "node-cron": "^4.0.0",
70
70
  "parse": "^8.6.2",
71
- "parse-server": "^9.10.1",
71
+ "parse-server": "^9.10.4",
72
72
  "reflect-metadata": "^0.2.2",
73
73
  "swagger-ui-express": "^5.0.1",
74
74
  "ts-jest": "^29.2.0",