@contember/engine-s3-plugin 2.1.0-rc.1 → 2.1.0-rc.3

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.
@@ -70,12 +70,13 @@ class S3Service {
70
70
  if (objectKey.startsWith(publicPrefix)) {
71
71
  objectKey = objectKey.substring(publicPrefix.length);
72
72
  }
73
- if (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {
73
+ const prefix = this.config.prefix ? this.config.prefix + "/" : "";
74
+ if (prefix && !objectKey.startsWith(prefix)) {
74
75
  throw new graphqlUtils.ForbiddenError(
75
76
  `Given object key "${objectKey}" does not start with a project prefix "${this.config.prefix}"`
76
77
  );
77
78
  }
78
- const localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey;
79
+ const localObjectKey = objectKey.substring(prefix.length);
79
80
  this.authorizator.verifyReadAccess({ key: localObjectKey });
80
81
  const url = this.signer.sign({
81
82
  action: "read",
@@ -1 +1 @@
1
- {"version":3,"file":"S3Service.cjs","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\tif (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["resolveS3PublicBaseUrl","S3Signer","resolveExtension","UserInputError","ForbiddenError"],"mappings":"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"}
1
+ {"version":3,"file":"S3Service.cjs","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\t// the separator is a part of the prefix: without it, a project prefixed \"blog\" reaches into \"blog-staging\" in a shared bucket\n\t\tconst prefix = this.config.prefix ? this.config.prefix + '/' : ''\n\t\tif (prefix && !objectKey.startsWith(prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = objectKey.substring(prefix.length)\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["resolveS3PublicBaseUrl","S3Signer","resolveExtension","UserInputError","ForbiddenError"],"mappings":"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"}
@@ -68,12 +68,13 @@ class S3Service {
68
68
  if (objectKey.startsWith(publicPrefix)) {
69
69
  objectKey = objectKey.substring(publicPrefix.length);
70
70
  }
71
- if (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {
71
+ const prefix = this.config.prefix ? this.config.prefix + "/" : "";
72
+ if (prefix && !objectKey.startsWith(prefix)) {
72
73
  throw new ForbiddenError(
73
74
  `Given object key "${objectKey}" does not start with a project prefix "${this.config.prefix}"`
74
75
  );
75
76
  }
76
- const localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey;
77
+ const localObjectKey = objectKey.substring(prefix.length);
77
78
  this.authorizator.verifyReadAccess({ key: localObjectKey });
78
79
  const url = this.signer.sign({
79
80
  action: "read",
@@ -1 +1 @@
1
- {"version":3,"file":"S3Service.js","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\tif (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["extension","resolveExtension"],"mappings":"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"}
1
+ {"version":3,"file":"S3Service.js","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\t// the separator is a part of the prefix: without it, a project prefixed \"blog\" reaches into \"blog-staging\" in a shared bucket\n\t\tconst prefix = this.config.prefix ? this.config.prefix + '/' : ''\n\t\tif (prefix && !objectKey.startsWith(prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = objectKey.substring(prefix.length)\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["extension","resolveExtension"],"mappings":"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"}
@@ -70,12 +70,13 @@ class S3Service {
70
70
  if (objectKey.startsWith(publicPrefix)) {
71
71
  objectKey = objectKey.substring(publicPrefix.length);
72
72
  }
73
- if (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {
73
+ const prefix = this.config.prefix ? this.config.prefix + "/" : "";
74
+ if (prefix && !objectKey.startsWith(prefix)) {
74
75
  throw new graphqlUtils.ForbiddenError(
75
76
  `Given object key "${objectKey}" does not start with a project prefix "${this.config.prefix}"`
76
77
  );
77
78
  }
78
- const localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey;
79
+ const localObjectKey = objectKey.substring(prefix.length);
79
80
  this.authorizator.verifyReadAccess({ key: localObjectKey });
80
81
  const url = this.signer.sign({
81
82
  action: "read",
@@ -1 +1 @@
1
- {"version":3,"file":"S3Service.cjs","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\tif (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["resolveS3PublicBaseUrl","S3Signer","resolveExtension","UserInputError","ForbiddenError"],"mappings":";;;;;;;;;AAyBO,MAAM,UAAU;AAAA,EAKtB,YACiB,QACC,WACA,cAChB;AAHe,SAAA,SAAA;AACC,SAAA,YAAA;AACA,SAAA,eAAA;AAPlB,kBAAA,MAAiB,eAAA;AAEjB,kBAAA,MAAiB,QAAA;AAOhB,SAAK,gBAAgBA,OAAAA,uBAAuB,MAAM;AAClD,SAAK,SAAS,IAAIC,kBAAS,QAAQ,SAAS;AAAA,EAC7C;AAAA,EAEO,mBACN,EAAE,KAAK,oBAAoB,aAAa,YAAY,QAAQ,QAAQ,UAAU,WAAW,KAAA,GACvE;AAClB,UAAM,MAAM,eAAe,cAAcC,UAAAA,UAAiB,WAAW,IAAI,SAAS;AAClF,UAAM,KAAK,KAAK,UAAU,KAAA;AAC1B,UAAM,kBAAkB,SAAS,SAAS,MAAM,MAAM,GAAG,EAAE,GAAG,UAAU,EAAE,IAAI,GAAG;AACjF,SAAK,aAAa,mBAAmB,EAAE,KAAK,gBAAgB,MAAM;AAElE,UAAM,aAAa,KAAK,OAAO,SAAS,KAAK,OAAO,SAAS,MAAM,MAAM;AAEzE,UAAM,SAAS,KAAK,OAAO;AAC3B,QAAI,OAAO,KAAK,OAAO,OAAO;AAC7B,YAAM,IAAIC,aAAAA,eAAe,sBAAsB;AAAA,IAChD;AAEA,UAAM,UAAkC;AAAA,MACvC,iBAAiB;AAAA,IAAA;AAElB,QAAI,aAAa;AAChB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI,CAAC,KAAK,OAAO,SAAS,QAAQ,QAAQ;AACzC,YAAM,UAAU,EAAE,aAAa,eAAe,SAAS,UAAA;AACvD,cAAQ,WAAW,IAAI,QAAQ,OAAO,aAAa;AAAA,IACpD;AACA,QAAI,YAAY,oBAAoB;AACnC,UAAI,2BAA2B,oBAAoB,YAAA,KAAiB;AACpE,UAAI,UAAU;AACb,oCAA4B,sBAAsB,mBAAmB,QAAQ,CAAC;AAAA,MAC/E;AACA,cAAQ,qBAAqB,IAAI;AAAA,IAClC;AACA,UAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,QAAQ;AAGpF,QAAI,MAAM;AACT,cAAQ,gBAAgB,IAAI,OAAO,IAAI;AAAA,IACxC;AAEA,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,EAAE,GAAG,QAAA;AAAA,IAAQ,CACtB;AAED,UAAM,YAAY,KAAK,gBAAgB,SAAS;AAChD,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,iBAAiB,EAAE,WAAW,cAA+E;AACnH,UAAM,SAAS,KAAK,OAAO;AAE3B,UAAM,eAAe,KAAK,gBAAgB,EAAE;AAC5C,QAAI,UAAU,WAAW,YAAY,GAAG;AACvC,kBAAY,UAAU,UAAU,aAAa,MAAM;AAAA,IACpD;AACA,QAAI,KAAK,OAAO,UAAU,CAAC,UAAU,WAAW,KAAK,OAAO,MAAM,GAAG;AACpE,YAAM,IAAIC,aAAAA;AAAAA,QACT,qBAAqB,SAAS,2CAA2C,KAAK,OAAO,MAAM;AAAA,MAAA;AAAA,IAE7F;AACA,UAAM,iBAAiB,KAAK,OAAO,SAAS,UAAU,UAAU,KAAK,OAAO,OAAO,SAAS,CAAC,IAAI;AACjG,SAAK,aAAa,iBAAiB,EAAE,KAAK,gBAAgB;AAE1D,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,CAAA;AAAA,IAAC,CACV;AAED,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,CAAA;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,gBAAgB,KAAqB;AAC3C,WAAO,GAAG,KAAK,aAAa,IAAI,GAAG;AAAA,EACpC;AACD;AAEO,MAAM,iBAAiB;AAAA,EACtB,OAAO,QAAkB,WAAsB,cAAoC;AACzF,WAAO,IAAI,UAAU,QAAQ,WAAW,YAAY;AAAA,EACrD;AACD;;;"}
1
+ {"version":3,"file":"S3Service.cjs","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\t// the separator is a part of the prefix: without it, a project prefixed \"blog\" reaches into \"blog-staging\" in a shared bucket\n\t\tconst prefix = this.config.prefix ? this.config.prefix + '/' : ''\n\t\tif (prefix && !objectKey.startsWith(prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = objectKey.substring(prefix.length)\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["resolveS3PublicBaseUrl","S3Signer","resolveExtension","UserInputError","ForbiddenError"],"mappings":";;;;;;;;;AAyBO,MAAM,UAAU;AAAA,EAKtB,YACiB,QACC,WACA,cAChB;AAHe,SAAA,SAAA;AACC,SAAA,YAAA;AACA,SAAA,eAAA;AAPlB,kBAAA,MAAiB,eAAA;AAEjB,kBAAA,MAAiB,QAAA;AAOhB,SAAK,gBAAgBA,OAAAA,uBAAuB,MAAM;AAClD,SAAK,SAAS,IAAIC,kBAAS,QAAQ,SAAS;AAAA,EAC7C;AAAA,EAEO,mBACN,EAAE,KAAK,oBAAoB,aAAa,YAAY,QAAQ,QAAQ,UAAU,WAAW,KAAA,GACvE;AAClB,UAAM,MAAM,eAAe,cAAcC,UAAAA,UAAiB,WAAW,IAAI,SAAS;AAClF,UAAM,KAAK,KAAK,UAAU,KAAA;AAC1B,UAAM,kBAAkB,SAAS,SAAS,MAAM,MAAM,GAAG,EAAE,GAAG,UAAU,EAAE,IAAI,GAAG;AACjF,SAAK,aAAa,mBAAmB,EAAE,KAAK,gBAAgB,MAAM;AAElE,UAAM,aAAa,KAAK,OAAO,SAAS,KAAK,OAAO,SAAS,MAAM,MAAM;AAEzE,UAAM,SAAS,KAAK,OAAO;AAC3B,QAAI,OAAO,KAAK,OAAO,OAAO;AAC7B,YAAM,IAAIC,aAAAA,eAAe,sBAAsB;AAAA,IAChD;AAEA,UAAM,UAAkC;AAAA,MACvC,iBAAiB;AAAA,IAAA;AAElB,QAAI,aAAa;AAChB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI,CAAC,KAAK,OAAO,SAAS,QAAQ,QAAQ;AACzC,YAAM,UAAU,EAAE,aAAa,eAAe,SAAS,UAAA;AACvD,cAAQ,WAAW,IAAI,QAAQ,OAAO,aAAa;AAAA,IACpD;AACA,QAAI,YAAY,oBAAoB;AACnC,UAAI,2BAA2B,oBAAoB,YAAA,KAAiB;AACpE,UAAI,UAAU;AACb,oCAA4B,sBAAsB,mBAAmB,QAAQ,CAAC;AAAA,MAC/E;AACA,cAAQ,qBAAqB,IAAI;AAAA,IAClC;AACA,UAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,QAAQ;AAGpF,QAAI,MAAM;AACT,cAAQ,gBAAgB,IAAI,OAAO,IAAI;AAAA,IACxC;AAEA,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,EAAE,GAAG,QAAA;AAAA,IAAQ,CACtB;AAED,UAAM,YAAY,KAAK,gBAAgB,SAAS;AAChD,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,iBAAiB,EAAE,WAAW,cAA+E;AACnH,UAAM,SAAS,KAAK,OAAO;AAE3B,UAAM,eAAe,KAAK,gBAAgB,EAAE;AAC5C,QAAI,UAAU,WAAW,YAAY,GAAG;AACvC,kBAAY,UAAU,UAAU,aAAa,MAAM;AAAA,IACpD;AAEA,UAAM,SAAS,KAAK,OAAO,SAAS,KAAK,OAAO,SAAS,MAAM;AAC/D,QAAI,UAAU,CAAC,UAAU,WAAW,MAAM,GAAG;AAC5C,YAAM,IAAIC,aAAAA;AAAAA,QACT,qBAAqB,SAAS,2CAA2C,KAAK,OAAO,MAAM;AAAA,MAAA;AAAA,IAE7F;AACA,UAAM,iBAAiB,UAAU,UAAU,OAAO,MAAM;AACxD,SAAK,aAAa,iBAAiB,EAAE,KAAK,gBAAgB;AAE1D,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,CAAA;AAAA,IAAC,CACV;AAED,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,CAAA;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,gBAAgB,KAAqB;AAC3C,WAAO,GAAG,KAAK,aAAa,IAAI,GAAG;AAAA,EACpC;AACD;AAEO,MAAM,iBAAiB;AAAA,EACtB,OAAO,QAAkB,WAAsB,cAAoC;AACzF,WAAO,IAAI,UAAU,QAAQ,WAAW,YAAY;AAAA,EACrD;AACD;;;"}
@@ -68,12 +68,13 @@ class S3Service {
68
68
  if (objectKey.startsWith(publicPrefix)) {
69
69
  objectKey = objectKey.substring(publicPrefix.length);
70
70
  }
71
- if (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {
71
+ const prefix = this.config.prefix ? this.config.prefix + "/" : "";
72
+ if (prefix && !objectKey.startsWith(prefix)) {
72
73
  throw new ForbiddenError(
73
74
  `Given object key "${objectKey}" does not start with a project prefix "${this.config.prefix}"`
74
75
  );
75
76
  }
76
- const localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey;
77
+ const localObjectKey = objectKey.substring(prefix.length);
77
78
  this.authorizator.verifyReadAccess({ key: localObjectKey });
78
79
  const url = this.signer.sign({
79
80
  action: "read",
@@ -1 +1 @@
1
- {"version":3,"file":"S3Service.js","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\tif (this.config.prefix && !objectKey.startsWith(this.config.prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = this.config.prefix ? objectKey.substring(this.config.prefix.length + 1) : objectKey\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["extension","resolveExtension"],"mappings":";;;;;;;AAyBO,MAAM,UAAU;AAAA,EAKtB,YACiB,QACC,WACA,cAChB;AAHe,SAAA,SAAA;AACC,SAAA,YAAA;AACA,SAAA,eAAA;AAPlB,kBAAA,MAAiB,eAAA;AAEjB,kBAAA,MAAiB,QAAA;AAOhB,SAAK,gBAAgB,uBAAuB,MAAM;AAClD,SAAK,SAAS,IAAI,SAAS,QAAQ,SAAS;AAAA,EAC7C;AAAA,EAEO,mBACN,EAAE,KAAK,oBAAoB,aAAa,YAAY,QAAQ,QAAQ,qBAAUA,aAAW,KAAA,GACvE;AAClB,UAAM,MAAMA,iBAAe,cAAcC,UAAiB,WAAW,IAAI,SAAS;AAClF,UAAM,KAAK,KAAK,UAAU,KAAA;AAC1B,UAAM,kBAAkB,SAAS,SAAS,MAAM,MAAM,GAAG,EAAE,GAAG,UAAU,EAAE,IAAI,GAAG;AACjF,SAAK,aAAa,mBAAmB,EAAE,KAAK,gBAAgB,MAAM;AAElE,UAAM,aAAa,KAAK,OAAO,SAAS,KAAK,OAAO,SAAS,MAAM,MAAM;AAEzE,UAAM,SAAS,KAAK,OAAO;AAC3B,QAAI,OAAO,KAAK,OAAO,OAAO;AAC7B,YAAM,IAAI,eAAe,sBAAsB;AAAA,IAChD;AAEA,UAAM,UAAkC;AAAA,MACvC,iBAAiB;AAAA,IAAA;AAElB,QAAI,aAAa;AAChB,cAAQ,cAAc,IAAI;AAAA,IAC3B;AAEA,QAAI,CAAC,KAAK,OAAO,SAAS,QAAQ,QAAQ;AACzC,YAAM,UAAU,EAAE,aAAa,eAAe,SAAS,UAAA;AACvD,cAAQ,WAAW,IAAI,QAAQ,OAAO,aAAa;AAAA,IACpD;AACA,QAAI,YAAY,oBAAoB;AACnC,UAAI,2BAA2B,oBAAoB,YAAA,KAAiB;AACpE,UAAI,UAAU;AACb,oCAA4B,sBAAsB,mBAAmB,QAAQ,CAAC;AAAA,MAC/E;AACA,cAAQ,qBAAqB,IAAI;AAAA,IAClC;AACA,UAAM,gBAAgB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,QAAQ;AAGpF,QAAI,MAAM;AACT,cAAQ,gBAAgB,IAAI,OAAO,IAAI;AAAA,IACxC;AAEA,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,EAAE,GAAG,QAAA;AAAA,IAAQ,CACtB;AAED,UAAM,YAAY,KAAK,gBAAgB,SAAS;AAChD,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,iBAAiB,EAAE,WAAW,cAA+E;AACnH,UAAM,SAAS,KAAK,OAAO;AAE3B,UAAM,eAAe,KAAK,gBAAgB,EAAE;AAC5C,QAAI,UAAU,WAAW,YAAY,GAAG;AACvC,kBAAY,UAAU,UAAU,aAAa,MAAM;AAAA,IACpD;AACA,QAAI,KAAK,OAAO,UAAU,CAAC,UAAU,WAAW,KAAK,OAAO,MAAM,GAAG;AACpE,YAAM,IAAI;AAAA,QACT,qBAAqB,SAAS,2CAA2C,KAAK,OAAO,MAAM;AAAA,MAAA;AAAA,IAE7F;AACA,UAAM,iBAAiB,KAAK,OAAO,SAAS,UAAU,UAAU,KAAK,OAAO,OAAO,SAAS,CAAC,IAAI;AACjG,SAAK,aAAa,iBAAiB,EAAE,KAAK,gBAAgB;AAE1D,UAAM,MAAM,KAAK,OAAO,KAAK;AAAA,MAC5B,QAAQ;AAAA,MACR,YAAY,cAAc;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,CAAA;AAAA,IAAC,CACV;AAED,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,CAAA;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EAEV;AAAA,EAEO,gBAAgB,KAAqB;AAC3C,WAAO,GAAG,KAAK,aAAa,IAAI,GAAG;AAAA,EACpC;AACD;AAEO,MAAM,iBAAiB;AAAA,EACtB,OAAO,QAAkB,WAAsB,cAAoC;AACzF,WAAO,IAAI,UAAU,QAAQ,WAAW,YAAY;AAAA,EACrD;AACD;"}
1
+ {"version":3,"file":"S3Service.js","sources":["../../../../packages/engine-s3-plugin/src/S3Service.ts"],"sourcesContent":["import { extension as resolveExtension } from 'mime-types'\nimport { resolveS3PublicBaseUrl, S3Config } from './Config'\nimport { ForbiddenError, UserInputError } from '@contember/graphql-utils'\nimport { Providers } from '@contember/engine-plugins'\nimport { S3Signer } from './S3Signer'\nimport { S3GenerateSignedUploadInput } from './S3SchemaTypes'\nimport { S3ObjectAuthorizator } from './S3ObjectAuthorizator'\n\ntype SignedReadUrl = {\n\turl: string\n\tbucket: string\n\tobjectKey: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\ntype SignedUploadUrl = {\n\tobjectKey: string\n\turl: string\n\tbucket: string\n\tpublicUrl: string\n\theaders: { key: string; value: string }[]\n\tmethod: string\n}\n\nexport class S3Service {\n\tprivate readonly publicBaseUrl: string\n\n\tprivate readonly signer: S3Signer\n\n\tconstructor(\n\t\tpublic readonly config: S3Config,\n\t\tprivate readonly providers: Pick<Providers, 'uuid' | 'now'>,\n\t\tprivate readonly authorizator: S3ObjectAuthorizator,\n\t) {\n\t\tthis.publicBaseUrl = resolveS3PublicBaseUrl(config)\n\t\tthis.signer = new S3Signer(config, providers)\n\t}\n\n\tpublic getSignedUploadUrl(\n\t\t{ acl, contentDisposition, contentType, expiration, prefix, suffix, fileName, extension, size }: S3GenerateSignedUploadInput,\n\t): SignedUploadUrl {\n\t\tconst ext = extension ?? ((contentType ? resolveExtension(contentType) : null) || 'bin')\n\t\tconst id = this.providers.uuid()\n\t\tconst localObjectKey = (prefix ? prefix + '/' : '') + `${id}${suffix ?? ''}.${ext}`\n\t\tthis.authorizator.verifyUploadAccess({ key: localObjectKey, size })\n\n\t\tconst objectKey = (this.config.prefix ? this.config.prefix + '/' : '') + localObjectKey\n\n\t\tconst bucket = this.config.bucket\n\t\tif (acl && this.config.noAcl) {\n\t\t\tthrow new UserInputError('ACL is not supported')\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Cache-Control': 'immutable',\n\t\t}\n\t\tif (contentType) {\n\t\t\theaders['Content-Type'] = contentType\n\t\t}\n\n\t\tif (!this.config.noAcl && acl !== 'NONE') {\n\t\t\tconst mapping = { PUBLIC_READ: 'public-read', PRIVATE: 'private' }\n\t\t\theaders['x-amz-acl'] = mapping[acl ?? 'PUBLIC_READ']\n\t\t}\n\t\tif (fileName || contentDisposition) {\n\t\t\tlet contentDispositionHeader = contentDisposition?.toLowerCase() ?? 'inline'\n\t\t\tif (fileName) {\n\t\t\t\tcontentDispositionHeader += `; filename*=UTF-8''${encodeURIComponent(fileName)}`\n\t\t\t}\n\t\t\theaders['Content-Disposition'] = contentDispositionHeader\n\t\t}\n\t\tconst headersToSend = Object.entries(headers).map(([key, value]) => ({ key, value }))\n\n\t\t// intentionally not sending following headers\n\t\tif (size) {\n\t\t\theaders['Content-Length'] = String(size)\n\t\t}\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'upload',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: { ...headers },\n\t\t})\n\n\t\tconst publicUrl = this.formatPublicUrl(objectKey)\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\tpublicUrl,\n\t\t\theaders: headersToSend,\n\t\t\tmethod: 'PUT',\n\t\t}\n\t}\n\n\tpublic getSignedReadUrl({ objectKey, expiration }: { objectKey: string; expiration: number | null }): SignedReadUrl {\n\t\tconst bucket = this.config.bucket\n\n\t\tconst publicPrefix = this.formatPublicUrl('')\n\t\tif (objectKey.startsWith(publicPrefix)) {\n\t\t\tobjectKey = objectKey.substring(publicPrefix.length)\n\t\t}\n\t\t// the separator is a part of the prefix: without it, a project prefixed \"blog\" reaches into \"blog-staging\" in a shared bucket\n\t\tconst prefix = this.config.prefix ? this.config.prefix + '/' : ''\n\t\tif (prefix && !objectKey.startsWith(prefix)) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t`Given object key \"${objectKey}\" does not start with a project prefix \"${this.config.prefix}\"`,\n\t\t\t)\n\t\t}\n\t\tconst localObjectKey = objectKey.substring(prefix.length)\n\t\tthis.authorizator.verifyReadAccess({ key: localObjectKey })\n\n\t\tconst url = this.signer.sign({\n\t\t\taction: 'read',\n\t\t\texpiration: expiration ?? 3600,\n\t\t\tkey: objectKey,\n\t\t\theaders: {},\n\t\t})\n\n\t\treturn {\n\t\t\tbucket,\n\t\t\tobjectKey,\n\t\t\turl,\n\t\t\theaders: [],\n\t\t\tmethod: 'GET',\n\t\t}\n\t}\n\n\tpublic formatPublicUrl(key: string): string {\n\t\treturn `${this.publicBaseUrl}/${key}`\n\t}\n}\n\nexport class S3ServiceFactory {\n\tpublic create(config: S3Config, providers: Providers, authorizator: S3ObjectAuthorizator) {\n\t\treturn new S3Service(config, providers, authorizator)\n\t}\n}\n"],"names":["extension","resolveExtension"],"mappings":"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"}