jamhall/s3rver

Support actions via presigned urls

Closed this issue · 3 comments

You can create presignedUrls with s3.getSignedUrl method.

A url looks like this: http://localhost:3001/file-uploads-temporary/de456c2c-0f93-4df7-ad82-0c2943e447f0.jpeg?AWSAccessKeyId=123&Content-Type=image%2Fjpeg&Expires=1519727198&Signature=L0RMhVZCyfrp37sDZbVfSLCuOSU%3D&x-amz-acl=private

Currently the metadata (aka Content-Type Content-Type=image%2Fjpeg) is not added to the .dummys3_metadata file.

I would suggest that we check for the Content-Type param and if present simply use that as the value for the objects content-type.

The current workaround is to add the header to your request e.g.

"use strict";
const fetch = require("node-fetch");
const S3 = require("aws-sdk");

const s3 = new S3(config);

const s3Params = {
  Bucket: "some-bucket-name",
  Key: "my-file.jpeg",
  ContentType: "image/jpeg",
  ACL: "private"
};

s3
  .getSignedUrl("putObject", s3Params)
  .promise()
  .then(uploadUrl => {
    const readStream = fs.createReadStream("my-local-image.jpeg");
    return fetch(uploadUrl, {
      method: `PUT`,
      body: readStream,
      // On AWS you do not have to specify this. The Header is taken from the presigned url
      headers: {
        "Content-Type": "image/jpeg"
      }
    });
  });

What do you think? @specialkk @leontastic?

If you agree I would come up with a PR for this.

This just means that we need to support options and metadata specified through query parameters instead of headers right? What happens to metadata specified through HTTP headers? I'm guessing that normally S3 responds with a 403 because the signature won't match, but we don't do signing yet.

Hey, I finally had some time to research (and look into implementing) this, but I think the specific example you gave with Content-Type doesn't actually work due to how request signing works. Omitting custom metadata works fine, but traditional headers (like Content-Type or Content-Disposition) must be specified as headers or else S3 will throw SignatureDoesNotMatch.

Anyway, if this sounds incorrect let me know. My plan is do perform faux-signature matching by ensuring that the metadata specified in query params match those specified in headers, with the exception of omitted x-amz-* headers.

This is actually more nuanced than I expected. So far I've determined the following behavior (most of it can be derived from here):

For a field in the query params starting with x-amz-*

  • use the value specified in the query params, ignore matching fields specified in the request headers

For a field in the query params or request headers not starting with x-amz-* (except for Content-Type and Content-MD5)

  • (interestingly) completely ignore them in signature verification
    • AWS doesn't include these headers when calculating the signature since adding/removing these fields from both query params and headers has no effect on the validity of the signature

For Content-MD5 and Content-Type in query params

  • values are ignored, AWS will only consider the values specified in request headers
  • reports SignatureDoesNotMatch if there isn't an exact match in the request headers
    • This includes omitting the header if the signature is calculated without Content-Type header

For a field in the request headers starting with x-amz-*

  • only report a mismatch if it was present in the signed canonical request but not included as a query param
  • ignore the value if the field is present as a query param

Once this is done I'll probably open a new issue tracking proper verification of request signatures.