0

I'm sorry to be posting yet another question about CORS but I just can't figure this one out.

enter image description here

I have a React app using an Express.js server (running on http://localhost:9001) to upload an image to a Google Cloud storage bucket. I keep getting a CORS error even though the image is uploaded successfully and this is preventing me from getting the image's URL returned. I don't really understand how I can get a CORS error even though the image is uploaded but that's what's happening.

I have configured CORS on the Google Cloud storage bucket as follows:

[
    {
      "origin": ["http://localhost:3000"],
      "responseHeader": "*",
      "method": ["POST"],
      "maxAgeSeconds": 3600
    }
]

When I inspect the CORS error I'm getting I see the following:

enter image description here

The origin is http://localhost:3000, so that's configured correctly and I'm using POST to upload the image so that should be allowed as well.

The function I've written to upload the image is as follows:

function postImage(file) {
    const formData = new FormData();
    formData.append('file', file);

    fetch(`${window.location.protocol}//${window.location.hostname}:9001/uploads`, {
        method: 'POST',
        mode: 'cors',
        cache: 'no-cache',
        // headers: {
        //     'Content-Type': 'multipart/form-data'
        // },
        body: formData
    })
        // .then((response) => response.json())
        .then((response) => console.log('This is your data:', response.data))

        .catch(error => {
            console.error('There has been a problem uploading your image', error);
        });
}

I've commented out the headers as including them kept throwing up a Multipart: Boundary not found error that I've seen others have an issue with and removing the headers setting hasn't caused any other issues.

I have a helper function on the Express server that uploads the image to the Google Cloud storage bucket:

const uploadImage = (file) => new Promise((resolve, reject) => {
    const { originalname, buffer } = file

    const blob = bucket.file(originalname.replace(/ /g, "_"))
    const filetype = blob.name.split('.').pop()
    const filename = `${uuidv4()}.${filetype}`
    const blobStream = blob.createWriteStream({
        resumable: false
    })
    blobStream.on('finish', () => {
        const publicUrl = format(
            `https://storage.googleapis.com/${bucket.name}/${filename}`
        )
        resolve(publicUrl)
    })
        .on('error', () => {
            reject(`Unable to upload image, something went wrong`)
        })
        .end(buffer)
})

Here are the functions on my Express server:

import { typeDefs } from './graphql-schema'
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
import neo4j from 'neo4j-driver'
import { makeAugmentedSchema } from 'neo4j-graphql-js'
import dotenv from 'dotenv'
import { initializeDatabase } from './initialize'
const bodyParser = require('body-parser')
const multer = require('multer')
const uploadImage = require('./helpers/helpers')

dotenv.config()

const app = express()

    const schema = makeAugmentedSchema({
      typeDefs,
      config: {
        query: {
          exclude: ['RatingCount'],
        },
        mutation: {
          exclude: ['RatingCount'],
        },
      },
    })
    
    const driver = neo4j.driver(
      process.env.NEO4J_URI,
      neo4j.auth.basic(
        process.env.NEO4J_USER,
        process.env.NEO4J_PASSWORD
      ),
      {
        encrypted: process.env.NEO4J_ENCRYPTED ? 'ENCRYPTION_ON' : 'ENCRYPTION_OFF',
      }
    )
    
    const init = async (driver) => {
      await initializeDatabase(driver)
    }
    
    init(driver)
    
    const server = new ApolloServer({
      context: { driver, neo4jDatabase: process.env.NEO4J_DATABASE },
      schema: schema,
      introspection: true,
      playground: true,
    })
    
    // Specify host, port and path for GraphQL endpoint
    const port = process.env.GRAPHQL_SERVER_PORT || 4001
    const path = process.env.GRAPHQL_SERVER_PATH || '/graphql'
    const host = process.env.GRAPHQL_SERVER_HOST || '0.0.0.0'
    
    
    // Code for uploading files to Google Cloud
    app.use((req, res, next, err) => {
      console.error(err.stack)
      res.header("Access-Control-Allow-Origin", "*");
      res.type('multipart/form-data')
      res.status(500).json({
        error: err,
        message: 'Internal server error!',
      })
      next()
    })
    
    const multerMid = multer({
      storage: multer.memoryStorage(),
      limits: {
        // no larger than 5mb.
        fileSize: 5 * 1024 * 1024,
      },
    })
    
    app.disable('x-powered-by')
    app.use(multerMid.single('file'))
    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({ extended: false }))
    
    app.post('/uploads', async (req, res, next) => {
      try {
        const myFile = req.file
        const imageUrl = await uploadImage(myFile)
        res
          .status(200)
          .json({
            message: "Upload was successful",
            data: imageUrl
          })
      } catch (error) {
        next(error)
      }
    })

    server.applyMiddleware({ app, path })
    
    app.listen({ host, port, path }, () => {
      console.log(`GraphQL server ready at http://${host}:${port}${path}`)
    })
    
    app.listen(9001, () => {
      console.log('Node.js -> GCP server now listening for requests!')
    })  

