Firebase Functions cannot always save a user to Realtime Database - firebase

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();.
});

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 can I apply an asynchronism to a firebase function?

I have an application that sends data to the firebase realtime database. Now I'm creating a dashboard to manage this data. At the moment I need to receive a notification on the dashboard when the user sends some new data to the firebase. I need to receive a message with the data id he sent and submit a notification similar to a social network.
I'm using FCM, I've already configured and tried to implement the onCreate () function. But when the bank is upgrading, this function is not being performed.
I'm implementing the code in the dashboard
I believe it is a lack of asynchronism. is there any way to apply it to the function call?
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.makeUppercase = functions.database
.ref("/users/{userID}")
.onCreate((snapshot, context) => {
const original = snapshot.val();
console.log("new User " + original);
});

create user with self-specified uid

I am using flutter with firebase to manage my users, and in this link, it says you can specify the uid during user creation: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
My question: What's the equivalent in dart/ flutter? I understand firebase auto-generates one for you, but in my use case I need to be able to specify mine.
For flutter, I am only aware of createUserWithEmailAndPassword method but it does not have a 'uid' argument.
FirebaseAuth.instance.createUserWithEmailAndPassword(email: null, password: null)
In the link above, however, they provided an example (node.js) with such methods.
admin.auth().createUser({
uid: 'some-uid',
email: 'user#example.com',
phoneNumber: '+11234567890'
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch(function(error) {
console.log('Error creating new user:', error);
});
You can fully control the creation of Firebase Authentication users by implementing a custom provider. But as you can probably imagine, this is a sensitive operation, so the code requires that you have full administrative access to the Firebase project. For that reason, you can't run this type of operation in your Flutter app, but must run it on a trusted environment, such as your development machine, a server you control, or Cloud Functions.
Typically this means:
you'll gather the user's credentials in your Flutter app
send them (securely) to a custom endpoint on the server
and there validate the the user credentials are correct
and use the Admin SDK to create a token for the user
that you then send back securely to the Flutter app
There is no such option for any of the Firebase the client SDKs on any platform (Android, iOS, web, Flutter, etc).
Firebase Admin is a server SDK, and trusts that the code calling its methods is privileged and running in a secure and trusted environment. This is considered to be safe for inventing UIDs. The client SDKs are run on user devices, which is considered untrusted and could be compromised. In that case, the Firebase Auth product has to come up with an appropriate UID for the user.
Use the firebase cloud functions
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.createUser1 = functions.https.onCall(async (data, _context) => {
try {
const user = await admin.auth().createUser({
uid: data.uid,
phoneNumber: data.phoneNumber,
disabled: false,
}); return {response: user};
} catch (error) {
throw new functions.https.HttpsError("failed to create a user");
}
});
then request from flutter app
{
"data":
{
"uid": "12345678",
"phoneNumber": "+905378227777",
"disabled": false
}
}

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.

How to get the email of any user in Firebase based on user id?

I need to get a user object, specifically the user email, I will have the user id in this format:
simplelogin:6
So I need to write a function something like this:
getUserEmail('simplelogin:6')
Is that possible?
It is possible with Admin SDK
Admin SDK cannot be used on client, only in Firebase Cloud Functions which you can then call from client. You will be provided with these promises: (it's really easy to set a cloud function up.)
admin.auth().getUser(uid)
admin.auth().getUserByEmail(email)
admin.auth().getUserByPhoneNumber(phoneNumber)
See here https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data
In short, this is what you are looking for
admin.auth().getUser(data.uid)
.then(userRecord => resolve(userRecord.toJSON().email))
.catch(error => reject({status: 'error', code: 500, error}))
full snippet
In the code below, I first verify that the user who calls this function is authorized to display such sensitive information about anybody by checking if his uid is under the node userRights/admin.
export const getUser = functions.https.onCall((data, context) => {
if (!context.auth) return {status: 'error', code: 401, message: 'Not signed in'}
return new Promise((resolve, reject) => {
// verify user's rights
admin.database().ref('userRights/admin').child(context.auth.uid).once('value', snapshot => {
if (snapshot.val() === true) {
// query user data
admin.auth().getUser(data.uid)
.then(userRecord => {
resolve(userRecord.toJSON()) // WARNING! Filter the json first, it contains password hash!
})
.catch(error => {
console.error('Error fetching user data:', error)
reject({status: 'error', code: 500, error})
})
} else {
reject({status: 'error', code: 403, message: 'Forbidden'})
}
})
})
})
BTW, read about difference between onCall() and onRequest() here.
Current solution as per latest update of Firebase framework:
firebase.auth().currentUser && firebase.auth().currentUser.email
See: https://firebase.google.com/docs/reference/js/firebase.auth.Auth.html#currentuser
Every provider haven't a defined email address, but if user authenticate with email. then it will be a possible way to achieve above solution.
To get the email address of the currently logged in user, use the getAuth function. For email and password / simplelogin you should be able to get the email like this:
ref = new Firebase('https://YourFirebase.firebaseio.com');
email = ref.getAuth().password.email;
In my opinion, the password object is not very aptly named, since it contains the email field.
I believe it is not a Firebase feature to get the email address of just any user by uid. Certainly, this would expose the emails of all users to all users. If you do want this, you will need to save the email of each user to the database, by their uid, at the time of account creation. Other users will then be able to retrieve the email from the database by the uid .
simple get the firebaseauth instance.
i created one default email and password in firebase. this is only for the security so that no one can get used other than who knows or who purchased our product to use our app.
Next step we are providing singup screen for user account creation.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String email = user.getEmail();
every time user opens the app, user redirecting to dashboard if current user is not equal to our default email.
below is the code
mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser() != null){
String EMAIL= mAuth.getCurrentUser().getEmail();
if (!EMAIL.equals("example#gmail.com")){
startActivity(new Intent(LoginActivity.this,MainActivity.class));
finish();
}
}
i Am also searching for the same solution finally i got it.
I had the same problem. Needed to replace email in Firestore by uid in order to not keep emails all around the place. It is possible to call it from a script on your computer using Service Account. You don't need Firebase Functions for this.
First Generate service account and download its json key.
Firebase Console > gear icon > Project settings > Service accounts > Generate a new private key button.
https://console.firebase.google.com/u/0/project/MYPROJECT/settings/serviceaccounts/adminsdk
Then create project, add the key and call the Admin SDK.
npm init
npm install dotenv firebase-admin
Place the json key file from above into .keys directory, keeping the project directory clean of keys files. Also .gitignore the directory.
Write the path of the json key file into .env file like this: GOOGLE_APPLICATION_CREDENTIALS=".keys/MYPROJECT-firebase-adminsdk-asdf-234lkjjfsoi.json". We will user dotenv to load it later.
Write following code into index.js:
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
(async () => {
const email = "admin#example.com";
const auth = admin.auth();
const user = await auth.getUserByEmail(email);
// Or by uid as asked
//const user = await auth.getUser(uid);
console.log(user.uid, user.email);
//const firestore = admin.firestore();
// Here be dragons...
})();
Run as follows node -r dotenv/config index.js
See the docs
Current solution (Xcode 11.0)
Auth.auth().currentUser? ?? "Mail"
Auth.auth().currentUser?.email ?? "User"

Resources