'Access-Control-Allow-Origin' CORS when trying to execute firebase function

4.8k Views Asked by At

I have used firebase functions with node js before and consistently run similar functions as below. However, I am now getting this error all of a sudden in my console:

Access to fetch at 'https://us-central1-myapp.cloudfunctions.net/test2' from origin 'http://localhost:8383' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

POST https://us-central1-myapp.cloudfunctions.net/test2 net::ERR_FAILED

This is my code I don't know what I am doing wrong:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const {Storage} = require('@google-cloud/storage');
var PizZip = require('pizzip');
var Docxtemplater = require('docxtemplater');
admin.initializeApp();
const BUCKET = 'gs://myapp.appspot.com';
const cors = require('cors');
const https = require('https');
const express = require('express');
const app = express();
app.use(cors({origin: true}));

    const storage = new Storage({
    projectId: 'myapp' });
    
    
    // The error object contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
function replaceErrors(key, value) {
    if (value instanceof Error) {
        return Object.getOwnPropertyNames(value).reduce(function(error, key) {
            error[key] = value[key];
            return error;
        }, {});
    }
    return value;
}

function errorHandler(error) {
    console.log(JSON.stringify({error: error}, replaceErrors));

    if (error.properties && error.properties.errors instanceof Array) {
        const errorMessages = error.properties.errors.map(function (error) {
            return error.properties.explanation;
        }).join("\n");
        console.log('errorMessages', errorMessages);
        // errorMessages is a humanly readable message looking like this :
        // 'The tag beginning with "foobar" is unopened'
    }
    throw error;
}

function readStreamToBuffer(stream) {
    return new Promise((resolve, reject) => {
        const chunkArray = [];
        
        stream
        .on('data', (chunk) => chunkArray.push(chunk))
        .on('error', (err) => {
            chunkArray = [];
            reject(err);
        })
        .on('end', () => {
            const buffer = Buffer.concat(chunkArray);
            chunkArray = [];
            resolve(buffer);
        });
    });
}


exports.test2 = functions.https.onCall(async(data, context) => {
 const file_name = 'tag-example.docx';
 const storageRef = storage.bucket(BUCKET).file(file_name);
  const buffer = await readStreamToBuffer(storageRef.createReadStream());
  const zip = new PizZip(buffer);

  let doc;
  try {
      doc = new Docxtemplater(zip);
  } catch(error) {
      // Catch compilation errors (errors caused by the compilation of the template : misplaced tags)
      errorHandler(error);
  }

  doc.setData({
      first_name: 'Fred',
      last_name: 'Flinstone',
      phone: '0652455478',
      description: 'Web app'
  });
          
  try {
      doc.render();
  } catch (error) {
      errorHandler(error);
  }

  const contentBuffer = doc.getZip()
      .generate({type: 'nodebuffer'});
  
  console.log(doc);
  const targetStorageRef = storage.bucket(BUCKET).file("compiled.docx");
  await targetStorageRef.put(contentBuffer);


});

Is there someway to get that error that shows up in my client side console to go away and my cloud function to run? any help will be appreciated

2

There are 2 best solutions below

0
On

I had the same problem and the issue was actually related to "user permissions". The documentation says

As of January 15, 2020, all HTTP functions by default require most invokers >to be authenticated. https://cloud.google.com/functions/docs/securing/managing-access-iam#allowing_unauthenticated_http_function_invocation

Solution

  • Give access to allUsers

To make your function public, you can either deploy it with the --allow-unauthenticated flag, or use the Console to grant the Cloud Functions Invoker role to allUsers. Then handle CORS and authentication in the function code. https://cloud.google.com/functions/docs/writing/http#limitations

  • Give access to your Firebase App (Firebase Admin SDK)
    • You can find it on Firebase Project > Settings, under the Service Accounts tab.

For both solutions, the problem was that by following the docs I could not get to the list of functions. So make sure you go to Cloud Functions in order to change/add permissions

enter image description here

Click on the function you want, then Add to insert a new Principal enter image description here

  • Add your new Principal
  • Select the Cloud functions invoker role
  • Save

enter image description here

3
On

First thing that I notice is you are using callable cloud functions but the express app is using the cors middleware here: app.use(cors({origin: true})) and there no routes in that app. I don't think you need to explicitly import express and CORS when using callable functions so try removing them. Quoting from here, "CORS should be automatically handled by the onCall handler".

If you want to use Express and CORS middleware like that, you should try onRequest functions as shown below:

const express = require('express');
const cors = require('cors');

const app = express();

// Automatically allow cross-origin requests
app.use(cors({ origin: true }));

// build multiple CRUD interfaces:
app.get('/test2', (req, res) => {
  //Handle your test2 methods here
  return res.send('your response')
});


// Expose Express API as a single Cloud Function:
exports.api = functions.https.onRequest(app);

Now in this case the CORS middleware you wrote will be running. Your function's URL may look something like - https://us-central1-myapp.cloudfunctions.net/api Then to use the /test2 route it becomes https://us-central1-myapp.cloudfunctions.net/api/test2. CORS shouldn't be an issue anymore here but do note it's an express app now so the parameters in the function are Request, Response instead of data, context.

Additionally, I see your are calling the API from localhost so using Firebase Functions Emulator may be a good idea. To use specify to use the emulator like this: firebase.functions().useFunctionsEmulator('http://localhost:5001')