GitHub continuous deployment to Firebase hosting and env variables - firebase

I am currently working on a React application with Firebase initialised. I am initialising my React App with Firebase by doing the following:
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID
})
const db = firebase.firestore()
export { db, app }
For obvious reasons, I do not want to push my env file to GitHub. But if I do not, my application build fails. I am wondering how I can configure GitHub to house my env variables and for my production build to use the GitHub env variables. I have attempted to set up a GitHub build workflow, which contains the env variables, but my build still seems to fail.
On a separate note, I am curious how much of a security risk it is for my Firebase config to be exposed. I read if my application is using an email/password sign-in method, I should protect these variables. Any advice, suggestions, critiques would be much appreciated.

Just use GitHub Actions with secrets and echo with the runner:
- name: 🗄️ Provide credentials
id: firebase-credentials
run: echo 'const app = firebase.initializeApp({apiKey: "${{ secrets.FIREBASE_API_KEY }}", ... })' > ./some.js

You can use Firebase Cloud Functions and set an environment variable in the terminal:
firebase functions:config:set someservice.key="API KEY" someservice.id="CLIENT ID"
For example, to set up the Cloud Function for Firebase:
firebase init functions
npm install --save firebase-functions#latest
npm install -g firebase-tools
You can then access the variables from a function:
const functions = require('firebase-functions');
const request = require('request-promise');
exports.userCreated = functions.database.ref('/users/{id}').onWrite(event => {
let email = event.data.child('email').val();
return request({
url: 'https://someservice.com/api/some/call',
headers: {
'X-Client-ID': functions.config().someservice.id,
'Authorization': `Bearer ${functions.config().someservice.key}`
},
body: {email: email}
});
});

Related

WebConfig doesn't return measurementId

I am trying to enable firebase analytics in my existing firebase project. The project is a static React website that only uses Firebase hosting.
Following this get start tutorial, I am getting the following error in my console:
Ignored "config" command. Invalid arguments found
Searching how to solve this problem, I found this comment and checked that my webConfig get request is not returning the measurementId. However I couldn't find any info about how to correct it.
//firebase.js
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics} from "firebase/analytics";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "{ApiKey}",
authDomain: "{projectId}.firebaseapp.com",
projectId: "{projectId}",
storageBucket: "{projectId}.appspot.com",
messagingSenderId: "{messagingSenderId}",
appId: "{appId}",
measurementId: "{measurementId}",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const analytics = getAnalytics(app);
WebConfig call (Http 200, Get):
response:
{
"projectId": "{projectId}",
"appId": "{appId}",
"storageBucket": "{projectId}.appspot.com",
"authDomain": "{projectId}.firebaseapp.com",
"messagingSenderId": "{messagingSenderId}"
}
Is there any config that I am missing? what should I do to make it work?
There could be something wrong with the stream for your web app that’s why the measurementId is not being configured. You could try to unlink and relink to your Google Analytics integration which usually resolves any broken integration. Make sure that the currently linked GA property is the one you’re going to use for relinking to avoid losing your data.

Firebase callable functions fail

