@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.
- package/CHANGELOG.md +264 -0
- package/README.md +724 -25
- package/SECURITY.md +101 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +91 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js +491 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
- package/dist/ai-sdk/index.d.ts +2 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/index.js +2 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/files-sdk/files-sdk.driver.d.ts +85 -3
- package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
- package/dist/files-sdk/files-sdk.driver.js +1437 -37
- package/dist/files-sdk/files-sdk.driver.js.map +1 -1
- package/dist/files-sdk/fs/index.d.ts +42 -0
- package/dist/files-sdk/fs/index.d.ts.map +1 -0
- package/dist/files-sdk/fs/index.js +1117 -0
- package/dist/files-sdk/fs/index.js.map +1 -0
- package/dist/files-sdk/index.d.ts +1 -1
- package/dist/files-sdk/index.d.ts.map +1 -1
- package/dist/files-sdk/index.js.map +1 -1
- package/dist/files-sdk/provider/index.d.ts +58 -0
- package/dist/files-sdk/provider/index.d.ts.map +1 -0
- package/dist/files-sdk/provider/index.js +147 -0
- package/dist/files-sdk/provider/index.js.map +1 -0
- package/dist/files-sdk/s3/construction-metadata.d.ts +9 -0
- package/dist/files-sdk/s3/construction-metadata.d.ts.map +1 -0
- package/dist/files-sdk/s3/construction-metadata.js +24 -0
- package/dist/files-sdk/s3/construction-metadata.js.map +1 -0
- package/dist/files-sdk/s3/index.d.ts +54 -0
- package/dist/files-sdk/s3/index.d.ts.map +1 -0
- package/dist/files-sdk/s3/index.js +1505 -0
- package/dist/files-sdk/s3/index.js.map +1 -0
- package/dist/gateway/index.d.ts +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/storage-gateway-fastify-parser.d.ts.map +1 -1
- package/dist/gateway/storage-gateway-fastify-parser.js.map +1 -1
- package/dist/gateway/storage-gateway.controller.d.ts +12 -11
- package/dist/gateway/storage-gateway.controller.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.controller.js +240 -70
- package/dist/gateway/storage-gateway.controller.js.map +1 -1
- package/dist/gateway/storage-gateway.guard.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.guard.js.map +1 -1
- package/dist/gateway/storage-gateway.module.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.module.js +60 -1
- package/dist/gateway/storage-gateway.module.js.map +1 -1
- package/dist/gateway/storage-gateway.tokens.d.ts +1 -0
- package/dist/gateway/storage-gateway.tokens.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.tokens.js +1 -0
- package/dist/gateway/storage-gateway.tokens.js.map +1 -1
- package/dist/gateway/storage-gateway.types.d.ts +53 -10
- package/dist/gateway/storage-gateway.types.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.types.js.map +1 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/inject-storage.decorator.js.map +1 -1
- package/dist/storage-etag.d.ts +13 -0
- package/dist/storage-etag.d.ts.map +1 -0
- package/dist/storage-etag.js +31 -0
- package/dist/storage-etag.js.map +1 -0
- package/dist/storage-upload-control.d.ts.map +1 -1
- package/dist/storage.client.d.ts +15 -3
- package/dist/storage.client.d.ts.map +1 -1
- package/dist/storage.client.js +204 -2
- package/dist/storage.client.js.map +1 -1
- package/dist/storage.driver.d.ts +28 -1
- package/dist/storage.driver.d.ts.map +1 -1
- package/dist/storage.driver.js.map +1 -1
- package/dist/storage.error.d.ts +17 -10
- package/dist/storage.error.d.ts.map +1 -1
- package/dist/storage.error.js +47 -1
- package/dist/storage.error.js.map +1 -1
- package/dist/storage.module.d.ts.map +1 -1
- package/dist/storage.module.js.map +1 -1
- package/dist/storage.service.d.ts.map +1 -1
- package/dist/storage.service.js +28 -3
- package/dist/storage.service.js.map +1 -1
- package/dist/storage.tokens.js.map +1 -1
- package/dist/storage.types.d.ts +150 -1
- package/dist/storage.types.d.ts.map +1 -1
- package/dist/storage.types.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/provider-conformance.d.ts +65 -0
- package/dist/testing/provider-conformance.d.ts.map +1 -0
- package/dist/testing/provider-conformance.js +869 -0
- package/dist/testing/provider-conformance.js.map +1 -0
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +5 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/storage-workspace.cursor.d.ts +71 -0
- package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.cursor.js +408 -0
- package/dist/workspace/storage-workspace.cursor.js.map +1 -0
- package/dist/workspace/storage-workspace.d.ts +6 -0
- package/dist/workspace/storage-workspace.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.d.ts +32 -0
- package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.js +75 -0
- package/dist/workspace/storage-workspace.error.js.map +1 -0
- package/dist/workspace/storage-workspace.js +849 -0
- package/dist/workspace/storage-workspace.js.map +1 -0
- package/dist/workspace/storage-workspace.path.d.ts +9 -0
- package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.path.js +68 -0
- package/dist/workspace/storage-workspace.path.js.map +1 -0
- package/dist/workspace/storage-workspace.types.d.ts +123 -0
- package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.types.js +22 -0
- package/dist/workspace/storage-workspace.types.js.map +1 -0
- 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
|
|
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 (
|
|
148
|
-
response.setHeader('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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
...(
|
|
228
|
-
responseContentDisposition
|
|
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
|
|
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.#
|
|
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(
|
|
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
|
|
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
|
-
|
|
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.#
|
|
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
|
-
|
|
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:
|
|
421
|
-
this.options.defaultSignedUrlExpiresIn,
|
|
525
|
+
expiresIn: this.#signedExpiry(input),
|
|
422
526
|
...(responseContentDisposition !== undefined && {
|
|
423
527
|
responseContentDisposition,
|
|
424
528
|
}),
|
|
425
529
|
};
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
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
|
|
443
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
...(maxSize !== undefined && { maxSize }),
|
|
576
|
+
contentType,
|
|
577
|
+
expiresIn: this.#signedExpiry(input),
|
|
578
|
+
maxSize,
|
|
453
579
|
...(minSize !== undefined && { minSize }),
|
|
454
580
|
};
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
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
|
|
466
|
-
.
|
|
467
|
-
|
|
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
|
|
476
|
-
.
|
|
477
|
-
|
|
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
|
-
#
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|