@twin.org/blob-storage-service 0.0.1-next.34 → 0.0.1-next.36
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/dist/cjs/index.cjs +95 -31
- package/dist/esm/index.mjs +97 -33
- package/dist/types/blobStorageService.d.ts +21 -5
- package/dist/types/entities/blobStorageEntry.d.ts +9 -0
- package/dist/types/models/IBlobStorageServiceConfig.d.ts +1 -2
- package/dist/types/models/IBlobStorageServiceConstructorOptions.d.ts +1 -1
- package/docs/changelog.md +32 -0
- package/docs/open-api/spec.json +72 -956
- package/docs/reference/classes/BlobStorageEntry.md +16 -0
- package/docs/reference/classes/BlobStorageService.md +42 -4
- package/docs/reference/interfaces/IBlobStorageServiceConfig.md +1 -7
- package/docs/reference/interfaces/IBlobStorageServiceConstructorOptions.md +1 -1
- package/locales/en.json +2 -1
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -98,7 +98,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
98
98
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
99
99
|
},
|
|
100
100
|
query: {
|
|
101
|
-
includeContent: true
|
|
101
|
+
includeContent: "true"
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -188,7 +188,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
188
188
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
189
189
|
},
|
|
190
190
|
query: {
|
|
191
|
-
download: true,
|
|
191
|
+
download: "true",
|
|
192
192
|
filename: "my-file.pdf"
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -277,7 +277,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
277
277
|
]
|
|
278
278
|
};
|
|
279
279
|
const blobStorageListRoute = {
|
|
280
|
-
operationId: `${camelTypeName}
|
|
280
|
+
operationId: `${camelTypeName}Query`,
|
|
281
281
|
summary: `Query the items from ${lowerName}`,
|
|
282
282
|
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
283
283
|
method: "GET",
|
|
@@ -400,7 +400,11 @@ async function blobStorageCreate(httpRequestContext, componentName, request) {
|
|
|
400
400
|
core.Guards.object(ROUTES_SOURCE, "request.body", request.body);
|
|
401
401
|
core.Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
|
|
402
402
|
const component = core.ComponentFactory.get(componentName);
|
|
403
|
-
const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata,
|
|
403
|
+
const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, {
|
|
404
|
+
disableEncryption: request.body.disableEncryption,
|
|
405
|
+
overrideVaultKeyId: request.body.overrideVaultKeyId,
|
|
406
|
+
namespace: request.body.namespace
|
|
407
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
404
408
|
return {
|
|
405
409
|
statusCode: web.HttpStatusCode.created,
|
|
406
410
|
headers: {
|
|
@@ -421,7 +425,11 @@ async function blobStorageGet(httpRequestContext, componentName, request) {
|
|
|
421
425
|
core.Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
422
426
|
const mimeType = request.headers?.[web.HeaderTypes.Accept] === web.MimeTypes.JsonLd ? "jsonld" : "json";
|
|
423
427
|
const component = core.ComponentFactory.get(componentName);
|
|
424
|
-
const result = await component.get(request.pathParams.id,
|
|
428
|
+
const result = await component.get(request.pathParams.id, {
|
|
429
|
+
includeContent: core.Coerce.boolean(request.query?.includeContent),
|
|
430
|
+
decompress: core.Coerce.boolean(request.query?.decompress),
|
|
431
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
432
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
425
433
|
return {
|
|
426
434
|
headers: {
|
|
427
435
|
[web.HeaderTypes.ContentType]: mimeType === "json" ? web.MimeTypes.Json : web.MimeTypes.JsonLd
|
|
@@ -441,16 +449,31 @@ async function blobStorageGetContent(httpRequestContext, componentName, request)
|
|
|
441
449
|
core.Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
442
450
|
core.Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
443
451
|
const component = core.ComponentFactory.get(componentName);
|
|
444
|
-
const
|
|
452
|
+
const decompress = core.Coerce.boolean(request.query?.decompress);
|
|
453
|
+
const result = await component.get(request.pathParams.id, {
|
|
454
|
+
includeContent: true,
|
|
455
|
+
decompress,
|
|
456
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
457
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
445
458
|
const encodingFormat = result?.encodingFormat ?? web.MimeTypes.OctetStream;
|
|
459
|
+
let compressedEncodingFormat;
|
|
460
|
+
let compressedExtension = "";
|
|
461
|
+
// If the entry is compressed and we are not decompressing
|
|
462
|
+
// we need to override the encoding format to the compressed type
|
|
463
|
+
// and append an additional extension to the filename.
|
|
464
|
+
if (result.compression && !decompress) {
|
|
465
|
+
compressedEncodingFormat =
|
|
466
|
+
result.compression === blobStorageModels.BlobStorageCompressionType.Gzip ? web.MimeTypes.Gzip : web.MimeTypes.Zlib;
|
|
467
|
+
compressedExtension = `.${web.MimeTypeHelper.defaultExtension(compressedEncodingFormat)}`;
|
|
468
|
+
}
|
|
446
469
|
let filename = request.query?.filename;
|
|
447
470
|
if (!core.Is.stringValue(filename)) {
|
|
448
|
-
filename = `file.${result.fileExtension ?? web.MimeTypeHelper.defaultExtension(encodingFormat)}`;
|
|
471
|
+
filename = `file.${result.fileExtension ?? web.MimeTypeHelper.defaultExtension(encodingFormat)}${compressedExtension}`;
|
|
449
472
|
}
|
|
450
473
|
return {
|
|
451
474
|
body: core.Is.stringBase64(result.blob) ? core.Converter.base64ToBytes(result.blob) : new Uint8Array(),
|
|
452
475
|
attachment: {
|
|
453
|
-
mimeType: encodingFormat,
|
|
476
|
+
mimeType: compressedEncodingFormat ?? encodingFormat,
|
|
454
477
|
filename,
|
|
455
478
|
inline: !(request.query?.download ?? false)
|
|
456
479
|
}
|
|
@@ -566,10 +589,10 @@ class BlobStorageService {
|
|
|
566
589
|
}
|
|
567
590
|
this._entryEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(options?.entryEntityStorageType ?? "blob-storage-entry");
|
|
568
591
|
if (core.Is.stringValue(options?.vaultConnectorType)) {
|
|
569
|
-
this._vaultConnector = vaultModels.VaultConnectorFactory.
|
|
592
|
+
this._vaultConnector = vaultModels.VaultConnectorFactory.get(options.vaultConnectorType);
|
|
570
593
|
}
|
|
571
594
|
this._defaultNamespace = options?.config?.defaultNamespace ?? names[0];
|
|
572
|
-
this._vaultKeyId = options?.config?.vaultKeyId
|
|
595
|
+
this._vaultKeyId = options?.config?.vaultKeyId;
|
|
573
596
|
this._includeNodeIdentity = options?.config?.includeNodeIdentity ?? true;
|
|
574
597
|
this._includeUserIdentity = options?.config?.includeUserIdentity ?? true;
|
|
575
598
|
standardsSchemaOrg.SchemaOrgDataTypes.registerRedirects();
|
|
@@ -580,21 +603,28 @@ class BlobStorageService {
|
|
|
580
603
|
* @param encodingFormat Mime type for the blob, will be detected if left undefined.
|
|
581
604
|
* @param fileExtension Extension for the blob, will be detected if left undefined.
|
|
582
605
|
* @param metadata Data for the custom metadata as JSON-LD.
|
|
583
|
-
* @param
|
|
606
|
+
* @param options Optional options for the creation of the blob.
|
|
607
|
+
* @param options.disableEncryption Disables encryption if enabled by default.
|
|
608
|
+
* @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
|
|
609
|
+
* @param options.compress Optional compression type to use for the blob, defaults to no compression.*
|
|
610
|
+
* @param options.namespace The namespace to use for storing, defaults to component configured namespace.
|
|
584
611
|
* @param userIdentity The user identity to use with storage operations.
|
|
585
612
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
586
613
|
* @returns The id of the stored blob in urn format.
|
|
587
614
|
*/
|
|
588
|
-
async create(blob, encodingFormat, fileExtension, metadata,
|
|
615
|
+
async create(blob, encodingFormat, fileExtension, metadata, options, userIdentity, nodeIdentity) {
|
|
589
616
|
core.Guards.stringBase64(this.CLASS_NAME, "blob", blob);
|
|
590
617
|
if (this._includeUserIdentity) {
|
|
591
618
|
core.Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
592
619
|
}
|
|
593
|
-
|
|
620
|
+
const disableEncryption = options?.disableEncryption ?? false;
|
|
621
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
622
|
+
const encryptionEnabled = !disableEncryption && core.Is.stringValue(vaultKeyId);
|
|
623
|
+
if (this._includeNodeIdentity || encryptionEnabled) {
|
|
594
624
|
core.Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
595
625
|
}
|
|
596
626
|
try {
|
|
597
|
-
const connectorNamespace = namespace ?? this._defaultNamespace;
|
|
627
|
+
const connectorNamespace = options?.namespace ?? this._defaultNamespace;
|
|
598
628
|
const blobStorageConnector = blobStorageModels.BlobStorageConnectorFactory.get(connectorNamespace);
|
|
599
629
|
// Convert the base64 data into bytes
|
|
600
630
|
let storeBlob = core.Converter.base64ToBytes(blob);
|
|
@@ -614,9 +644,15 @@ class BlobStorageService {
|
|
|
614
644
|
core.Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
|
|
615
645
|
}
|
|
616
646
|
const blobHash = `sha256:${core.Converter.bytesToBase64(crypto.Sha256.sum256(storeBlob))}`;
|
|
647
|
+
if (!core.Is.empty(options?.compress)) {
|
|
648
|
+
storeBlob = await core.Compression.compress(storeBlob, options.compress);
|
|
649
|
+
}
|
|
617
650
|
// If we have a vault connector then encrypt the data.
|
|
618
|
-
if (
|
|
619
|
-
|
|
651
|
+
if (encryptionEnabled) {
|
|
652
|
+
if (core.Is.empty(this._vaultConnector)) {
|
|
653
|
+
throw new core.GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
654
|
+
}
|
|
655
|
+
storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${vaultKeyId}`, vaultModels.VaultEncryptionType.ChaCha20Poly1305, storeBlob);
|
|
620
656
|
}
|
|
621
657
|
// Set the blob in the storage connector, which may now be encrypted
|
|
622
658
|
const blobId = await blobStorageConnector.set(storeBlob);
|
|
@@ -628,7 +664,9 @@ class BlobStorageService {
|
|
|
628
664
|
blobHash,
|
|
629
665
|
encodingFormat,
|
|
630
666
|
fileExtension,
|
|
631
|
-
metadata
|
|
667
|
+
metadata,
|
|
668
|
+
isEncrypted: encryptionEnabled,
|
|
669
|
+
compression: options?.compress
|
|
632
670
|
};
|
|
633
671
|
const conditions = [];
|
|
634
672
|
if (this._includeUserIdentity) {
|
|
@@ -649,14 +687,19 @@ class BlobStorageService {
|
|
|
649
687
|
/**
|
|
650
688
|
* Get the blob entry.
|
|
651
689
|
* @param id The id of the blob to get in urn format.
|
|
652
|
-
* @param
|
|
690
|
+
* @param options Optional options for the retrieval of the blob.
|
|
691
|
+
* @param options.includeContent Include the content, or just get the metadata.
|
|
692
|
+
* @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
|
|
693
|
+
* @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
|
|
653
694
|
* @param userIdentity The user identity to use with storage operations.
|
|
654
695
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
655
696
|
* @returns The entry and data for the blob if it can be found.
|
|
656
697
|
* @throws Not found error if the blob cannot be found.
|
|
657
698
|
*/
|
|
658
|
-
async get(id,
|
|
699
|
+
async get(id, options, userIdentity, nodeIdentity) {
|
|
659
700
|
core.Urn.guard(this.CLASS_NAME, "id", id);
|
|
701
|
+
const includeContent = options?.includeContent ?? false;
|
|
702
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
660
703
|
const conditions = [];
|
|
661
704
|
if (this._includeUserIdentity) {
|
|
662
705
|
core.Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
@@ -683,9 +726,16 @@ class BlobStorageService {
|
|
|
683
726
|
if (core.Is.undefined(returnBlob)) {
|
|
684
727
|
throw new core.NotFoundError(this.CLASS_NAME, "blobNotFound", id);
|
|
685
728
|
}
|
|
686
|
-
// If
|
|
687
|
-
|
|
688
|
-
|
|
729
|
+
// If the data is encrypted then decrypt it.
|
|
730
|
+
const decryptionEnabled = blobEntry.isEncrypted && core.Is.stringValue(vaultKeyId);
|
|
731
|
+
if (decryptionEnabled) {
|
|
732
|
+
if (core.Is.empty(this._vaultConnector)) {
|
|
733
|
+
throw new core.GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
734
|
+
}
|
|
735
|
+
returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${vaultKeyId}`, vaultModels.VaultEncryptionType.ChaCha20Poly1305, returnBlob);
|
|
736
|
+
}
|
|
737
|
+
if (!core.Is.empty(blobEntry.compression) && (options?.decompress ?? true)) {
|
|
738
|
+
returnBlob = await core.Compression.decompress(returnBlob, blobEntry.compression);
|
|
689
739
|
}
|
|
690
740
|
}
|
|
691
741
|
const jsonLd = this.entryToJsonLd(blobEntry, returnBlob);
|
|
@@ -733,7 +783,9 @@ class BlobStorageService {
|
|
|
733
783
|
blobHash: blobEntry.blobHash,
|
|
734
784
|
encodingFormat: encodingFormat ?? blobEntry.encodingFormat,
|
|
735
785
|
fileExtension: fileExtension ?? blobEntry.fileExtension,
|
|
736
|
-
metadata: metadata ?? blobEntry.metadata
|
|
786
|
+
metadata: metadata ?? blobEntry.metadata,
|
|
787
|
+
isEncrypted: blobEntry.isEncrypted,
|
|
788
|
+
compression: blobEntry.compression
|
|
737
789
|
};
|
|
738
790
|
const conditions = [];
|
|
739
791
|
if (this._includeUserIdentity) {
|
|
@@ -828,10 +880,6 @@ class BlobStorageService {
|
|
|
828
880
|
sortDirection: orderDirection
|
|
829
881
|
}
|
|
830
882
|
], undefined, cursor, pageSize);
|
|
831
|
-
for (const entity of result.entities) {
|
|
832
|
-
core.ObjectHelper.propertyDelete(entity, "nodeIdentity");
|
|
833
|
-
core.ObjectHelper.propertyDelete(entity, "userIdentity");
|
|
834
|
-
}
|
|
835
883
|
let context = [
|
|
836
884
|
standardsSchemaOrg.SchemaOrgContexts.ContextRoot,
|
|
837
885
|
blobStorageModels.BlobStorageContexts.ContextRoot,
|
|
@@ -915,9 +963,7 @@ class BlobStorageService {
|
|
|
915
963
|
if (core.Is.empty(entity$1)) {
|
|
916
964
|
throw new core.NotFoundError(this.CLASS_NAME, "entityNotFound", id);
|
|
917
965
|
}
|
|
918
|
-
core.ObjectHelper.
|
|
919
|
-
core.ObjectHelper.propertyDelete(entity$1, "userIdentity");
|
|
920
|
-
return entity$1;
|
|
966
|
+
return core.ObjectHelper.omit(entity$1, ["nodeIdentity", "userIdentity"]);
|
|
921
967
|
}
|
|
922
968
|
/**
|
|
923
969
|
* Convert the entry to JSON-LD.
|
|
@@ -942,7 +988,9 @@ class BlobStorageService {
|
|
|
942
988
|
encodingFormat: entry?.encodingFormat,
|
|
943
989
|
fileExtension: entry?.fileExtension,
|
|
944
990
|
metadata: entry?.metadata,
|
|
945
|
-
blob: core.Is.uint8Array(blob) ? core.Converter.bytesToBase64(blob) : undefined
|
|
991
|
+
blob: core.Is.uint8Array(blob) ? core.Converter.bytesToBase64(blob) : undefined,
|
|
992
|
+
isEncrypted: entry.isEncrypted,
|
|
993
|
+
compression: entry.compression
|
|
946
994
|
};
|
|
947
995
|
return jsonLd;
|
|
948
996
|
}
|
|
@@ -984,6 +1032,14 @@ exports.BlobStorageEntry = class BlobStorageEntry {
|
|
|
984
1032
|
* The metadata for the blob as JSON-LD.
|
|
985
1033
|
*/
|
|
986
1034
|
metadata;
|
|
1035
|
+
/**
|
|
1036
|
+
* Is the entry encrypted.
|
|
1037
|
+
*/
|
|
1038
|
+
isEncrypted;
|
|
1039
|
+
/**
|
|
1040
|
+
* Is the entry compressed.
|
|
1041
|
+
*/
|
|
1042
|
+
compression;
|
|
987
1043
|
/**
|
|
988
1044
|
* The user identity that created the blob.
|
|
989
1045
|
*/
|
|
@@ -1030,6 +1086,14 @@ __decorate([
|
|
|
1030
1086
|
entity.property({ type: "object", itemTypeRef: "IJsonLdNodeObject", optional: true }),
|
|
1031
1087
|
__metadata("design:type", Object)
|
|
1032
1088
|
], exports.BlobStorageEntry.prototype, "metadata", void 0);
|
|
1089
|
+
__decorate([
|
|
1090
|
+
entity.property({ type: "boolean" }),
|
|
1091
|
+
__metadata("design:type", Boolean)
|
|
1092
|
+
], exports.BlobStorageEntry.prototype, "isEncrypted", void 0);
|
|
1093
|
+
__decorate([
|
|
1094
|
+
entity.property({ type: "string", optional: true }),
|
|
1095
|
+
__metadata("design:type", String)
|
|
1096
|
+
], exports.BlobStorageEntry.prototype, "compression", void 0);
|
|
1033
1097
|
__decorate([
|
|
1034
1098
|
entity.property({ type: "string", optional: true }),
|
|
1035
1099
|
__metadata("design:type", String)
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpParameterHelper } from '@twin.org/api-models';
|
|
2
|
-
import { BlobStorageTypes, BlobStorageContexts, BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
|
|
3
|
-
import { StringHelper, Guards, ComponentFactory, Is, Converter,
|
|
2
|
+
import { BlobStorageTypes, BlobStorageContexts, BlobStorageCompressionType, BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
|
|
3
|
+
import { StringHelper, Guards, ComponentFactory, Coerce, Is, Converter, GeneralError, Validation, Compression, ObjectHelper, Urn, NotFoundError } from '@twin.org/core';
|
|
4
4
|
import { SchemaOrgContexts, SchemaOrgTypes, SchemaOrgDataTypes } from '@twin.org/standards-schema-org';
|
|
5
5
|
import { HttpStatusCode, HeaderTypes, MimeTypes, MimeTypeHelper } from '@twin.org/web';
|
|
6
6
|
import { Sha256 } from '@twin.org/crypto';
|
|
@@ -96,7 +96,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
96
96
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
97
97
|
},
|
|
98
98
|
query: {
|
|
99
|
-
includeContent: true
|
|
99
|
+
includeContent: "true"
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -186,7 +186,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
186
186
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
187
187
|
},
|
|
188
188
|
query: {
|
|
189
|
-
download: true,
|
|
189
|
+
download: "true",
|
|
190
190
|
filename: "my-file.pdf"
|
|
191
191
|
}
|
|
192
192
|
}
|
|
@@ -275,7 +275,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
|
275
275
|
]
|
|
276
276
|
};
|
|
277
277
|
const blobStorageListRoute = {
|
|
278
|
-
operationId: `${camelTypeName}
|
|
278
|
+
operationId: `${camelTypeName}Query`,
|
|
279
279
|
summary: `Query the items from ${lowerName}`,
|
|
280
280
|
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
281
281
|
method: "GET",
|
|
@@ -398,7 +398,11 @@ async function blobStorageCreate(httpRequestContext, componentName, request) {
|
|
|
398
398
|
Guards.object(ROUTES_SOURCE, "request.body", request.body);
|
|
399
399
|
Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
|
|
400
400
|
const component = ComponentFactory.get(componentName);
|
|
401
|
-
const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata,
|
|
401
|
+
const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, {
|
|
402
|
+
disableEncryption: request.body.disableEncryption,
|
|
403
|
+
overrideVaultKeyId: request.body.overrideVaultKeyId,
|
|
404
|
+
namespace: request.body.namespace
|
|
405
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
402
406
|
return {
|
|
403
407
|
statusCode: HttpStatusCode.created,
|
|
404
408
|
headers: {
|
|
@@ -419,7 +423,11 @@ async function blobStorageGet(httpRequestContext, componentName, request) {
|
|
|
419
423
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
420
424
|
const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
|
|
421
425
|
const component = ComponentFactory.get(componentName);
|
|
422
|
-
const result = await component.get(request.pathParams.id,
|
|
426
|
+
const result = await component.get(request.pathParams.id, {
|
|
427
|
+
includeContent: Coerce.boolean(request.query?.includeContent),
|
|
428
|
+
decompress: Coerce.boolean(request.query?.decompress),
|
|
429
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
430
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
423
431
|
return {
|
|
424
432
|
headers: {
|
|
425
433
|
[HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
|
|
@@ -439,16 +447,31 @@ async function blobStorageGetContent(httpRequestContext, componentName, request)
|
|
|
439
447
|
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
440
448
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
441
449
|
const component = ComponentFactory.get(componentName);
|
|
442
|
-
const
|
|
450
|
+
const decompress = Coerce.boolean(request.query?.decompress);
|
|
451
|
+
const result = await component.get(request.pathParams.id, {
|
|
452
|
+
includeContent: true,
|
|
453
|
+
decompress,
|
|
454
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
455
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
443
456
|
const encodingFormat = result?.encodingFormat ?? MimeTypes.OctetStream;
|
|
457
|
+
let compressedEncodingFormat;
|
|
458
|
+
let compressedExtension = "";
|
|
459
|
+
// If the entry is compressed and we are not decompressing
|
|
460
|
+
// we need to override the encoding format to the compressed type
|
|
461
|
+
// and append an additional extension to the filename.
|
|
462
|
+
if (result.compression && !decompress) {
|
|
463
|
+
compressedEncodingFormat =
|
|
464
|
+
result.compression === BlobStorageCompressionType.Gzip ? MimeTypes.Gzip : MimeTypes.Zlib;
|
|
465
|
+
compressedExtension = `.${MimeTypeHelper.defaultExtension(compressedEncodingFormat)}`;
|
|
466
|
+
}
|
|
444
467
|
let filename = request.query?.filename;
|
|
445
468
|
if (!Is.stringValue(filename)) {
|
|
446
|
-
filename = `file.${result.fileExtension ?? MimeTypeHelper.defaultExtension(encodingFormat)}`;
|
|
469
|
+
filename = `file.${result.fileExtension ?? MimeTypeHelper.defaultExtension(encodingFormat)}${compressedExtension}`;
|
|
447
470
|
}
|
|
448
471
|
return {
|
|
449
472
|
body: Is.stringBase64(result.blob) ? Converter.base64ToBytes(result.blob) : new Uint8Array(),
|
|
450
473
|
attachment: {
|
|
451
|
-
mimeType: encodingFormat,
|
|
474
|
+
mimeType: compressedEncodingFormat ?? encodingFormat,
|
|
452
475
|
filename,
|
|
453
476
|
inline: !(request.query?.download ?? false)
|
|
454
477
|
}
|
|
@@ -564,10 +587,10 @@ class BlobStorageService {
|
|
|
564
587
|
}
|
|
565
588
|
this._entryEntityStorage = EntityStorageConnectorFactory.get(options?.entryEntityStorageType ?? "blob-storage-entry");
|
|
566
589
|
if (Is.stringValue(options?.vaultConnectorType)) {
|
|
567
|
-
this._vaultConnector = VaultConnectorFactory.
|
|
590
|
+
this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);
|
|
568
591
|
}
|
|
569
592
|
this._defaultNamespace = options?.config?.defaultNamespace ?? names[0];
|
|
570
|
-
this._vaultKeyId = options?.config?.vaultKeyId
|
|
593
|
+
this._vaultKeyId = options?.config?.vaultKeyId;
|
|
571
594
|
this._includeNodeIdentity = options?.config?.includeNodeIdentity ?? true;
|
|
572
595
|
this._includeUserIdentity = options?.config?.includeUserIdentity ?? true;
|
|
573
596
|
SchemaOrgDataTypes.registerRedirects();
|
|
@@ -578,21 +601,28 @@ class BlobStorageService {
|
|
|
578
601
|
* @param encodingFormat Mime type for the blob, will be detected if left undefined.
|
|
579
602
|
* @param fileExtension Extension for the blob, will be detected if left undefined.
|
|
580
603
|
* @param metadata Data for the custom metadata as JSON-LD.
|
|
581
|
-
* @param
|
|
604
|
+
* @param options Optional options for the creation of the blob.
|
|
605
|
+
* @param options.disableEncryption Disables encryption if enabled by default.
|
|
606
|
+
* @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
|
|
607
|
+
* @param options.compress Optional compression type to use for the blob, defaults to no compression.*
|
|
608
|
+
* @param options.namespace The namespace to use for storing, defaults to component configured namespace.
|
|
582
609
|
* @param userIdentity The user identity to use with storage operations.
|
|
583
610
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
584
611
|
* @returns The id of the stored blob in urn format.
|
|
585
612
|
*/
|
|
586
|
-
async create(blob, encodingFormat, fileExtension, metadata,
|
|
613
|
+
async create(blob, encodingFormat, fileExtension, metadata, options, userIdentity, nodeIdentity) {
|
|
587
614
|
Guards.stringBase64(this.CLASS_NAME, "blob", blob);
|
|
588
615
|
if (this._includeUserIdentity) {
|
|
589
616
|
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
590
617
|
}
|
|
591
|
-
|
|
618
|
+
const disableEncryption = options?.disableEncryption ?? false;
|
|
619
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
620
|
+
const encryptionEnabled = !disableEncryption && Is.stringValue(vaultKeyId);
|
|
621
|
+
if (this._includeNodeIdentity || encryptionEnabled) {
|
|
592
622
|
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
593
623
|
}
|
|
594
624
|
try {
|
|
595
|
-
const connectorNamespace = namespace ?? this._defaultNamespace;
|
|
625
|
+
const connectorNamespace = options?.namespace ?? this._defaultNamespace;
|
|
596
626
|
const blobStorageConnector = BlobStorageConnectorFactory.get(connectorNamespace);
|
|
597
627
|
// Convert the base64 data into bytes
|
|
598
628
|
let storeBlob = Converter.base64ToBytes(blob);
|
|
@@ -612,9 +642,15 @@ class BlobStorageService {
|
|
|
612
642
|
Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
|
|
613
643
|
}
|
|
614
644
|
const blobHash = `sha256:${Converter.bytesToBase64(Sha256.sum256(storeBlob))}`;
|
|
645
|
+
if (!Is.empty(options?.compress)) {
|
|
646
|
+
storeBlob = await Compression.compress(storeBlob, options.compress);
|
|
647
|
+
}
|
|
615
648
|
// If we have a vault connector then encrypt the data.
|
|
616
|
-
if (
|
|
617
|
-
|
|
649
|
+
if (encryptionEnabled) {
|
|
650
|
+
if (Is.empty(this._vaultConnector)) {
|
|
651
|
+
throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
652
|
+
}
|
|
653
|
+
storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, storeBlob);
|
|
618
654
|
}
|
|
619
655
|
// Set the blob in the storage connector, which may now be encrypted
|
|
620
656
|
const blobId = await blobStorageConnector.set(storeBlob);
|
|
@@ -626,7 +662,9 @@ class BlobStorageService {
|
|
|
626
662
|
blobHash,
|
|
627
663
|
encodingFormat,
|
|
628
664
|
fileExtension,
|
|
629
|
-
metadata
|
|
665
|
+
metadata,
|
|
666
|
+
isEncrypted: encryptionEnabled,
|
|
667
|
+
compression: options?.compress
|
|
630
668
|
};
|
|
631
669
|
const conditions = [];
|
|
632
670
|
if (this._includeUserIdentity) {
|
|
@@ -647,14 +685,19 @@ class BlobStorageService {
|
|
|
647
685
|
/**
|
|
648
686
|
* Get the blob entry.
|
|
649
687
|
* @param id The id of the blob to get in urn format.
|
|
650
|
-
* @param
|
|
688
|
+
* @param options Optional options for the retrieval of the blob.
|
|
689
|
+
* @param options.includeContent Include the content, or just get the metadata.
|
|
690
|
+
* @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
|
|
691
|
+
* @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
|
|
651
692
|
* @param userIdentity The user identity to use with storage operations.
|
|
652
693
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
653
694
|
* @returns The entry and data for the blob if it can be found.
|
|
654
695
|
* @throws Not found error if the blob cannot be found.
|
|
655
696
|
*/
|
|
656
|
-
async get(id,
|
|
697
|
+
async get(id, options, userIdentity, nodeIdentity) {
|
|
657
698
|
Urn.guard(this.CLASS_NAME, "id", id);
|
|
699
|
+
const includeContent = options?.includeContent ?? false;
|
|
700
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
658
701
|
const conditions = [];
|
|
659
702
|
if (this._includeUserIdentity) {
|
|
660
703
|
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
@@ -681,9 +724,16 @@ class BlobStorageService {
|
|
|
681
724
|
if (Is.undefined(returnBlob)) {
|
|
682
725
|
throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
|
|
683
726
|
}
|
|
684
|
-
// If
|
|
685
|
-
|
|
686
|
-
|
|
727
|
+
// If the data is encrypted then decrypt it.
|
|
728
|
+
const decryptionEnabled = blobEntry.isEncrypted && Is.stringValue(vaultKeyId);
|
|
729
|
+
if (decryptionEnabled) {
|
|
730
|
+
if (Is.empty(this._vaultConnector)) {
|
|
731
|
+
throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
732
|
+
}
|
|
733
|
+
returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, returnBlob);
|
|
734
|
+
}
|
|
735
|
+
if (!Is.empty(blobEntry.compression) && (options?.decompress ?? true)) {
|
|
736
|
+
returnBlob = await Compression.decompress(returnBlob, blobEntry.compression);
|
|
687
737
|
}
|
|
688
738
|
}
|
|
689
739
|
const jsonLd = this.entryToJsonLd(blobEntry, returnBlob);
|
|
@@ -731,7 +781,9 @@ class BlobStorageService {
|
|
|
731
781
|
blobHash: blobEntry.blobHash,
|
|
732
782
|
encodingFormat: encodingFormat ?? blobEntry.encodingFormat,
|
|
733
783
|
fileExtension: fileExtension ?? blobEntry.fileExtension,
|
|
734
|
-
metadata: metadata ?? blobEntry.metadata
|
|
784
|
+
metadata: metadata ?? blobEntry.metadata,
|
|
785
|
+
isEncrypted: blobEntry.isEncrypted,
|
|
786
|
+
compression: blobEntry.compression
|
|
735
787
|
};
|
|
736
788
|
const conditions = [];
|
|
737
789
|
if (this._includeUserIdentity) {
|
|
@@ -826,10 +878,6 @@ class BlobStorageService {
|
|
|
826
878
|
sortDirection: orderDirection
|
|
827
879
|
}
|
|
828
880
|
], undefined, cursor, pageSize);
|
|
829
|
-
for (const entity of result.entities) {
|
|
830
|
-
ObjectHelper.propertyDelete(entity, "nodeIdentity");
|
|
831
|
-
ObjectHelper.propertyDelete(entity, "userIdentity");
|
|
832
|
-
}
|
|
833
881
|
let context = [
|
|
834
882
|
SchemaOrgContexts.ContextRoot,
|
|
835
883
|
BlobStorageContexts.ContextRoot,
|
|
@@ -913,9 +961,7 @@ class BlobStorageService {
|
|
|
913
961
|
if (Is.empty(entity)) {
|
|
914
962
|
throw new NotFoundError(this.CLASS_NAME, "entityNotFound", id);
|
|
915
963
|
}
|
|
916
|
-
ObjectHelper.
|
|
917
|
-
ObjectHelper.propertyDelete(entity, "userIdentity");
|
|
918
|
-
return entity;
|
|
964
|
+
return ObjectHelper.omit(entity, ["nodeIdentity", "userIdentity"]);
|
|
919
965
|
}
|
|
920
966
|
/**
|
|
921
967
|
* Convert the entry to JSON-LD.
|
|
@@ -940,7 +986,9 @@ class BlobStorageService {
|
|
|
940
986
|
encodingFormat: entry?.encodingFormat,
|
|
941
987
|
fileExtension: entry?.fileExtension,
|
|
942
988
|
metadata: entry?.metadata,
|
|
943
|
-
blob: Is.uint8Array(blob) ? Converter.bytesToBase64(blob) : undefined
|
|
989
|
+
blob: Is.uint8Array(blob) ? Converter.bytesToBase64(blob) : undefined,
|
|
990
|
+
isEncrypted: entry.isEncrypted,
|
|
991
|
+
compression: entry.compression
|
|
944
992
|
};
|
|
945
993
|
return jsonLd;
|
|
946
994
|
}
|
|
@@ -982,6 +1030,14 @@ let BlobStorageEntry = class BlobStorageEntry {
|
|
|
982
1030
|
* The metadata for the blob as JSON-LD.
|
|
983
1031
|
*/
|
|
984
1032
|
metadata;
|
|
1033
|
+
/**
|
|
1034
|
+
* Is the entry encrypted.
|
|
1035
|
+
*/
|
|
1036
|
+
isEncrypted;
|
|
1037
|
+
/**
|
|
1038
|
+
* Is the entry compressed.
|
|
1039
|
+
*/
|
|
1040
|
+
compression;
|
|
985
1041
|
/**
|
|
986
1042
|
* The user identity that created the blob.
|
|
987
1043
|
*/
|
|
@@ -1028,6 +1084,14 @@ __decorate([
|
|
|
1028
1084
|
property({ type: "object", itemTypeRef: "IJsonLdNodeObject", optional: true }),
|
|
1029
1085
|
__metadata("design:type", Object)
|
|
1030
1086
|
], BlobStorageEntry.prototype, "metadata", void 0);
|
|
1087
|
+
__decorate([
|
|
1088
|
+
property({ type: "boolean" }),
|
|
1089
|
+
__metadata("design:type", Boolean)
|
|
1090
|
+
], BlobStorageEntry.prototype, "isEncrypted", void 0);
|
|
1091
|
+
__decorate([
|
|
1092
|
+
property({ type: "string", optional: true }),
|
|
1093
|
+
__metadata("design:type", String)
|
|
1094
|
+
], BlobStorageEntry.prototype, "compression", void 0);
|
|
1031
1095
|
__decorate([
|
|
1032
1096
|
property({ type: "string", optional: true }),
|
|
1033
1097
|
__metadata("design:type", String)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IBlobStorageComponent, type IBlobStorageEntry, type IBlobStorageEntryList } from "@twin.org/blob-storage-models";
|
|
1
|
+
import { type BlobStorageCompressionType, type IBlobStorageComponent, type IBlobStorageEntry, type IBlobStorageEntryList } from "@twin.org/blob-storage-models";
|
|
2
2
|
import { type IJsonLdNodeObject } from "@twin.org/data-json-ld";
|
|
3
3
|
import { SortDirection, type EntityCondition } from "@twin.org/entity";
|
|
4
4
|
import type { IBlobStorageServiceConstructorOptions } from "./models/IBlobStorageServiceConstructorOptions";
|
|
@@ -25,22 +25,38 @@ export declare class BlobStorageService implements IBlobStorageComponent {
|
|
|
25
25
|
* @param encodingFormat Mime type for the blob, will be detected if left undefined.
|
|
26
26
|
* @param fileExtension Extension for the blob, will be detected if left undefined.
|
|
27
27
|
* @param metadata Data for the custom metadata as JSON-LD.
|
|
28
|
-
* @param
|
|
28
|
+
* @param options Optional options for the creation of the blob.
|
|
29
|
+
* @param options.disableEncryption Disables encryption if enabled by default.
|
|
30
|
+
* @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
|
|
31
|
+
* @param options.compress Optional compression type to use for the blob, defaults to no compression.*
|
|
32
|
+
* @param options.namespace The namespace to use for storing, defaults to component configured namespace.
|
|
29
33
|
* @param userIdentity The user identity to use with storage operations.
|
|
30
34
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
31
35
|
* @returns The id of the stored blob in urn format.
|
|
32
36
|
*/
|
|
33
|
-
create(blob: string, encodingFormat?: string, fileExtension?: string, metadata?: IJsonLdNodeObject,
|
|
37
|
+
create(blob: string, encodingFormat?: string, fileExtension?: string, metadata?: IJsonLdNodeObject, options?: {
|
|
38
|
+
disableEncryption?: boolean;
|
|
39
|
+
overrideVaultKeyId?: string;
|
|
40
|
+
compress?: BlobStorageCompressionType;
|
|
41
|
+
namespace?: string;
|
|
42
|
+
}, userIdentity?: string, nodeIdentity?: string): Promise<string>;
|
|
34
43
|
/**
|
|
35
44
|
* Get the blob entry.
|
|
36
45
|
* @param id The id of the blob to get in urn format.
|
|
37
|
-
* @param
|
|
46
|
+
* @param options Optional options for the retrieval of the blob.
|
|
47
|
+
* @param options.includeContent Include the content, or just get the metadata.
|
|
48
|
+
* @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
|
|
49
|
+
* @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
|
|
38
50
|
* @param userIdentity The user identity to use with storage operations.
|
|
39
51
|
* @param nodeIdentity The node identity to use with storage operations.
|
|
40
52
|
* @returns The entry and data for the blob if it can be found.
|
|
41
53
|
* @throws Not found error if the blob cannot be found.
|
|
42
54
|
*/
|
|
43
|
-
get(id: string,
|
|
55
|
+
get(id: string, options?: {
|
|
56
|
+
includeContent?: boolean;
|
|
57
|
+
decompress?: boolean;
|
|
58
|
+
overrideVaultKeyId?: string;
|
|
59
|
+
}, userIdentity?: string, nodeIdentity?: string): Promise<IBlobStorageEntry>;
|
|
44
60
|
/**
|
|
45
61
|
* Update the blob with metadata.
|
|
46
62
|
* @param id The id of the blob entry to update.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BlobStorageCompressionType } from "@twin.org/blob-storage-models";
|
|
1
2
|
import type { IJsonLdNodeObject } from "@twin.org/data-json-ld";
|
|
2
3
|
/**
|
|
3
4
|
* Class representing entry for the blob storage.
|
|
@@ -35,6 +36,14 @@ export declare class BlobStorageEntry {
|
|
|
35
36
|
* The metadata for the blob as JSON-LD.
|
|
36
37
|
*/
|
|
37
38
|
metadata?: IJsonLdNodeObject;
|
|
39
|
+
/**
|
|
40
|
+
* Is the entry encrypted.
|
|
41
|
+
*/
|
|
42
|
+
isEncrypted: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Is the entry compressed.
|
|
45
|
+
*/
|
|
46
|
+
compression?: BlobStorageCompressionType;
|
|
38
47
|
/**
|
|
39
48
|
* The user identity that created the blob.
|
|
40
49
|
*/
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface IBlobStorageServiceConfig {
|
|
5
5
|
/**
|
|
6
|
-
* The name of the vault key to use for encryption if
|
|
7
|
-
* @default blob-storage.
|
|
6
|
+
* The name of the vault key to use for encryption, if not configured no encryption will happen.
|
|
8
7
|
*/
|
|
9
8
|
vaultKeyId?: string;
|
|
10
9
|
/**
|
|
@@ -9,7 +9,7 @@ export interface IBlobStorageServiceConstructorOptions {
|
|
|
9
9
|
*/
|
|
10
10
|
entryEntityStorageType?: string;
|
|
11
11
|
/**
|
|
12
|
-
* The type of the vault connector for encryption
|
|
12
|
+
* The type of the vault connector for encryption.
|
|
13
13
|
*/
|
|
14
14
|
vaultConnectorType?: string;
|
|
15
15
|
/**
|