@filelayer/core 0.3.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 +338 -0
- package/LICENSE +202 -0
- package/MIGRATIONS.md +328 -0
- package/NOTICE +37 -0
- package/README.md +343 -0
- package/SEMANTICS.md +729 -0
- package/dist/authz.d.ts +524 -0
- package/dist/authz.d.ts.map +1 -0
- package/dist/authz.js +889 -0
- package/dist/authz.js.map +1 -0
- package/dist/db.d.ts +145 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +217 -0
- package/dist/db.js.map +1 -0
- package/dist/delivery.d.ts +293 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +519 -0
- package/dist/delivery.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/filelayer.d.ts +542 -0
- package/dist/filelayer.d.ts.map +1 -0
- package/dist/filelayer.js +1360 -0
- package/dist/filelayer.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/simple.d.ts +297 -0
- package/dist/simple.d.ts.map +1 -0
- package/dist/simple.js +492 -0
- package/dist/simple.js.map +1 -0
- package/dist/storage.d.ts +269 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +700 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +432 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +862 -0
- package/dist/store.js.map +1 -0
- package/package.json +77 -0
- package/schema.sql +1190 -0
- package/src/authz.ts +1398 -0
- package/src/db.ts +271 -0
- package/src/delivery.ts +737 -0
- package/src/errors.ts +24 -0
- package/src/filelayer.ts +1836 -0
- package/src/index.ts +7 -0
- package/src/simple.ts +666 -0
- package/src/storage.ts +917 -0
- package/src/store.ts +1072 -0
- package/test/delivery.test.ts +0 -0
- package/test/group-subjects.test.ts +1072 -0
- package/test/helpers.ts +65 -0
- package/test/listing.test.ts +689 -0
- package/test/local-s3.d.mts +33 -0
- package/test/local-s3.mjs +400 -0
- package/test/persistence.test.ts +953 -0
- package/test/regression.test.ts +619 -0
- package/test/s3-live.test.ts +322 -0
- package/test/security.test.ts +1652 -0
- package/test/semantics.test.ts +888 -0
- package/test/storage.test.ts +437 -0
- package/test/tiers.test.ts +432 -0
- package/test/vault-example.test.ts +302 -0
- package/tsconfig.build.json +29 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FILELAYER -- the surface an application developer actually touches.
|
|
3
|
+
*
|
|
4
|
+
* Design rule for this file: every method that touches a file or an org calls
|
|
5
|
+
* into the authorization engine and does nothing before it. There is no
|
|
6
|
+
* "internal" variant that skips the check, because the moment such a variant
|
|
7
|
+
* exists someone will call it from a route handler at 6pm on a Friday.
|
|
8
|
+
*
|
|
9
|
+
* SECOND design rule, added after the security review: this file contains no
|
|
10
|
+
* security LOGIC, only security PLUMBING. Every rule that used to live here as
|
|
11
|
+
* a "patch at the wrong layer" has moved into `authz.ts` or `schema.sql`:
|
|
12
|
+
*
|
|
13
|
+
* - capability attenuation on share -> authorizeShare() + a BEFORE
|
|
14
|
+
* INSERT trigger on file_grant
|
|
15
|
+
* - the 410/409 existence-oracle downgrade
|
|
16
|
+
* and its `hasStanding()` helper -> evaluation order in authorize()
|
|
17
|
+
* - the membership / viewer checks in
|
|
18
|
+
* upload() -> authorizeOrg('create_file')
|
|
19
|
+
* - the admin check in auditLog() -> authorizeOrg('read_audit')
|
|
20
|
+
* - the unaudited early return in redeem()
|
|
21
|
+
* for unknown secrets -> the engine's system chain
|
|
22
|
+
*
|
|
23
|
+
* Errors are thrown as `FilelayerError`, already collapsed through
|
|
24
|
+
* `toPublicError`, so the developer cannot accidentally return our internal
|
|
25
|
+
* deny reason to an attacker. That collapsing is a security-sensitive decision
|
|
26
|
+
* we make once, here, instead of asking the developer to make it per-route.
|
|
27
|
+
*/
|
|
28
|
+
import { type Capability, type FileRef, type FileVisibility, type GrantSubjectType, type OrgRole, type Principal } from './authz.ts';
|
|
29
|
+
import { type Queryable } from './db.ts';
|
|
30
|
+
import { PostgresStore, type AuditRow, type AuditChainResult } from './store.ts';
|
|
31
|
+
import { type PutBody, type StorageAdapter } from './storage.ts';
|
|
32
|
+
import { FilesApi, OrgsApi, SharesApi } from './simple.ts';
|
|
33
|
+
import { type Disposition, type RedirectDeliveryConfig, type StreamedDelivery } from './delivery.ts';
|
|
34
|
+
import { FilelayerError } from './errors.ts';
|
|
35
|
+
export { FilelayerError };
|
|
36
|
+
export interface UploadInput {
|
|
37
|
+
name: string;
|
|
38
|
+
contentType: string;
|
|
39
|
+
/**
|
|
40
|
+
* Advisory. The AUTHORITATIVE size is what the adapter reports it actually
|
|
41
|
+
* wrote, and that is what lands in `size_bytes`. A caller-supplied size that
|
|
42
|
+
* disagrees with the object is how a `content-length` ends up truncating a
|
|
43
|
+
* download.
|
|
44
|
+
*/
|
|
45
|
+
size?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Bytes, or a stream of bytes.
|
|
48
|
+
*
|
|
49
|
+
* A `Uint8Array` is the convenient form for the small-file case that
|
|
50
|
+
* dominates (avatars, PDFs, attachments) and is kept for exactly that reason.
|
|
51
|
+
* A `ReadableStream` is the form that does not put the whole object on the
|
|
52
|
+
* heap: with the S3 adapter it becomes a multipart upload whose peak memory
|
|
53
|
+
* is one part, whatever the object's size.
|
|
54
|
+
*/
|
|
55
|
+
body: PutBody;
|
|
56
|
+
/**
|
|
57
|
+
* Who, inside the owning org, can see this file before anybody shares it.
|
|
58
|
+
*
|
|
59
|
+
* 'private' (DEFAULT) -- owner + org admins/owners only. Everyone else
|
|
60
|
+
* needs an explicit grant.
|
|
61
|
+
* 'org' -- every member of the org may read it.
|
|
62
|
+
*
|
|
63
|
+
* The default is the restrictive one. See schema.sql, `file_visibility`.
|
|
64
|
+
*/
|
|
65
|
+
visibility?: FileVisibility;
|
|
66
|
+
/** Seconds until the file itself expires (lifecycle, not a grant). */
|
|
67
|
+
expiresIn?: number;
|
|
68
|
+
/** Seconds of retention floor: deletion is blocked until it passes. */
|
|
69
|
+
retainFor?: number;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* WHO a grant is for. A grant's subject is a PRINCIPAL SET (RFC-001):
|
|
74
|
+
*
|
|
75
|
+
* actor exactly one person
|
|
76
|
+
* role every member of an org at role >= `minRole`
|
|
77
|
+
* org every member of an org, at any role
|
|
78
|
+
* link whoever holds the secret (bearer, not identity)
|
|
79
|
+
* anonymous everyone
|
|
80
|
+
*
|
|
81
|
+
* `org` and `role` may name an org OTHER than the file's own -- that is the
|
|
82
|
+
* point ("the company that posted this job may read this CV") -- but it must be
|
|
83
|
+
* an org in the same PROJECT. Cross-project is unrepresentable, by composite
|
|
84
|
+
* foreign key, and refused here with a 404 before it gets that far.
|
|
85
|
+
*
|
|
86
|
+
* I6: an issuer whose own authority came from a GRANT may only mint `actor` or
|
|
87
|
+
* `link`. See `authorizeShare`.
|
|
88
|
+
*/
|
|
89
|
+
export type ShareSubject = {
|
|
90
|
+
type: 'link';
|
|
91
|
+
} | {
|
|
92
|
+
type: 'anonymous';
|
|
93
|
+
} | {
|
|
94
|
+
type: 'actor';
|
|
95
|
+
actorId: string;
|
|
96
|
+
}
|
|
97
|
+
/** Every member of `orgId`, at any role. */
|
|
98
|
+
| {
|
|
99
|
+
type: 'org';
|
|
100
|
+
orgId: string;
|
|
101
|
+
}
|
|
102
|
+
/** Every member of `orgId` at `minRole` or above. */
|
|
103
|
+
| {
|
|
104
|
+
type: 'role';
|
|
105
|
+
orgId: string;
|
|
106
|
+
minRole: OrgRole;
|
|
107
|
+
};
|
|
108
|
+
export interface ShareInput {
|
|
109
|
+
subject: ShareSubject;
|
|
110
|
+
capabilities?: Capability[];
|
|
111
|
+
expiresIn?: number;
|
|
112
|
+
maxDownloads?: number;
|
|
113
|
+
password?: string;
|
|
114
|
+
}
|
|
115
|
+
export interface ShareResult {
|
|
116
|
+
grantId: string;
|
|
117
|
+
/** Returned exactly once. Only its SHA-256 is persisted. */
|
|
118
|
+
secret?: string;
|
|
119
|
+
url?: string;
|
|
120
|
+
/**
|
|
121
|
+
* The EFFECTIVE lifetime and cap, after attenuation against the parent grant.
|
|
122
|
+
* A delegated share can never exceed the authority it came from, so these may
|
|
123
|
+
* be tighter than what was asked for. They are returned rather than silently
|
|
124
|
+
* applied so that the clamp is visible to the caller.
|
|
125
|
+
*/
|
|
126
|
+
expiresAt: Date | null;
|
|
127
|
+
maxDownloads: number | null;
|
|
128
|
+
/** Non-null when this grant was delegated from another grant (P4). */
|
|
129
|
+
parentGrantId: string | null;
|
|
130
|
+
}
|
|
131
|
+
export interface FileRecord extends FileRef {
|
|
132
|
+
name: string;
|
|
133
|
+
contentType: string;
|
|
134
|
+
sizeBytes: number | null;
|
|
135
|
+
/**
|
|
136
|
+
* WHICH store the bytes are in. This column used to be written as the literal
|
|
137
|
+
* `'memory'` on every insert regardless of the configured adapter, which
|
|
138
|
+
* meant a production deployment recorded every object as living in an
|
|
139
|
+
* in-process Map. It participates in `file_storage_key_idx`
|
|
140
|
+
* (UNIQUE (storage_provider, storage_key)), so it is half of an object's
|
|
141
|
+
* identity, not a label.
|
|
142
|
+
*/
|
|
143
|
+
storageProvider: string;
|
|
144
|
+
storageKey: string;
|
|
145
|
+
createdAt: Date;
|
|
146
|
+
}
|
|
147
|
+
export interface GrantSummary {
|
|
148
|
+
id: string;
|
|
149
|
+
fileId: string;
|
|
150
|
+
parentGrantId: string | null;
|
|
151
|
+
subjectType: GrantSubjectType;
|
|
152
|
+
subjectId: string | null;
|
|
153
|
+
/** The org whose members are the subject, for 'org' and 'role' grants. */
|
|
154
|
+
subjectOrgId: string | null;
|
|
155
|
+
/** The role floor, for 'role' grants. Null on 'org' reads as 'viewer'. */
|
|
156
|
+
subjectMinRole: OrgRole | null;
|
|
157
|
+
capabilities: Capability[];
|
|
158
|
+
hasPassword: boolean;
|
|
159
|
+
expiresAt: Date | null;
|
|
160
|
+
maxDownloads: number | null;
|
|
161
|
+
downloadCount: number;
|
|
162
|
+
revokedAt: Date | null;
|
|
163
|
+
/** Recursive liveness: false if this grant OR any ancestor is dead. */
|
|
164
|
+
live: boolean;
|
|
165
|
+
createdBy: string | null;
|
|
166
|
+
createdAt: Date;
|
|
167
|
+
}
|
|
168
|
+
export interface FilelayerOptions {
|
|
169
|
+
baseUrl?: string;
|
|
170
|
+
/**
|
|
171
|
+
* The customer application this instance speaks for (P8).
|
|
172
|
+
*
|
|
173
|
+
* In a hosted deployment the API layer resolves a project from the request's API
|
|
174
|
+
* key and constructs one of these bound to it. A bound instance cannot see,
|
|
175
|
+
* list, audit or address anything in another project. Omit it and everything
|
|
176
|
+
* lands in the default project, which is the correct behaviour for a
|
|
177
|
+
* single-project deployment. Pass `null` for the control plane.
|
|
178
|
+
*/
|
|
179
|
+
projectId?: string | null;
|
|
180
|
+
/**
|
|
181
|
+
* OPT-IN REDIRECT DELIVERY. Absent means every delivery is proxied, which is
|
|
182
|
+
* the only mode with an unqualified "revocation is immediate" guarantee.
|
|
183
|
+
*
|
|
184
|
+
* Read the DELIVERY MODES block at the top of `delivery.ts` before setting
|
|
185
|
+
* this. It will not typecheck without the acknowledgement string, and the
|
|
186
|
+
* acknowledgement string says what you are accepting.
|
|
187
|
+
*/
|
|
188
|
+
redirectDelivery?: RedirectDeliveryConfig;
|
|
189
|
+
}
|
|
190
|
+
export declare class Filelayer {
|
|
191
|
+
#private;
|
|
192
|
+
readonly store: PostgresStore;
|
|
193
|
+
private readonly db;
|
|
194
|
+
private readonly storage;
|
|
195
|
+
private readonly opts;
|
|
196
|
+
private _files?;
|
|
197
|
+
private _orgs?;
|
|
198
|
+
private _shares?;
|
|
199
|
+
/** Null unless redirect delivery was configured AND acknowledged. */
|
|
200
|
+
private readonly redirect;
|
|
201
|
+
constructor(db: Queryable, storage: StorageAdapter, opts?: FilelayerOptions);
|
|
202
|
+
/**
|
|
203
|
+
* A throwaway, in-process instance: PGlite + in-memory bytes.
|
|
204
|
+
*
|
|
205
|
+
* For a five-minute first run and for tests. **Everything is lost when the
|
|
206
|
+
* process exits** -- there is no file on disk and no bucket. Production is
|
|
207
|
+
* `new Filelayer(pgPool, new S3Storage({...}), { baseUrl })`; see
|
|
208
|
+
* docs/QUICKSTART.md, which does not hide the three configuration steps.
|
|
209
|
+
*/
|
|
210
|
+
static quickstart(opts?: {
|
|
211
|
+
baseUrl?: string;
|
|
212
|
+
}): Promise<Filelayer>;
|
|
213
|
+
/** Where public URLs are rooted. Read-only; set once at construction. */
|
|
214
|
+
get baseUrl(): string | undefined;
|
|
215
|
+
/** The project this instance is bound to. Null means unscoped. */
|
|
216
|
+
get projectId(): string | null;
|
|
217
|
+
get files(): FilesApi;
|
|
218
|
+
get orgs(): OrgsApi;
|
|
219
|
+
get shares(): SharesApi;
|
|
220
|
+
/**
|
|
221
|
+
* Create an organization, optionally with its first owner.
|
|
222
|
+
*
|
|
223
|
+
* Creating a tenant is a control-plane operation: there is no principal
|
|
224
|
+
* inside the system yet who could be authorized to do it, and pretending
|
|
225
|
+
* otherwise would be theatre. Passing `ownerActorId` closes the bootstrap
|
|
226
|
+
* gap that would otherwise exist -- an org is never memberless, so there is
|
|
227
|
+
* never a "the org has no members yet, let anyone in" path for an attacker to
|
|
228
|
+
* find. Every subsequent membership change is authorized (see `addMember`).
|
|
229
|
+
*/
|
|
230
|
+
createOrg(externalId: string, name?: string, opts?: {
|
|
231
|
+
ownerActorId?: string;
|
|
232
|
+
}): Promise<{
|
|
233
|
+
id: string;
|
|
234
|
+
}>;
|
|
235
|
+
createActor(externalId: string): Promise<{
|
|
236
|
+
id: string;
|
|
237
|
+
}>;
|
|
238
|
+
/**
|
|
239
|
+
* Register a customer application. Control plane; see the note on the
|
|
240
|
+
* lifecycle methods below for why these three take no principal.
|
|
241
|
+
*/
|
|
242
|
+
createProject(key: string, name?: string): Promise<{
|
|
243
|
+
id: string;
|
|
244
|
+
}>;
|
|
245
|
+
/**
|
|
246
|
+
* Soft-delete a tenant. Every grant on every file in it is dead on the next
|
|
247
|
+
* request; every membership stops conferring anything. Nothing is erased and
|
|
248
|
+
* no row a retention hold protects is touched, so this cannot be used to
|
|
249
|
+
* defeat retention -- see SEMANTICS.md.
|
|
250
|
+
*/
|
|
251
|
+
softDeleteOrg(orgId: string): Promise<void>;
|
|
252
|
+
/** Exactly reverses `softDeleteOrg`. Liveness is derived, so nothing is lost. */
|
|
253
|
+
restoreOrg(orgId: string): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Soft-delete an identity.
|
|
256
|
+
*
|
|
257
|
+
* Three things die at once, and all three are derived rather than written:
|
|
258
|
+
* their role-derived access, every grant issued TO them, and every grant they
|
|
259
|
+
* ISSUED. The third is the judgement call; the reasoning is in schema.sql at
|
|
260
|
+
* `grant_scope_is_live` and in SEMANTICS.md. It is loud on purpose: deleting
|
|
261
|
+
* a prolific sharer revokes a lot of links, and that is the correct reading of
|
|
262
|
+
* P4, not a side effect.
|
|
263
|
+
*/
|
|
264
|
+
softDeleteActor(actorId: string): Promise<void>;
|
|
265
|
+
restoreActor(actorId: string): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Soft-delete a customer application: every org, every file and every grant
|
|
268
|
+
* inside it stops working immediately. This is the "we terminated that
|
|
269
|
+
* customer" operation and it is the widest blast radius in the system.
|
|
270
|
+
*/
|
|
271
|
+
softDeleteProject(projectId: string): Promise<void>;
|
|
272
|
+
restoreProject(projectId: string): Promise<void>;
|
|
273
|
+
/**
|
|
274
|
+
* Add a member, or change an existing member's role.
|
|
275
|
+
*
|
|
276
|
+
* This used to take no principal at all. Anyone who could reach it could
|
|
277
|
+
* make anyone an owner of any org, and nothing was written to the audit log.
|
|
278
|
+
* Membership is the privilege that confers every other privilege, so it is
|
|
279
|
+
* now authorized by the same engine as everything else and audited on every
|
|
280
|
+
* outcome.
|
|
281
|
+
*/
|
|
282
|
+
addMember(principal: Principal, orgId: string, actorId: string, role: OrgRole): Promise<void>;
|
|
283
|
+
removeMember(principal: Principal, orgId: string, actorId: string): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* Creation is the one file operation with no file to authorize against, so
|
|
286
|
+
* the question is org-scoped: does this actor hold `create_file` in this org?
|
|
287
|
+
* That is now asked of the engine rather than answered here.
|
|
288
|
+
*
|
|
289
|
+
* SIGNATURE CHANGE. This used to take a bare
|
|
290
|
+
* `actorId: string` while every other method took a `Principal`. That
|
|
291
|
+
* inconsistency was itself the defect: at the one call site in the example the
|
|
292
|
+
* developer passed `b.uploaderId` -- a value out of the REQUEST BODY -- rather
|
|
293
|
+
* than the authenticated actor, because the parameter's type did not tell them
|
|
294
|
+
* which one it wanted. Files are private-by-default AND owner-readable, so
|
|
295
|
+
* forging `owner_id` hands the wrong person permanent read access to the
|
|
296
|
+
* document, silently. A `Principal` is not confusable with a request field.
|
|
297
|
+
*/
|
|
298
|
+
upload(principal: Principal, orgId: string, input: UploadInput): Promise<FileRecord>;
|
|
299
|
+
/**
|
|
300
|
+
* Read a file's bytes, WITH the headers required to serve them safely.
|
|
301
|
+
*
|
|
302
|
+
* The `headers` field closes a header-handling defect. Before it, this method returned a
|
|
303
|
+
* `Uint8Array` and the application decided what `Content-Type`,
|
|
304
|
+
* `Content-Disposition`, `X-Content-Type-Options` and `Cache-Control` to put
|
|
305
|
+
* on the response -- three security-sensitive decisions the library was
|
|
306
|
+
* handing back to the developer while claiming to have removed them, and
|
|
307
|
+
* which the example got wrong. They are computed here now, from the file
|
|
308
|
+
* record, with no option to disable them. See `delivery.ts`.
|
|
309
|
+
*
|
|
310
|
+
* This path now CHARGES the download cap. See `deliver()` below for the
|
|
311
|
+
* semantics and the reasoning.
|
|
312
|
+
*/
|
|
313
|
+
read(principal: Principal, fileId: string, opts?: {
|
|
314
|
+
disposition?: Disposition;
|
|
315
|
+
}): Promise<{
|
|
316
|
+
file: FileRecord;
|
|
317
|
+
body: Uint8Array;
|
|
318
|
+
headers: Record<string, string>;
|
|
319
|
+
grantId?: string;
|
|
320
|
+
/** Null when no cap binds this delivery (a role-derived read, or no cap). */
|
|
321
|
+
remainingDownloads: number | null;
|
|
322
|
+
}>;
|
|
323
|
+
/**
|
|
324
|
+
* The streaming read. Same decision, same charge, same audit -- no buffer.
|
|
325
|
+
*
|
|
326
|
+
* Returns either a `ProxyDelivery` (bytes, as a stream) or, when redirect
|
|
327
|
+
* delivery is configured AND this delivery is eligible, a `RedirectDelivery`
|
|
328
|
+
* (a 302 to a short-lived presigned URL). The mode is on the returned object
|
|
329
|
+
* and in the audit log; nothing about it is implicit.
|
|
330
|
+
*/
|
|
331
|
+
readStream(principal: Principal, fileId: string,
|
|
332
|
+
/**
|
|
333
|
+
* `mode` defaults to 'auto', which means "apply this instance's redirect
|
|
334
|
+
* policy". On an instance that has not configured `redirectDelivery` -- the
|
|
335
|
+
* default -- that policy is "never redirect", so 'auto' and 'proxy' are the
|
|
336
|
+
* same thing and nothing becomes cacheable that was not before. Pass
|
|
337
|
+
* 'proxy' to force proxying on an instance that HAS opted in.
|
|
338
|
+
*/
|
|
339
|
+
opts?: {
|
|
340
|
+
disposition?: Disposition;
|
|
341
|
+
mode?: 'proxy' | 'auto';
|
|
342
|
+
range?: {
|
|
343
|
+
start: number;
|
|
344
|
+
end?: number;
|
|
345
|
+
};
|
|
346
|
+
}): Promise<StreamedDelivery & {
|
|
347
|
+
file: FileRecord;
|
|
348
|
+
grantId?: string;
|
|
349
|
+
remainingDownloads: number | null;
|
|
350
|
+
}>;
|
|
351
|
+
/**
|
|
352
|
+
* Metadata without bytes, authorized exactly like `read`.
|
|
353
|
+
*
|
|
354
|
+
* This exists because `getFileRecord()` used to be public and took no
|
|
355
|
+
* principal -- see the note on it below. Callers that wanted a file's
|
|
356
|
+
* metadata had an unauthorized way to get it; now they have an authorized one.
|
|
357
|
+
*
|
|
358
|
+
* It does NOT charge the download cap, and that asymmetry is the whole point
|
|
359
|
+
* of the delivery-cap rule: the cap counts BYTES LEAVING, and `stat` delivers
|
|
360
|
+
* none.
|
|
361
|
+
*/
|
|
362
|
+
stat(principal: Principal, fileId: string): Promise<FileRecord>;
|
|
363
|
+
/**
|
|
364
|
+
* Which files in this org may this principal `capability`?
|
|
365
|
+
*
|
|
366
|
+
* This is the primitive that was missing, and its absence was
|
|
367
|
+
* the reason the "0 authorization lines" claim survived: the example simply
|
|
368
|
+
* did not have a listing screen, and building one meant hand-rolling the org
|
|
369
|
+
* filter, the visibility rule, the owner check, the role check and the union
|
|
370
|
+
* over `file_grant` in application SQL.
|
|
371
|
+
*
|
|
372
|
+
* WHY IT CANNOT BE GOT WRONG.
|
|
373
|
+
*
|
|
374
|
+
* - There is no filter parameter. The signature takes a principal, an org,
|
|
375
|
+
* a capability, a page size and an opaque cursor. There is nothing here to
|
|
376
|
+
* forget to pass and nothing that widens the result set.
|
|
377
|
+
* - The predicate is generated from the same role table and the same
|
|
378
|
+
* lifecycle gate `authorize()` uses (see `listPredicate` in authz.ts), so
|
|
379
|
+
* the two cannot drift by editing one of them.
|
|
380
|
+
* - `test/listing.test.ts` asserts set equality against `authorize()` over a
|
|
381
|
+
* randomized corpus, on every capability, on every run.
|
|
382
|
+
*
|
|
383
|
+
* WHAT IT COSTS. One SQL query and one audit event, independent of page size.
|
|
384
|
+
* A per-file `authorize()` loop would be 4 round trips x N.
|
|
385
|
+
*
|
|
386
|
+
* The empty page is a valid answer: a caller with no standing sees nothing,
|
|
387
|
+
* and so does a caller naming an org that does not exist. Neither is an error,
|
|
388
|
+
* because distinguishing them would rebuild the existence oracle.
|
|
389
|
+
*/
|
|
390
|
+
listFiles(principal: Principal, orgId: string, opts?: ListFilesOptions): Promise<FileListPage>;
|
|
391
|
+
/**
|
|
392
|
+
* ORDERING, and it is the mirror image of `upload()`.
|
|
393
|
+
*
|
|
394
|
+
* The metadata delete COMMITS FIRST -- the decision, the audit event the
|
|
395
|
+
* engine wrote for it, and the state change, all in one transaction -- and
|
|
396
|
+
* only then are the bytes removed. A crash in between leaves an object no row
|
|
397
|
+
* points at, which is an orphan and therefore a garbage-collection problem.
|
|
398
|
+
* The other ordering would leave a live, listable, authorizable `file` row
|
|
399
|
+
* whose object is gone, which is data loss.
|
|
400
|
+
*
|
|
401
|
+
* The bytes are removed OUTSIDE the transaction for the same reason they are
|
|
402
|
+
* written outside it: object storage cannot roll back, so including it would
|
|
403
|
+
* mean a rolled-back transaction had already destroyed the object.
|
|
404
|
+
*/
|
|
405
|
+
delete(principal: Principal, fileId: string): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* COLLECT ORPHANED OBJECTS. A REQUIRED OPERATIONAL JOB.
|
|
408
|
+
*
|
|
409
|
+
* An orphan is an object in the store with no `file` row pointing at
|
|
410
|
+
* (provider, key). Two things produce them, both of them by design:
|
|
411
|
+
*
|
|
412
|
+
* - a crash between `storage.put()` and the metadata commit in `upload()`;
|
|
413
|
+
* - a crash between the metadata commit and `storage.delete()` in
|
|
414
|
+
* `delete()`.
|
|
415
|
+
*
|
|
416
|
+
* Neither is a correctness problem -- an orphan is unreachable, because every
|
|
417
|
+
* read path in the system starts from a `file` row, and keys are fresh UUIDs
|
|
418
|
+
* that are never reissued -- but both cost money, and an uncollected orphan
|
|
419
|
+
* from a delete is a compliance problem: the customer was told the bytes were
|
|
420
|
+
* gone.
|
|
421
|
+
*
|
|
422
|
+
* WHAT MAKES THIS SAFE. Two things, and they are both load-bearing:
|
|
423
|
+
*
|
|
424
|
+
* 1. `olderThanSeconds` (default 1 hour, minimum 60s). An object written
|
|
425
|
+
* seconds ago may belong to an upload whose transaction has not committed
|
|
426
|
+
* yet. Deleting it would turn a successful upload into permanent data
|
|
427
|
+
* loss -- the exact failure this whole ordering exists to avoid. The grace
|
|
428
|
+
* period must exceed the longest plausible upload-plus-commit.
|
|
429
|
+
* 2. The `file` lookup is by (storage_provider, storage_key), the pair the
|
|
430
|
+
* UNIQUE index is on, and it is NOT project-scoped and NOT filtered on
|
|
431
|
+
* `deleted_at`. A soft-deleted file whose bytes were never removed still
|
|
432
|
+
* has a row; this job must not race the delete path into removing bytes a
|
|
433
|
+
* retention hold is protecting. It only removes what NOTHING references.
|
|
434
|
+
*
|
|
435
|
+
* Control plane: it takes no principal for the same reason the other
|
|
436
|
+
* lifecycle operations do not (see above). `dryRun` is the default.
|
|
437
|
+
*/
|
|
438
|
+
collectStorageOrphans(opts?: {
|
|
439
|
+
prefix?: string;
|
|
440
|
+
olderThanSeconds?: number;
|
|
441
|
+
limit?: number;
|
|
442
|
+
dryRun?: boolean;
|
|
443
|
+
}): Promise<{
|
|
444
|
+
scanned: number;
|
|
445
|
+
orphans: string[];
|
|
446
|
+
deleted: number;
|
|
447
|
+
truncated: boolean;
|
|
448
|
+
}>;
|
|
449
|
+
share(principal: Principal, fileId: string, input: ShareInput): Promise<ShareResult>;
|
|
450
|
+
/**
|
|
451
|
+
* Revoke a grant.
|
|
452
|
+
*
|
|
453
|
+
* Nothing cascades, and nothing needs to: liveness is evaluated over the
|
|
454
|
+
* ancestor chain, so every grant ever delegated from this one dies in the
|
|
455
|
+
* same instant, at any depth, with no second write to get wrong (P4).
|
|
456
|
+
*/
|
|
457
|
+
revoke(principal: Principal, grantId: string): Promise<void>;
|
|
458
|
+
listGrants(principal: Principal, fileId: string): Promise<GrantSummary[]>;
|
|
459
|
+
/**
|
|
460
|
+
* The share-link download path.
|
|
461
|
+
*
|
|
462
|
+
* Order matters: authorize FIRST (which re-validates the grant and its whole
|
|
463
|
+
* ancestor chain against `live_grant` -- P4, revocation beats a live URL),
|
|
464
|
+
* then consume the counter atomically (P6). Consuming before authorizing
|
|
465
|
+
* would let a revoked link burn a download; authorizing without consuming
|
|
466
|
+
* would make the cap a suggestion.
|
|
467
|
+
*/
|
|
468
|
+
redeem(linkSecret: string, opts?: {
|
|
469
|
+
password?: string;
|
|
470
|
+
ip?: string;
|
|
471
|
+
userAgent?: string;
|
|
472
|
+
disposition?: Disposition;
|
|
473
|
+
}): Promise<{
|
|
474
|
+
file: FileRecord;
|
|
475
|
+
body: Uint8Array;
|
|
476
|
+
headers: Record<string, string>;
|
|
477
|
+
remainingDownloads: number | null;
|
|
478
|
+
}>;
|
|
479
|
+
/** The streaming share-link path. See `redeem()` for the ordering rationale. */
|
|
480
|
+
redeemStream(linkSecret: string, opts?: {
|
|
481
|
+
password?: string;
|
|
482
|
+
ip?: string;
|
|
483
|
+
userAgent?: string;
|
|
484
|
+
disposition?: Disposition;
|
|
485
|
+
mode?: 'proxy' | 'auto';
|
|
486
|
+
range?: {
|
|
487
|
+
start: number;
|
|
488
|
+
end?: number;
|
|
489
|
+
};
|
|
490
|
+
}): Promise<StreamedDelivery & {
|
|
491
|
+
file: FileRecord;
|
|
492
|
+
remainingDownloads: number | null;
|
|
493
|
+
}>;
|
|
494
|
+
/** Requires `read_audit` in the org, which is admin+. Asked of the engine. */
|
|
495
|
+
auditLog(principal: Principal, orgId: string, filter?: {
|
|
496
|
+
decision?: 'allow' | 'deny';
|
|
497
|
+
fileId?: string;
|
|
498
|
+
actorId?: string;
|
|
499
|
+
action?: string;
|
|
500
|
+
limit?: number;
|
|
501
|
+
}): Promise<AuditRow[]>;
|
|
502
|
+
/**
|
|
503
|
+
* Verify the tamper-evidence chain for an org.
|
|
504
|
+
*
|
|
505
|
+
* Authorized, because `checked` is a count of everything that has ever
|
|
506
|
+
* happened in the org and an unauthenticated caller should not be able to
|
|
507
|
+
* measure another tenant's activity.
|
|
508
|
+
*/
|
|
509
|
+
verifyAuditChain(principal: Principal, orgId: string): Promise<AuditChainResult>;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Listing options.
|
|
513
|
+
*
|
|
514
|
+
* Note what is absent: any way to express a WHERE clause, a raw filter, an
|
|
515
|
+
* "include everything" flag, or a way to name another org. Everything here
|
|
516
|
+
* NARROWS the authorized set; nothing widens it. That is what "fail-closed by
|
|
517
|
+
* construction" has to mean for a query API -- not that the default is safe,
|
|
518
|
+
* but that the unsafe result is not expressible.
|
|
519
|
+
*/
|
|
520
|
+
export interface ListFilesOptions {
|
|
521
|
+
/** Which capability the caller must hold. Default `read`. */
|
|
522
|
+
capability?: Capability;
|
|
523
|
+
/** Page size. Clamped to [1, 200]. */
|
|
524
|
+
limit?: number;
|
|
525
|
+
/** Opaque keyset cursor from a previous page's `nextCursor`. */
|
|
526
|
+
cursor?: string | null;
|
|
527
|
+
}
|
|
528
|
+
export interface FileListPage {
|
|
529
|
+
files: FileRecord[];
|
|
530
|
+
/** Null when this is the last page. */
|
|
531
|
+
nextCursor: string | null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Postgres array literal. The driver will not serialize a JS array into an
|
|
535
|
+
* enum[] parameter, and a silent misencoding here would either error loudly
|
|
536
|
+
* (fine) or store a single bogus capability (not fine), so the encoding is
|
|
537
|
+
* explicit. Enum labels are a closed set of [a-z]+ and cannot contain a
|
|
538
|
+
* separator, so no quoting is required -- but we assert that rather than
|
|
539
|
+
* assume it.
|
|
540
|
+
*/
|
|
541
|
+
export declare function pgArrayLiteral(values: readonly string[]): string;
|
|
542
|
+
//# sourceMappingURL=filelayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filelayer.d.ts","sourceRoot":"","sources":["../src/filelayer.ts"],"names":[],"mappings":"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"}
|