diff --git a/packages/providers/upload-aws-s3/lib/__tests__/getBucketFromUrl.test.js b/packages/providers/upload-aws-s3/lib/__tests__/getBucketFromUrl.test.js new file mode 100644 index 0000000000..7b253babf1 --- /dev/null +++ b/packages/providers/upload-aws-s3/lib/__tests__/getBucketFromUrl.test.js @@ -0,0 +1,43 @@ +'use strict'; + +const { getBucketFromUrl } = require('../utils'); + +describe('Test for URLs', () => { + test('Virtual hosted style', async () => { + const url = 'https://bucket.s3.us-east-1.amazonaws.com/img.png'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); + + describe('Path style', () => { + test('No key', async () => { + const url = 'https://s3.us-east-1.amazonaws.com/bucket'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); + + test('With trailing slash', async () => { + const url = 'https://s3.us-east-1.amazonaws.com/bucket/'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); + + test('With key', async () => { + const url = 'https://s3.us-east-1.amazonaws.com/bucket/img.png'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); + }); + + test('S3 access point', async () => { + const url = 'https://bucket.s3-accesspoint.us-east-1.amazonaws.com'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); + + test('S3://', async () => { + const url = 'S3://bucket/img.png'; + const { bucket } = getBucketFromUrl(url); + expect(bucket).toEqual('bucket'); + }); +}); diff --git a/packages/providers/upload-aws-s3/lib/index.js b/packages/providers/upload-aws-s3/lib/index.js index 524ce01abd..331b15c10b 100644 --- a/packages/providers/upload-aws-s3/lib/index.js +++ b/packages/providers/upload-aws-s3/lib/index.js @@ -8,6 +8,7 @@ // Public node modules. const get = require('lodash/get'); const AWS = require('aws-sdk'); +const { getBucketFromUrl } = require('./utils'); function assertUrlProtocol(url) { // Regex to test protocol like "http://", "https://" @@ -66,6 +67,12 @@ module.exports = { * @returns {Promise<{url: string}>} */ getSignedUrl(file, customParams = {}) { + // Do not sign the url if it does not come from the same bucket. + const { bucket } = getBucketFromUrl(file.url); + if (bucket !== config.params.Bucket) { + return { url: file.url }; + } + return new Promise((resolve, reject) => { const path = file.path ? `${file.path}/` : ''; const fileKey = `${path}${file.hash}${file.ext}`; diff --git a/packages/providers/upload-aws-s3/lib/utils.js b/packages/providers/upload-aws-s3/lib/utils.js new file mode 100644 index 0000000000..b59ff48a0d --- /dev/null +++ b/packages/providers/upload-aws-s3/lib/utils.js @@ -0,0 +1,63 @@ +'use strict'; + +const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./; + +/** + * Parse the bucket name from a URL. + * See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + * + * @param {string} fileUrl - the URL to parse + * @returns {object} result + * @returns {string} result.bucket - the bucket name + * @returns {string} result.error - if any + */ +function getBucketFromUrl(fileUrl) { + const uri = new URL(fileUrl); + + // S3:/// + if (uri.protocol === 's3:') { + const bucket = uri.host; + + if (!bucket) { + return { err: `Invalid S3 URI: no bucket: ${uri}` }; + } + return { bucket }; + } + + if (!uri.host) { + return { err: `Invalid S3 URI: no hostname: ${uri}` }; + } + + const matches = uri.host.match(ENDPOINT_PATTERN); + if (!matches) { + return { err: `Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: ${uri}` }; + } + + const prefix = matches[1]; + // https://s3.amazonaws.com/ + if (!prefix) { + if (uri.pathname === '/') { + return { bucket: null }; + } + + const index = uri.pathname.indexOf('/', 1); + + // https://s3.amazonaws.com/ + if (index === -1) { + return { bucket: uri.pathname.substring(1) }; + } + + // https://s3.amazonaws.com// + if (index === uri.pathname.length - 1) { + return { bucket: uri.pathname.substring(1, index) }; + } + + // https://s3.amazonaws.com//key + return { bucket: uri.pathname.substring(1, index) }; + } + + // https://.s3.amazonaws.com/ + return { bucket: prefix.substring(0, prefix.length - 1) }; +} + +module.exports = { getBucketFromUrl };