@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 +82 -0
- package/dist/files/fileAccess.d.ts +224 -0
- package/dist/files/fileAccess.d.ts.map +1 -0
- package/dist/files/fileAccess.js +294 -0
- package/dist/files/fileAccess.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjI,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAE3G,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAC1G,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAC9E,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,0BAA0B,EAChE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAChE,cAAc,EAAE,aAAa,EAC7B,gBAAgB,EAAE,eAAe,EACjC,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE9E,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EACnF,sBAAsB,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEpJ,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtH,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAChF,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAG1H,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACxE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAC3D,sBAAsB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,cAAc,EACnE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;
|
|
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.
|
|
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":";AAAA,+DAA+D
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.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.
|
|
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.
|
|
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",
|