@90soft/parse-server-kit 2.13.0 → 2.14.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 +66 -0
- package/dist/files/fileAccess.d.ts +100 -121
- package/dist/files/fileAccess.d.ts.map +1 -1
- package/dist/files/fileAccess.js +139 -150
- package/dist/files/fileAccess.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,72 @@ All notable changes to `@90soft/parse-server-kit`.
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [2.14.0]
|
|
12
|
+
|
|
13
|
+
### Changed — BREAKING (file access rules)
|
|
14
|
+
|
|
15
|
+
- **The owning row's ACL is now evaluated at READ time, for every file. The
|
|
16
|
+
prefix and the per-class flag are gone.**
|
|
17
|
+
|
|
18
|
+
2.12.0/2.13.0 marked files governed at UPLOAD time, by storing them under a
|
|
19
|
+
prefix. That cannot work for an application where content moves between
|
|
20
|
+
public and private, and the reason is structural: **storage location is
|
|
21
|
+
permanent, visibility is not.** A file stored while its row was public stays
|
|
22
|
+
world-readable forever, whatever the row later says — and the failure is
|
|
23
|
+
invisible, because the file loads perfectly.
|
|
24
|
+
|
|
25
|
+
Now every file is stored under `<ClassName>/<objectId>/`, so the path always
|
|
26
|
+
names its owner, and permission is decided on each request from that row's
|
|
27
|
+
ACL:
|
|
28
|
+
|
|
29
|
+
| | before | now |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| row made private | needed the bytes re-stored | takes effect on the next request |
|
|
32
|
+
| row made public | needed the bytes re-stored | takes effect immediately |
|
|
33
|
+
| class becomes public | flag flip **+ re-upload every file** | nothing to do |
|
|
34
|
+
| per-row visibility | inexpressible | the normal case |
|
|
35
|
+
|
|
36
|
+
| 2.13.0 | 2.14.0 |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `acl/<Class>/<objectId>/` | `<Class>/<objectId>/` |
|
|
39
|
+
| `ACL_PREFIX` | *gone* |
|
|
40
|
+
| `isAclGoverned` | *gone* — every path is governed |
|
|
41
|
+
| `aclOwnerOf` | `fileOwnerOf` |
|
|
42
|
+
| `aclDirectoryFor` | `ownerDirectoryFor` |
|
|
43
|
+
| `attachAclFile` | `attachOwnedFile` |
|
|
44
|
+
| `aclFileRule` | `rowAclFileRule` |
|
|
45
|
+
| `AclFileOwner` | `FileOwner` |
|
|
46
|
+
| `AclFileRuleOptions` | `RowAclFileRuleOptions` |
|
|
47
|
+
|
|
48
|
+
**A path with no identifiable owner is REFUSED**, so files stored before this
|
|
49
|
+
(flat names, or under the old prefix) stop serving until re-uploaded. That is
|
|
50
|
+
deliberate: the alternative is a permanent ungoverned tier that nobody
|
|
51
|
+
remembers.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **`Cache-Control` derived from the caller, not from configuration.** A rule's
|
|
56
|
+
`headers` now receives the access context. `rowAclFileRule` uses it: an
|
|
57
|
+
**anonymous** success proves the row is readable by everyone, so the response
|
|
58
|
+
is `public, max-age=60`; an identified caller gets `private, max-age=60`,
|
|
59
|
+
because from there it cannot be told whether the row is public or restricted
|
|
60
|
+
to them — and a second query to find out is not worth doubling the cost.
|
|
61
|
+
|
|
62
|
+
`maxAge: 0` gives instant revocation at the price of re-downloading, since
|
|
63
|
+
Parse sends no `ETag` and answers no conditional request. The default 60
|
|
64
|
+
bounds less than it appears: a cached copy only ever exists for someone who
|
|
65
|
+
was already allowed to see the file, and anyone who has viewed an image has
|
|
66
|
+
it on their device regardless.
|
|
67
|
+
|
|
68
|
+
### Note
|
|
69
|
+
|
|
70
|
+
- Clients must send `X-Parse-Session-Token` on image requests for any class
|
|
71
|
+
whose CLP is not public — a browser `<img src>` cannot, so protected images
|
|
72
|
+
need fetching and binding. This is the honest cost of the ACL being the
|
|
73
|
+
source of truth, and no design avoids it.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
11
77
|
## [2.13.0]
|
|
12
78
|
|
|
13
79
|
### Changed — BREAKING (file access rules, added in 2.12.0)
|
|
@@ -1,63 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File access rules —
|
|
2
|
+
* File access rules — the owning row's ACL decides who gets the bytes.
|
|
3
3
|
*
|
|
4
4
|
* Parse has no per-file permission and cannot have one: the file router is
|
|
5
5
|
* handed a name, and a name does not know which row references it. So a file
|
|
6
6
|
* url is a bearer capability — anyone holding the string gets the bytes, with
|
|
7
|
-
* no session, no ACL check and no expiry.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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".
|
|
7
|
+
* no session, no ACL check and no expiry. Fine for a logo. Not fine for a
|
|
8
|
+
* document, a profile photo, or anything a person can make private, because a
|
|
9
|
+
* url that leaks once works forever.
|
|
13
10
|
*
|
|
14
|
-
* Parse
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Parse gives one hook: `beforeFind` on `Parse.File`, which runs on every
|
|
12
|
+
* `GET /files/...` and can throw to refuse. This module owns that hook.
|
|
13
|
+
*
|
|
14
|
+
* **Every file is stored under `<ClassName>/<objectId>/`**, so the path always
|
|
15
|
+
* names the row that owns it, and permission is decided **at read time** from
|
|
16
|
+
* that row's ACL. That matters more than it sounds:
|
|
17
|
+
*
|
|
18
|
+
* - a row made private takes its file out of service on the NEXT request;
|
|
19
|
+
* - a row made public starts serving immediately;
|
|
20
|
+
* - neither needs the bytes re-stored, re-named or migrated.
|
|
21
|
+
*
|
|
22
|
+
* The alternative — marking files private *when they are uploaded* — cannot do
|
|
23
|
+
* either. Storage location is permanent and visibility is not, so an
|
|
24
|
+
* upload-time decision is wrong the moment anything changes, and wrong in the
|
|
25
|
+
* fail-open direction: a file stored while its row was public stays
|
|
26
|
+
* world-readable forever, whatever the row later says. For an application where
|
|
27
|
+
* content moves between public and private, that design is not merely weaker,
|
|
28
|
+
* it is unable to express the requirement.
|
|
29
|
+
*
|
|
30
|
+
* **Five traps this absorbs**, each of which cost real debugging:
|
|
17
31
|
*
|
|
18
32
|
* 1. **There is exactly ONE slot.** File triggers are keyed on the
|
|
19
|
-
* pseudo-class `@File`,
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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:
|
|
33
|
+
* pseudo-class `@File`, so a second `beforeFind(Parse.File)` REPLACES the
|
|
34
|
+
* first — Parse logs "Only the last one will be used" and keeps serving.
|
|
35
|
+
* Two features each registering a rule would silently disable one. Hence
|
|
36
|
+
* a registry: this module holds the slot, rules compose.
|
|
37
|
+
* 2. **Response headers come from `afterFind`.** `beforeFind`'s are
|
|
38
|
+
* discarded. A rule that controls caching has to act there.
|
|
39
|
+
* 3. **Only a header authenticates.** The file route reads the session from
|
|
40
|
+
* `X-Parse-Session-Token` and nowhere else — no cookie, no query
|
|
41
|
+
* parameter. A browser `<img src>` cannot send one, so a client must
|
|
42
|
+
* fetch protected images itself and bind the bytes. The upside: nobody
|
|
43
|
+
* can "simplify" this by putting a session token in a url.
|
|
44
|
+
* 4. **A throw is global.** This runs on every file download in the
|
|
45
|
+
* application, so a mistake does not break documents — it blanks every
|
|
46
|
+
* image at once. A rule whose predicate throws is skipped, not fatal.
|
|
47
|
+
* 5. **Fail-open is the invisible failure.** A path whose owner cannot be
|
|
48
|
+
* identified must be REFUSED. Treating it as public means the file loads
|
|
49
|
+
* perfectly and nobody ever notices.
|
|
50
|
+
*
|
|
51
|
+
* **Nothing is registered until a rule is added**, so upgrading changes no
|
|
52
|
+
* behaviour in a project that has not opted in.
|
|
45
53
|
*
|
|
46
54
|
* ```ts
|
|
47
|
-
*
|
|
48
|
-
*
|
|
55
|
+
* registerFileAccessRule(rowAclFileRule());
|
|
56
|
+
* await attachOwnedFile(row, 'document', upload); // -> Row/<objectId>/...
|
|
49
57
|
* ```
|
|
50
|
-
*
|
|
51
|
-
* [aclFileRule] is the general case: a file stored under
|
|
52
|
-
* `acl/<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
58
|
*/
|
|
56
59
|
/**
|
|
57
60
|
* What a FILE trigger is handed, which is not what a class trigger gets: there
|
|
58
61
|
* is no `object` and no `query`, only the file.
|
|
59
62
|
*
|
|
60
|
-
* Declared
|
|
63
|
+
* Declared here rather than taken from the ambient Parse types on purpose:
|
|
61
64
|
* `@types/parse` does not describe file triggers at all, so a consumer relying
|
|
62
65
|
* on it could not name the argument of [evaluateFileAccess] — a published
|
|
63
66
|
* library should not require its callers to declare types for its own API.
|
|
@@ -74,7 +77,7 @@ export interface FileTriggerRequest {
|
|
|
74
77
|
}
|
|
75
78
|
/** The decision a rule is asked to make. */
|
|
76
79
|
export interface FileAccessContext {
|
|
77
|
-
/** The stored path, including
|
|
80
|
+
/** The stored path, including the owner directory. */
|
|
78
81
|
path: string;
|
|
79
82
|
/** The caller, when they sent a session token. */
|
|
80
83
|
user?: Parse.User;
|
|
@@ -92,26 +95,26 @@ export interface FileAccessRule {
|
|
|
92
95
|
* Does this rule govern that path?
|
|
93
96
|
*
|
|
94
97
|
* **Must not throw and should not await.** It is consulted for every file
|
|
95
|
-
* download in the application
|
|
98
|
+
* download in the application.
|
|
96
99
|
*/
|
|
97
100
|
governs(path: string): boolean;
|
|
98
101
|
/**
|
|
99
|
-
* Decide. Throw a `Parse.Error` to refuse.
|
|
102
|
+
* Decide. Throw a `Parse.Error` to refuse; return normally to allow.
|
|
100
103
|
*
|
|
101
|
-
*
|
|
102
|
-
* yes and the request is not master-key.
|
|
104
|
+
* Only called when [governs] matched and the request is not master-key.
|
|
103
105
|
*/
|
|
104
106
|
authorize(context: FileAccessContext): Promise<void> | void;
|
|
105
107
|
/**
|
|
106
|
-
* Headers
|
|
108
|
+
* Headers for a governed response.
|
|
107
109
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
+
* Receives the same context, so a rule can vary caching by caller — see
|
|
111
|
+
* [rowAclFileRule], where an anonymous success is itself the proof that a
|
|
112
|
+
* row is public and therefore safe for a shared cache.
|
|
110
113
|
*/
|
|
111
|
-
headers
|
|
114
|
+
headers?(context: FileAccessContext): Record<string, string>;
|
|
112
115
|
}
|
|
113
116
|
/**
|
|
114
|
-
* The gate.
|
|
117
|
+
* The gate. Ungoverned files leave before anything is awaited.
|
|
115
118
|
*
|
|
116
119
|
* Exported so a project can unit-test its rules without going through HTTP.
|
|
117
120
|
*/
|
|
@@ -135,101 +138,77 @@ export declare function getFileAccessRules(): FileAccessRule[];
|
|
|
135
138
|
/**
|
|
136
139
|
* Drop every rule. For tests.
|
|
137
140
|
*
|
|
138
|
-
* The Parse hooks stay attached —
|
|
139
|
-
*
|
|
141
|
+
* The Parse hooks stay attached — harmless with no rules, since every path is
|
|
142
|
+
* then ungoverned — and re-attaching would hit Parse's duplicate warning.
|
|
140
143
|
*/
|
|
141
144
|
export declare function clearFileAccessRules(): void;
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
* **Named for the mechanism, not the outcome.** It was `private` first, which
|
|
146
|
-
* was wrong in the most confusing way: the commonest case is a PUBLICLY
|
|
147
|
-
* readable row, whose file is then served to everybody — from a path that
|
|
148
|
-
* said "private". The prefix does not mean secret. It means **the owning row
|
|
149
|
-
* decides**, and a public row decides "everyone".
|
|
150
|
-
*
|
|
151
|
-
* Renamed before anything depended on it, because the prefix is baked into
|
|
152
|
-
* stored filenames: changing it later orphans every file already written.
|
|
153
|
-
*
|
|
154
|
-
* Parse's own `validateDirectory` reserves `metadata` and requires a directory
|
|
155
|
-
* to match `^[a-zA-Z0-9][a-zA-Z0-9_\-/]*$`, so a single lowercase word is safe.
|
|
156
|
-
* It is baked into stored file names, so changing it would orphan every file
|
|
157
|
-
* already written under the old one.
|
|
158
|
-
*/
|
|
159
|
-
export declare const ACL_PREFIX = "acl";
|
|
160
|
-
/** Who an ACL-governed file's path says owns it. */
|
|
161
|
-
export interface AclFileOwner {
|
|
145
|
+
/** Who a stored path says owns the bytes. */
|
|
146
|
+
export interface FileOwner {
|
|
162
147
|
className: string;
|
|
163
148
|
objectId: string;
|
|
164
149
|
}
|
|
165
150
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* into a fail-OPEN. It would answer `null` both for an ordinary image and for
|
|
172
|
-
* `acl/<Class>/<id>/extra/doc.jpg` — one directory deeper than expected —
|
|
173
|
-
* and so serve that document to anybody.
|
|
174
|
-
*/
|
|
175
|
-
export declare function isAclGoverned(path: string): boolean;
|
|
176
|
-
/**
|
|
177
|
-
* Read the owner out of a governed path, or null if it is not well formed.
|
|
151
|
+
* Read the owner out of a stored path, or null if the path has no owner.
|
|
152
|
+
*
|
|
153
|
+
* **Must never throw.** It runs on every file download in the application, and
|
|
154
|
+
* a throw inside the file trigger does not break one feature — it blanks every
|
|
155
|
+
* image at once. Hence a plain `split` and nothing clever.
|
|
178
156
|
*
|
|
179
|
-
* `null` means "
|
|
180
|
-
*
|
|
157
|
+
* `null` means "this file predates owner directories, or was stored by
|
|
158
|
+
* something else". A rule must treat that as a REFUSAL rather than as
|
|
159
|
+
* permission; see the fail-open note in the module docs.
|
|
181
160
|
*/
|
|
182
|
-
export declare function
|
|
161
|
+
export declare function fileOwnerOf(path: string): FileOwner | null;
|
|
183
162
|
/**
|
|
184
|
-
* The directory a row's
|
|
163
|
+
* The directory a row's files live in.
|
|
185
164
|
*
|
|
186
|
-
* Throws rather than quietly producing something Parse
|
|
187
|
-
*
|
|
165
|
+
* Throws rather than quietly producing something Parse rejects: a class whose
|
|
166
|
+
* name cannot be a path segment (`_User`, since a directory must start
|
|
188
167
|
* alphanumeric) surfaces here, at the one call site, instead of as a confusing
|
|
189
168
|
* upload failure later.
|
|
190
169
|
*/
|
|
191
|
-
export declare function
|
|
170
|
+
export declare function ownerDirectoryFor(className: string, objectId: string): string;
|
|
192
171
|
/**
|
|
193
172
|
* Store a file so that the owning row's ACL governs it.
|
|
194
173
|
*
|
|
195
174
|
* The row must already be saved — its objectId is part of the path, which is
|
|
196
|
-
* what lets
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* **This should be the only way an application stores a governed file.** The
|
|
201
|
-
* prefix is the entire boundary: a document saved without it is simply public,
|
|
202
|
-
* and nothing anywhere will complain.
|
|
175
|
+
* what lets a rule find the owner with no index and no second source of truth.
|
|
176
|
+
* For a new record that means save, attach, save again: one extra write on a
|
|
177
|
+
* rare operation.
|
|
203
178
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* travels inside DTOs where the document itself does not.
|
|
179
|
+
* **This should be the only way an application stores a file.** A file stored
|
|
180
|
+
* without an owner directory is ungoverned, and nothing anywhere complains.
|
|
207
181
|
*/
|
|
208
|
-
export declare function
|
|
182
|
+
export declare function attachOwnedFile(row: Parse.Object, field: string, upload: {
|
|
209
183
|
base64: string;
|
|
210
184
|
name?: string;
|
|
211
185
|
}): Promise<void>;
|
|
212
|
-
/** Options for [
|
|
213
|
-
export interface
|
|
186
|
+
/** Options for [rowAclFileRule]. */
|
|
187
|
+
export interface RowAclFileRuleOptions {
|
|
214
188
|
/**
|
|
215
|
-
*
|
|
189
|
+
* How long a response may be cached, in seconds. Default 60.
|
|
216
190
|
*
|
|
217
|
-
*
|
|
191
|
+
* This is the only lag in the design, and it is a straight trade: Parse sends
|
|
192
|
+
* no `ETag` and answers no conditional request, so "revalidate every time"
|
|
193
|
+
* costs the same bytes as "do not cache". `0` makes revocation instant and
|
|
194
|
+
* re-downloads every image on every view.
|
|
218
195
|
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
196
|
+
* It bounds less than it appears to. A cached copy only ever exists in the
|
|
197
|
+
* cache of someone who was ALREADY allowed to see the file — a caller who
|
|
198
|
+
* was refused has nothing stored. And anyone who has viewed an image has it
|
|
199
|
+
* on their device regardless: revocation stops future access, it cannot
|
|
200
|
+
* un-see.
|
|
222
201
|
*/
|
|
223
|
-
|
|
202
|
+
maxAge?: number;
|
|
224
203
|
}
|
|
225
204
|
/**
|
|
226
|
-
* Serve a
|
|
205
|
+
* Serve a file only to a caller who can read the row that owns it.
|
|
227
206
|
*
|
|
228
|
-
* The caller is resolved from their session token, so Parse evaluates the
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
207
|
+
* The caller is resolved from their session token, so Parse evaluates the row's
|
|
208
|
+
* ACL and the class's CLP exactly as it would for a query. With no session the
|
|
209
|
+
* row is loaded with no auth, which is right rather than a shortcut: if the
|
|
210
|
+
* owning row is publicly readable then its file is public too, because the
|
|
211
|
+
* row's ACL is the whole rule.
|
|
233
212
|
*/
|
|
234
|
-
export declare function
|
|
213
|
+
export declare function rowAclFileRule(options?: RowAclFileRuleOptions): FileAccessRule;
|
|
235
214
|
//# sourceMappingURL=fileAccess.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileAccess.d.ts","sourceRoot":"","sources":["../../src/files/fileAccess.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fileAccess.d.ts","sourceRoot":"","sources":["../../src/files/fileAccess.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;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,sDAAsD;IACtD,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;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5D;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9D;AAuCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAYxE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAqBjE;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAID,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAW1D;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,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,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAkDhB"}
|
package/dist/files/fileAccess.js
CHANGED
|
@@ -1,71 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* File access rules —
|
|
3
|
+
* File access rules — the owning row's ACL decides who gets the bytes.
|
|
4
4
|
*
|
|
5
5
|
* Parse has no per-file permission and cannot have one: the file router is
|
|
6
6
|
* handed a name, and a name does not know which row references it. So a file
|
|
7
7
|
* url is a bearer capability — anyone holding the string gets the bytes, with
|
|
8
|
-
* no session, no ACL check and no expiry.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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".
|
|
8
|
+
* no session, no ACL check and no expiry. Fine for a logo. Not fine for a
|
|
9
|
+
* document, a profile photo, or anything a person can make private, because a
|
|
10
|
+
* url that leaks once works forever.
|
|
14
11
|
*
|
|
15
|
-
* Parse
|
|
16
|
-
*
|
|
17
|
-
* and makes it usable, because using it directly has five traps:
|
|
12
|
+
* Parse gives one hook: `beforeFind` on `Parse.File`, which runs on every
|
|
13
|
+
* `GET /files/...` and can throw to refuse. This module owns that hook.
|
|
18
14
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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.
|
|
15
|
+
* **Every file is stored under `<ClassName>/<objectId>/`**, so the path always
|
|
16
|
+
* names the row that owns it, and permission is decided **at read time** from
|
|
17
|
+
* that row's ACL. That matters more than it sounds:
|
|
18
|
+
*
|
|
19
|
+
* - a row made private takes its file out of service on the NEXT request;
|
|
20
|
+
* - a row made public starts serving immediately;
|
|
21
|
+
* - neither needs the bytes re-stored, re-named or migrated.
|
|
22
|
+
*
|
|
23
|
+
* The alternative — marking files private *when they are uploaded* — cannot do
|
|
24
|
+
* either. Storage location is permanent and visibility is not, so an
|
|
25
|
+
* upload-time decision is wrong the moment anything changes, and wrong in the
|
|
26
|
+
* fail-open direction: a file stored while its row was public stays
|
|
27
|
+
* world-readable forever, whatever the row later says. For an application where
|
|
28
|
+
* content moves between public and private, that design is not merely weaker,
|
|
29
|
+
* it is unable to express the requirement.
|
|
30
|
+
*
|
|
31
|
+
* **Five traps this absorbs**, each of which cost real debugging:
|
|
40
32
|
*
|
|
41
|
-
* **
|
|
42
|
-
*
|
|
43
|
-
*
|
|
33
|
+
* 1. **There is exactly ONE slot.** File triggers are keyed on the
|
|
34
|
+
* pseudo-class `@File`, so a second `beforeFind(Parse.File)` REPLACES the
|
|
35
|
+
* first — Parse logs "Only the last one will be used" and keeps serving.
|
|
36
|
+
* Two features each registering a rule would silently disable one. Hence
|
|
37
|
+
* a registry: this module holds the slot, rules compose.
|
|
38
|
+
* 2. **Response headers come from `afterFind`.** `beforeFind`'s are
|
|
39
|
+
* discarded. A rule that controls caching has to act there.
|
|
40
|
+
* 3. **Only a header authenticates.** The file route reads the session from
|
|
41
|
+
* `X-Parse-Session-Token` and nowhere else — no cookie, no query
|
|
42
|
+
* parameter. A browser `<img src>` cannot send one, so a client must
|
|
43
|
+
* fetch protected images itself and bind the bytes. The upside: nobody
|
|
44
|
+
* can "simplify" this by putting a session token in a url.
|
|
45
|
+
* 4. **A throw is global.** This runs on every file download in the
|
|
46
|
+
* application, so a mistake does not break documents — it blanks every
|
|
47
|
+
* image at once. A rule whose predicate throws is skipped, not fatal.
|
|
48
|
+
* 5. **Fail-open is the invisible failure.** A path whose owner cannot be
|
|
49
|
+
* identified must be REFUSED. Treating it as public means the file loads
|
|
50
|
+
* perfectly and nobody ever notices.
|
|
44
51
|
*
|
|
45
|
-
*
|
|
52
|
+
* **Nothing is registered until a rule is added**, so upgrading changes no
|
|
53
|
+
* behaviour in a project that has not opted in.
|
|
46
54
|
*
|
|
47
55
|
* ```ts
|
|
48
|
-
*
|
|
49
|
-
*
|
|
56
|
+
* registerFileAccessRule(rowAclFileRule());
|
|
57
|
+
* await attachOwnedFile(row, 'document', upload); // -> Row/<objectId>/...
|
|
50
58
|
* ```
|
|
51
|
-
*
|
|
52
|
-
* [aclFileRule] is the general case: a file stored under
|
|
53
|
-
* `acl/<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
59
|
*/
|
|
57
60
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.ACL_PREFIX = void 0;
|
|
59
61
|
exports.evaluateFileAccess = evaluateFileAccess;
|
|
60
62
|
exports.applyFileAccessHeaders = applyFileAccessHeaders;
|
|
61
63
|
exports.registerFileAccessRule = registerFileAccessRule;
|
|
62
64
|
exports.getFileAccessRules = getFileAccessRules;
|
|
63
65
|
exports.clearFileAccessRules = clearFileAccessRules;
|
|
64
|
-
exports.
|
|
65
|
-
exports.
|
|
66
|
-
exports.
|
|
67
|
-
exports.
|
|
68
|
-
exports.aclFileRule = aclFileRule;
|
|
66
|
+
exports.fileOwnerOf = fileOwnerOf;
|
|
67
|
+
exports.ownerDirectoryFor = ownerDirectoryFor;
|
|
68
|
+
exports.attachOwnedFile = attachOwnedFile;
|
|
69
|
+
exports.rowAclFileRule = rowAclFileRule;
|
|
69
70
|
/** Ordered; the first rule that governs a path decides it. */
|
|
70
71
|
const rules = [];
|
|
71
72
|
/** The hook is attached once, lazily, on the first registration. */
|
|
@@ -74,17 +75,15 @@ let attached = false;
|
|
|
74
75
|
* 404, never 403.
|
|
75
76
|
*
|
|
76
77
|
* A 403 confirms the file is there and merely forbidden, which for a document
|
|
77
|
-
* is itself the leak
|
|
78
|
-
* "Not found" is the same answer an invented name would get.
|
|
78
|
+
* is itself the leak. "Not found" is the answer an invented name would get.
|
|
79
79
|
*/
|
|
80
80
|
function notFound() {
|
|
81
81
|
return new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'File not found');
|
|
82
82
|
}
|
|
83
|
-
/** The first rule governing this path, or undefined when
|
|
83
|
+
/** The first rule governing this path, or undefined when none does. */
|
|
84
84
|
function ruleFor(path) {
|
|
85
85
|
for (const rule of rules) {
|
|
86
|
-
//
|
|
87
|
-
// misbehaving rule must not take the public path down with it.
|
|
86
|
+
// One misbehaving rule must not blank every image in the app.
|
|
88
87
|
try {
|
|
89
88
|
if (rule.governs(path))
|
|
90
89
|
return rule;
|
|
@@ -95,25 +94,28 @@ function ruleFor(path) {
|
|
|
95
94
|
}
|
|
96
95
|
return undefined;
|
|
97
96
|
}
|
|
97
|
+
function contextFor(request) {
|
|
98
|
+
return {
|
|
99
|
+
path: request.file?.name() ?? '',
|
|
100
|
+
user: request.user,
|
|
101
|
+
master: request.master === true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
98
104
|
/**
|
|
99
|
-
* The gate.
|
|
105
|
+
* The gate. Ungoverned files leave before anything is awaited.
|
|
100
106
|
*
|
|
101
107
|
* Exported so a project can unit-test its rules without going through HTTP.
|
|
102
108
|
*/
|
|
103
109
|
async function evaluateFileAccess(request) {
|
|
104
|
-
const
|
|
105
|
-
const rule = ruleFor(path);
|
|
110
|
+
const context = contextFor(request);
|
|
111
|
+
const rule = ruleFor(context.path);
|
|
106
112
|
if (!rule)
|
|
107
113
|
return;
|
|
108
|
-
// Cloud code reading deliberately
|
|
109
|
-
//
|
|
110
|
-
if (
|
|
114
|
+
// Cloud code reading deliberately. NOT how a worker that fetches a url
|
|
115
|
+
// reaches a file — a plain GET carries no key.
|
|
116
|
+
if (context.master)
|
|
111
117
|
return;
|
|
112
|
-
await rule.authorize(
|
|
113
|
-
path,
|
|
114
|
-
user: request.user,
|
|
115
|
-
master: false,
|
|
116
|
-
});
|
|
118
|
+
await rule.authorize(context);
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
119
121
|
* Add the governed response's headers.
|
|
@@ -122,12 +124,16 @@ async function evaluateFileAccess(request) {
|
|
|
122
124
|
* the `afterFind` request and ignores `beforeFind`'s.
|
|
123
125
|
*/
|
|
124
126
|
function applyFileAccessHeaders(request) {
|
|
125
|
-
const
|
|
127
|
+
const context = contextFor(request);
|
|
128
|
+
const rule = ruleFor(context.path);
|
|
126
129
|
if (!rule?.headers)
|
|
127
130
|
return;
|
|
128
131
|
// The incoming object already carries `X-Content-Type-Options: nosniff`, so
|
|
129
132
|
// extend it rather than replacing it.
|
|
130
|
-
request.responseHeaders = {
|
|
133
|
+
request.responseHeaders = {
|
|
134
|
+
...(request.responseHeaders ?? {}),
|
|
135
|
+
...rule.headers(context),
|
|
136
|
+
};
|
|
131
137
|
}
|
|
132
138
|
/**
|
|
133
139
|
* Register a rule, attaching the Parse hooks on the first call.
|
|
@@ -149,9 +155,8 @@ function registerFileAccessRule(rule) {
|
|
|
149
155
|
attached = true;
|
|
150
156
|
/*
|
|
151
157
|
* `Parse.File` is passed as the class, which Parse maps to `@File`. This is
|
|
152
|
-
* the only pair of triggers in a kit project
|
|
153
|
-
*
|
|
154
|
-
* on.
|
|
158
|
+
* the only pair of triggers in a kit project not declared on a model with
|
|
159
|
+
* `@BeforeFind` / `@AfterFind`, because there is no model to declare it on.
|
|
155
160
|
*/
|
|
156
161
|
Parse.Cloud.beforeFind(Parse.File, evaluateFileAccess);
|
|
157
162
|
Parse.Cloud.afterFind(Parse.File, applyFileAccessHeaders);
|
|
@@ -163,131 +168,93 @@ function getFileAccessRules() {
|
|
|
163
168
|
/**
|
|
164
169
|
* Drop every rule. For tests.
|
|
165
170
|
*
|
|
166
|
-
* The Parse hooks stay attached —
|
|
167
|
-
*
|
|
171
|
+
* The Parse hooks stay attached — harmless with no rules, since every path is
|
|
172
|
+
* then ungoverned — and re-attaching would hit Parse's duplicate warning.
|
|
168
173
|
*/
|
|
169
174
|
function clearFileAccessRules() {
|
|
170
175
|
rules.length = 0;
|
|
171
176
|
}
|
|
172
|
-
// ── The ACL-governed rule ───────────────────────────────────────────────────
|
|
173
177
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* **Named for the mechanism, not the outcome.** It was `private` first, which
|
|
177
|
-
* was wrong in the most confusing way: the commonest case is a PUBLICLY
|
|
178
|
-
* readable row, whose file is then served to everybody — from a path that
|
|
179
|
-
* said "private". The prefix does not mean secret. It means **the owning row
|
|
180
|
-
* decides**, and a public row decides "everyone".
|
|
178
|
+
* Read the owner out of a stored path, or null if the path has no owner.
|
|
181
179
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
180
|
+
* **Must never throw.** It runs on every file download in the application, and
|
|
181
|
+
* a throw inside the file trigger does not break one feature — it blanks every
|
|
182
|
+
* image at once. Hence a plain `split` and nothing clever.
|
|
184
183
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* already written under the old one.
|
|
184
|
+
* `null` means "this file predates owner directories, or was stored by
|
|
185
|
+
* something else". A rule must treat that as a REFUSAL rather than as
|
|
186
|
+
* permission; see the fail-open note in the module docs.
|
|
189
187
|
*/
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* Is this path governed at all?
|
|
193
|
-
*
|
|
194
|
-
* Deliberately separate from [aclOwnerOf], and the separation is the
|
|
195
|
-
* safety property: "under the prefix" and "parses into an owner" are different
|
|
196
|
-
* questions, and a single predicate returning `owner | null` conflates them
|
|
197
|
-
* into a fail-OPEN. It would answer `null` both for an ordinary image and for
|
|
198
|
-
* `acl/<Class>/<id>/extra/doc.jpg` — one directory deeper than expected —
|
|
199
|
-
* and so serve that document to anybody.
|
|
200
|
-
*/
|
|
201
|
-
function isAclGoverned(path) {
|
|
202
|
-
return typeof path === 'string' && path.startsWith(`${exports.ACL_PREFIX}/`);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Read the owner out of a governed path, or null if it is not well formed.
|
|
206
|
-
*
|
|
207
|
-
* `null` means "I cannot tell who owns this", which the rule treats as a
|
|
208
|
-
* refusal — never as permission.
|
|
209
|
-
*/
|
|
210
|
-
function aclOwnerOf(path) {
|
|
211
|
-
if (!isAclGoverned(path))
|
|
188
|
+
function fileOwnerOf(path) {
|
|
189
|
+
if (typeof path !== 'string' || path === '')
|
|
212
190
|
return null;
|
|
213
|
-
// Exactly:
|
|
191
|
+
// Exactly: ClassName / objectId / filename
|
|
214
192
|
const parts = path.split('/');
|
|
215
|
-
if (parts.length !==
|
|
193
|
+
if (parts.length !== 3)
|
|
216
194
|
return null;
|
|
217
|
-
const [
|
|
195
|
+
const [className, objectId, filename] = parts;
|
|
218
196
|
if (!className || !objectId || !filename)
|
|
219
197
|
return null;
|
|
220
198
|
return { className, objectId };
|
|
221
199
|
}
|
|
222
200
|
/**
|
|
223
|
-
* The directory a row's
|
|
201
|
+
* The directory a row's files live in.
|
|
224
202
|
*
|
|
225
|
-
* Throws rather than quietly producing something Parse
|
|
226
|
-
*
|
|
203
|
+
* Throws rather than quietly producing something Parse rejects: a class whose
|
|
204
|
+
* name cannot be a path segment (`_User`, since a directory must start
|
|
227
205
|
* alphanumeric) surfaces here, at the one call site, instead of as a confusing
|
|
228
206
|
* upload failure later.
|
|
229
207
|
*/
|
|
230
|
-
function
|
|
208
|
+
function ownerDirectoryFor(className, objectId) {
|
|
231
209
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_]*$/.test(className)) {
|
|
232
|
-
throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${className} cannot own
|
|
210
|
+
throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${className} cannot own files: Parse requires a directory segment to start with a letter or digit`);
|
|
233
211
|
}
|
|
234
212
|
if (!/^[a-zA-Z0-9]+$/.test(objectId)) {
|
|
235
|
-
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '
|
|
213
|
+
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'A file needs its owning row saved first, so that it has an objectId');
|
|
236
214
|
}
|
|
237
|
-
return `${
|
|
215
|
+
return `${className}/${objectId}`;
|
|
238
216
|
}
|
|
239
217
|
/**
|
|
240
218
|
* Store a file so that the owning row's ACL governs it.
|
|
241
219
|
*
|
|
242
220
|
* The row must already be saved — its objectId is part of the path, which is
|
|
243
|
-
* what lets
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* **This should be the only way an application stores a governed file.** The
|
|
248
|
-
* prefix is the entire boundary: a document saved without it is simply public,
|
|
249
|
-
* and nothing anywhere will complain.
|
|
221
|
+
* what lets a rule find the owner with no index and no second source of truth.
|
|
222
|
+
* For a new record that means save, attach, save again: one extra write on a
|
|
223
|
+
* rare operation.
|
|
250
224
|
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* travels inside DTOs where the document itself does not.
|
|
225
|
+
* **This should be the only way an application stores a file.** A file stored
|
|
226
|
+
* without an owner directory is ungoverned, and nothing anywhere complains.
|
|
254
227
|
*/
|
|
255
|
-
async function
|
|
228
|
+
async function attachOwnedFile(row, field, upload) {
|
|
256
229
|
if (!row.id) {
|
|
257
|
-
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Save the row before attaching a
|
|
230
|
+
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Save the row before attaching a file — its objectId is part of the path');
|
|
258
231
|
}
|
|
259
|
-
const file = new Parse.File(encodeURIComponent(upload.name ?? '
|
|
232
|
+
const file = new Parse.File(encodeURIComponent(upload.name ?? 'file'), {
|
|
260
233
|
base64: upload.base64,
|
|
261
234
|
});
|
|
262
235
|
// Sent as `X-Parse-File-Directory`; honoured by Parse Server from 9.5.0.
|
|
263
|
-
file.setDirectory(
|
|
236
|
+
file.setDirectory(ownerDirectoryFor(row.className, row.id));
|
|
264
237
|
const saved = await file.save({ useMasterKey: true });
|
|
265
238
|
row.set(field, saved);
|
|
266
239
|
}
|
|
267
240
|
/**
|
|
268
|
-
* Serve a
|
|
241
|
+
* Serve a file only to a caller who can read the row that owns it.
|
|
269
242
|
*
|
|
270
|
-
* The caller is resolved from their session token, so Parse evaluates the
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
243
|
+
* The caller is resolved from their session token, so Parse evaluates the row's
|
|
244
|
+
* ACL and the class's CLP exactly as it would for a query. With no session the
|
|
245
|
+
* row is loaded with no auth, which is right rather than a shortcut: if the
|
|
246
|
+
* owning row is publicly readable then its file is public too, because the
|
|
247
|
+
* row's ACL is the whole rule.
|
|
275
248
|
*/
|
|
276
|
-
function
|
|
277
|
-
const
|
|
249
|
+
function rowAclFileRule(options = {}) {
|
|
250
|
+
const maxAge = options.maxAge ?? 60;
|
|
278
251
|
return {
|
|
279
|
-
name: 'acl
|
|
280
|
-
|
|
281
|
-
|
|
252
|
+
name: 'row-acl',
|
|
253
|
+
// Every path. A file with no identifiable owner is REFUSED below, not
|
|
254
|
+
// ignored — ignoring it is the fail-open case.
|
|
255
|
+
governs: () => true,
|
|
282
256
|
async authorize({ path, user }) {
|
|
283
|
-
|
|
284
|
-
* Under the prefix but not a shape we understand: REFUSE.
|
|
285
|
-
*
|
|
286
|
-
* Treating it as public because no owner could be found is the one
|
|
287
|
-
* failure mode of this design that nobody would notice, because the
|
|
288
|
-
* document would load perfectly.
|
|
289
|
-
*/
|
|
290
|
-
const owner = aclOwnerOf(path);
|
|
257
|
+
const owner = fileOwnerOf(path);
|
|
291
258
|
if (!owner)
|
|
292
259
|
throw notFound();
|
|
293
260
|
const sessionToken = user?.getSessionToken();
|
|
@@ -298,6 +265,28 @@ function aclFileRule(options = {}) {
|
|
|
298
265
|
if (!row)
|
|
299
266
|
throw notFound();
|
|
300
267
|
},
|
|
268
|
+
headers({ user }) {
|
|
269
|
+
/*
|
|
270
|
+
* `public` only for an ANONYMOUS success.
|
|
271
|
+
*
|
|
272
|
+
* Reaching here means the row was readable by this caller. If the caller
|
|
273
|
+
* sent no session and still succeeded, the row is readable by everyone —
|
|
274
|
+
* so the bytes are genuinely public and a shared cache may hold them.
|
|
275
|
+
*
|
|
276
|
+
* Otherwise the caller was identified, and we cannot tell from here
|
|
277
|
+
* whether the row is public or restricted to them specifically.
|
|
278
|
+
* `private` is the safe answer: the response may sit in that one user's
|
|
279
|
+
* own cache and in no shared one. It costs nothing when the row happens
|
|
280
|
+
* to be public, and it is correct when it is not.
|
|
281
|
+
*
|
|
282
|
+
* Deliberately NOT a second query to find out. One database read per
|
|
283
|
+
* cold fetch is the budget; a caching hint is not worth doubling it.
|
|
284
|
+
*/
|
|
285
|
+
const scope = user ? 'private' : 'public';
|
|
286
|
+
return {
|
|
287
|
+
'Cache-Control': maxAge > 0 ? `${scope}, max-age=${maxAge}` : `${scope}, no-store`,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
301
290
|
};
|
|
302
291
|
}
|
|
303
292
|
//# sourceMappingURL=fileAccess.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileAccess.js","sourceRoot":"","sources":["../../src/files/fileAccess.ts"],"names":[],"mappings":";AAAA
|
|
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,8 +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,
|
|
28
|
-
export type { FileAccessRule, FileAccessContext, FileTriggerRequest,
|
|
27
|
+
export { registerFileAccessRule, getFileAccessRules, clearFileAccessRules, evaluateFileAccess, applyFileAccessHeaders, rowAclFileRule, attachOwnedFile, fileOwnerOf, ownerDirectoryFor } from './files/fileAccess';
|
|
28
|
+
export type { FileAccessRule, FileAccessContext, FileTriggerRequest, FileOwner, RowAclFileRuleOptions } from './files/fileAccess';
|
|
29
29
|
export { createSchemaConfig } from './database/schema';
|
|
30
30
|
export type { SchemaConfigOptions } from './database/schema';
|
|
31
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;AAOxD,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,oBAAoB,EACvE,kBAAkB,EAAE,sBAAsB,EAC1C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
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.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.
|
|
6
|
-
exports.setupSwagger = exports.getSwaggerJson = 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.ownerDirectoryFor = exports.fileOwnerOf = exports.attachOwnedFile = exports.rowAclFileRule = 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;
|
|
7
6
|
// ── Decorators ──
|
|
8
7
|
var parseDecorators_1 = require("./decorators/parseDecorators");
|
|
9
8
|
Object.defineProperty(exports, "ParseClass", { enumerable: true, get: function () { return parseDecorators_1.ParseClass; } });
|
|
@@ -97,23 +96,21 @@ Object.defineProperty(exports, "removeResultMiddleware", { enumerable: true, get
|
|
|
97
96
|
Object.defineProperty(exports, "conditionalJsonMiddleware", { enumerable: true, get: function () { return middleware_1.conditionalJsonMiddleware; } });
|
|
98
97
|
var rateLimit_1 = require("./middleware/rateLimit");
|
|
99
98
|
Object.defineProperty(exports, "checkRateLimit", { enumerable: true, get: function () { return rateLimit_1.checkRateLimit; } });
|
|
100
|
-
// ── File access
|
|
101
|
-
// Parse has no per-file permission; these
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
99
|
+
// ── File access ──────────────────────────────────────────────────
|
|
100
|
+
// Parse has no per-file permission; these make the OWNING ROW'S ACL the
|
|
101
|
+
// permission, decided at READ time so a visibility change needs no migration.
|
|
102
|
+
// The kit owns the single `beforeFind(Parse.File)` slot so rules compose, and
|
|
103
|
+
// nothing is registered until a rule is added.
|
|
105
104
|
var fileAccess_1 = require("./files/fileAccess");
|
|
106
105
|
Object.defineProperty(exports, "registerFileAccessRule", { enumerable: true, get: function () { return fileAccess_1.registerFileAccessRule; } });
|
|
107
106
|
Object.defineProperty(exports, "getFileAccessRules", { enumerable: true, get: function () { return fileAccess_1.getFileAccessRules; } });
|
|
108
107
|
Object.defineProperty(exports, "clearFileAccessRules", { enumerable: true, get: function () { return fileAccess_1.clearFileAccessRules; } });
|
|
109
108
|
Object.defineProperty(exports, "evaluateFileAccess", { enumerable: true, get: function () { return fileAccess_1.evaluateFileAccess; } });
|
|
110
109
|
Object.defineProperty(exports, "applyFileAccessHeaders", { enumerable: true, get: function () { return fileAccess_1.applyFileAccessHeaders; } });
|
|
111
|
-
Object.defineProperty(exports, "
|
|
112
|
-
Object.defineProperty(exports, "
|
|
113
|
-
Object.defineProperty(exports, "
|
|
114
|
-
Object.defineProperty(exports, "
|
|
115
|
-
Object.defineProperty(exports, "aclDirectoryFor", { enumerable: true, get: function () { return fileAccess_1.aclDirectoryFor; } });
|
|
116
|
-
Object.defineProperty(exports, "ACL_PREFIX", { enumerable: true, get: function () { return fileAccess_1.ACL_PREFIX; } });
|
|
110
|
+
Object.defineProperty(exports, "rowAclFileRule", { enumerable: true, get: function () { return fileAccess_1.rowAclFileRule; } });
|
|
111
|
+
Object.defineProperty(exports, "attachOwnedFile", { enumerable: true, get: function () { return fileAccess_1.attachOwnedFile; } });
|
|
112
|
+
Object.defineProperty(exports, "fileOwnerOf", { enumerable: true, get: function () { return fileAccess_1.fileOwnerOf; } });
|
|
113
|
+
Object.defineProperty(exports, "ownerDirectoryFor", { enumerable: true, get: function () { return fileAccess_1.ownerDirectoryFor; } });
|
|
117
114
|
// ── Database ──
|
|
118
115
|
var schema_1 = require("./database/schema");
|
|
119
116
|
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.14.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",
|