jamhall/s3rver

putObject with signed URL gets SignatureDoesNotMatch response

Closed this issue · 1 comments

Hello, I'm using serverless-s3-local (which uses this library underneath) to create an S3 signed URL for uploading from the browser. Here's a snippet of my backend code:

const initializeS3 = () => {
  const isDevMode = process.env.NODE_ENV === 'dev';
  const bucketUrl = isDevMode ? process.env.S3_LOCAL_BUCKET_URL : process.env.S3_BUCKET_URL;
  if (!isDevMode) { // production, staging
    return new AWS.S3();
  }
  return new AWS.S3({
    endpoint: bucketUrl, // resolves to http://localhost:15004/<bucket-name>
    s3BucketEndpoint: true,
    accessKeyId: 'S3RVER',
    secretAccessKey: 'S3RVER'
  });
};

const createS3PresignedUrl = fileType => {
  const s3 = initializeS3();
  const Key = generateSaveKey(); // uuid()
  const params = {
    ACL: 'public-read',
    Bucket: process.env.S3_BUCKET_NAME,
    ContentType: 'image/jpeg',
    Expires: 60,
    Key
  };
  return new Promise((resolve, reject) => {
    s3.getSignedUrl('putObject', params, (err, s3SignedUrl) => {
      if (err) {
        console.error(err);
        reject(err);
        return;
      }
      resolve({s3SignedUrl});
    });
  });
};

Then on frontend, I call this endpoint to get the s3SignedUrl and do a PUT request to that returned URL.

// my s3rver is running on http://localhost:15004

// call backend to get the signed URL
// http://localhost:15004/<bucket-name>/<Key>?AWSAccessKeyId=S3RVER&Content-Type=image%2Fjpeg&Expires=1234&Signature=xyz123&x-amz-acl=public-read
const backendService = axios.create({baseURL: process.env.BASE_URL})
const {data: {s3SignedUrl}} = await backendService.post('/images', {}, {headers: {authorization: `Bearer ${jwtToken}`}});

// use the s3SignedUrl returned to do a PUT request
// file is an instance of File
await axios.put(s3SignedUrl, file, {headers: {'Content-Type': file.type}});

However, this resolves to the following error:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
    <AWSAccessKeyId>S3RVER</AWSAccessKeyId>
    <StringToSign>GET

image/jpeg
1558325033
x-amz-acl:public-read
/dev.jemput.co/44b5b78a-5f5e-4640-9831-6299a214ccb9.jpg</StringToSign>
    <StringToSignBytes>47 45 54 0a 0a 69 6d 61 67 65 2f 6a 70 65 67 0a 31 35 35 38 33 32 35 30 33 33 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 2f 64 65 76 2e 6a 65 6d 70 75 74 2e 63 6f 2f 72 65 73 6f 75 72 63 65 73 2f 34 34 62 35 62 37 38 61 2d 35 66 35 65 2d 34 36 34 30 2d 39 38 33 31 2d 36 32 39 39 61 32 31 34 63 63 62 39 2e 6a 70 67</StringToSignBytes>
</Error>

This code works, however, on my staging and production instances, when it's accessing real S3 buckets.

Thoughts?

Nevermind. I realize it's because of the s3BucketEndpoint: true parameter. I don't know why I even had that in the first place. Removed it and it works fine now.