@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/LICENSE +202 -0
  3. package/MIGRATIONS.md +328 -0
  4. package/NOTICE +37 -0
  5. package/README.md +343 -0
  6. package/SEMANTICS.md +729 -0
  7. package/dist/authz.d.ts +524 -0
  8. package/dist/authz.d.ts.map +1 -0
  9. package/dist/authz.js +889 -0
  10. package/dist/authz.js.map +1 -0
  11. package/dist/db.d.ts +145 -0
  12. package/dist/db.d.ts.map +1 -0
  13. package/dist/db.js +217 -0
  14. package/dist/db.js.map +1 -0
  15. package/dist/delivery.d.ts +293 -0
  16. package/dist/delivery.d.ts.map +1 -0
  17. package/dist/delivery.js +519 -0
  18. package/dist/delivery.js.map +1 -0
  19. package/dist/errors.d.ts +16 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +21 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/filelayer.d.ts +542 -0
  24. package/dist/filelayer.d.ts.map +1 -0
  25. package/dist/filelayer.js +1360 -0
  26. package/dist/filelayer.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/simple.d.ts +297 -0
  32. package/dist/simple.d.ts.map +1 -0
  33. package/dist/simple.js +492 -0
  34. package/dist/simple.js.map +1 -0
  35. package/dist/storage.d.ts +269 -0
  36. package/dist/storage.d.ts.map +1 -0
  37. package/dist/storage.js +700 -0
  38. package/dist/storage.js.map +1 -0
  39. package/dist/store.d.ts +432 -0
  40. package/dist/store.d.ts.map +1 -0
  41. package/dist/store.js +862 -0
  42. package/dist/store.js.map +1 -0
  43. package/package.json +77 -0
  44. package/schema.sql +1190 -0
  45. package/src/authz.ts +1398 -0
  46. package/src/db.ts +271 -0
  47. package/src/delivery.ts +737 -0
  48. package/src/errors.ts +24 -0
  49. package/src/filelayer.ts +1836 -0
  50. package/src/index.ts +7 -0
  51. package/src/simple.ts +666 -0
  52. package/src/storage.ts +917 -0
  53. package/src/store.ts +1072 -0
  54. package/test/delivery.test.ts +0 -0
  55. package/test/group-subjects.test.ts +1072 -0
  56. package/test/helpers.ts +65 -0
  57. package/test/listing.test.ts +689 -0
  58. package/test/local-s3.d.mts +33 -0
  59. package/test/local-s3.mjs +400 -0
  60. package/test/persistence.test.ts +953 -0
  61. package/test/regression.test.ts +619 -0
  62. package/test/s3-live.test.ts +322 -0
  63. package/test/security.test.ts +1652 -0
  64. package/test/semantics.test.ts +888 -0
  65. package/test/storage.test.ts +437 -0
  66. package/test/tiers.test.ts +432 -0
  67. package/test/vault-example.test.ts +302 -0
  68. package/tsconfig.build.json +29 -0
  69. package/tsconfig.json +19 -0
