Firebase Functions triggered by database change in my simple case - firebase

In my firebase database, I have a "users" node.
I use Firebase Functions for backend functions. My javascript code (node.js) running in Functions needs to do something when a new user is added to database, here is the backend function code:
// required modules
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// my backend function
exports.onUserCreated = functions.auth.user().onCreate(event => {
// Do something when a new user is created
}
I successfully deployed my backend function to firebase Functions.
Then, I manually added a user under users node of firebase database. Then, I go to Firebase Functions, I see the onUserCreated Function listed there in console, but the number of executions is 0.
What could be the reason why my function is not triggered when I manually added a user in database?

Your function is an authentication trigger, not a database trigger:
functions.auth.user().onCreate(...)
It will trigger when a new user is added via Firebase Authentication, not when there is a change in your database.
If you want to write a database trigger, follow the instructions here instead.

What's happening is that you've created a function that is triggered by Firebase Auth events. Which means that it will only be triggered when a new User signs up to use your application. Not when he writes to the database.
If you want it to be triggered when you create a new user under the "users" node, you should use a Realtime Database trigger:
exports.onUserCreated = functions.database.ref('/user/{userId}')
.onWrite(event => {
// Do something when a new user is created
}
EDIT: Accessing the user's email stored in the database:
exports.onUserCreated = functions.database.ref('/user/{userId}')
.onWrite(event => {
// Do something when a new user is created
var email = event.data.val().email;
}

Related

What is the correct way to use next-auth with Google OAuth and users stored in the database?

I'm new to next-auth, and I'm looking for some help.
I have added the Google OAuth provider, and now when I run signIn("google") function on the frontend, it automatically takes me to the google's login page, and logs me in, somehow, without ever touching my database.
When the google authentication is complete, I need to be able to create a new user in my database, or retrieve the existing one if they have already signed up before (because I need to store all kinds of custom information about the user, not just their email).
And I want to make user's information available on the session object from useSession()hook. Right now I'm seeing some kind of default user info (with name, email, and image field which I didn't define).
When I was using a regular Express server and Passport, the code looked kinda like this:
const googleAuth = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/api/v1/profiles/google/callback",
},
async (req, accessToken, refreshToken, profile, done) => {
const existingProfile = await Profile.findOne({ email: profile.emails[0].value })
/* Found user, return him and move along. */
if (existingProfile) return done(null, existingProfile)
/* Haven't found profile with this googleId, create a new one. */
const newProfile = await new Profile({
googleId: profile.id,
email: profile.emails[0].value,
})
newProfile.save()
done(null, newProfile)
}
)
So I would still be creating the users in my database, and retrieving their information on log in, so that I could send it to the client.
Where does this kind of code supposed to go when I'm using the serverless next-auth?
And a second, but kind of related question - what's that default user object that gets provided to me in the session object? The one with the name, email, and image fields that next-auth seems to create for me? How can I make it use the user object I'm returning from my database instead?
(I've done my best to look through the tutorials and examples, but couldn't find one that explains this clearly.)
I don't know if you still need this, but I hope it helps someone:
Oauth kinda mixes up Sign In and Sign Up, so if you want to have Google authentication what you probably want to do is create a callback of the Sign In function in /api/auth/[...nextauth].js, then get the account and profile as parameters and access to its provider.
async signIn({account, profile}) {
if(account.provider === 'google') {
//check if user is in your database
if(user NOT in DB) {
//add your user in DB here with profile data (profile.email, profile.name)
}
return true
}
You always want to return true since you always want to log in independently if it is in your DB or not.
Regarding the session object, you can also add a callback and access to the default session (that you can modify), token and user. Here you can retrieve all information you want from your database, add it to the session object and return it.
async session({ session, token, user }) {
const newData = DB.find(...).data
session.newfield = newInfo
return session
}

How to check if client's contacts are using my app?

I'm currently developing an app using Firebase.
My Firestore Database looks like below:
Once the user passes the Firebase authentication procedure, I'm creating a user document with a field "Phone:" which contains his phone number. Basically, everyone who is gonna using the app will be listed in the database.
And here is my challenge:
I'm using the plugin easy_contact_picker to store all the contacts of the users device to a List.
How can I find out whether the users contacts are using the app or whether they are listed in the database?
My goal is create a contact List Widget which shows me all my contacts. But those contacts which are using the app or which are listed in the database, should be highlighted or marked particularly.
Which is the best way to realize that if we consider that millions of users (to minimize computing power)are listed in the database?
Anyone has an idea?
Thanks a lot
First of all try to awoid giving everyone access to read all users. That is something most ppl do when handling such a problem. The do it because the query over all users won't work if you don't give the rights to read all of them.
Because of security reasons I would move the logic for checking if a user exists into callable function (not a http function!). That way you can call it inside of your app and check for a single user or multiple of them in an array. That would depend how your frontend would handle it.
Very importand would be to store all phone numbers in the absolute same format. That way you could query for them. Regardless of the number of users you could always find a specific one like here:
var citiesRef = db.collection("users");
var query = citiesRef.where("Phone", "==", "+4912345679");
The numbers need to be absolutely the same without any emtpy spaces - chars and the +49 or 0049 also needs to be the same.
You could create two callable funcitons. One to check if a single user exists in your app and another where you send an array of phone numbers and you get an array back. The cloud function can use Promise.all to performe such queries in parallel so you get your responce quite fast.
I'm using a similar approach to add users in my app as admins to specific groups where you just enter the email of the user and if he is in the app he will be added. I not he get's an invitation on the email to join the App.
With the help of Tarik's answer, Ayrix and I came up with the following solution.
Important: Read Tarik's answer for more information.
Client: callable_compare_contacts.dart
import 'package:cloud_functions/cloud_functions.dart';
Future<List<Object>> getMembersByPhoneNumber(List<String> allPhoneNumbers) async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('membersByPhoneNumber');
final results = await callable.call(<String, dynamic>{'allPhoneNumbers': allPhoneNumbers});
return results.data;
}
Server: index.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
if (admin.apps.length === 0) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
}
exports.membersByPhoneNumber = functions.https.onCall((data, context) => {
return new Promise((resolve, reject) => {
if (!data || !data.allPhoneNumbers.length) return resolve([]);
const phoneNumbers = data.allPhoneNumbers;
// TODO: different scope? move vars for future use
const db = admin.firestore();
const collectionRef = db.collection("User");
let batches = [];
// because of wrong eslint parsing (dirty)
batches = [];
while (phoneNumbers.length) {
// firestore limits batches to 10
const batch = phoneNumbers.splice(0, 10);
// add the batch request to to a queue
batches.push(
new Promise((response) => {
collectionRef.where("Phone", "in", [...batch]).get()
.then((results) =>
response(results.docs.map(function(result) {
return result.data().Phone;
} )));
})
);
}
// response / return to client
Promise.all(batches).then(function(content) {
// console.log("content.flat()");
// console.log(content.flat());
return resolve(content.flat());
});
});
});
Note: This is our first callable/cloud function .. so Suggestions for changes are welcome.

Firebase Functions - Function not being called

I have created the following functions:
It has been deployed and is there on the firebase hosting, but it just doesn't ever get called (usage is 0)...
It should be called when a user document is changed and then update a field to null if it wasn't already null.
Can anyone see why this is not running?
exports.deleteField = functions.database.ref('/Users/{userID}')
.onUpdate((change, context) => {
const overrideTag = change.after.data().overrideTag
if (overrideTag !== null) {
const db = admin.firestore()
db.collection('Users').doc(userID).set({ overrideTag: null })
}
})
Kind Regards,
Josh
Your function is configured to trigger on changes to a node called "Users" in Realtime Database. Realtime Database doesn't have "documents". However, Firestore does have documents. If you meant to trigger when a document is changed in Firestore, you will have to write a Firestore trigger instead. It will use functions.firestore instead of functions.database.

Firebase database .on('value') method called even if child added in same level

I am a newbie in using Firebase database. I am trying to store user login time in Firebase database. Sample code below.
const USER_LOGIN_TIME = "UserLoginTime";
const userId = localStorage.getItem("userId")!;
const spaceId = localStorage.getItem("spaceId")!;
const loginTime = database.ref(`${spaceId}/${USER_LOGIN_TIME}/${userId}`);
loginTime.set(time);
loginTime.on('value', (snapshot: any) => {
let latestTime = snapshot.val();
// This is getting called even if some new user logs in
// some logic goes here
});
loginTime is stored properly.
I am assuming .on('value') will be called only if the value changes in ${spaceId}/${USER_LOGIN_TIME}/${userId}.
But the issue is .on('value') method is getting called whenever there is new entry in ${spaceId}/${USER_LOGIN_TIME}. New entry will be added when new user logs in a different browser.
Not sure what I am doing wrong here! Any help is appreciated.
Update
My Firebase DB schema:
- <spaceID>
- UserLoginTime
- <UserID>
- time: <timestamp>
- <UserID>
- time: <timestamp>
The issue is not related to Firebase, but it is my code. During the initial load, spaceID and userId is null for all users and time is set for null key. This gets updated whenever a new user logs in and old users in the same session will get the updates and hence logged out.

How to create unique and safe UID on Admin- Custom Token- Firebase

I use custom auth function on my app, in order to login anonymous users (by saving a unique permanent custom token on their device).
The functions work fine both on app side and admin side but I need to provide a unique UID on the admin side- in order to save the user on Firebase.
Here's my code on Admin (index.js):
exports.createToken = functions.https.onCall((data, context) => {
const uid = "?????"; // how to create some unique uid here?
return admin.auth()
.createCustomToken(uid)
.then(customToken => {
console.log(`The customToken is: ${customToken}`);
return {status: 'success', customToken: customToken};
})
});
I need to create unique uid (that isn't on my Authentication users
table already).
It should be "safe" (that two users won't get it at the same time).

Resources