Firestore in functions throws billing error about external traffic - firebase

I have a firebase function for creating thumbnails whenever an image is uploaded. It's working great!
I then decided I wanted to store the thumbnail URL in the proper document in a firestore collection. I went through the examples and I found the relevant code to access my firestore through the admin object
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
// etc
return admin.firestore()
.collection("my-collection")
.doc(colId)
.set({ thumbUrl: fileUrl });
But when I added this code the function started failing with this message:
Billing account not configured. External network is not accessible and
quotas are severely limited. Configure billing account to remove these
restrictions
My code
// before is the upload to bucket that works
.then(() => {
fs.unlinkSync(tempFilePath);
return Promise.all([
tempFilePath.getSignedUrl({
action: "read",
expires: "03-09-2491"
})
]);
})
.then(results => {
console.log("Got thumb signed URL");
const thumbResult = results[0];
const thumbFileUrl = thumbResult[0];
// Add the URLs to the Database
return admin
.firestore()
.collection("my-collection")
.doc(colId)
.set({ thumbUrl: thumbFileUrl });
})
.then(() => console.log("User was updated with thumb url"));
Well, I understand that the free plan only allows external access to Google's internal stuff but I'm using firestore... it's Google.
What could be wrong?
firebase-admin is at v5.4.2
firebase-functions is at v0.7.1

When you are using the free Spark plan these messages will appear even if you are not trying to do any external access.

Related

Firebase Admin SDK within Firebase Functions CLI - Error fetching access token

