@nestm/storage 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (122) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/README.md +724 -25
  3. package/SECURITY.md +101 -0
  4. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +91 -0
  5. package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
  6. package/dist/ai-sdk/ai-sdk-workspace-tools.js +491 -0
  7. package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
  8. package/dist/ai-sdk/index.d.ts +2 -0
  9. package/dist/ai-sdk/index.d.ts.map +1 -0
  10. package/dist/ai-sdk/index.js +2 -0
  11. package/dist/ai-sdk/index.js.map +1 -0
  12. package/dist/core/index.d.ts +6 -0
  13. package/dist/core/index.d.ts.map +1 -0
  14. package/dist/core/index.js +4 -0
  15. package/dist/core/index.js.map +1 -0
  16. package/dist/files-sdk/files-sdk.driver.d.ts +85 -3
  17. package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
  18. package/dist/files-sdk/files-sdk.driver.js +1437 -37
  19. package/dist/files-sdk/files-sdk.driver.js.map +1 -1
  20. package/dist/files-sdk/fs/index.d.ts +42 -0
  21. package/dist/files-sdk/fs/index.d.ts.map +1 -0
  22. package/dist/files-sdk/fs/index.js +1117 -0
  23. package/dist/files-sdk/fs/index.js.map +1 -0
  24. package/dist/files-sdk/index.d.ts +1 -1
  25. package/dist/files-sdk/index.d.ts.map +1 -1
  26. package/dist/files-sdk/index.js.map +1 -1
  27. package/dist/files-sdk/provider/index.d.ts +58 -0
  28. package/dist/files-sdk/provider/index.d.ts.map +1 -0
  29. package/dist/files-sdk/provider/index.js +147 -0
  30. package/dist/files-sdk/provider/index.js.map +1 -0
  31. package/dist/files-sdk/s3/construction-metadata.d.ts +9 -0
  32. package/dist/files-sdk/s3/construction-metadata.d.ts.map +1 -0
  33. package/dist/files-sdk/s3/construction-metadata.js +24 -0
  34. package/dist/files-sdk/s3/construction-metadata.js.map +1 -0
  35. package/dist/files-sdk/s3/index.d.ts +54 -0
  36. package/dist/files-sdk/s3/index.d.ts.map +1 -0
  37. package/dist/files-sdk/s3/index.js +1505 -0
  38. package/dist/files-sdk/s3/index.js.map +1 -0
  39. package/dist/gateway/index.d.ts +1 -1
  40. package/dist/gateway/index.d.ts.map +1 -1
  41. package/dist/gateway/index.js.map +1 -1
  42. package/dist/gateway/storage-gateway-fastify-parser.d.ts.map +1 -1
  43. package/dist/gateway/storage-gateway-fastify-parser.js.map +1 -1
  44. package/dist/gateway/storage-gateway.controller.d.ts +12 -11
  45. package/dist/gateway/storage-gateway.controller.d.ts.map +1 -1
  46. package/dist/gateway/storage-gateway.controller.js +240 -70
  47. package/dist/gateway/storage-gateway.controller.js.map +1 -1
  48. package/dist/gateway/storage-gateway.guard.d.ts.map +1 -1
  49. package/dist/gateway/storage-gateway.guard.js.map +1 -1
  50. package/dist/gateway/storage-gateway.module.d.ts.map +1 -1
  51. package/dist/gateway/storage-gateway.module.js +60 -1
  52. package/dist/gateway/storage-gateway.module.js.map +1 -1
  53. package/dist/gateway/storage-gateway.tokens.d.ts +1 -0
  54. package/dist/gateway/storage-gateway.tokens.d.ts.map +1 -1
  55. package/dist/gateway/storage-gateway.tokens.js +1 -0
  56. package/dist/gateway/storage-gateway.tokens.js.map +1 -1
  57. package/dist/gateway/storage-gateway.types.d.ts +53 -10
  58. package/dist/gateway/storage-gateway.types.d.ts.map +1 -1
  59. package/dist/gateway/storage-gateway.types.js.map +1 -1
  60. package/dist/index.d.ts +1 -5
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/inject-storage.decorator.js.map +1 -1
  65. package/dist/storage-etag.d.ts +13 -0
  66. package/dist/storage-etag.d.ts.map +1 -0
  67. package/dist/storage-etag.js +31 -0
  68. package/dist/storage-etag.js.map +1 -0
  69. package/dist/storage-upload-control.d.ts.map +1 -1
  70. package/dist/storage.client.d.ts +15 -3
  71. package/dist/storage.client.d.ts.map +1 -1
  72. package/dist/storage.client.js +204 -2
  73. package/dist/storage.client.js.map +1 -1
  74. package/dist/storage.driver.d.ts +28 -1
  75. package/dist/storage.driver.d.ts.map +1 -1
  76. package/dist/storage.driver.js.map +1 -1
  77. package/dist/storage.error.d.ts +17 -10
  78. package/dist/storage.error.d.ts.map +1 -1
  79. package/dist/storage.error.js +47 -1
  80. package/dist/storage.error.js.map +1 -1
  81. package/dist/storage.module.d.ts.map +1 -1
  82. package/dist/storage.module.js.map +1 -1
  83. package/dist/storage.service.d.ts.map +1 -1
  84. package/dist/storage.service.js +28 -3
  85. package/dist/storage.service.js.map +1 -1
  86. package/dist/storage.tokens.js.map +1 -1
  87. package/dist/storage.types.d.ts +150 -1
  88. package/dist/storage.types.d.ts.map +1 -1
  89. package/dist/storage.types.js.map +1 -1
  90. package/dist/testing/index.d.ts +1 -0
  91. package/dist/testing/index.d.ts.map +1 -1
  92. package/dist/testing/index.js +1 -0
  93. package/dist/testing/index.js.map +1 -1
  94. package/dist/testing/provider-conformance.d.ts +65 -0
  95. package/dist/testing/provider-conformance.d.ts.map +1 -0
  96. package/dist/testing/provider-conformance.js +869 -0
  97. package/dist/testing/provider-conformance.js.map +1 -0
  98. package/dist/workspace/index.d.ts +5 -0
  99. package/dist/workspace/index.d.ts.map +1 -0
  100. package/dist/workspace/index.js +5 -0
  101. package/dist/workspace/index.js.map +1 -0
  102. package/dist/workspace/storage-workspace.cursor.d.ts +71 -0
  103. package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
  104. package/dist/workspace/storage-workspace.cursor.js +408 -0
  105. package/dist/workspace/storage-workspace.cursor.js.map +1 -0
  106. package/dist/workspace/storage-workspace.d.ts +6 -0
  107. package/dist/workspace/storage-workspace.d.ts.map +1 -0
  108. package/dist/workspace/storage-workspace.error.d.ts +32 -0
  109. package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
  110. package/dist/workspace/storage-workspace.error.js +75 -0
  111. package/dist/workspace/storage-workspace.error.js.map +1 -0
  112. package/dist/workspace/storage-workspace.js +849 -0
  113. package/dist/workspace/storage-workspace.js.map +1 -0
  114. package/dist/workspace/storage-workspace.path.d.ts +9 -0
  115. package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
  116. package/dist/workspace/storage-workspace.path.js +68 -0
  117. package/dist/workspace/storage-workspace.path.js.map +1 -0
  118. package/dist/workspace/storage-workspace.types.d.ts +123 -0
  119. package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
  120. package/dist/workspace/storage-workspace.types.js +22 -0
  121. package/dist/workspace/storage-workspace.types.js.map +1 -0
  122. package/package.json +103 -35
