Woah, the documentation from AWS on these headers is terrible. Here is what I figured out.
- The output of
createPresignedPost() is an object with two keys: url and fields. The fields object contains all the form fields and respective values that you must use when submitting the post. You would copy these fields and only these fields to your Postman request.
- There are a few fields that the AWS SDK will add automatically, including
bucket, key, policy, and several fields required to generate the signature. With the JavaScript SDK, these were X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, and X-Amz-Signature (though for the Python SDK, these were AWSAccessKeyId and signature). These will be returned in the fields key from createPresignedPost(), so you do not need to manually generate them.
- The
Fields parameter of createPresignedPost() is for additional fields that you want to add to the form. The entire set of fields that could possibly be added is listed here. However, the SDK handles many of these for you, particularly the required ones. According to the Python SDK docs, the fields that you could include in Fields are acl, Cache-Control, Content-Type, Content-Disposition, Content-Encoding, Expires, success_action_redirect, redirect, success_action_status, and x-amz-meta-.
- The
Fields and Conditions parameters of createPresignedPost() need to be synchronized such that any field in Fields also has a condition in Conditions and vice versa.
Putting this altogether, I believe the problem with your getSignedUrl() function was that it was missing a Fields parameter for createPresignedPost() that had a key-value pair for Content-Type (since you have a condition for Content-Type). Without the Content-Type in the Fields parameter, the calculated signature would not include the Content-Type field. If you then left out the Content-Type field your signature might match, but your policy conditions would fail because it required Content-Type to be present and equal to image/jpeg. If you included Content-Type, then your signature would not match.
Here is could that should work for you.
const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
signatureVersion: "v4",
region: "eu-west-2",
});
async function getSignedUrl() {
const params = {
Bucket: "richbits-test",
Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
Conditions: [["eq", "$Content-Type", "image/jpeg"]],
Fields: {"Content-Type": "image/jpeg"},
};
console.log(params);
const signedUrl = await createPresignedPost(s3, params);
return signedUrl;
}
getSignedUrl().then(res => {
console.log(res)
})
The output of this should be
{
Bucket: 'richbits-test',
Key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Conditions: [ [ 'eq', '$Content-Type', 'image/jpeg' ] ],
Fields: { 'Content-Type': 'image/jpeg' }
}
{
url: 'https://s3.eu-west-2.amazonaws.com/richbits-test',
fields: {
'Content-Type': 'image/jpeg',
bucket: 'richbits-test',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
'X-Amz-Credential': '<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request',
'X-Amz-Date': '20211005T111446Z',
key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Policy: '<SOME_BASE64_ENCODED_STRING>',
'X-Amz-Signature': '<THE_SIGNATURE>'
}
}
Then your POST request would include the form fields Content-Type, bucket, X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, Policy, and X-Amz-Signature. Here is what an HTML form would look like.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<form action="https://s3.eu-west-2.amazonaws.com/richbits-test" method="post" enctype="multipart/form-data">
Bucket:
<input type="input" name="bucket" value="richbits-test" /><br />
Key to upload:
<input type="input" name="key" value="d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f" /><br />
Content-Type:
<input type="input" name="Content-Type" value="image/jpeg" /><br />
<input type="text" name="X-Amz-Credential" value="<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request" />
<input type="text" name="X-Amz-Date" value="20211005T111446Z" />
<input type="hidden" name="Policy" value="<SOME_BASE64_ENCODED_STRING>" />'
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Signature" value="<THE_SIGNATURE>" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
As far as debugging this issue, I did not find anything useful from the POST request to S3. I either got no response or an unauthorized status, without any indication what the problem was.