Problem
I'm trying to upload files to an S3 bucket using presigned post requests (roughly following this blog post). My server (node) generates a presigned post request using createPresignedPost
, and then returns it to the client (React), which uses it to upload a user-generated file to our S3 bucket.
Currently, any POST
requests from the client to the bucket all fail with 405 Method Not Allowed
. I checked the response headers, and saw that the Allow
header does not list POST
, although the Access-Control-Allow-Methods
does.
Question: is there something else I need to do in order to allow POST requests to the bucket?
note: the reason I'm trying to used presigned POST
requests as opposed to the regular presigned urls (which use PUT
), is because I need to specify restrictions on file type and size, which the latter doesn't seem to support - if i'm mistaken about this, and anyone knows a way to specify restrictions through presigned PUT requests, then this would also solve my problem!
Context / other info
The S3 bucket has no policy. It does have a CORS policy, which is
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"POST",
"PUT",
"HEAD",
"DELETE"
],
"AllowedOrigins": [
"http://localhost:3000",
"http://localhost:8000",
...
],
"ExposeHeaders": [
"ETag",
"x-amz-meta-custom-header"
]
}
]
The server-side code for generating the presigned POST request:
const signS3PostRequest = async ({ fileName, fileType, prefix, bucket }) => {
const params = {
Bucket: bucket,
Fields: {
$key,
},
Expires: 60,
ContentType: fileType,
ACL: 'private',
Conditions: [['content-length-range', 0, 10000000]],
}
const s3 = new aws.S3({
apiVersion: 'latest',
region: $region,
accessKeyId: process.env.AWS_ACCESSKEYID,
secretAccessKey: process.env.AWS_SECRETACCESSKEY,
})
return new Promise((resolve, reject) => {
s3.createPresignedPost(params, (err, data) => {
if (err) {
...
} else {
resolve(data)
}
})
})
}
And the client-side code for uploading a file using this presigned request:
export const uploadFileToS3UsingSignedPostRequest = (
file,
signedRequestData
) => {
let form = new FormData()
Object.keys(signedRequestData.fields).forEach((k) =>
form.append(k, signedRequestData.fields[k])
)
form.append('file', file)
return fetch(signedRequestData.url, { method: 'POST', body: form })
}
The response I get back from s3.createPresignedPost
looks like this:
{
"url": "https://s3.amazonaws.com/$BUCKET_NAME",
"fields": {
"key": "test/6zb3rewm8cjydpslti3g/test.jpeg",
"bucket": "$BUCKET_NAME",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "$CREDENTIAL",
"X-Amz-Date": "20221230T090441Z",
"Policy": "eyJleHBpcmF0aW9uIjoiMjAyMi0xMi0zMFQwOTowNTo0MVoiLCJjb25kaXRpb25zIjp...",
"X-Amz-Signature": "$SIGNATURE"
}
}
and the response from S3 when trying to use the signed post request to upload the file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>MethodNotAllowed</Code>
<Message>The specified method is not allowed against this resource.</Message>
<Method>POST</Method>
<ResourceType>OBJECT</ResourceType>
<RequestId>HTYWNACA6A1J6BBS</RequestId>
<HostId>BSV4mFijEfgZEiMu6nVxqP9aocuhAwN7et/UfrO4r1PmkeFZ8nVUsNmcLVZoWCe06zrEFJXDfsE=</HostId>
</Error>