@@ -14,12 +14,84 @@ import { BadRequestException, Body, Controller, Delete, Get, Head, HttpException
14
14
  import { ServerResponse } from 'node:http';
15
15
  import { Readable } from 'node:stream';
16
16
  import { pipeline } from 'node:stream/promises';
17
- import { StorageError, StorageErrorCode } from '../storage.error.js';
17
+ import { StorageError, StorageErrorCode, isStorageError, } from '../storage.error.js';
18
+ import { isCanonicalStorageEtag, storageEtagHeader } from '../storage-etag.js';
18
19
  import { StorageService } from '../storage.service.js';
19
20
  import { StorageGatewayGuard } from './storage-gateway.guard.js';
20
21
  import { FASTIFY_STORAGE_UPLOAD_CONFIG } from './storage-gateway-fastify-parser.js';
21
- import { STORAGE_GATEWAY_OPTIONS } from './storage-gateway.tokens.js';
22
+ import { STORAGE_GATEWAY_KEY_POLICY, STORAGE_GATEWAY_OPTIONS, } from './storage-gateway.tokens.js';
22
23
  import { StorageGatewayOperation, } from './storage-gateway.types.js';
24
+ const MAX_GATEWAY_PATH_LENGTH = 1024;
25
+ function parseGatewayPath(value, target) {
26
+ if (value === undefined) {
27
+ if (target === 'prefix') {
28
+ return undefined;
29
+ }
30
+ throw new BadRequestException(`${target} is required.`);
31
+ }
32
+ if (value.length > MAX_GATEWAY_PATH_LENGTH) {
33
+ throw new BadRequestException(`${target} cannot exceed ${MAX_GATEWAY_PATH_LENGTH} characters.`);
34
+ }
35
+ if (value.length === 0) {
36
+ if (target === 'prefix') {
37
+ return Object.freeze({
38
+ segments: Object.freeze([]),
39
+ trailingSlash: false,
40
+ value,
41
+ });
42
+ }
43
+ throw new BadRequestException(`${target} must be a non-empty string.`);
44
+ }
45
+ if ([...value].some((character) => {
46
+ const codePoint = character.codePointAt(0);
47
+ return (character === '\\' ||
48
+ codePoint === undefined ||
49
+ codePoint <= 0x1f ||
50
+ codePoint === 0x7f);
51
+ })) {
52
+ throw new BadRequestException(`${target} cannot contain control characters or backslashes.`);
53
+ }
54
+ if (target === 'pattern') {
55
+ return Object.freeze({
56
+ segments: Object.freeze(value.split('/')),
57
+ trailingSlash: value.endsWith('/'),
58
+ value,
59
+ });
60
+ }
61
+ if (value.startsWith('/') || value.trim() !== value) {
62
+ throw new BadRequestException(`${target} must be a relative object path without surrounding whitespace.`);
63
+ }
64
+ const trailingSlash = value.endsWith('/');
65
+ const segments = value.split('/');
66
+ if (trailingSlash) {
67
+ segments.pop();
68
+ }
69
+ if (segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) {
70
+ throw new BadRequestException(`${target} cannot contain empty, dot, or parent path segments.`);
71
+ }
72
+ return Object.freeze({
73
+ segments: Object.freeze(segments),
74
+ trailingSlash,
75
+ value,
76
+ });
77
+ }
78
+ function exactContentType(value, field) {
79
+ const normalized = value.toLowerCase();
80
+ if (value !== normalized ||
81
+ !/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/u.test(value)) {
82
+ throw new BadRequestException(`${field} must be a lowercase exact MIME type without parameters.`);
83
+ }
84
+ return normalized;
85
+ }
86
+ function safeContentDisposition(value) {
87
+ if (value === undefined) {
88
+ return undefined;
89
+ }
90
+ if (value !== 'attachment' && value !== 'inline') {
91
+ throw new BadRequestException('responseContentDisposition must be either "attachment" or "inline".');
92
+ }
93
+ return value;
94
+ }
23
95
  function recordOf(value, label) {
24
96
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
25
97
  throw new BadRequestException(`${label} must be a JSON object.`);
@@ -141,11 +213,21 @@ async function* enforceUploadLimit(source, maxBytes) {
141
213
  yield bytes;
142
214
  }
143
215
  }
144
- function setObjectHeaders(response, object) {
216
+ function providerEtagHeader(object) {
217
+ const etag = object.etag === undefined ? undefined : storageEtagHeader(object.etag);
218
+ if (object.etag !== undefined && etag === undefined) {
219
+ throw new StorageError('Storage provider returned a malformed ETag.', {
220
+ code: StorageErrorCode.PROVIDER,
221
+ permanent: true,
222
+ });
223
+ }
224
+ return etag;
225
+ }
226
+ function setObjectHeaders(response, object, etag) {
145
227
  response.setHeader('content-length', String(object.size));
146
228
  response.setHeader('content-type', object.contentType);
147
- if (object.etag !== undefined) {
148
- response.setHeader('etag', object.etag);
229
+ if (etag !== undefined) {
230
+ response.setHeader('etag', etag);
149
231
  }
150
232
  if (object.lastModified !== undefined) {
151
233
  response.setHeader('last-modified', object.lastModified.toUTCString());
@@ -190,11 +272,17 @@ function toHttpException(error) {
190
272
  if (error instanceof HttpException) {
191
273
  return error;
192
274
  }
193
- if (error instanceof StorageError) {
275
+ if (isStorageError(error)) {
194
276
  return new HttpException({
195
277
  error: {
196
278
  code: error.code,
197
279
  message: publicStorageMessages[error.code],
280
+ ...(error.applied === true && { applied: true }),
281
+ ...(error.applied === true &&
282
+ error.appliedEtag !== undefined &&
283
+ isCanonicalStorageEtag(error.appliedEtag) && {
284
+ appliedEtag: error.appliedEtag,
285
+ }),
198
286
  },
199
287
  }, storageHttpStatus(error), { cause: error });
200
288
  }
@@ -208,24 +296,28 @@ function toHttpException(error) {
208
296
  let StorageGatewayController = class StorageGatewayController {
209
297
  storage;
210
298
  options;
211
- constructor(storage, options) {
299
+ keyPolicy;
300
+ constructor(storage, options, keyPolicy) {
212
301
  this.storage = storage;
213
302
  this.options = options;
303
+ this.keyPolicy = keyPolicy;
214
304
  }
215
- async download(key, proxy, rangeStart, rangeEnd, disposition, response) {
305
+ async download(key, proxy, rangeStart, rangeEnd, disposition, request, response) {
216
306
  await this.#run(async () => {
217
307
  this.#assertAllowed(StorageGatewayOperation.DOWNLOAD);
218
- const objectKey = this.#key(key);
308
+ const responseContentDisposition = safeContentDisposition(disposition);
219
309
  const client = this.storage.use(this.options.store);
220
310
  const forceProxy = proxy === 'true' || proxy === '1';
221
311
  const canSign = this.options.operations.has(StorageGatewayOperation.SIGN_DOWNLOAD) &&
222
- client.capabilities.signedDownload.supported;
312
+ client.capabilities.signedDownload.supported &&
313
+ client.capabilities.signedDownloadPolicy?.expiresIn === true;
223
314
  if (!forceProxy && this.options.mode !== 'proxy' && canSign) {
224
315
  try {
225
- const url = await client.signDownload(objectKey, {
316
+ const signedObjectKey = await this.#resolvePath(request, StorageGatewayOperation.SIGN_DOWNLOAD, 'key', key);
317
+ const url = await client.signDownload(signedObjectKey, {
226
318
  expiresIn: this.options.defaultSignedUrlExpiresIn,
227
- ...(disposition !== undefined && {
228
- responseContentDisposition: disposition,
319
+ ...(responseContentDisposition !== undefined && {
320
+ responseContentDisposition,
229
321
  }),
230
322
  });
231
323
  const raw = rawResponseOf(response);
@@ -236,7 +328,7 @@ let StorageGatewayController = class StorageGatewayController {
236
328
  }
237
329
  catch (error) {
238
330
  if (this.options.mode !== 'hybrid' ||
239
- !(error instanceof StorageError) ||
331
+ !isStorageError(error) ||
240
332
  error.code !== StorageErrorCode.NOT_SUPPORTED) {
241
333
  throw error;
242
334
  }
@@ -245,10 +337,10 @@ let StorageGatewayController = class StorageGatewayController {
245
337
  if (this.options.mode === 'signed') {
246
338
  throw new StorageError('Signed downloads are unavailable for this store.', {
247
339
  code: StorageErrorCode.NOT_SUPPORTED,
248
- key: objectKey,
249
340
  permanent: true,
250
341
  });
251
342
  }
343
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.DOWNLOAD, 'key', key);
252
344
  const start = queryInteger(rangeStart, 'rangeStart');
253
345
  const end = queryInteger(rangeEnd, 'rangeEnd');
254
346
  if (end !== undefined && start === undefined) {
@@ -260,10 +352,18 @@ let StorageGatewayController = class StorageGatewayController {
260
352
  const object = await client.downloadStream(objectKey, {
261
353
  ...(range !== undefined && { range }),
262
354
  });
355
+ let etag;
356
+ try {
357
+ etag = providerEtagHeader(object);
358
+ }
359
+ catch (error) {
360
+ await object.body.cancel(error).catch(() => undefined);
361
+ throw error;
362
+ }
263
363
  const raw = rawResponseOf(response);
264
364
  raw.statusCode =
265
365
  range === undefined ? HttpStatus.OK : HttpStatus.PARTIAL_CONTENT;
266
- setObjectHeaders(raw, object);
366
+ setObjectHeaders(raw, object, etag);
267
367
  const baseContentType = object.contentType
268
368
  .split(';', 1)[0]
269
369
  ?.trim()
@@ -300,7 +400,7 @@ let StorageGatewayController = class StorageGatewayController {
300
400
  permanent: true,
301
401
  });
302
402
  }
303
- const objectKey = this.#key(key);
403
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.UPLOAD, 'key', key);
304
404
  const transportContentType = requestHeader(request, 'content-type')
305
405
  ?.split(';', 1)[0]
306
406
  ?.trim()
@@ -322,53 +422,58 @@ let StorageGatewayController = class StorageGatewayController {
322
422
  }
323
423
  const source = uploadSourceOf(request);
324
424
  const body = Readable.toWeb(Readable.from(enforceUploadLimit(source, this.options.maxUploadBytes)));
325
- const contentType = requestHeader(request, 'x-storage-content-type') ??
326
- 'application/octet-stream';
425
+ const contentType = exactContentType(requestHeader(request, 'x-storage-content-type') ??
426
+ 'application/octet-stream', 'x-storage-content-type');
327
427
  const result = await this.storage
328
428
  .use(this.options.store)
329
429
  .upload(objectKey, body, { contentType, multipart: true });
330
430
  return { data: result };
331
431
  });
332
432
  }
333
- async head(key, response) {
433
+ async head(key, request, response) {
334
434
  await this.#run(async () => {
335
435
  this.#assertAllowed(StorageGatewayOperation.HEAD);
436
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.HEAD, 'key', key);
336
437
  const metadata = await this.storage
337
438
  .use(this.options.store)
338
- .head(this.#key(key));
439
+ .head(objectKey);
440
+ const etag = providerEtagHeader(metadata);
339
441
  const raw = rawResponseOf(response);
340
442
  raw.statusCode = HttpStatus.OK;
341
- setObjectHeaders(raw, metadata);
443
+ setObjectHeaders(raw, metadata, etag);
342
444
  raw.end();
343
445
  });
344
446
  }
345
- async delete(key) {
447
+ async delete(key, request) {
346
448
  return this.#run(async () => {
347
449
  this.#assertAllowed(StorageGatewayOperation.DELETE);
348
- await this.storage.use(this.options.store).delete(this.#key(key));
450
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.DELETE, 'key', key);
451
+ await this.storage.use(this.options.store).delete(objectKey);
349
452
  return { data: { deleted: true } };
350
453
  });
351
454
  }
352
- async list(prefix, cursor, limit, delimiter) {
455
+ async list(prefix, cursor, limit, delimiter, request) {
353
456
  return this.#run(async () => {
354
457
  this.#assertAllowed(StorageGatewayOperation.LIST);
355
458
  const parsedLimit = queryInteger(limit, 'limit') ?? this.options.maxListResults;
356
459
  if (parsedLimit === 0 || parsedLimit > this.options.maxListResults) {
357
460
  throw new BadRequestException(`limit must be between 1 and ${this.options.maxListResults}.`);
358
461
  }
462
+ const resolvedPrefix = await this.#resolvePath(request, StorageGatewayOperation.LIST, 'prefix', prefix);
359
463
  const result = await this.storage.use(this.options.store).list({
360
464
  ...(cursor !== undefined && { cursor }),
361
465
  ...(delimiter !== undefined && { delimiter }),
362
466
  limit: parsedLimit,
363
- ...(prefix !== undefined && { prefix }),
467
+ prefix: resolvedPrefix,
364
468
  });
365
469
  return { data: result };
366
470
  });
367
471
  }
368
- async search(pattern, prefix, limit, maxResults, match, caseInsensitive) {
472
+ async search(pattern, prefix, limit, maxResults, match, caseInsensitive, request) {
369
473
  return this.#run(async () => {
370
474
  this.#assertAllowed(StorageGatewayOperation.SEARCH);
371
- const searchPattern = this.#key(pattern, 'pattern');
475
+ const searchPattern = await this.#resolvePath(request, StorageGatewayOperation.SEARCH, 'pattern', pattern);
476
+ const resolvedPrefix = await this.#resolvePath(request, StorageGatewayOperation.SEARCH, 'prefix', prefix);
372
477
  if (match !== undefined &&
373
478
  !['glob', 'regex', 'substring', 'exact'].includes(match)) {
374
479
  throw new BadRequestException('Invalid search match mode.');
@@ -395,7 +500,7 @@ let StorageGatewayController = class StorageGatewayController {
395
500
  match: match,
396
501
  }),
397
502
  maxResults: parsedMaxResults,
398
- ...(prefix !== undefined && { prefix }),
503
+ prefix: resolvedPrefix,
399
504
  })) {
400
505
  objects.push(object);
401
506
  if (objects.length === parsedMaxResults) {
@@ -405,7 +510,7 @@ let StorageGatewayController = class StorageGatewayController {
405
510
  return { data: objects };
406
511
  });
407
512
  }
408
- async signDownload(body) {
513
+ async signDownload(body, request) {
409
514
  return this.#run(async () => {
410
515
  this.#assertAllowed(StorageGatewayOperation.SIGN_DOWNLOAD);
411
516
  if (this.options.mode === 'proxy') {
@@ -415,21 +520,28 @@ let StorageGatewayController = class StorageGatewayController {
415
520
  });
416
521
  }
417
522
  const input = recordOf(body, 'body');
418
- const responseContentDisposition = optionalString(input, 'responseContentDisposition');
523
+ const responseContentDisposition = safeContentDisposition(optionalString(input, 'responseContentDisposition'));
419
524
  const options = {
420
- expiresIn: optionalPositiveInteger(input, 'expiresIn') ??
421
- this.options.defaultSignedUrlExpiresIn,
525
+ expiresIn: this.#signedExpiry(input),
422
526
  ...(responseContentDisposition !== undefined && {
423
527
  responseContentDisposition,
424
528
  }),
425
529
  };
426
- const url = await this.storage
427
- .use(this.options.store)
428
- .signDownload(requiredString(input, 'key'), options);
530
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.SIGN_DOWNLOAD, 'key', requiredString(input, 'key'));
531
+ const client = this.storage.use(this.options.store);
532
+ if (client.capabilities.signedDownloadPolicy?.expiresIn !== true) {
533
+ throw new StorageError('This store cannot prove that signed-download expiry is provider-enforced.', {
534
+ code: StorageErrorCode.NOT_SUPPORTED,
535
+ key: objectKey,
536
+ operation: 'signDownload',
537
+ permanent: true,
538
+ });
539
+ }
540
+ const url = await client.signDownload(objectKey, options);
429
541
  return { data: { url } };
430
542
  });
431
543
  }
432
- async signUpload(body) {
544
+ async signUpload(body, request) {
433
545
  return this.#run(async () => {
434
546
  this.#assertAllowed(StorageGatewayOperation.SIGN_UPLOAD);
435
547
  if (this.options.mode === 'proxy') {
@@ -439,42 +551,70 @@ let StorageGatewayController = class StorageGatewayController {
439
551
  });
440
552
  }
441
553
  const input = recordOf(body, 'body');
442
- const contentType = optionalString(input, 'contentType');
443
- const maxSize = optionalPositiveInteger(input, 'maxSize');
554
+ const rawContentType = optionalString(input, 'contentType');
555
+ if (rawContentType === undefined) {
556
+ throw new BadRequestException('contentType is required for signed uploads.');
557
+ }
558
+ const contentType = exactContentType(rawContentType, 'contentType');
559
+ if (!this.options.signedUploadContentTypes.has(contentType)) {
560
+ throw new BadRequestException(`contentType "${contentType}" is not allowed for signed uploads.`);
561
+ }
562
+ const requestedMaxSize = optionalPositiveInteger(input, 'maxSize');
563
+ if (requestedMaxSize !== undefined &&
564
+ requestedMaxSize > this.options.maxSignedUploadBytes) {
565
+ throw new StorageError(`Signed upload exceeds the ${this.options.maxSignedUploadBytes}-byte gateway limit.`, {
566
+ code: StorageErrorCode.LIMIT_EXCEEDED,
567
+ permanent: true,
568
+ });
569
+ }
570
+ const maxSize = requestedMaxSize ?? this.options.maxSignedUploadBytes;
444
571
  const minSize = optionalNonNegativeInteger(input, 'minSize');
445
572
  if (maxSize !== undefined && minSize !== undefined && minSize > maxSize) {
446
573
  throw new BadRequestException('minSize cannot be greater than maxSize.');
447
574
  }
448
575
  const options = {
449
- expiresIn: optionalPositiveInteger(input, 'expiresIn') ??
450
- this.options.defaultSignedUrlExpiresIn,
451
- ...(contentType !== undefined && { contentType }),
452
- ...(maxSize !== undefined && { maxSize }),
576
+ contentType,
577
+ expiresIn: this.#signedExpiry(input),
578
+ maxSize,
453
579
  ...(minSize !== undefined && { minSize }),
454
580
  };
455
- const result = await this.storage
456
- .use(this.options.store)
457
- .signUpload(requiredString(input, 'key'), options);
581
+ const objectKey = await this.#resolvePath(request, StorageGatewayOperation.SIGN_UPLOAD, 'key', requiredString(input, 'key'));
582
+ const client = this.storage.use(this.options.store);
583
+ const policyCapability = client.capabilities.signedUploadPolicy;
584
+ if (policyCapability?.contentType !== true ||
585
+ policyCapability.sizeRange !== true) {
586
+ throw new StorageError('This store cannot prove that signed-upload MIME and size constraints are provider-enforced.', {
587
+ code: StorageErrorCode.NOT_SUPPORTED,
588
+ key: objectKey,
589
+ operation: 'signUpload',
590
+ permanent: true,
591
+ });
592
+ }
593
+ const result = await client.signUpload(objectKey, options);
458
594
  return { data: result };
459
595
  });
460
596
  }
461
- async copy(body) {
597
+ async copy(body, request) {
462
598
  return this.#run(async () => {
463
599
  this.#assertAllowed(StorageGatewayOperation.COPY);
464
600
  const input = recordOf(body, 'body');
465
- await this.storage
466
- .use(this.options.store)
467
- .copy(requiredString(input, 'from'), requiredString(input, 'to'));
601
+ const [from, to] = await Promise.all([
602
+ this.#resolvePath(request, StorageGatewayOperation.COPY, 'from', requiredString(input, 'from')),
603
+ this.#resolvePath(request, StorageGatewayOperation.COPY, 'to', requiredString(input, 'to')),
604
+ ]);
605
+ await this.storage.use(this.options.store).copy(from, to);
468
606
  return { data: { copied: true } };
469
607
  });
470
608
  }
471
- async move(body) {
609
+ async move(body, request) {
472
610
  return this.#run(async () => {
473
611
  this.#assertAllowed(StorageGatewayOperation.MOVE);
474
612
  const input = recordOf(body, 'body');
475
- await this.storage
476
- .use(this.options.store)
477
- .move(requiredString(input, 'from'), requiredString(input, 'to'));
613
+ const [from, to] = await Promise.all([
614
+ this.#resolvePath(request, StorageGatewayOperation.MOVE, 'from', requiredString(input, 'from')),
615
+ this.#resolvePath(request, StorageGatewayOperation.MOVE, 'to', requiredString(input, 'to')),
616
+ ]);
617
+ await this.storage.use(this.options.store).move(from, to);
478
618
  return { data: { moved: true } };
479
619
  });
480
620
  }
@@ -488,11 +628,31 @@ let StorageGatewayController = class StorageGatewayController {
488
628
  }, HttpStatus.FORBIDDEN);
489
629
  }
490
630
  }
491
- #key(value, label = 'key') {
492
- if (value === undefined || value.length === 0) {
493
- throw new BadRequestException(`${label} is required.`);
631
+ async #resolvePath(request, operation, target, value) {
632
+ const input = parseGatewayPath(value, target);
633
+ const resolved = await this.keyPolicy.resolve({
634
+ input,
635
+ operation,
636
+ request,
637
+ target,
638
+ });
639
+ const parsed = parseGatewayPath(resolved, target);
640
+ if (parsed === undefined) {
641
+ throw new StorageError(`Storage gateway key policy did not resolve ${target}.`, {
642
+ code: StorageErrorCode.UNAUTHORIZED,
643
+ operation,
644
+ permanent: true,
645
+ });
646
+ }
647
+ return parsed.value;
648
+ }
649
+ #signedExpiry(input) {
650
+ const expiresIn = optionalPositiveInteger(input, 'expiresIn') ??
651
+ this.options.defaultSignedUrlExpiresIn;
652
+ if (expiresIn > this.options.maxSignedUrlExpiresIn) {
653
+ throw new BadRequestException(`expiresIn cannot exceed ${this.options.maxSignedUrlExpiresIn} seconds.`);
494
654
  }
495
- return value;
655
+ return expiresIn;
496
656
  }