I'm trying to use firebase-admin sdk to update my users passwords manually, the idea is to use a onCreate trigger to achieve this by creating a new document in firestore (with the right rules obviously).
According to firebase documentation i don't need to use anything else than this to autenticate from my firebase functions environment:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
In order to test the function i just manually added the document right from the firebase console ui for firestore, and as i can see the trigger is just doing it's work, the problem is when updatin the user password using the firebase-admin sdk.
I'm getting the next error message from the logs:
Error updating user: { Error: Credential implementation provided to
initializeApp() via the "credential" property failed to fetch a valid
Google OAuth2 access token with the following error: "Error fetching
access token
this is the whole firebase cloud function if you want to see how it's implemented:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
triggerNewDocument();
function triggerNewDocument() {
exports.updateUserData = functions.firestore
.document('updateUserPasswords/{userId}')
.onCreate((snap, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = snap.data();
console.log(newValue);
// access a particular field as you would any JS property
const uid = newValue.uid;
const newPassword = newValue.password;
return updateUserPassword(uid, newPassword);
// perform desired operations ...
});
}
function updateUserPassword(uid, newPassword) {
admin.auth().updateUser(uid, {
password: newPassword,
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
return userRecord.toJSON();
})
.catch(function(error) {
return error;
});
}
Is there anything may i be missing here?, thanks in advance for any hint or help you could provide.
The whole issue was somehow the service account autogenerated with the admin-sdk was inactive.
Anyway i had to disable and enable the service account at least 3 times to make it work, hopefully this can be helpful for anyone having the same issue.
A simple mistake

How to disable account creation in firebase authentication

I've a project in which I used to authenticate the users with firebase-auth.In my project users can not create their accounts on their own.Only admin have the privilege to add the user accounts.
In order to use onAuthStateChanged() function I must use firebase-auth in my page.But the issue is because of using firebase-auth on client side one can esaily create accounts by running createUserWithEmailAndPassword() function on the console without having the admin privilege.
Now how can I restrict the people from using createUserWithEmailAndPassword() function on client side?
The only way you can stop clients apps from creating accounts is to disable all authentication providers for your project in the Firebase console. You could write an auth onCreate Cloud Function that attempts to figure out if a new account was created by client or admin code if you want to try to delete it immediately.
I think you can add a claim once the user is added, via a cloud function, which requires authorization, so that if the user doesn't have that claim he can't use the app or can't login.
In 2022 with Firebase Auth with Identity Platform and blocking functions, we can accomplish that the following way:
Create an HTTP function that receives email, password and displayName, and creates user using firebase-admin:
import { https } from 'firebase-functions';
import { getAuth } from 'firebase-admin/auth';
import cors from 'cors';
const auth = getAuth();
// Register an HTTP function with the Functions Framework
export const signupUser = https.onRequest((req, res) => {
const options = {
origin: 'http://localhost:3000'
};
cors(options)(req, res, () => {
console.log('all good');
auth
.createUser({
email: 'example#email.com',
emailVerified: false,
password: 'secretPassword',
displayName: 'John Doe',
disabled: false,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch((error) => {
console.log('Error creating new user:', error);
});
// Send an HTTP response
res.send('OK');
});
});
Modify response and origin in CORS as you need.
Now create a blocking beforeCreate function and check for user's display name, if there is no display name, throw an error:
import { auth } from "firebase-functions";
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import postmark from 'postmark';
const app = initializeApp({
credential: applicationDefault(),
projectId: 'your_project_id',
});
const tnc = getAuth(app);
export const signUp = auth
.user().beforeCreate((user, context) => {
if (!user.displayName) {
throw new auth.HttpsError('permission-denied');
}
});
This will work because there is no way to include "display name" when signing up via client side
So you, in short, point is to create a Cloud Function that will register users and make sure to add the check to beforeCreate for something that you know is only possible to do on server-side via firebase-admin sdk.
EDIT: CORRECTION
Just found out you can now disable client side signup from Firebase Console if you have Auth + Identity Platform

Firebase Functions cannot always save a user to Realtime Database

I use Firebase auth and realtime database in my Android app. This is the code that I use in Firebase functions to save the user email into the realtime database when they register in the app with email:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.initializeUserProfile = functions.auth.user().onCreate(user => {
const userUid = user.uid;
return admin.auth().getUser(userUid).then(userRecord => {
const userProfile = {
email: userRecord.email
};
return admin.database().ref(`/profiles/${userUid}`).set(userProfile);
}).catch(error => {
console.log("Error fetching user data: ", error);
});
});
exports.removeUserProfile = functions.auth.user().onCreate(user => {
const userUid = user.uid;
return admin.database().ref(`/profiles/${userUid}`).remove();
});
When I register an user in the android app (I use the built in registration UI for Firebase), it gives me no error in the Functions logs:
My problem is that although I don't have an error in the log and the user was added to the Firebase Authentication section, the Realtime database doesn't contain the node with the email. The problem is very sporadic. Sometimes it registers it fine into the realtime database, but sometimes it doesn't (like in the log of Jun 25). In the Android app I try to query the database node of the user after registration to display they email and there I get an error (maybe it is an bug in my app, but anyhow, that code up there should be run on server side and the email should be in the Firebase Realtime Database).
What I also don't know is that why do I have those removeUserProfile calls in the log as I didn't remove any user from the Authentication database or from the Realtime database.
Actually, your two Cloud Functions are triggered with exactly the same event, i.e. onCreate(user). So it is normal that they are triggered (almost) simultaneously and that you see the two invocations in the log.
Since you write that "The problem is very sporadic" what is probably happening is that the new record is first created at /profiles/${userUid} by the initializeUserProfile Cloud Function BUT is then removed by the removeUserProfile Cloud Function.
So you should change the trigger of the removeUserProfile Cloud Function to onDelete():
exports.removeUserProfile = functions.auth.user().onDelete((user) => {
const userUid = user.uid;
return admin.database().ref(`/profiles/${userUid}`).remove();.
});

Add firebase console user with permission to add Auth users, but not delete database

I have a client who wants access to the Firebase console so they can add users manually themselves in the Authentication module.
I tried to add them via "Users and Permissions" but could not find any roles which fit adding users in authentication and no write permission in the database.
For the moment I added them as Project Editor, but not comfortable with it.
Granting admin access to your app dashboard is probably not the right answer for administrating in-app users. It could even be a security risk. It is, in my mind, equivalent to giving your app users access to your physical server via a shell prompt instead of creating an API for them to call.
A better alternative here would be to set up a Google Cloud Functions endpoint which would accept API requests and create users on their behalf, validating their access privileges by some criteria you determine.
1) Enable and deploy Cloud Functions
2) Set up an Authenticated HTTPS endpoint
3) Function code for creating a new user would look something like this:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();
// See https://github.com/firebase/functions-samples/blob/Node-8/authorized-https-endpoint/functions/index.js
const validateFirebaseIdToken = require('./validateFirebaseIdToken');
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/createUser', (req, res) => {
const userData = req.params;
// This represents some criteria you set for determining who can call this endpoint
// possible a list of approved uids in your database?
if( req.user.uid !== VALID_ADMIN_USER ) {
res.status(401).send('Unauthorized');
return;
}
// See https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
admin.auth().createUser({
email: userData.email,
displayName: userData.name,
...
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
res.json({result: 'success', uid: userRecord.uid});
console.log("Successfully created new user:", userRecord.uid);
})
.catch(function(error) {
console.error("Failed to create new user");
console.error(error);
res.status(500).json({status: 'error', error: 'Unable to process the request'});
});
});
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);
4) Provide the API endpoint to your client or build a rudimentary app/web interface they can use that calls this endpoint.
So go to the Google Cloud Platform(from Firebase Console) and then choose Manage Roles from where you can create Custom roles.
Note that Custom Roles is currently in Beta and you might not be able to achieve what you need but as docs suggest:
Custom roles let you group permissions and assign them to members of
your project or organization. You can manually select permissions or
import permissions from another role.

Secure HTTP trigger for Cloud Functions for Firebase

Is there a way to check if a user is firebase-authorized before triggering a cloud function? (Or within the function)
Yes. You will need to send the Firebase ID token along with the request (for example in the Authorization header of an AJAX request), then verify it using the Firebase Admin SDK. There is an in-depth example in the Cloud Functions for Firebase samples repository. It looks something like this (made shorter for SO post):
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')();
const validateFirebaseIdToken = (req, res, next) => {
cors(req, res, () => {
const idToken = req.headers.authorization.split('Bearer ')[1];
admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
}).catch(error => {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
});
});
};
exports.myFn = functions.https.onRequest((req, res) => {
validateFirebaseIdToken(req, res, () => {
// now you know they're authorized and `req.user` has info about them
});
});
Since the question asks for auth-based access (1) within, or (2) before a function, here's an method for the "before" case: >
Since every Firebase Project is also a Google Cloud Project -- and GCP allows for "private" functions, you can set project-wide or per-function permissions outside the function(s), so that only authenticated users can cause the function to fire.
Unauthorized users will be rejected before function invocation, even if they try to hit the endpoint.
Here's documentation on setting permissions and authenticating users. As of writing, I believe using this method requires users to have a Google account to authenticate.

Resources