0
votes

I have a React frontend that lets users add and view recipes, which contain text and a picture. The backend is an Express Node.JS app which reads from and writes to a DynamoDB database. The application is deployed on AWS using the Serverless Framework, so it uses API Gateway, Lambda, DynamoDB, and S3 for photo storage. I'm working on getting the uploadphoto route working, but CORS errors are preventing it from working.

I've imported the cors NPM module and am using it on the app. I've tried explicity specifying the origin in the config, but that doesn't make a difference. I also have cors: true on every route in my serverless.yml file.

serverless.yml excerpt:
service: recipes-api

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource:
        - { "Fn::GetAtt": ["RecipesDynamoDBTable", "Arn"] }
  environment:
    RECIPES_TABLE: ${self:custom.tableName}
    S3_BUCKET: ${self:custom.bucketName}

functions:
  app:
    handler: index.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
  getRecipe:
    handler: index.handler
    events:
      - http: 
         path: recipes/{name}
         method: get
         cors: true
  allRecipes:
    handler: index.handler
    events:
      - http:
         path: allrecipes
         method: get
         cors: true
  addRecipe:
    handler: index.handler
    events:
      - http:
         path: recipes
         method: post
         cors: true
  uploadPhoto:
    handler: index.handler
    events:
      - http:
         path: uploadphoto
         method: post
         cors: true
  getPhoto:
    handler: index.handler
    events:
      - http:
         path: photo/{name}
         method: get
         cors: true

index.js excerpt:
const serverless = require('serverless-http');
const express = require('express');
const app = express();
const AWS = require('aws-sdk');
const cors = require('cors');
...
app.use(cors({origin: 'https://recipes.example.co'}))
//Upload Photo Endpoint
app.post('/uploadphoto', function (req, res) {
    const s3 = new AWS.S3();  // Create a new instance of S3
    const fileName = req.body.fileName;
    const fileType = req.body.fileType;

    const s3Params = {
        Bucket: S3_BUCKET,
        Key: fileName,
        Expires: 500,
        ContentType: fileType,
        ACL: 'public-read'
    };

    s3.getSignedUrl('putObject', s3Params, (err, data) => {
        if(err){
            console.log(err);
            res.json({success: false, error: err})
        }
    // Data payload of what we are sending back, the url of the signedRequest and a URL where we can access the content after its saved. 
        const returnData = {
            signedRequest: data,
            url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}`
        };
    // Send it all back
        res.json({success:true, data:{returnData}});
  });

})

AddRecipeForm.js excerpt:
handleUpload = (ev) => {
    console.log("handleUpload")
    console.log(ev)
    let file = this.uploadInput.files[0];
    // Split the filename to get the name and type
    let fileParts = this.uploadInput.files[0].name.split('.');
    let fileName = fileParts[0];
    let fileType = fileParts[1];
    console.log("Preparing the upload");
    axios.post("https://bqdu4plthq.execute-api.us-east-1.amazonaws.com/dev/uploadphoto",{
      fileName : fileName,
      fileType : fileType
    })
    .then(response => {
      var returnData = response.data.data.returnData;
      var signedRequest = returnData.signedRequest;
      var url = returnData.url;
      this.setState({url: url})
      console.log("Recieved a signed request " + signedRequest);

     // Put the fileType in the headers for the upload
      var options = {
        headers: {
          'Content-Type': fileType
        }
      };
      axios.put(signedRequest,file,options)
      .then(result => {
        console.log("Response from s3")
        this.setState({success: true});
      })
      .catch(error => {
        console.error(error);
      })
    })
    .catch(error => {
      console.error(error);
    })
  }

When clicking the Upload Photo button which calls the handleUpload funciton in AddRecipeForm.js, I get the following errors in my console:

Origin https://recipes.example.co is not allowed by Access-Control-Allow-Origin.

XMLHttpRequest cannot load https://bqdu4plthq.execute-api.us-east-1.amazonaws.com/dev/uploadphoto due to access control checks.

Failed to load resource: Origin https://recipes.example.co is not allowed by Access-Control-Allow-Origin.

Note that every other route works (getRecipe, allRecipes, addRecipe) and sends CORS headers, so I'm not sure why my addphoto request from React to API Gateway isn't sending CORS headers, even though it should be using it in index.js. Thanks in advance for help!

2
What’s the HTTP status code of the response? You can use the Network pane in browser devtools to check. Is it a 4xx or 5xx error rather than a 200 OK success response?sideshowbarker
I don’t actually receive any HTTP response from this call. I believe the request doesn’t even leave my browser because I just get the errors shown above, when I look on the network tab I see the failed call but it doesn’t have any HTTP response. Hope that helpsuser2411857
By my understanding of CORS, the request will leave the browser, and return to the browser, no matter what CORS policies are in place. But your browser won't pass that response on to your javascript code if its CORS check fails. Your browser can't know the server's CORS settings without making a request. (If you're not seeing a GET/POST request, there will probably be an OPTIONS pre-flight request to the same route.) Have you checked the network pane?jameslol

2 Answers

0
votes

Your serverless.yml has the default defined function 'app', which is a catch-all route ({proxy+} matches all). It looks like that's the route you're hitting, instead of uploadPhoto, because your uploadPhoto route is defined as method POST, but your frontend axios request is using PUT.

0
votes

Besides all the technical things (to be added) which are stated on several other Q/A make sure that your function returns the response to the front-end within 29 seconds. This is the default timeout (and maximum timeout) set for your response. After fixing this I was able to break the CORS error.