I've tried a lot of different things to get this working:

  1. I've tried adding http://localhost:9001 to the CORS configuration, as well as other URLs
  2. I've tried opening up all origins with "*" for
  3. I've read through all the documentation [here][3]
  4. I've tried following all the troubleshooting documentation Google has here
  5. I've cleared my browser cache as I've seen that can cause the CORS errors to persist - see another post here
  6. I've tried waiting over night for my Google Cloud CORS configuration to take effect as I've heard the configuration can take a bit of time to propagate

Despite all of this I'm still getting the CORS error but my upload is still working. I just need to clear the error so I can get the returned image URL.

Sean
  • 494
  • 6
  • 23
  • What’s the HTTP status code of the response? You can use the Network pane in browser devtools to check. If Chrome doesn’t show it to you, use the Network pane in Firefox devtools. Is it a 4xx or 5xx error rather than a 200 OK success response? – sideshowbarker Aug 05 '20 at 08:53
  • Does this answer your question? [How does Access-Control-Allow-Origin header work?](https://stackoverflow.com/questions/10636611/how-does-access-control-allow-origin-header-work) – mappie Aug 05 '20 at 08:59
  • _“I have configured CORS on the Google Cloud storage bucket as follows”_ - why, what does that have to do with the communication between your react frontend and your express server? – CBroe Aug 05 '20 at 09:04
  • Yes. Thats the problem @CBroe and he just Down voted my answer – Edson Magombe Aug 05 '20 at 09:07
  • did you try to set `mode: "no-cors"` like how the error suggests you? – bill.gates Aug 05 '20 at 09:08
  • I didn't down vote your answer - I haven't had a chance to review all of this yet. I'm guessing from your answers the issue is not between Express and GCP but between my React app and the Express server? – Sean Aug 05 '20 at 09:08
  • @ifaruki - yes, but that returns an opaque object and so I can't get the returned URL of the image that I need – Sean Aug 05 '20 at 09:09
  • 1
    That is it @Sean – Edson Magombe Aug 05 '20 at 09:10

1 Answers1

1

You add cors to Google Cloud storage bucket but you forgot to add it to express server POST function. Or use it as global on your express server.

Try this on your express POST function:

res.header("Access-Control-Allow-Origin", "http://example.com");

Or

res.header("Access-Control-Allow-Origin", "*");

Or even better:

    /* Headers */
app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*"); // update to match the domain you will make the request from
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});
Edson Magombe
  • 313
  • 2
  • 14
  • Hi Edson, thank you so much for taking the time to look at this. I've tried your solution and I'm still getting the CORS error. I've added my POST and USE functions to the original question so you can see how I'm implementing your code. – Sean Aug 05 '20 at 10:32
  • The app.use() has to be above all app functions – Edson Magombe Aug 05 '20 at 10:34
  • Thanks Edson, I've added my entire Express code to the original question so it's clearer where `app.use()` is running - I had no idea things had to be in such a specific order to avoid the CORS errors! – Sean Aug 05 '20 at 10:43
  • No...unfortunately not! – Sean Aug 05 '20 at 10:46
  • Sorry bro. Its correct to use app.use(function (err, req, res, next) {, and not the comment i deleted – Edson Magombe Aug 05 '20 at 10:49
  • Just updated the answer to the code i implemented in my project for the app.use() – Edson Magombe Aug 05 '20 at 10:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/219259/discussion-between-sean-and-edson-magombe). – Sean Aug 05 '20 at 10:58