How to configure S3 bucket to allow POSTs to API Gateway without 405 error

Viewed 2638

I have a static website hosted on AWS S3. The website can be accessed from either: https://s3.amazonaws.com/example-bucket-name-to-avoid-spam/index.html Or http://example-bucket-name-to-avoid-spam.s3-website-us-east-1.amazonaws.com/

When a user completes the form and the captcha on the page and presses submit the following JavaScript is triggered:

<script>
    // Replace the YOUR_API_ENDPOINT_URL with yours
    // It should look something like this:
    // https://qw324asdasd.execute-api.us-east-1.amazonaws.com/dev/api/sendSms
    const API_ENDPOINT = 'https://fak3ur1.execute-api.us-east-1.amazonaws.com/dev/api/sendSms';
    var messageDiv = document.getElementById('error-message')
    // Handle public api call
    document.getElementById('send-text').addEventListener('click', function () {
      const captchtaResponse = grecaptcha.getResponse() || false
      const textMessage = document.getElementById('message').value
      const phoneNumber = document.getElementById('phone-number').value

      if (!textMessage) {
        messageDiv.innerHTML = 'Remember to enter a message!'
        return false
      }

      if (!phoneNumber) {
        messageDiv.innerHTML = 'Don\'t forget to enter a phone number!'
        return false
      }

      if (!captchtaResponse) {
        messageDiv.innerHTML = 'Complete the Captcha please!'
        return false
      }

      const data = JSON.stringify({
        to: phoneNumber,
        message: textMessage,
        captcha: captchtaResponse
      })

      // post to API with native browser Fetch
      const getdata = fetch(API_ENDPOINT, {
        headers: {
          "Content-type": "application/json"
        },
        method: 'POST',
        body: data,
        mode: 'cors',
        cache: false,
      });

      getdata.then(function(response) {
        response.json().then(function(data) {
          console.log('Response:', data);
          const body = JSON.parse(data.body);
          messageDiv.textContent = '';
          messageDiv.textContent = (body && body.message) ? body.message : '';
        });
      }).catch(function(err) {
        console.log(err)
      });
    });
  </script>

This successfully sends a POST through to the AWS API Gateway which processes the request correctly and sends a text with Twilio to the desired phone number.

I've also confirmed in Postman that the API Gateway/Lambda response itself is correctly coming back:

{
    "statusCode": 200,
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "body": "{\"message\":\"Text message successfully sent!\",\"data\":{\"dateCreated\":\"2017-07-06T16:49:00.000Z\",\"dateUpdated\":\"2017-07-06T16:49:00.000Z\",\"from\":\"+13334445566\",\"body\":\"Sent from your Twilio trial account - test 5 with postman - looking at Lambda logs\"}}"
}

The issue happens after that POST is (presumably) sent to API Gateway. The static site redirects to an error page with this message when on the non-https url above:

405 Method Not Allowed

Code: MethodNotAllowed
Message: The specified method is not allowed against this resource.
Method: POST
ResourceType: OBJECT
RequestId: IDSTRING123
HostId: Longhostidthing1237ygausdhasdgylh231ctidasd=

And this for the https url:

<Error>
<Code>MethodNotAllowed</Code>
<Message>
The specified method is not allowed against this resource.
</Message>
<Method>POST</Method>
<ResourceType>OBJECT</ResourceType>
<RequestId>IDSTRING123</RequestId>
<HostId>
Longhostidthing1237ygausdhasdgylh231ctidasd=
</HostId>
</Error>

Initially I thought this was a CORS issue, but I've checked and rechecked several times the CORS policy on the bucket is very permissive:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <MaxAgeSeconds>0</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>0</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

I've also fiddled around with this a few times to confirm I was being as permissive as possible.

Finally, I've tried this incognito in Chrome, with cookies and all browsing data cleared in a separate profile, and all of the above with a plugin that disables CORS from the browser-side. None of these attempts appear to resolve my issue.

Any ideas?

1 Answers
Related