Many functions have started to fail intermittently with the following error even before the first line of code is executed:
FIREBASE WARNING: {"code":"app/invalid-credential","message":"Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Failed to parse access token response: SyntaxError: Unexpected token p in JSON at position 4"."}
It's been more than 5 five days since i had opened a bug issue with Firebase support and i still don't have any feedback.
Does anyone why this is happening and how it can be fixed?
Libraries used and their versions:
"firebase": "^8.2.9",
"firebase-admin": "^9.5.0",
"firebase-functions": "^3.1.0"
Initializations:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var config = {
apiKey: "...",
authDomain: "...",
databaseURL: "..."
};
var firebase = require("firebase");
firebase.initializeApp(config);
In a Cloud Function, if you want to interact with the Firebase services (e.g. Firestore, Auth service, etc), you need to use the Admin SDK.
So, you need to load the firebase-functions and firebase-admin modules, and initialize an admin app instance from which you interact with the services, as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestoreDB = admin.firestore();
const authService = admin.auth();
// ...
// Examples:
// In a Cloud Function
return firestoreDB.collection("cities").doc("LA").set({
name: "Los Angeles",
state: "CA",
country: "USA"
});
// In another Cloud Function
return authService.updateUser(uid, {
email: 'modifiedUser#example.com',
phoneNumber: '+11234567890',
})
.then((userRecord) => {
// ...
// return ...
})
In other words, you don't need to do:
var config = {
apiKey: "...",
authDomain: "...",
databaseURL: "..."
};
var firebase = require("firebase");
firebase.initializeApp(config);
Also note this note from the doc:
In many cases, new features and bug fixes are available only with the
latest version of the Firebase CLI and the firebase-functions SDK.
It's a good practice to frequently update both the Firebase CLI and
the SDK with these commands inside the functions folder of your
Firebase project:
npm install firebase-functions#latest firebase-admin#latest --save
npm install -g firebase-tools

How do I wire up the firestore emulator with my firebase functions tests?

Currently we are using 'firebase-functions-test' in online mode to test our firebase functions (as described here https://firebase.google.com/docs/functions/unit-testing), which we setup like so:
//setupTests.ts
import * as admin from 'firebase-admin';
const serviceAccount = require('./../test-service-account.json');
export const testEnv = require('firebase-functions-test')({
projectId: 'projectId',
credential: admin.credential.cert(serviceAccount),
storageBucket: 'projectId.appspot.com'
});
const testConfig = {
dropbox: {
token: 'dropboxToken',
working_dir: 'someFolder'
}
};
testEnv.mockConfig(testConfig);
// ensure default firebase app exists:
try {
admin.initializeApp();
} catch (e) {}
We would like to move away from testing against an actual firestore instance in our tests, and use the emulator instead.
The docs, issues, and examples I've been able to find on the net are either outdated, or describe how to set up the emulator for testing security rules, or the web frontend.
Attempts using firebase.initializeAdminApp({ projectId: "my-test-project" }); did not do the trick.
I also tried setting FIRESTORE_EMULATOR_HOST=[::1]:8080,127.0.0.1:8080
So the question is: How can I initialise the firebaseApp in my tests, so that my functions are wired up to the firestore emulator?
I had another crack at it today, more than a year later, so some things have changed, which I can't all list out. Here is what worked for me:
1. Install and run the most recent version of firebase-tools and emulators:
$ npm i -g firebase-tools // v9.2.0 as of now
$ firebase init emulators
# You will be asked which emulators you want to install.
# For my purposes, I found the firestore and auth emulators to be sufficient
$ firebase -P <project-id> emulators:start --only firestore,auth
Take note of the ports at which your emulators are available:
2. Testsetup
The purpose of this file is to serve as a setup for tests which rely on emulators. This is where we let our app know where to find the emulators.
// setupFunctions.ts
import * as admin from 'firebase-admin';
// firebase automatically picks up on these environment variables:
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';
admin.initializeApp({
projectId: 'project-id',
credential: admin.credential.applicationDefault()
});
export const testEnv = require('firebase-functions-test')();
3. Testing a simple function
For this, we setup a simple script which writes a document to firestore. In the test, we assert that the document exists within the emulator, only after we have run the function.
// myFunction.ts
import * as functions from 'firebase-functions';
import {firestore} from 'firebase-admin';
export const myFunction = functions
.region('europe-west1')
.runWith({timeoutSeconds: 540, memory: '2GB'})
.https.onCall(async () => {
await firestore()
.collection('myCollection')
.doc('someDoc')
.set({hello: 'world'});
return {result: 'success'};
});
// myTest.ts
// import testEnv first, to ensure that emulators are wired up
import {testEnv} from './setupFunctions';
import {myFunction} from './myFunction';
import * as admin from 'firebase-admin';
// wrap the function
const testee = testEnv.wrap(myFunction);
describe('myFunction', () => {
it('should add hello world doc', async () => {
// ensure doc does not exist before test
await admin
.firestore()
.doc('myCollection/someDoc')
.delete()
// run the function under test
const result = await testee();
// assertions
expect(result).toEqual({result: 'success'});
const doc = await admin
.firestore()
.doc('myCollection/someDoc')
.get();
expect(doc.data()).toEqual({hello: 'world'});
});
});
And sure enough, after running the tests, I can observe that the data is present in the firestore emulator. Visit http://localhost:4000/firestore while the emulator is running to get this view.

Firebase: Update Firestore Fails If Document Does Not Exist [duplicate]

This is my very basic Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore()
exports.createdNewAccount = functions.auth.user().onCreate(event => {
return db.collection('users').doc(event.data.uid).update({
"creationDate" : Date.now()
})
})
And I get the error
Error: no entity to update: app
What is wrong with my code?
Most likely, the document for event.data.uid does not exist. The documentation for update() states:
The update will fail if applied to a document that does not exist.
Use set() instead.
I faced a similar error when testing my app locally with the Firebase emulator. My firebase config file looked like:
import firebase from "firebase";
import "firebase/firestore";
const firebaseConfig = {
apiKey: <FIREBASE_API_KEY>,
authDomain: <FIREBASE_AUTH_DOMAIN>,
databaseURL: <FIREBASE_DB_URL>,
projectId: <FIREBASE_PROJECT_ID>,
storageBucket: <FIREBASE_STORAGE_BUCKET>,
messagingSenderId: <FIREBASE_MSG_SENDER_ID>,
appId: <FIREBASE_APP_ID>,
measurementId: <FIREBASE_MEASUREMENT_ID>,
};
// Initialize Firebase
const firebaseApp = firebase.initializeApp(firebaseConfig);
// for local instances, use the emulator to connect the frontend to firestore & functions
if (location.hostname === "localhost") {
firebase.firestore().settings({
host: "localhost:8080",
ssl: false,
});
firebase.functions().useFunctionsEmulator("http://localhost:5001");
}
Turns out my local Firestore database (expected to be running at localhost:8080) wasn't being hit. So my cloud functions were trying to write to a non-existent db. The underlying issue was that I also had my backend initializing to a different database:
// functions/index.js
const admin = require("firebase-admin");
var serviceAccount = require("./serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://other-firebase-project-id.firebaseio.com",
});
The solution (originally adapted from a Fireship tutorial) was to remove this [incorrect] re-initialization of the database altogether:
// functions/index.js
...
admin.initializeApp();
...
After all, according to the docs, we can initialize the Firebase Admin SDK without parameters since the FIREBASE_CONFIG environment variable is included automatically in Cloud Functions for Firebase functions that are deployed via the Firebase CLI.
FWIW, also be sure to set the correct Firebase project on the CLI. Doing so with this allows the Firebase CLI to hook Functions/Firestore to the right project:
firebase use <desired-firebase-project-id>
If you have multiple Firebase projects, you can list them out with: firebase projects:list

Use firebase auto SDK setup with Webpack

I am creating a web app that uses Vue webpack with firebase. I would like to have my firebase credentials automatically change when i use firebase use <some_alias> on the firebase cli. In other projects, this simply meant including the /__/firebase/init.js file of firebase hosting. In this project, I am using the npm firebase library and can load in a specific firebase set of credentials with
import firebase from 'firebase'
var config = {
apiKey: '...',
authDomain: '...',
databaseURL: '...',
projectId: '...',
storageBucket: '...',
messagingSenderId: '...'
}
firebase.initializeApp(config)
export default {
database: firebase.database,
storage: firebase.storage,
auth: firebase.auth
}
However, this does not get my credentials based on my current firebase workspace. Instead, I would like something like
import firebase from 'firebase'
const fbcli = require('firebase-tools');
export const getFirebaseInstance = () => {
return fbcli.setup.web().then(config => {
firebase.initializeApp(config)
return firebase
});
}
though synchronous. Is there any way to synchronously load in my firebase credentials?
This was solved by checking window.location.host when in the prod environment and having a production config object if the host was our production hostname and reading from the values of a configuration file otherwise.
Try using fs.writeFileSync as described in this example from a firebase blog post about reading credentials:
const fbcli = require('firebase-tools');
const fs = require('fs');
// by default, uses the current project and logged in user
fbcli.setup.web().then(config => {
fs.writeFileSync(
'build/initFirebase.js',
`firebase.initializeApp(${JSON.stringify(config)});`
);
});

Resources