Firebase Cloud Functions - onCall not working - javascript

I'm testing a very simple implementation as described on FB docs (https://firebase.google.com/docs/functions/callable), and it's not working.
Here's my Firebase Function, deployed to cloud:
exports.getRecSkills = functions.https.onCall((data, context) => {
return {text: data.text};
});
...and my client call (after initializing FB):
var getRecSkills = firebase.functions().httpsCallable('getRecSkills');
getRecSkills({text: '123'}).then(function(result) {
console.log(result);
}).catch(function(error) {
console.log(error.code);
console.log(error.message);
});
I get a CORS header related issue but in the docs, it doesn't mention the need for CORS... am I missing something?
Some notes:
I've been able to execute other Firebase Functions (i.e. HTTPS,
Database) so I don't think it's me setting up Firebase wrong.
Updated to latest Firebase, so don't think that's an issue either.
Gives me an "internal" error, which the API docs aren't helpful, other than "something is seriously wrong".
I can't seem to get the function to work (it keeps giving me
400-errors) when testing locally via the shell, even though I got it
to work with any other database and https functions
Been struggling with this for quite some time... Please help!

To get rid of your CORS error, make sure your firebase.json has the following headers:
"hosting": [
{
"headers": [
{
"source": "**",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
]
}
]
If you're running on Firebase Emulator on local device, make sure you have the following after initializing your Firebase Functions, otherwise your local device will still be calling the remote the Firebase Function and you'll hit the CORS error again:
if (window.location.hostname === "localhost") {
console.log("localhost detected!");
firebase.functions().useFunctionsEmulator('http://localhost:5001');
};

I had the same problem just recently but solved it after including my "projectId" in my config object. Below is a code snippet of the Firebase config object for Javascript. Make sure all fields have been filled in your config object and it should solve your undefined issue.
var config = {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
projectId: "<PROJECT_ID>",
storageBucket: "<BUCKET>.appspot.com",
messagingSenderId: "<SENDER_ID>",
};

If you have CORS issues and you are using express in order to expose the API functions you have to allow cors:
import * as cors from 'cors';
import * as express from 'express';
const corsHandler = cors({origin: true});
const app = express();
app.use(corsHandler);
app.post('/createUser', async (request, response) => {
await createUser(request, response);
});
exports.api = functions.https.onRequest(app);

Related

Firebase Callable Cloud Function CORS Error using Firebase Emulator and Vite

The following is the client-side code to call the cloud function:
// add a new post
addPostForm.addEventListener('click', (e) => {
e.preventDefault();
const addPost = httpsCallable(functions, 'addPost');
addPost({
title: addPostForm.postTitle.value,
description: addPostForm.postDescription.value,
})
.then(() => {
addPostForm.reset(),
addPostModal.classList.remove('open');
addPostForm.querySelector('.error').textContent = '';
})
.catch(error => {
addPostForm.querySelector('.error').textContent = error.message;
})
});
The cloud function:
exports.addPost = functions.https.onCall((data, context) => {
if(!context.auth){
throw new functions.https.HttpsError(
'unauthenticated',
'only authenticated users can post'
);
}
if(data.text.length > 140){
throw new functions.https.HttpsError(
'invalid-argument',
'description must be no more than 140 characters long'
);
}
return admin.firestore().collection('Posts').add({
title: data.title,
description: data.description,
likes: '',
bookmarks: '',
});
});
Firebase Setup:
import { initializeApp, getApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import { getStorage, connectStorageEmulator } from "firebase/storage";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
const firebaseConfig = {
"config"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const storage = getStorage(app);
const functions = getFunctions(getApp(), app);
if (window.location.hostname.includes("localhost")) {
connectAuthEmulator(auth, "http://localhost:9099");
connectFirestoreEmulator(db, 'localhost', 8080);
connectStorageEmulator(storage, 'localhost', 9199);
connectFunctionsEmulator(functions, "localhost", 5001);
}
export { auth, db, storage, functions };
Error
Access to fetch at 'http://localhost:5001/app/object/addPost'
From origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 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.
What's the problem here? Will I need to set up firebase admin to grant access rights, and if I turn CORS off in the browser will that present a security issue on production?
After over a week of learning and trying to find the right solution for this problem, I came across a video from one of my favorite YT creators Web Dev Simplified.
Here he elegantly explains the CORS error and provides a simple yet effective solution. Installing the Express and CORS library through NPM within my cloud functions folder and requiring them both in the index.js file. Writing a function that allows you to change the access origin should be enough to solve this error.
const express = require("express");
const app = express();
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const cors = require('cors');
app.use(
cors({
origin: "*",
})
);
Changing the origin to "*" allows the access request origin to be from any source. You can also change this to a specific URL if you needed.
CORS error occurs when the request from a certain origin (eg. abc.com) is restricted to access resources on another origin (eg. xyz.com). This is a security measure enforced at the browser level and this is not related to Firebase setup.
There are two ways through which this can be solved,
Disable CORS in the browser. This is NOT recommended and should not be done for most cases.
If you are in control of the server, then you can modify your application to receive requests from a certain source.
In this case, you are in control of both the server and the client.
you just have to configure the server application (Cloud Functions in this case) to accept requests from the client.
For this, in the CF code, you can set the header Access-Control-Allow-Origin to the value of the client origin (Eg. localhost:PORT_NUM) or simply set the value to * to receive requests from all client origins.
As mentioned in the Answer, you can follow the checklist for the firebase callable functions cors errors :
Ensure the function is deployed.
Ensure the function name is correct. I was calling recalculatY when it should have been recalculateY. Got a cors error for some
reason.
Ensure the function code itself is not throwing an error. Use the emulator to
help.
This didn't throw a cors error, still helpful to know.
Ensure your regions match - I am using europe-west2. I had to both deploy the function with that region, and call it using the
region. For a while, I assumed the client would infer the correct
region if the function name was correct.
you can refer to the Documentation which explains about how to connect your app to cloud function emulator.
For more information , you can refer to the Answer and git issue.

Receiving 401 Unauthorized error in Sendgrid with Strapi

I'm trying to add a "forgot password" link in my application, using the built-in API provided by Strapi. I've included the configuration for Sendgrid in config/plugins.js:
module.exports = ({ env }) => ({
email: {
provider: "sendgrid",
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: "myemail#gmail.com",
defaultReplyTo: "myemail#gmail.com",
},
},
});
Every answer I find about this error is about adding the API key in the environment variables, which I've already done (and re-done) several times. I also re-created my API key twice, in case it expired, but it's still the same. I don't understand, it was working perfectly well a couple days ago but now I'm stuck on this error. Any idea what could be the issue here?
Probably indeed the expression env('SENDGRID_API_KEY') is not resolving the correct api key. You can also directly put your api key in the json like this:
module.exports = ({ env }) => ({
email: {
provider: "sendgrid",
providerOptions: {
apiKey: 'SG.MY_SENDGRID_API_KEY',// <== not using the env function
},
settings: {
defaultFrom: "myemail#gmail.com",
defaultReplyTo: "myemail#gmail.com",
},
},
If this works, the plugin is working, and you can focus on why the env() function is not resolving the variable

Error on Heroku when trying to post data ( error:0909006C Pem routines..._

I am trying to make a small nodeJS server deployed on Heroku, saving data to Firestore database. The server is deployed and running correctly.
I use Insomnia to test my requests and when I use it locally I can send a post request and save my data object to the database without issues. However, when I try to send a post request to Heroku, I get a 500 error with this message:
{ "code": 16, "details": "Failed to retrieve auth metadata with
error: error:0909006C:PEM routines:get_name:no start line",
"metadata": {}, "note": "Exception occurred in retry method that was
not classified as transient" }
I have read that it is necessary to add real line breaks in the env variable but I guess I am doing it already. This is my initializeApp function:
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.PROJECT_ID,
privateKey: process.env.PRIVATE_KEY
? process.env.PRIVATE_KEY.replace(/\\n/gm, "\n")
: undefined,
clientEmail: process.env.CLIENT_EMAIL
}),
databaseURL: "XXXXX"
});
Any idea how to fix this?
I solved it by copy-pasting the key with break lines directly into Heroku config variables and removing .replace(/\\n/gm, "\n") from the code.
privateKey: process.env.PRIVATE_KEY
? process.env.PRIVATE_KEY
: undefined

Getting `Uncaught TypeError: firebase.database is not a function` [duplicate]

I am trying to upgrade from earlier firebase version to the latest in my ionic project. I followed this tutorial for upgrade. In step 4 from this page I am stuck on the last statement firebase.database().ref();.
Error message
TypeError: firebase.database is not a function
Below is my code. Kindly help.
...
// Initialize Firebase
this.config = {
apiKey: "some-api-key",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
storageBucket: "project-somenumber.appspot.com",
};
...
this.authWithOAuthPopup = function(type) {
var deferred = $q.defer();
console.log(service.config); // ---> Object {apiKey: "some-api-key", authDomain: "myapp.firebaseapp.com", databaseURL: "https://myapp.firebaseio.com", storageBucket: "project-somenumber.appspot.com"}
firebase.initializeApp(service.config);
console.log(firebase); // ---> Object {SDK_VERSION: "3.0.5", INTERNAL: Object}
service.rootRef = firebase.database().ref(); //new Firebase("https://rsb2.firebaseio.com"); ---> I am getting error on this line "TypeError: firebase.database is not a function"
service.rootRef.authWithOAuthPopup(type, function(error, authData) {
if (error) {
service.authError = error;
switch (error.code) {
case "INVALID_EMAIL":
console.log("The specified user account email is invalid.");
break;
case "INVALID_PASSWORD":
console.log("The specified user account password is incorrect.");
break;
case "INVALID_USER":
console.log("The specified user account does not exist.");
break;
default:
console.log("Error logging user in:", error);
}
deferred.resolve(service.authError);
} else {
service.authData = authData;
console.log("Authenticated successfully with payload:", authData);
deferred.resolve(service.authData);
}
return deferred.promise;
});
return deferred.promise;
}
var service = this;
Update
After adding latest database library this questions problem is solved.
Updating my code here
this.authWithOAuthPopup = function(type) {
var deferred = $q.defer();
console.log(service.config);
firebase.initializeApp(service.config);
console.log(firebase);
service.rootRef = firebase.database(); //.ref(); //new Firebase("https://rsb2.firebaseio.com");
var provider = new firebase.auth.FacebookAuthProvider();
firebase.auth().signInWithRedirect(provider);
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
console.log(result);
// ...
}
// The signed-in user info.
var user = result.user;
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
return deferred.promise;
}
I ran into this with Ionic and it turned out that I wasn't including everything when using the latest Firebase Client. If you've included Firebase as firebase-app, then the Database and Auth pieces need to be required separately since they aren't bundled when including Firebase in this way.
Add the following to your index.html after you include firebase-app.js
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-database.js"></script>
Obviously you don't need to use the CDN, you could use bower (probably the preferred way with Ionic) or NPM with Browserify.
// Browserify Setup
var firebase = require('firebase/app');
require('firebase/auth');
require('firebase/database');
Snippet below taken from the Firebase Web Setup Docs
You can reduce the amount of code your app uses by just including the features you need. The individually installable components are:
firebase-app - The core firebase client (required).
firebase-auth - Firebase Authentication (optional).
firebase-database - The Firebase Realtime Database (optional).
firebase-storage - Firebase Storage (optional).
From the CDN, include the individual components you need (include firebase-app first)
A bit late to the party, but in case some one wanted to know the syntax in angular, (or Ionic 4) just add this to your .module.ts file (Note, as peterb mentioned, the /database import)
import { AuthService } from './auth.service';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFireDatabaseModule } from 'angularfire2/database';
#NgModule({
imports: [
AngularFireAuthModule,
AngularFireDatabaseModule,
AngularFireModule.initializeApp(environment.firebase),
],
providers: [
]
})
i solved this issue by giving the url in the constructor
firebase.database('https://123.firebaseio.com')
First, make sure you are using
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase.js"></script>
Firebase authWithOAuthPopup has changed a little bit in the new version.
Now you don't use the ref to call authentication methods. You should be using firebase.auth() insted.
var auth = firebase.auth();
var provider = new firebase.auth.TwitterAuthProvider();
auth.signInWithPopup(provider).then(function(result) {
// User signed in!
var uid = result.user.uid;
}).catch(function(error) {
// An error occurred
});
Also faced this problem on #angular/firebase 5.1.2, solved when updated #angular/cli and all dependencies to the latest version.
For people facing similar error(this._database.native.on is not a function) in React-native -
run the pod install - after adding firebase new service(database/auth ...)
terminate the metro bundler and restart it using "npx react-native start"
run "npx react-native run-ios"
This will create a new build and the error should be gone.
Ref: https://github.com/invertase/react-native-firebase/issues/3379
Use
var firebase = require('firebase/app');
require('firebase/database');
npm install --save firebase
Then:
require("firebase/database");
You need to add all of the firebase products you are using by way of require() as shown above.
I have the same error -firebase.database is not a function- but with different situation you just need to add
above and the link of javascript that contain the Firebase configuration.
You may also try to use the defer Attribute in your script as it will not load the scripts until page elements are loaded.

