3

uploading files from strapi to s3 works fine. I am trying to secure the files by using signed urls:

var params = {Bucket:process.env.AWS_BUCKET, Key: `${path}${file.hash}${file.ext}`, Expires: 3000};
      var secretUrl = ''
      S3.getSignedUrl('getObject', params, function (err, url) {
        console.log('Signed URL: ' + url);
        secretUrl = url
      });

      
      S3.upload(
        {
          Key: `${path}${file.hash}${file.ext}`,
          Body: Buffer.from(file.buffer, 'binary'),
          //ACL: 'public-read',
          ContentType: file.mime,
          ...customParams,
        },
        (err, data) => {
          if (err) {
            return reject(err);
          }

          // set the bucket file url
          //file.url = data.Location;
          file.url = secretUrl;
          console.log('FIle URL: ' + file.url);

          resolve();
        }
      );

file.url (secretUrl) contains the correct URL which i can use in browser to retrieve the file. But whenever reading the file form strapi admin panel no file nor tumbnail is shown. I figured out that strapi adds a parameter to the file e.g ?2304.4005 which corrupts the get of the file to AWS. Where and how do I change that behaviour

Help is appreciated

sunwarr10r
  • 4,420
  • 8
  • 54
  • 109
Capsloq
  • 31
  • 2
  • As the strapi provider api doesn't support a `get` method I'm not sure you will be able to solve it that way. https://strapi.io/documentation/developer-docs/latest/plugins/upload.html#create-providers Maybe you could create a new model and update it's controller to return a signed url on request (as the signed url should be available only for a short amount of time). – Alex Feb 08 '21 at 20:49
  • @Alex any news on this topic, could anybody find a solution? – sunwarr10r Mar 03 '21 at 07:31

2 Answers2

1

Here is my solution to create a signed URL to secure your assets. The URL will be valid for a certain amount of time.

  1. Create a collection type with a media field, which you want to secure. In my example the collection type is called invoice and the media field is called document.

  2. Create an S3 bucket

  3. Install and configure strapi-provider-upload-aws-s3 and AWS SDK for JavaScript

  4. Customize the Strapi controller for your invoice endpoint (in this exmaple I use the core controller findOne)

const { sanitizeEntity } = require('strapi-utils');
var S3 = require('aws-sdk/clients/s3');

module.exports = {

  async findOne(ctx) {
    const { id } = ctx.params;

    const entity = await strapi.services.invoice.findOne({ id });
    // key is hashed name + file extension of your entity
    const key = entity.document.hash + entity.document.ext;

    // create signed url
    const s3 = new S3({
        endpoint: 's3.eu-central-1.amazonaws.com',   // s3.region.amazonaws.com
        accessKeyId: '...',       // your accessKeyId
        secretAccessKey: '...',   // your secretAccessKey
        Bucket: '...',         // your bucket name
        signatureVersion: 'v4',
        region: 'eu-central-1'           // your region
    });

    var params = {
        Bucket:'',   // your bucket name
        Key: key, 
        Expires: 20 // expires in 20 seconds
    };

    var url = s3.getSignedUrl('getObject', params);

    entity.document.url = url  // overwrite the url with signed url

    return sanitizeEntity(entity, { model: strapi.models.invoice });
  },

};
sunwarr10r
  • 4,420
  • 8
  • 54
  • 109
1

It seems like although overwriting controllers and lifecycle of the collection models and strapi-plugin-content-manager to take into account the S3 signed urls, one of the Strapi UI components adds a strange hook/refs ?123.123 to the actual url that is received from the backend, resulting in the following error from AWS There were headers present in the request which were not signed when trying to see images from the CMS UI.

Screenshot with the faulty component

After digging the code & node_modules used by Strapi, it seems like you will find the following within strapi-plugin-upload/admin/src/components/CardPreview/index.js


return (
 <Wrapper>
   {isVideo ? (
     <VideoPreview src={url} previewUrl={previewUrl} hasIcon={hasIcon} />
   ) : (
     // Adding performance.now forces the browser no to cache the img
     // https://stackoverflow.com/questions/126772/how-to-force-a-web-browser-not-to-cache-images
     <Image src={`${url}${withFileCaching ? `?${cacheRef.current}` : ''}`} />
   )}
 </Wrapper>
);
};

CardPreview.defaultProps = {
extension: null,
hasError: false,
hasIcon: false,
previewUrl: null,
url: null,
type: '',
withFileCaching: true,
};

The default is set to true for withFileCaching, which therefore appends the const cacheRef = useRef(performance.now()); query param to the url for avoiding browser caches.

By setting it to false, or leaving just <Image src={url} /> should solve the issue of the extra query param and allow you to use S3 signed URLs previews also from Strapi UI.

This would also translate to use the docs https://strapi.io/documentation/developer-docs/latest/development/plugin-customization.html to customize the module strapi-plugin-upload in your /extensions/strapi-plugin-upload/...