497
657
  async #run(operation) {
498
658
  try {
@@ -510,9 +670,10 @@ __decorate([
510
670
  __param(2, Query('rangeStart')),
511
671
  __param(3, Query('rangeEnd')),
512
672
  __param(4, Query('disposition')),
513
- __param(5, Res()),
673
+ __param(5, Req()),
674
+ __param(6, Res()),
514
675
  __metadata("design:type", Function),
515
- __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object]),
676
+ __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object]),
516
677
  __metadata("design:returntype", Promise)
517
678
  ], StorageGatewayController.prototype, "download", null);
518
679
  __decorate([
@@ -529,16 +690,18 @@ __decorate([
529
690
  __decorate([
530
691
  Head('metadata'),
531
692
  __param(0, Query('key')),
532
- __param(1, Res()),
693
+ __param(1, Req()),
694
+ __param(2, Res()),
533
695
  __metadata("design:type", Function),
534
- __metadata("design:paramtypes", [Object, Object]),
696
+ __metadata("design:paramtypes", [Object, Object, Object]),
535
697
  __metadata("design:returntype", Promise)
536
698
  ], StorageGatewayController.prototype, "head", null);
537
699
  __decorate([
538
700
  Delete('object'),
539
701
  __param(0, Query('key')),
702
+ __param(1, Req()),
540
703
  __metadata("design:type", Function),
541
- __metadata("design:paramtypes", [Object]),
704
+ __metadata("design:paramtypes", [Object, Object]),
542
705
  __metadata("design:returntype", Promise)
543
706
  ], StorageGatewayController.prototype, "delete", null);
544
707
  __decorate([
@@ -547,8 +710,9 @@ __decorate([
547
710
  __param(1, Query('cursor')),
548
711
  __param(2, Query('limit')),
549
712
  __param(3, Query('delimiter')),
713
+ __param(4, Req()),
550
714
  __metadata("design:type", Function),
551
- __metadata("design:paramtypes", [Object, Object, Object, Object]),
715
+ __metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
552
716
  __metadata("design:returntype", Promise)
553
717
  ], StorageGatewayController.prototype, "list", null);
554
718
  __decorate([
@@ -559,43 +723,49 @@ __decorate([
559
723
  __param(3, Query('maxResults')),
560
724
  __param(4, Query('match')),
561
725
  __param(5, Query('caseInsensitive')),
726
+ __param(6, Req()),
562
727
  __metadata("design:type", Function),
563
- __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object]),
728
+ __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object]),
564
729
  __metadata("design:returntype", Promise)
565
730
  ], StorageGatewayController.prototype, "search", null);
566
731
  __decorate([
567
732
  Post('sign-download'),
568
733
  __param(0, Body()),
734
+ __param(1, Req()),
569
735
  __metadata("design:type", Function),
570
- __metadata("design:paramtypes", [Object]),
736
+ __metadata("design:paramtypes", [Object, Object]),
571
737
  __metadata("design:returntype", Promise)
572
738
  ], StorageGatewayController.prototype, "signDownload", null);
573
739
  __decorate([
574
740
  Post('sign-upload'),
575
741
  __param(0, Body()),
742
+ __param(1, Req()),
576
743
  __metadata("design:type", Function),
577
- __metadata("design:paramtypes", [Object]),
744
+ __metadata("design:paramtypes", [Object, Object]),
578
745
  __metadata("design:returntype", Promise)
579
746
  ], StorageGatewayController.prototype, "signUpload", null);
580
747
  __decorate([
581
748
  Post('copy'),
582
749
  __param(0, Body()),
750
+ __param(1, Req()),
583
751
  __metadata("design:type", Function),
584
- __metadata("design:paramtypes", [Object]),
752
+ __metadata("design:paramtypes", [Object, Object]),
585
753
  __metadata("design:returntype", Promise)
586
754
  ], StorageGatewayController.prototype, "copy", null);
587
755
  __decorate([
588
756
  Post('move'),
589
757
  __param(0, Body()),
758
+ __param(1, Req()),
590
759
  __metadata("design:type", Function),
591
- __metadata("design:paramtypes", [Object]),
760
+ __metadata("design:paramtypes", [Object, Object]),
592
761
  __metadata("design:returntype", Promise)
593
762
  ], StorageGatewayController.prototype, "move", null);
594
763
  StorageGatewayController = __decorate([
595
764
  Controller('storage'),
596
765
  UseGuards(StorageGatewayGuard),
597
766
  __param(1, Inject(STORAGE_GATEWAY_OPTIONS)),
598
- __metadata("design:paramtypes", [StorageService, Object])
767
+ __param(2, Inject(STORAGE_GATEWAY_KEY_POLICY)),
768
+ __metadata("design:paramtypes", [StorageService, Object, Object])
599
769
  ], StorageGatewayController);
600
770
  export { StorageGatewayController };
601
771
  //# sourceMappingURL=storage-gateway.controller.js.map