@@ -0,0 +1,519 @@
1
+ /**
2
+ * BYTE DELIVERY -- the library owns the response, not the developer.
3
+ *
4
+ * A security review found three live defects here and correctly identified their
5
+ * common root cause: `read()` and `redeem()` handed back a `Uint8Array` and
6
+ * left everything that happens to those bytes on the way to a browser as the
7
+ * application's problem. Three of the four security-sensitive decisions the
8
+ * library was still leaving to the developer lived in that gap:
9
+ *
10
+ * - no `X-Content-Type-Options: nosniff` / `Content-Disposition` on the
11
+ * authenticated read -> a user-uploaded .html or .svg executes in OUR OWN
12
+ * origin. Stored XSS with full session access, cross-tenant reach.
13
+ * - no `Cache-Control: no-store` on the share download -> a revoked link is
14
+ * replayable from disk cache or an intermediary. This one is worse than it
15
+ * sounds: immediate revocation is the product's headline property, and a
16
+ * cache replay partially defeats it.
17
+ * - the share password in the query string -> the secret AND the password
18
+ * land in access logs, proxy logs, browser history, and the `Referer`
19
+ * header of any outbound link inside the delivered document.
20
+ *
21
+ * The fix is not "document the headers". A decision the developer can forget is
22
+ * a decision they will eventually forget, which is the entire premise of this
23
+ * design. So:
24
+ *
25
+ * 1. `read()` and `redeem()` now return a DELIVERY DESCRIPTOR -- bytes plus
26
+ * the exact headers required to serve them safely. The headers are
27
+ * computed by the library from the file record; there is no argument that
28
+ * turns them off.
29
+ * 2. Framework-agnostic writers are provided (`toResponse` for anything with
30
+ * a WHATWG `Response`, `sendNodeResponse` for `node:http`), so the
31
+ * developer writes one line and cannot write the wrong headers.
32
+ * 3. For the share path the library owns the ROUTE, not just the response,
33
+ * because the password-in-the-URL defect is a routing decision rather than
34
+ * a response decision. `shareDownloadRoute()` reads the password from the
35
+ * request body and REFUSES, loudly, with 400, if a credential appears in
36
+ * the query string. A silent decision has been converted into a noisy one,
37
+ * which by our own definition means it stops being a security-sensitive
38
+ * decision at all.
39
+ *
40
+ * Residual, stated plainly: `delivery.body` is still a public field, so a
41
+ * developer who ignores the helpers and hand-writes `res.end(d.body)` gets the
42
+ * old behaviour. This is "hard to get wrong", not "impossible to get wrong".
43
+ * Making it impossible would mean never exposing the bytes, which would break
44
+ * every non-HTTP consumer (a queue worker, a virus scanner, a thumbnailer).
45
+ * We chose the weaker guarantee deliberately and state it rather than hide it.
46
+ */
47
+ import { FilelayerError } from "./errors.js";
48
+ /**
49
+ * The literal a developer must type to enable redirect delivery.
50
+ *
51
+ * A boolean would be typed once and forgotten. A sentence has to be read to be
52
+ * copied, it appears verbatim in the diff, and it shows up in a grep of the
53
+ * codebase when someone asks "do we ever hand out URLs that outlive a
54
+ * revocation?" -- which is the question this whole mode exists to make
55
+ * answerable.
56
+ */
57
+ export const REDIRECT_ACKNOWLEDGEMENT = 'I accept a revocation window of up to ttlSeconds on redirected deliveries';
58
+ /**
59
+ * Our ceiling, not the object store's. Five minutes is long enough for a client
60
+ * to follow a redirect on a bad mobile connection and short enough that the
61
+ * residual window is something you can put in a compliance document without
62
+ * flinching.
63
+ */
64
+ export const MAX_REDIRECT_TTL_SECONDS = 300;
65
+ export const DEFAULT_REDIRECT_TTL_SECONDS = 60;
66
+ export function resolveRedirectConfig(cfg) {
67
+ if (cfg.acknowledgeRevocationWindow !== REDIRECT_ACKNOWLEDGEMENT) {
68
+ throw new FilelayerError(500, 'redirect_not_acknowledged', 'redirect delivery requires the verbatim REDIRECT_ACKNOWLEDGEMENT string');
69
+ }
70
+ const requested = cfg.ttlSeconds ?? DEFAULT_REDIRECT_TTL_SECONDS;
71
+ if (!Number.isFinite(requested) || requested < 1) {
72
+ throw new FilelayerError(500, 'redirect_bad_ttl', 'ttlSeconds must be >= 1');
73
+ }
74
+ return {
75
+ // Clamped, not rejected: a config that asks for a day gets five minutes and
76
+ // keeps working. Rejecting would tempt someone to "fix" it by removing the
77
+ // bound.
78
+ ttlSeconds: Math.min(Math.floor(requested), MAX_REDIRECT_TTL_SECONDS),
79
+ scope: cfg.scope ?? 'anonymous-grants-only',
80
+ };
81
+ }
82
+ /**
83
+ * Content types that execute, or can be made to execute, in the origin that
84
+ * serves them. Served inline, any of these is stored XSS against your own
85
+ * application. `Content-Disposition: attachment` already defeats this in every
86
+ * current browser, so this list is the second layer: when a caller explicitly
87
+ * asks for `inline` (PDF preview, image thumbnails -- both legitimate), an
88
+ * active type is downgraded to `application/octet-stream` and the disposition
89
+ * is forced back to `attachment`.
90
+ *
91
+ * The predicate is deliberately broader than the list: anything whose type or
92
+ * subtype mentions html, xml, svg or script is treated as active, because the
93
+ * failure mode of being too strict is "the PDF downloads instead of previewing"
94
+ * and the failure mode of being too lax is session theft.
95
+ */
96
+ const ACTIVE_TYPE_RE = /(html|xml|svg|script|xsl|ecmascript)/i;
97
+ export function isActiveContentType(contentType) {
98
+ return ACTIVE_TYPE_RE.test(contentType.split(';')[0] ?? '');
99
+ }
100
+ /**
101
+ * A content type we are willing to put on a response.
102
+ *
103
+ * A `content-type` is attacker-controlled: it is whatever the uploader sent.
104
+ * Anything with a CR, an LF or a NUL is a header-injection attempt and is not
105
+ * negotiable; anything that is not a plausible media type is served as bytes.
106
+ */
107
+ export function safeContentType(contentType, disposition) {
108
+ const raw = contentType.trim();
109
+ if (!/^[a-zA-Z0-9!#$&^_.+-]{1,127}\/[a-zA-Z0-9!#$&^_.+-]{1,127}$/.test(raw)) {
110
+ return 'application/octet-stream';
111
+ }
112
+ if (disposition === 'inline' && isActiveContentType(raw))
113
+ return 'application/octet-stream';
114
+ return raw;
115
+ }
116
+ // eslint-disable-next-line no-control-regex
117
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f]/g;
118
+ /**
119
+ * RFC 6266 `Content-Disposition`, with the filename encoded rather than
120
+ * interpolated.
121
+ *
122
+ * NEW FINDING, found while doing this work: the example app wrote
123
+ * `filename="${file.name}"` directly. `file.name` is whatever the uploader
124
+ * sent. A name containing a double quote truncates the header; a name
125
+ * containing CR/LF injects an arbitrary header, including a second
126
+ * `Content-Type` or a `Set-Cookie`. That is a response-splitting bug in the
127
+ * shipped example, and it is not one the comparable integrations have, because
128
+ * none of them interpolate the name unescaped. It is fixed here, once, for
129
+ * every caller.
130
+ */
131
+ export function contentDisposition(name, disposition) {
132
+ // ASCII fallback: printable ASCII only, no quote, no backslash, no path
133
+ // separators, no control characters. Never empty.
134
+ // Control characters (CR/LF are the header-injection vector) are dropped
135
+ // outright; quote, backslash and path separators become underscores; anything
136
+ // outside printable ASCII is replaced, because the extended form below is
137
+ // what actually carries a non-ASCII name.
138
+ const stripped = name.replace(CONTROL_CHARS, '');
139
+ const ascii = stripped
140
+ .replace(/[\\"/]/g, '_')
141
+ .replace(/[^\x20-\x7e]/g, '_')
142
+ .trim()
143
+ .slice(0, 120) || 'download';
144
+ // RFC 5987 extended form, which carries the real (possibly non-ASCII) name.
145
+ const utf8 = encodeURIComponent(stripped.slice(0, 200)).replace(/['()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
146
+ return `${disposition}; filename="${ascii}"; filename*=UTF-8''${utf8}`;
147
+ }
148
+ /**
149
+ * The headers every Filelayer byte response carries. There is no option to
150
+ * omit any of them.
151
+ *
152
+ * nosniff - stops content-type sniffing turning a .txt into HTML.
153
+ * Content-Disposition- attachment by default; the browser never renders it.
154
+ * Cache-Control - `private, no-store` + `no-cache` + `must-revalidate`.
155
+ * This is the one that matters most: it is what makes
156
+ * "revocation is immediate" true at the browser and at
157
+ * every intermediary, not just at our origin.
158
+ * Pragma / Expires - the HTTP/1.0 spelling of the same thing, because
159
+ * corporate proxies are real.
160
+ * Referrer-Policy - `no-referrer`. A share link's secret is in the URL. Any
161
+ * outbound link inside a delivered document would leak it
162
+ * in the Referer header. This closes the third vector of
163
+ * the credential-in-the-URL defect, the one that survives
164
+ * moving the password out of the query string.
165
+ * CSP sandbox - defence in depth for the inline case and for any future
166
+ * caller that overrides disposition.
167
+ * X-Frame-Options - the delivered bytes cannot be framed by a third party.
168
+ */
169
+ export function deliveryHeaders(file, opts = {}) {
170
+ const requested = opts.disposition ?? 'attachment';
171
+ // An active type is never served inline, whatever was asked for.
172
+ const disposition = requested === 'inline' && isActiveContentType(file.contentType) ? 'attachment' : requested;
173
+ const headers = {
174
+ // Found by test/tiers.test.ts: `disposition` here has ALREADY been
175
+ // flipped to 'attachment' for an active type, so passing it to
176
+ // `safeContentType` meant the `disposition === 'inline' && isActive`
177
+ // branch could never fire and the type neutralisation was dead code. It
178
+ // must be evaluated against what the CALLER ASKED FOR, not against the
179
+ // value we corrected it to. Belt and braces are both wanted here: the
180
+ // disposition defeats current browsers, the neutralised type defeats a
181
+ // future one (and any non-browser client that ignores disposition).
182
+ 'content-type': safeContentType(file.contentType, requested),
183
+ 'content-disposition': contentDisposition(file.name, disposition),
184
+ 'x-content-type-options': 'nosniff',
185
+ 'cache-control': 'private, no-store, no-cache, must-revalidate, max-age=0',
186
+ pragma: 'no-cache',
187
+ expires: '0',
188
+ 'referrer-policy': 'no-referrer',
189
+ 'content-security-policy': "default-src 'none'; sandbox",
190
+ 'x-frame-options': 'DENY',
191
+ };
192
+ if (file.sizeBytes != null)
193
+ headers['content-length'] = String(file.sizeBytes);
194
+ return headers;
195
+ }
196
+ /** The same headers, minus the body ones, for a JSON error on a delivery path. */
197
+ export function errorHeaders() {
198
+ return {
199
+ 'content-type': 'application/json',
200
+ 'cache-control': 'private, no-store, no-cache, must-revalidate, max-age=0',
201
+ pragma: 'no-cache',
202
+ 'referrer-policy': 'no-referrer',
203
+ 'x-content-type-options': 'nosniff',
204
+ };
205
+ }
206
+ /**
207
+ * The headers on a 302 to a presigned URL.
208
+ *
209
+ * `cacheable` is true for an ANONYMOUS grant and only for an anonymous grant.
210
+ * That is the "public/anonymous grants should be able to use a cacheable path"
211
+ * requirement, and the negative half of it is the important half: nothing that
212
+ * was not already public becomes cacheable by a shared cache, ever.
213
+ *
214
+ * The cache lifetime is HALF the presigned URL's TTL. A cached 302 hands a
215
+ * client a URL that is already partly used up, so caching for the full TTL
216
+ * would let a client receive a redirect with milliseconds of life left and see
217
+ * a spurious 403. Half guarantees at least half the TTL remains.
218
+ *
219
+ * The 302 itself carries no bytes, so it carries no content-type protections;
220
+ * those are pinned INTO the presigned URL by the caller (see
221
+ * `Filelayer.readStream`), which is what stops a redirect from being a way to
222
+ * lose them.
223
+ */
224
+ export function redirectHeaders(url, opts) {
225
+ return {
226
+ location: url,
227
+ 'cache-control': opts.cacheable
228
+ ? `public, max-age=${Math.max(1, Math.floor(opts.ttlSeconds / 2))}`
229
+ : 'private, no-store, no-cache, must-revalidate, max-age=0',
230
+ // The presigned URL is a credential. It is in `Location`, so it will be in
231
+ // the browser's address bar and therefore in `Referer` on any onward
232
+ // navigation unless we say otherwise.
233
+ 'referrer-policy': 'no-referrer',
234
+ 'x-content-type-options': 'nosniff',
235
+ ...(opts.cacheable ? {} : { pragma: 'no-cache', expires: '0' }),
236
+ };
237
+ }
238
+ // -----------------------------------------------------------------------------
239
+ // Writers
240
+ // -----------------------------------------------------------------------------
241
+ /** WHATWG `Response` -- Workers, Deno, Bun, Next.js route handlers, Hono. */
242
+ export function toResponse(delivery, status = 200) {
243
+ return new Response(delivery.body, {
244
+ status,
245
+ headers: delivery.headers,
246
+ });
247
+ }
248
+ /**
249
+ * 206 when the body is a byte range, 200 when it is the whole object.
250
+ *
251
+ * `readStream()` and `redeemStream()` accept a byte range, and when the store
252
+ * serves one they attach `Content-Range`. A partial body sent under a 200 is
253
+ * silent data corruption: every HTTP client on earth treats 200 as "this is the
254
+ * complete representation", so it stores, caches, hashes and hands on a
255
+ * truncated file without a single error anywhere. The one thing that makes it
256
+ * a range is the status code.
257
+ *
258
+ * So the status is DERIVED from the headers rather than left to the caller.
259
+ * There is no value a caller could pass that this does not already know, and
260
+ * every value they could pass by mistake is wrong.
261
+ */
262
+ function proxyStatus(delivery) {
263
+ return delivery.headers['content-range'] ? 206 : 200;
264
+ }
265
+ /**
266
+ * The streaming/redirect equivalent. One function for both modes, because the
267
+ * developer must not have to branch on the mode -- branching is where the
268
+ * `Cache-Control` gets copied from the wrong arm.
269
+ */
270
+ export function toStreamResponse(delivery) {
271
+ if (delivery.mode === 'redirect') {
272
+ return new Response(null, { status: delivery.status, headers: delivery.headers });
273
+ }
274
+ return new Response(delivery.body, {
275
+ status: proxyStatus(delivery),
276
+ headers: delivery.headers,
277
+ });
278
+ }
279
+ /** `node:http` / Express. */
280
+ export function sendNodeResponse(res, delivery, status = 200) {
281
+ // content-length is recomputed from the actual buffer rather than trusted
282
+ // from the record: a size column that disagrees with the object would
283
+ // otherwise truncate or hang the response.
284
+ const headers = { ...delivery.headers, 'content-length': String(delivery.body.byteLength) };
285
+ res.writeHead(status, headers);
286
+ res.end(delivery.body);
287
+ }
288
+ /**
289
+ * `node:http`, streaming. Nothing is buffered: the object's bytes go from the
290
+ * store's socket to the client's socket a chunk at a time.
291
+ *
292
+ * The `content-length` from the record is dropped unless the STORE reported one
293
+ * for this response, for the same reason `sendNodeResponse` recomputes it: a
294
+ * `size_bytes` that disagrees with the object truncates or hangs the response,
295
+ * and on a streamed response the hang is the one you notice in production
296
+ * rather than in a test.
297
+ */
298
+ export async function sendNodeStream(res, delivery) {
299
+ if (delivery.mode === 'redirect') {
300
+ res.writeHead(delivery.status, delivery.headers);
301
+ res.end();
302
+ return;
303
+ }
304
+ const headers = { ...delivery.headers };
305
+ if (delivery.bytes === null)
306
+ delete headers['content-length'];
307
+ else
308
+ headers['content-length'] = String(delivery.bytes);
309
+ // 206 if this is a byte range. See `proxyStatus` -- a partial body under a
310
+ // 200 is a truncated file that no client can detect.
311
+ res.writeHead(proxyStatus(delivery), headers);
312
+ const reader = delivery.body.getReader();
313
+ try {
314
+ for (;;) {
315
+ const { done, value } = await reader.read();
316
+ if (done)
317
+ break;
318
+ if (!value)
319
+ continue;
320
+ if (!res.write(value)) {
321
+ await new Promise((resolve) => res.once('drain', resolve));
322
+ }
323
+ }
324
+ res.end();
325
+ }
326
+ catch (err) {
327
+ // Headers are already sent, so there is no way to turn this into a status
328
+ // code. Destroying the socket is the only honest signal that the body is
329
+ // incomplete; ending it normally would deliver a truncated file that looks
330
+ // like a successful download.
331
+ res.destroy(err instanceof Error ? err : new Error(String(err)));
332
+ }
333
+ finally {
334
+ reader.releaseLock();
335
+ }
336
+ }
337
+ function sendNodeError(res, err) {
338
+ const e = err instanceof FilelayerError ? err : new FilelayerError(500, 'internal');
339
+ res.writeHead(e.status, errorHeaders());
340
+ res.end(JSON.stringify({ error: e.code }));
341
+ }
342
+ // -----------------------------------------------------------------------------
343
+ // Routes the library owns
344
+ // -----------------------------------------------------------------------------
345
+ /**
346
+ * Credential names that must never appear in a URL. `secret` is not listed:
347
+ * the share secret IS the path segment, which is the standard design and is
348
+ * what `Referrer-Policy: no-referrer` above is for.
349
+ */
350
+ const FORBIDDEN_QUERY_KEYS = ['password', 'pw', 'pass', 'passwd', 'token', 'secret', 'key'];
351
+ async function readBody(req, limit = 64 * 1024) {
352
+ const chunks = [];
353
+ let size = 0;
354
+ for await (const c of req) {
355
+ size += c.byteLength;
356
+ if (size > limit)
357
+ throw new FilelayerError(413, 'payload_too_large');
358
+ chunks.push(c);
359
+ }
360
+ return Buffer.concat(chunks).toString('utf8');
361
+ }
362
+ /** JSON or form-urlencoded, whichever the client sent. Never the query string. */
363
+ function extractPassword(raw, contentType) {
364
+ if (raw.length === 0)
365
+ return undefined;
366
+ if (/application\/x-www-form-urlencoded/i.test(contentType)) {
367
+ const v = new URLSearchParams(raw).get('password');
368
+ return v === null ? undefined : v;
369
+ }
370
+ try {
371
+ const parsed = JSON.parse(raw);
372
+ const v = parsed['password'];
373
+ return typeof v === 'string' ? v : undefined;
374
+ }
375
+ catch {
376
+ throw new FilelayerError(400, 'bad_request');
377
+ }
378
+ }
379
+ /**
380
+ * `GET|POST <prefix>/:secret` -- the entire public share-link download path.
381
+ *
382
+ * The application supplies nothing. Identity is the secret itself, the password
383
+ * is read from the body, the counter is consumed by `redeem()`, and the
384
+ * response headers come from `deliveryHeaders()`. There is no parameter here
385
+ * whose wrong value leaks data.
386
+ *
387
+ * Returns true if it handled the request, so it can be dropped into any router.
388
+ */
389
+ export function shareDownloadRoute(fl, opts = {}) {
390
+ const prefix = (opts.prefix ?? '/d').replace(/\/+$/, '');
391
+ return async (req, res) => {
392
+ const url = new URL(req.url ?? '/', 'http://filelayer.invalid');
393
+ const segments = url.pathname.split('/').filter(Boolean);
394
+ const prefixSegments = prefix.split('/').filter(Boolean);
395
+ if (segments.length !== prefixSegments.length + 1)
396
+ return false;
397
+ if (prefixSegments.some((s, i) => segments[i] !== s))
398
+ return false;
399
+ if (req.method !== 'GET' && req.method !== 'POST')
400
+ return false;
401
+ const secret = decodeURIComponent(segments[prefixSegments.length]);
402
+ try {
403
+ // THE FIX FOR THE CREDENTIAL-IN-THE-URL DEFECT, and the reason this
404
+ // route exists.
405
+ // A password (or any other credential) in the query string ends up in
406
+ // access logs, proxy logs and browser history. The old example accepted
407
+ // one. We refuse -- loudly, before doing any work, and without consuming
408
+ // a download -- so the mistake cannot be made silently.
409
+ for (const k of FORBIDDEN_QUERY_KEYS) {
410
+ if (url.searchParams.has(k)) {
411
+ throw new FilelayerError(400, 'credential_in_query', `query_param:${k}`);
412
+ }
413
+ }
414
+ let password;
415
+ if (req.method === 'POST') {
416
+ password = extractPassword(await readBody(req), String(req.headers['content-type'] ?? ''));
417
+ }
418
+ // STREAMED, not buffered. This route is the one every share link goes
419
+ // through, so buffering here was the whole-file-in-memory tax on the
420
+ // busiest path in the product.
421
+ const delivery = await fl.redeemStream(secret, {
422
+ ...(password !== undefined ? { password } : {}),
423
+ ...(req.socket.remoteAddress ? { ip: req.socket.remoteAddress } : {}),
424
+ ...(req.headers['user-agent'] ? { userAgent: String(req.headers['user-agent']) } : {}),
425
+ ...(opts.disposition ? { disposition: opts.disposition } : {}),
426
+ ...(opts.mode ? { mode: opts.mode } : {}),
427
+ });
428
+ await sendNodeStream(res, {
429
+ ...delivery,
430
+ headers: {
431
+ ...delivery.headers,
432
+ // Useful to the recipient and safe to expose: they already hold the
433
+ // credential this counts against.
434
+ 'x-downloads-remaining': String(delivery.remainingDownloads ?? ''),
435
+ 'x-filelayer-delivery': delivery.mode,
436
+ },
437
+ });
438
+ return true;
439
+ }
440
+ catch (err) {
441
+ // 401 means "this link has a password". The client must re-issue as a
442
+ // POST with the password in the body; there is no supported way to put it
443
+ // in the URL.
444
+ if (err instanceof FilelayerError && err.status === 401) {
445
+ res.writeHead(401, { ...errorHeaders(), 'www-authenticate': 'FilelayerShare' });
446
+ res.end(JSON.stringify({ error: err.code, retry: { method: 'POST', field: 'password' } }));
447
+ return true;
448
+ }
449
+ sendNodeError(res, err);
450
+ return true;
451
+ }
452
+ };
453
+ }
454
+ /**
455
+ * `GET <prefix>/:fileId` -- the authenticated read path, headers included.
456
+ *
457
+ * The one thing the application must supply is who the caller is, because that
458
+ * is the one thing Filelayer cannot know. Everything downstream of that -- the
459
+ * decision, the audit event, the status code, and every response header -- is
460
+ * the library's.
461
+ */
462
+ export function fileDownloadRoute(fl, opts) {
463
+ const prefix = (opts.prefix ?? '/files').replace(/\/+$/, '');
464
+ return async (req, res) => {
465
+ const url = new URL(req.url ?? '/', 'http://filelayer.invalid');
466
+ const segments = url.pathname.split('/').filter(Boolean);
467
+ const prefixSegments = prefix.split('/').filter(Boolean);
468
+ if (req.method !== 'GET')
469
+ return false;
470
+ if (segments.length !== prefixSegments.length + 1)
471
+ return false;
472
+ if (prefixSegments.some((s, i) => segments[i] !== s))
473
+ return false;
474
+ try {
475
+ const delivery = await fl.readStream(await opts.principal(req), segments[prefixSegments.length], {
476
+ ...(opts.disposition ? { disposition: opts.disposition } : {}),
477
+ ...(opts.mode ? { mode: opts.mode } : {}),
478
+ });
479
+ await sendNodeStream(res, {
480
+ ...delivery,
481
+ headers: { ...delivery.headers, 'x-filelayer-delivery': delivery.mode },
482
+ });
483
+ }
484
+ catch (err) {
485
+ sendNodeError(res, err);
486
+ }
487
+ return true;
488
+ };
489
+ }
490
+ /**
491
+ * The whole byte-delivery surface as one `node:http` request listener.
492
+ *
493
+ * `createServer(deliveryHandler(fl))` is a complete, correct file server: both
494
+ * delivery paths, every security header, the query-string credential refusal,
495
+ * and a 404 for everything else. The developer writes no header and no status
496
+ * code, which means there is no header or status code for them to get wrong.
497
+ */
498
+ export function deliveryHandler(fl, opts = {}) {
499
+ const files = fileDownloadRoute(fl, {
500
+ prefix: opts.filePrefix ?? '/f',
501
+ principal: opts.principal ?? (() => ({ actorId: null })),
502
+ ...(opts.disposition ? { disposition: opts.disposition } : {}),
503
+ ...(opts.mode ? { mode: opts.mode } : {}),
504
+ });
505
+ const shares = shareDownloadRoute(fl, {
506
+ prefix: opts.sharePrefix ?? '/d',
507
+ ...(opts.disposition ? { disposition: opts.disposition } : {}),
508
+ ...(opts.mode ? { mode: opts.mode } : {}),
509
+ });
510
+ return async (req, res) => {
511
+ if (await files(req, res))
512
+ return;
513
+ if (await shares(req, res))
514
+ return;
515
+ res.writeHead(404, errorHeaders());
516
+ res.end(JSON.stringify({ error: 'not_found' }));
517
+ };
518
+ }
519
+ //# sourceMappingURL=delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAKH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAsE7C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,2EAA2E,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAC5C,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAyB/C,MAAM,UAAU,qBAAqB,CAAC,GAA2B;IAC/D,IAAI,GAAG,CAAC,2BAA2B,KAAK,wBAAwB,EAAE,CAAC;QACjE,MAAM,IAAI,cAAc,CACtB,GAAG,EACH,2BAA2B,EAC3B,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,4BAA4B,CAAC;IACjE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE,kBAAkB,EAAE,yBAAyB,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO;QACL,4EAA4E;QAC5E,2EAA2E;QAC3E,SAAS;QACT,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,wBAAwB,CAAC;QACrE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,uBAAuB;KAC5C,CAAC;AACJ,CAAC;AA+BD;;;;;;;;;;;;;GAaG;AACH,MAAM,cAAc,GAAG,uCAAuC,CAAC;AAE/D,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,WAAwB;IAC3E,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,4DAA4D,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5E,OAAO,0BAA0B,CAAC;IACpC,CAAC;IACD,IAAI,WAAW,KAAK,QAAQ,IAAI,mBAAmB,CAAC,GAAG,CAAC;QAAE,OAAO,0BAA0B,CAAC;IAC5F,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4CAA4C;AAC5C,MAAM,aAAa,GAAG,wBAAwB,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,WAAwB;IACvE,wEAAwE;IACxE,kDAAkD;IAClD,yEAAyE;IACzE,8EAA8E;IAC9E,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,KAAK,GACT,QAAQ;SACL,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;SAC7B,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,UAAU,CAAC;IACjC,4EAA4E;IAC5E,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAC7D,SAAS,EACT,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CACxD,CAAC;IACF,OAAO,GAAG,WAAW,eAAe,KAAK,uBAAuB,IAAI,EAAE,CAAC;AACzE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAqB,EACrB,OAAsC,EAAE;IAExC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,IAAI,YAAY,CAAC;IACnD,iEAAiE;IACjE,MAAM,WAAW,GACf,SAAS,KAAK,QAAQ,IAAI,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7F,MAAM,OAAO,GAA2B;QACtC,mEAAmE;QACnE,+DAA+D;QAC/D,qEAAqE;QACrE,wEAAwE;QACxE,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,oEAAoE;QACpE,cAAc,EAAE,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC;QAC5D,qBAAqB,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;QACjE,wBAAwB,EAAE,SAAS;QACnC,eAAe,EAAE,yDAAyD;QAC1E,MAAM,EAAE,UAAU;QAClB,OAAO,EAAE,GAAG;QACZ,iBAAiB,EAAE,aAAa;QAChC,yBAAyB,EAAE,6BAA6B;QACxD,iBAAiB,EAAE,MAAM;KAC1B,CAAC;IACF,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,cAAc,EAAE,kBAAkB;QAClC,eAAe,EAAE,yDAAyD;QAC1E,MAAM,EAAE,UAAU;QAClB,iBAAiB,EAAE,aAAa;QAChC,wBAAwB,EAAE,SAAS;KACpC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,IAAgD;IAEhD,OAAO;QACL,QAAQ,EAAE,GAAG;QACb,eAAe,EAAE,IAAI,CAAC,SAAS;YAC7B,CAAC,CAAC,mBAAmB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE;YACnE,CAAC,CAAC,yDAAyD;QAC7D,2EAA2E;QAC3E,qEAAqE;QACrE,sCAAsC;QACtC,iBAAiB,EAAE,aAAa;QAChC,wBAAwB,EAAE,SAAS;QACnC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CAAC,QAAsB,EAAE,MAAM,GAAG,GAAG;IAC7D,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAA2B,EAAE;QACxD,MAAM;QACN,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,WAAW,CAAC,QAAuB;IAC1C,OAAO,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA0B;IACzD,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAA2B,EAAE;QACxD,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;QAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,gBAAgB,CAC9B,GAAmB,EACnB,QAAsB,EACtB,MAAM,GAAG,GAAG;IAEZ,0EAA0E;IAC1E,sEAAsE;IACtE,2CAA2C;IAC3C,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;IAC5F,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAmB,EACnB,QAA0B;IAE1B,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACjC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjD,GAAG,CAAC,GAAG,EAAE,CAAC;QACV,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;IACxC,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC,gBAAgB,CAAC,CAAC;;QACzD,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,2EAA2E;IAC3E,qDAAqD;IACrD,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0EAA0E;QAC1E,yEAAyE;QACzE,2EAA2E;QAC3E,8BAA8B;QAC9B,GAAG,CAAC,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,GAAmB,EAAE,GAAY;IACtD,MAAM,CAAC,GACL,GAAG,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAC5E,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACxC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,gFAAgF;AAChF,0BAA0B;AAC1B,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AAE5F,KAAK,UAAU,QAAQ,CAAC,GAAoB,EAAE,KAAK,GAAG,EAAE,GAAG,IAAI;IAC7D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QAC1B,IAAI,IAAK,CAAY,CAAC,UAAU,CAAC;QACjC,IAAI,IAAI,GAAG,KAAK;YAAE,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,kFAAkF;AAClF,SAAS,eAAe,CAAC,GAAW,EAAE,WAAmB;IACvD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,qCAAqC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5D,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAmBD;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAa,EACb,OAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEzD,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,0BAA0B,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QACnE,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QAEhE,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAE,CAAC,CAAC;QAEpE,IAAI,CAAC;YACH,oEAAoE;YACpE,gBAAgB;YAChB,sEAAsE;YACtE,wEAAwE;YACxE,yEAAyE;YACzE,wDAAwD;YACxD,KAAK,MAAM,CAAC,IAAI,oBAAoB,EAAE,CAAC;gBACrC,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC5B,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE,qBAAqB,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;YAED,IAAI,QAA4B,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC1B,QAAQ,GAAG,eAAe,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC7F,CAAC;YAED,sEAAsE;YACtE,qEAAqE;YACrE,+BAA+B;YAC/B,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE;gBAC7C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/C,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtF,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1C,CAAC,CAAC;YAEH,MAAM,cAAc,CAAC,GAAG,EAAE;gBACxB,GAAG,QAAQ;gBACX,OAAO,EAAE;oBACP,GAAG,QAAQ,CAAC,OAAO;oBACnB,oEAAoE;oBACpE,kCAAkC;oBAClC,uBAAuB,EAAE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,IAAI,EAAE,CAAC;oBAClE,sBAAsB,EAAE,QAAQ,CAAC,IAAI;iBACtC;aACkB,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,sEAAsE;YACtE,0EAA0E;YAC1E,cAAc;YACd,IAAI,GAAG,YAAY,cAAc,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,GAAG,YAAY,EAAE,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAChF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC3F,OAAO,IAAI,CAAC;YACd,CAAC;YACD,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAAa,EACb,IAAsB;IAEtB,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE7D,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,0BAA0B,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAEnE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,UAAU,CAClC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EACzB,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAE,EAChC;gBACE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1C,CACF,CAAC;YACF,MAAM,cAAc,CAAC,GAAG,EAAE;gBACxB,GAAG,QAAQ;gBACX,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,sBAAsB,EAAE,QAAQ,CAAC,IAAI,EAAE;aACpD,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAmBD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,EAAa,EACb,OAA+B,EAAE;IAEjC,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,EAAE;QAClC,MAAM,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QAC/B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,EAAE;QACpC,MAAM,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QAChC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;IAEH,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,IAAI,MAAM,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAClC,IAAI,MAAM,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACnC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;QACnC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The one error type the library throws.
3
+ *
4
+ * It lives in its own module so that the delivery layer (which must classify
5
+ * errors in order to write a correct HTTP response) does not have to import the
6
+ * whole `Filelayer` class, which imports the delivery layer.
7
+ */
8
+ import type { DenyReason } from './authz.ts';
9
+ export declare class FilelayerError extends Error {
10
+ readonly status: number;
11
+ readonly code: string;
12
+ /** Internal deny reason. Logged, never serialized to an untrusted caller. */
13
+ readonly reason: DenyReason | string | undefined;
14
+ constructor(status: number, code: string, reason?: DenyReason | string);
15
+ }
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;gBAErC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM;CAOvE"}
package/dist/errors.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The one error type the library throws.
3
+ *
4
+ * It lives in its own module so that the delivery layer (which must classify
5
+ * errors in order to write a correct HTTP response) does not have to import the
6
+ * whole `Filelayer` class, which imports the delivery layer.
7
+ */
8
+ export class FilelayerError extends Error {
9
+ status;
10
+ code;
11
+ /** Internal deny reason. Logged, never serialized to an untrusted caller. */
12
+ reason;
13
+ constructor(status, code, reason) {
14
+ super(code);
15
+ this.name = 'FilelayerError';
16
+ this.status = status;
17
+ this.code = code;
18
+ this.reason = reason;
19
+ }
20
+ }
21
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,CAAS;IACf,IAAI,CAAS;IACtB,6EAA6E;IACpE,MAAM,CAAkC;IAEjD,YAAY,MAAc,EAAE,IAAY,EAAE,MAA4B;QACpE,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}