DEV Community

loading...

FIXED: Upload to DigitalOcean Spaces with AWS S3 getSignedUrl with correct permissions and Content Type

R☭
Development anarchy ↙↙↙ http://pronoun.is/he
・2 min read

Solution:

What i actually had to do, was allow for custom headers to be set in the put request. This was done through the DigitalOcean interface in the CORS settings for your spaces.

I am trying to upload assets through the getSignedUrl method that the aws-sdk provides, a NodeJS backend with Axios where the upload happens from a VueJS 2 frontend. Now whatever combination i try, or the file is able to upload but always sets it to private and the wrong Content-Type, or i get 403 permission errors.

If i leave out all the ACL and ContentType stuff, the file is displayed on the Spaces interface as being private.

This is how it should be shown, and what i get when i upload manually through the interface:

Alt Text

What i want to do is that the upload has an ACL that is public-read and the correct Content-Type.

The following is the code that i use:

To get a signed Url

const s3 = new AWS.S3({
    accessKeyId: keys.spacesAccessKeyId,
    secretAccessKey: keys.spacesSecretAccessKey,
    signatureVersion: 'v4',
    endpoint: 'https://REGIO.digitaloceanspaces.com',
    region: 'REGIO'
});

app.get('/api/upload/image', (req, res) => {
        const type = req.query.type;

        s3.getSignedUrl('putObject', {
            Bucket: 'BUCKET',
            ContentType: type,
            ACL: 'public-read',
            Key: 'random-key'
        }, (error, url) => {
            if (error) {
                console.log(error);
            }
            console.log('KEY:', key);
            console.log('URL:', url);
            res.send({ key, url });
        });
    });
Enter fullscreen mode Exit fullscreen mode

This provides a signed url that looks like:

https://BUCKET.REGION.digitaloceanspaces.com/images/e173ca50-fafe-11e9-b18c-d3cfd4814069.png?Content-Type=image%2Fpng&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=NSX3EZOFXE57BAFSKDIL%2F20191030%2Ffra1%2Fs3%2Faws4_request&X-Amz-Date=20191030T102022Z&X-Amz-Expires=900&X-Amz-Signature=f6bcdcd35da5dc237f881a714ec9ca09cb7aa3f3ffbd2389648a7c30808ab56e&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read
Enter fullscreen mode Exit fullscreen mode

Then for actually uploading the file i use the following:

await UploadService.uploadImage(uploadConfig.data.url, file, {
          'x-amz-acl': 'public-read',
          'Content-Type': file.type
      });
Enter fullscreen mode Exit fullscreen mode

With the service looking like:

  async uploadImage (url, file, headers) {
    await axios.put(url, file, headers.headers);
  }
Enter fullscreen mode Exit fullscreen mode

disclaimer: this works with S3 itself as you can set the permissions on a per bucket level so you don't have to worry about it. But Spaces providers a CDN as well and is just generally cheaper. :)

Discussion (1)

Collapse
nickfoden profile image
Nick Foden

LIFESAVER! Thank you thank you for posting this! Was going round and round with different ways to upload and losing my mind. Didn't realized had to put the ACL public headers both when generating url and also when uploading.

Working well for me now when deployed.

Now if I could just figure out how to get around the Digital Ocean Cors config when on localhost . .