The incoming JSON object does not contain a client_email field

I'm trying to create a firebase cloud function. So I would to run my firebase cloud function locally.
But it do not work how to setup authentication.
I have installed firebase tools : https://firebase.google.com/docs/functions/local-emulator
I've runned the command firebase login, so now I'm logged.
Then I've created my json key with this tutorial : https://cloud.google.com/docs/authentication/getting-started
Now if I type echo $GOOGLE_APPLICATION_CREDENTIALS the result is /home/$USER/.google/****.json which contain
"project_id","private_key_id","private_key","client_email", "client_id", "auth_uri", "token_uri", "auth_provider_x509_cert_url", "client_x509_cert_url"
Also I've tried to install the full google cloud sdk and I runned : gcloud auth application-default login but no success.
Npm package versions :
"firebase-functions":"3.0.2"
"firebase-admin": "8.2.0"
I think I've provided enought information but feel free to ask me more if you want.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const app = express();
app.get("/", async (req, res) => {
admin.firestore().collection('something').get().then((collection) =>
return res.send({"count": collection.docs.length, "status": 200});
});
exports.exports = functions.https.onRequest(app);
the code is not important, the most important thing is that even I've done all theses steps, when I emulate my firebase locally with firebase serve and I trigger a function, I have this error :
Error: The incoming JSON object does not contain a client_email field
I can ensure you the json file contains client_email field.
Can you help me to authenticate with google ?
Thanks for your help.
I had a similar problem. It's likely a bug in version 7.0.2 of firebase-tools. I rolled back to version 7.0.0 and it works now.
So the temporary solution is:
npm i firebase-tools#7.0.0 -g
In short:
admin.initializeApp({ credential: admin.credential.applicationDefault() });
See docs for admin.credential.applicationDefault()
Update: Note that this is only recommended for testing/experimenting:
This strategy is useful when testing and experimenting, but can make
it hard to tell which credentials your application is using. We
recommend explicitly specifying which credentials the application
should use, ... Source
A little more info
I had the same when trying to call a firebase function locally which tries to update some documents in firestore database in batch. (Didn't test without batch).
To start calling firebase functions locally, I use:
firebase function:shell
As you probably know, this lists the available functions for your project.
I called my function and got the following error callstack:
Unhandled error Error: The incoming JSON object does not contain a client_email field
> at JWT.fromJSON (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\google-auth-library\build\src\auth\jwtclient.js:165:19)
> at GoogleAuth.fromJSON (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\google-auth-library\build\src\auth\googleauth.js:294:16)
> at GoogleAuth.getClient (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\google-auth-library\build\src\auth\googleauth.js:476:52)
> at GrpcClient._getCredentials (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\google-gax\build\src\grpc.js:107:40)
> at GrpcClient.createStub (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\google-gax\build\src\grpc.js:223:34)
> at new FirestoreClient (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\#google-cloud\firestore\build\src\v1\firestore_client.js:128:39)
> at ClientPool.Firestore._clientPool.pool_1.ClientPool [as clientFactory] (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\#google-cloud\firestore\build\src\index.js:315:26)
> at ClientPool.acquire (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\#google-cloud\firestore\build\src\pool.js:61:35)
> at ClientPool.run (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\#google-cloud\firestore\build\src\pool.js:114:29)
> at Firestore.readStream (D:\thdk\Projects\timesheets\functions\node_modules\firebase-admin\node_modules\#google-cloud\firestore\build\src\index.js:995:26)
RESPONSE RECEIVED FROM FUNCTION: 500, {
"error": {
"status": "INTERNAL",
"message": "INTERNAL"
}
}
I was running my function locally using the command line:
firebase functions:shell
I was using this code:
// Reference report in Firestore
const db = admin.firestore();
admin.initializeApp();
export const performMyCallableFirebaseFunction = (db, { from, to }) => {
return db.collection("collectionName").where("prop", "==", from).limit(500).get().then(snapshot => {
if (snapshot.empty) return new Promise(resolve => resolve(`No docs found with prop: ${from}`));
const batch = db.batch();
snapshot.forEach(doc => batch.update(doc.ref, { prop: to }));
return batch.commit();
});
};
exports.myCallableFirebaseFunction = functions.https.onCall(data => performMyCallableFirebaseFunction(db, data.from, data.to));
I changed the line
admin.initializeApp();
to
admin.initializeApp({ credential: admin.credential.applicationDefault() });
and now I was able to call my function locally using:
firebase functions:shell
firebase > myCallableFirebaseFunction({from: "foo", to: "bar"})
See docs for admin.credential.applicationDefault()
You probably need to set up the Firebase Admin SDK to use the Firebase emulator. You can do it by passing a credential property when calling the admin.initializeApp() method:
const serviceAccount = require('../serviceAccount.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
You can download your service account JSON file in the Firebase console:
Click on the "settings" icon;
Go to "Users and permissions";
Click on the link where it says "N service accounts also have access to this project";
Click on the "Generate new private key" button.
Here is how I've solved the problem after struggling couple of hours:
Short answer:
Create Firebase-adminsdk key
How to do it:
Go to Google-cloud-platform > Service accounts https://console.cloud.google.com/iam-admin/serviceaccounts/
Select your project
Select your firebase-admin-sdk looks like firebase-adminsdk-u4k3i#example..
Enable edit mode
Create key and select JSON
You get the option to download a .json. Which has ProjectID, PrivateKey and ClientEmail in it
use the information like this where you initialize your app:
// Providing a service account object inline
admin.initializeApp({
credential: admin.credential.cert({
projectId: "<PROJECT_ID>",
clientEmail: "foo#<PROJECT_ID>.iam.gserviceaccount.com",
privateKey: "-----BEGIN PRIVATE KEY-----<KEY>-----END PRIVATE KEY-----\n"
})
});
Once you have created a Firebase project, you can initialize the SDK with an authorization strategy that combines your service account file together with Google Application Default Credentials.
To authenticate a service account and authorize it to access Firebase services, you must generate a private key file in JSON format.
To generate a private key file for your service account:
In the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, then confirm by clicking Generate Key.
Securely store the JSON file containing the key.
Set the environment variable GOOGLE_APPLICATION_CREDENTIALS to the file path of the JSON file that contains your service account key. This variable only applies to your current shell session, so if you open a new session, set the variable again.
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\Users\username\Downloads\service-account-file.json"
https://firebase.google.com/docs/admin/setup?authuser=3
I was getting this error when running firebase emulators:start.
As per the investigation from this bug: https://github.com/firebase/firebase-tools/issues/1451, it seems that this is an issue with referencing the app directly instead of via the admin module.
i.e. this causes the error:
const app = admin.initializeApp();
const firestore = app.firestore();
but this does not:
admin.initializeApp();
const firestore = admin.firestore();
However for the original question, you're using admin.firestore() so that wouldn't be the problem. It seems that admin.initializeApp() is never called. Perhaps that could be the cause of your issue?

Categories