@90soft/parse-server-kit 2.12.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 +109 -0
- package/dist/files/fileAccess.d.ts +101 -111
- package/dist/files/fileAccess.d.ts.map +1 -1
- package/dist/files/fileAccess.js +139 -141
- 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,115 @@ 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
|
+
|
|
77
|
+
## [2.13.0]
|
|
78
|
+
|
|
79
|
+
### Changed — BREAKING (file access rules, added in 2.12.0)
|
|
80
|
+
|
|
81
|
+
- **The governed prefix is `acl/`, not `private/`, and the API is renamed to
|
|
82
|
+
match.**
|
|
83
|
+
|
|
84
|
+
`private` was wrong in the most confusing way possible. The commonest case is
|
|
85
|
+
a **publicly readable** row — a login background, say — whose file is then
|
|
86
|
+
correctly served to everybody, from a url that said `private`. The prefix
|
|
87
|
+
never meant "secret". It means **the owning row decides**, and a public row
|
|
88
|
+
decides "everyone".
|
|
89
|
+
|
|
90
|
+
Renamed now because the prefix is baked into stored filenames: changing it
|
|
91
|
+
later orphans every file already written under the old one. 2.12.0 was hours
|
|
92
|
+
old and had one consumer.
|
|
93
|
+
|
|
94
|
+
| 2.12.0 | 2.13.0 |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `private/<Class>/<objectId>/` | `acl/<Class>/<objectId>/` |
|
|
97
|
+
| `PRIVATE_PREFIX` | `ACL_PREFIX` |
|
|
98
|
+
| `isPrivatePath` | `isAclGoverned` |
|
|
99
|
+
| `privateOwnerOf` | `aclOwnerOf` |
|
|
100
|
+
| `privateDirectoryFor` | `aclDirectoryFor` |
|
|
101
|
+
| `attachPrivateFile` | `attachAclFile` |
|
|
102
|
+
| `privateFileRule` | `aclFileRule` |
|
|
103
|
+
| `PrivateFileOwner` | `AclFileOwner` |
|
|
104
|
+
| `PrivateFileRuleOptions` | `AclFileRuleOptions` |
|
|
105
|
+
|
|
106
|
+
**Migrating:** rename the imports, then **re-upload** anything already stored
|
|
107
|
+
under `private/`. Those files keep working — but as ordinary public Parse
|
|
108
|
+
files, because the guard no longer recognises their path. There is no
|
|
109
|
+
rewriting a stored filename.
|
|
110
|
+
|
|
111
|
+
The `Cache-Control: private, no-store` default is unchanged: that `private`
|
|
112
|
+
is the HTTP cache directive and has nothing to do with the prefix.
|
|
113
|
+
|
|
114
|
+
Version 2.13.0 rather than 3.0.0 despite being breaking: 3.0.0 is reserved
|
|
115
|
+
for the public `parse-server-kit` package (see the note at the top of this
|
|
116
|
+
file).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
11
120
|
## [2.12.0]
|
|
12
121
|
|
|
13
122
|
### Added
|
|
@@ -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
|
-
* [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
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,90 +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
|
-
* 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 {
|
|
145
|
+
/** Who a stored path says owns the bytes. */
|
|
146
|
+
export interface FileOwner {
|
|
153
147
|
className: string;
|
|
154
148
|
objectId: string;
|
|
155
149
|
}
|
|
156
150
|
/**
|
|
157
|
-
*
|
|
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.
|
|
151
|
+
* Read the owner out of a stored path, or null if the path has no owner.
|
|
169
152
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
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.
|
|
156
|
+
*
|
|
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.
|
|
172
160
|
*/
|
|
173
|
-
export declare function
|
|
161
|
+
export declare function fileOwnerOf(path: string): FileOwner | null;
|
|
174
162
|
/**
|
|
175
|
-
* The directory a row's
|
|
163
|
+
* The directory a row's files live in.
|
|
176
164
|
*
|
|
177
|
-
* Throws rather than quietly producing something Parse
|
|
178
|
-
*
|
|
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
|
|
179
167
|
* alphanumeric) surfaces here, at the one call site, instead of as a confusing
|
|
180
168
|
* upload failure later.
|
|
181
169
|
*/
|
|
182
|
-
export declare function
|
|
170
|
+
export declare function ownerDirectoryFor(className: string, objectId: string): string;
|
|
183
171
|
/**
|
|
184
172
|
* Store a file so that the owning row's ACL governs it.
|
|
185
173
|
*
|
|
186
174
|
* The row must already be saved — its objectId is part of the path, which is
|
|
187
|
-
* what lets
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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.
|
|
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.
|
|
194
178
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* 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.
|
|
198
181
|
*/
|
|
199
|
-
export declare function
|
|
182
|
+
export declare function attachOwnedFile(row: Parse.Object, field: string, upload: {
|
|
200
183
|
base64: string;
|
|
201
184
|
name?: string;
|
|
202
185
|
}): Promise<void>;
|
|
203
|
-
/** Options for [
|
|
204
|
-
export interface
|
|
186
|
+
/** Options for [rowAclFileRule]. */
|
|
187
|
+
export interface RowAclFileRuleOptions {
|
|
205
188
|
/**
|
|
206
|
-
*
|
|
189
|
+
* How long a response may be cached, in seconds. Default 60.
|
|
190
|
+
*
|
|
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.
|
|
207
195
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
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.
|
|
211
201
|
*/
|
|
212
|
-
|
|
202
|
+
maxAge?: number;
|
|
213
203
|
}
|
|
214
204
|
/**
|
|
215
|
-
* Serve a
|
|
205
|
+
* Serve a file only to a caller who can read the row that owns it.
|
|
216
206
|
*
|
|
217
|
-
* The caller is resolved from their session token, so Parse evaluates the
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
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.
|
|
222
212
|
*/
|
|
223
|
-
export declare function
|
|
213
|
+
export declare function rowAclFileRule(options?: RowAclFileRuleOptions): FileAccessRule;
|
|
224
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
|
-
* [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
59
|
*/
|
|
57
60
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.PRIVATE_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.privateFileRule = privateFileRule;
|
|
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,122 +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 private-file rule ───────────────────────────────────────────────────
|
|
173
177
|
/**
|
|
174
|
-
*
|
|
178
|
+
* Read the owner out of a stored path, or null if the path has no owner.
|
|
175
179
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* already written under the old one.
|
|
180
|
-
*/
|
|
181
|
-
exports.PRIVATE_PREFIX = 'private';
|
|
182
|
-
/**
|
|
183
|
-
* Is this path governed at all?
|
|
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
|
-
* 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.
|
|
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.
|
|
191
187
|
*/
|
|
192
|
-
function
|
|
193
|
-
|
|
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))
|
|
188
|
+
function fileOwnerOf(path) {
|
|
189
|
+
if (typeof path !== 'string' || path === '')
|
|
203
190
|
return null;
|
|
204
|
-
// Exactly:
|
|
191
|
+
// Exactly: ClassName / objectId / filename
|
|
205
192
|
const parts = path.split('/');
|
|
206
|
-
if (parts.length !==
|
|
193
|
+
if (parts.length !== 3)
|
|
207
194
|
return null;
|
|
208
|
-
const [
|
|
195
|
+
const [className, objectId, filename] = parts;
|
|
209
196
|
if (!className || !objectId || !filename)
|
|
210
197
|
return null;
|
|
211
198
|
return { className, objectId };
|
|
212
199
|
}
|
|
213
200
|
/**
|
|
214
|
-
* The directory a row's
|
|
201
|
+
* The directory a row's files live in.
|
|
215
202
|
*
|
|
216
|
-
* Throws rather than quietly producing something Parse
|
|
217
|
-
*
|
|
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
|
|
218
205
|
* alphanumeric) surfaces here, at the one call site, instead of as a confusing
|
|
219
206
|
* upload failure later.
|
|
220
207
|
*/
|
|
221
|
-
function
|
|
208
|
+
function ownerDirectoryFor(className, objectId) {
|
|
222
209
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_]*$/.test(className)) {
|
|
223
|
-
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`);
|
|
224
211
|
}
|
|
225
212
|
if (!/^[a-zA-Z0-9]+$/.test(objectId)) {
|
|
226
|
-
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'A
|
|
213
|
+
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'A file needs its owning row saved first, so that it has an objectId');
|
|
227
214
|
}
|
|
228
|
-
return `${
|
|
215
|
+
return `${className}/${objectId}`;
|
|
229
216
|
}
|
|
230
217
|
/**
|
|
231
218
|
* Store a file so that the owning row's ACL governs it.
|
|
232
219
|
*
|
|
233
220
|
* The row must already be saved — its objectId is part of the path, which is
|
|
234
|
-
* what lets
|
|
235
|
-
*
|
|
236
|
-
*
|
|
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.
|
|
237
224
|
*
|
|
238
|
-
* **This should be the only way an application stores a
|
|
239
|
-
*
|
|
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.
|
|
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.
|
|
245
227
|
*/
|
|
246
|
-
async function
|
|
228
|
+
async function attachOwnedFile(row, field, upload) {
|
|
247
229
|
if (!row.id) {
|
|
248
|
-
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');
|
|
249
231
|
}
|
|
250
|
-
const file = new Parse.File(encodeURIComponent(upload.name ?? '
|
|
232
|
+
const file = new Parse.File(encodeURIComponent(upload.name ?? 'file'), {
|
|
251
233
|
base64: upload.base64,
|
|
252
234
|
});
|
|
253
235
|
// Sent as `X-Parse-File-Directory`; honoured by Parse Server from 9.5.0.
|
|
254
|
-
file.setDirectory(
|
|
236
|
+
file.setDirectory(ownerDirectoryFor(row.className, row.id));
|
|
255
237
|
const saved = await file.save({ useMasterKey: true });
|
|
256
238
|
row.set(field, saved);
|
|
257
239
|
}
|
|
258
240
|
/**
|
|
259
|
-
* Serve a
|
|
241
|
+
* Serve a file only to a caller who can read the row that owns it.
|
|
260
242
|
*
|
|
261
|
-
* The caller is resolved from their session token, so Parse evaluates the
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
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.
|
|
266
248
|
*/
|
|
267
|
-
function
|
|
268
|
-
const
|
|
249
|
+
function rowAclFileRule(options = {}) {
|
|
250
|
+
const maxAge = options.maxAge ?? 60;
|
|
269
251
|
return {
|
|
270
|
-
name: '
|
|
271
|
-
|
|
272
|
-
|
|
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,
|
|
273
256
|
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);
|
|
257
|
+
const owner = fileOwnerOf(path);
|
|
282
258
|
if (!owner)
|
|
283
259
|
throw notFound();
|
|
284
260
|
const sessionToken = user?.getSessionToken();
|
|
@@ -289,6 +265,28 @@ function privateFileRule(options = {}) {
|
|
|
289
265
|
if (!row)
|
|
290
266
|
throw notFound();
|
|
291
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
|
+
},
|
|
292
290
|
};
|
|
293
291
|
}
|
|
294
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, "privateDirectoryFor", { enumerable: true, get: function () { return fileAccess_1.privateDirectoryFor; } });
|
|
116
|
-
Object.defineProperty(exports, "PRIVATE_PREFIX", { enumerable: true, get: function () { return fileAccess_1.PRIVATE_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",
|