Firebase Functions won't read document on Firestore - node.js

Hi I'm trying to read a users document stored on Firestore using Firebase Functions. Each user has a unique document with extra data that cannot be stored on Firebase Auth. The document name is the user UID.
But I can't access the doc when I'm trying to read it on my callable function.
Code to create doc when user is created:
exports.createdacc = functions.auth.user().onCreate(user => {
console.log('User created', user.phoneNumber);
return admin.firestore().collection('users').doc(user.uid).set({
number: user.phoneNumber,
verified: false,
});
});
Callable function to read that doc so I can make some decisions
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth){
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
user.get().then(doc => {
//temp code -- Not working
console.log('data read');
if (doc.get().verified){
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
Why cant I read the doc? Nothing inside there executes.

Try to use data() at callback of user.get()
user.get().then(doc => {
//you get user doc value by using data()
const userData = doc.data();
// then you can use all properties from userData
const verified = userData.verified;
});

You don't return the promise returned by user.get().then(...);: your Cloud Function may be cleaned up before the asynchronous work is complete and the response sent back to the front-end.
Note that doing doc.get().verified is incorrect: as you will see in the doc, you need to pass the field path of a specific field to this method. So either you do doc.get("verified") or you can do doc.data().verified;.
Therefore the following should work:
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
return user.get().then(doc => {
console.log('data read');
if (doc.get("verified") {
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
In addition, note that you may throw an error if the user document does not exist and return a specific error to the front-end, i.e. not the generic internal one (maybe not-found, see the list of possible codes).

I have seen, on occasion, that information coming in to the function via context and data are actually JSON, and not strictly a standard Javascript object. In a similar issue of matching (in my case, a customClaim on the context.auth.token), I had to do something like:
JSON.parse(JSON.stringify(context.auth.token.customCLaim))
They behave like an object (i.e. I can call/assign context.auth.token.customClaim), but results from a console.log are different.
console.log(context.auth.token.customCLaim);
//prints {"userID": "1234567890"}
console.log(JSON.parse(JSON.stringify(context.auth.token.customClaim)));
//prints {userID: "1234567890"}
Subtle, but it tripped me up in a few authentication cases.

Related

How to update the user object in back4app?

I use Node.js and back4app.com
I try to update the user object. Therefore I have read a lot and found this promissing documentation:
let progressId = "xyz";
let userId = "12354"; //aka objectId
const User = new Parse.User();
const query = new Parse.Query(User);
// Finds the user by its ID
query.get(userId).then((user) => {
// Updates the data we want
user.set('progressId', progressId);
// Saves the user with the updated data
user.save()
.then((response) => {
console.log('Updated user', response);
})
.catch((error) => {
console.error('Error while updating user', error);
});
});
But there also is a warning. It states:
The Parse.User class is secured by default, you are not able to invoke save method unless the Parse.User was obtained using an authenticated method, like logIn, signUp or current
How would this look like in code?
My solution
Well, I got it to work. While I figured it out, I have found some small show stoppers. I list it for anyone it may concern.
Thanks #RamosCharles I added the Master Key in Parse._initialize. Only with that .save(null, {useMasterKey: true}) works. Take notice, without null it also won't work.
That's my working code:
let progressId = "xyz";
const User = Parse.Object.extend('User'); //instead of const User = new Parse.User();
const query = new Parse.Query(User);
query.equalTo("objectId", '123xyz');
query.get(userId).then((userObj) => {
// Updates the data we want
userObj.set('progressId', progressId);
// Saves the user with the updated data
userObj.save(null, {useMasterKey: true}).then((response) => {
console.log('Updated user', response);
}).catch((error) => {
console.error('Error while updating user', error);
});
});
Now I'm wondering
why my working code is different from documentation?
how secure is my code? And what is to do to get it more secure?
Yes, their API Reference is very helpful! On this section, there's a "try on JSFiddle" button, have you already seen that?
To update a user object, you must use the Master Key. On the frontend, it's not recommended, and it's better to create a cloud code function and call it on your frontend. However, for test purposes, you can keep using the API Reference, but on JSFiddle, you need to do some changes, here is their sample code, but with the adjustments:
Parse.serverURL = 'https://parseapi.back4app.com';
Parse._initialize('<your-appID-here>', '<your-JSKey-here>', '<Your-MasterKey-here>');
const MyCustomClass = Parse.Object.extend('User');
const query = new Parse.Query(MyCustomClass);
query.equalTo("objectId", "<object-ID-here>");
query.find({useMasterKey: true}).then((results) => {
if (typeof document !== 'undefined') document.write(`ParseObjects found: ${JSON.stringify(results)}`);
console.log('ParseObjects found:', results);
}, (error) => {
if (typeof document !== 'undefined') document.write(`Error while fetching ParseObjects: ${JSON.stringify(error)}`);
console.error('Error while fetching ParseObjects', error);
});
You'll need to insert the "_" before the "initialize" in your "Parse._initialize" and insert the Master Key in your query as I did on the query.find.

Cloud Firestore | Getting all documents given a list of ObjectIds

Cloud Firestore doesn't yet support batch read of documents in a collection and so the next best thing is to use firestore.getAll() to retrieve documents given an array of ObjectId's as given in this link.
A similar question has already been posted and the above function was the answer to the question. But when I run the same snippet it somehow isn't working. Can someone please tell me why?
db.collection("users")
.doc(uid)
.get()
.then(data => {
// return array of accountId's associated with user
return data.data().accounts;
})
.then(arr => {
console.log(arr);
if (!arr) {
res.json({ message: "no associated accounts" }); // check if array is empty
}
let userAccounts = [], docRefs = [];
arr.forEach(id => docRefs.push(db.doc(`accounts/${id})`))); // dynamically creating DocumentReferences for each _id
console.log(docRefs); // valid output -> an array of 2 DocumentReference() objects
db.getAll(...docRefs).then(docs => { // I
docs.forEach(doc => {
if (doc.exists) {
console.log(doc.data()); // II
userAccounts.push(doc.data());
}
});
});
console.log(userAccounts); // III
res.json(userAccounts);
})
.catch(err => {
res.status(500).json({
status: err.code,
message: err.message
});
});
I. Since it's a dynamically generated array, I've used the spread operator.
II. This line is never executed as there is no output for this console.log() statement, not even an undefined.
III. Therefore the userAccounts is still [] after this, since II is never executed and neither is the push() after that and so, this is the response I get. No errors.
P.S. The arr is fine. I've cross-checked with the db. The db variable is a firestore() object.

NodeJs/React- How to store query results from firestore in variable

function collectres () {
var store ='';
var docRef = db.collection("cities").doc("SF");
docRef.get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
store = doc.data();// when referenced outside, it doesnt hold anything.
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch(function (error) {
console.log("Error getting document:", error);
});
return store; // returns nothing and seems to not notice the assignment.
}
I have this problem, where i want to store firebase query results into a variable. However, when i try and assign the variable some data from the query, it seems to not be able to store it.
Any help or advice in the right direction would help.
Edit 1:
After implementing the callback function, i was wondering how to set the state for a component or permanently store the results so that many components can access it.
user.CollectRes(function(store){
console.log(store.name);
name =store.name;
console.log(name);
// this.setState({name:store.name});
});
console.log(name); // want to be able to reference this outside the callback function. So i can display it on the page.
It is because JavaScript is asynchrone.
Because of that, your variable doc doesn't exists yet outside your .then function.
To return this value, you can use Promises way, or easier, you can have a callback function to return your document like this :
function collectres (callback) {
var docRef = db.collection("cities").doc("SF");
docRef.get().then(function (doc) {
if (doc && doc.exists) {
callback(doc.data()); // Return your data inside the callback function
} else {
callback(null); // Return null if data doesn't exists
}
}).catch(function (error) {
callback(null); // Return null in error case
});
}
collectres(function (store) { // Call collectres to get your data
console.log(store);
// continue here
});
I recommand you to read this article to learn more about asynchronous.
Hope it helps.

Failed to initialize to firebase

Doing a basic inquiry chatbot app on dialogflow using firebase as our database and for some reason, the code works like when it wants to.
Majority of the time, it doesn't work, it keeps providing this error "Not available"
But it actually did work twice with no changes to the code, which leads me to think that it is a database issue, if so, is there any way to solve it?
The code and the database will be below.
The code is currently being hard coded into "Animation" but it still does not work.
function getCourseMotto(agent){
if (action === 'CD_CMotto') {
var FullTimecourse = agent.parameters.Course;
//For course
//const product = request.body.queryResult.parameters.motto.trim();
const ref = db.ref(`CourseCategory/Full-Time/Animation/CourseMotto`);
//const ref = db.ref('Course Category/Full-Time/Animation');
return
ref.once('value').then((snapshot) => {
const product = snapshot.val();
if (product === null) {
response.json({
fulfillmentText: `No data found.`
});
return ;
}
else{
response.json({
fulfillmentText: `The course motto ${FullTimecourse} is ${product}`,
source: action
});
return ;
}
}).catch((err) => {
response.json({
fulfillmentText: `I don't know what is it error`
});
return;
});
we have also noticed that when the app connects to the database within 11ms, it works, but it usually takes much longer than that, which is probably the reason why it does not.

Check If Firebase User Exist Without Throwing Error

I have a website that offers a simple messaging service. Individuals can pay for the service, or a business can pay for a monthly subscription and then add their clients/users for free. When the business adds a client/user email, that triggers the function below. I'm using firebase functions and createUser to create the user on my server(less). However, sometimes a business tries to register a user and that user already exist. In this case, I want to send the user a reminder email.
The code I have works fine, but it feels funky having a chain within my catch/error. Is there another way to detect if an email is already registered with a Firebase account that won't throw an error?
exports.newUserRegisteredByBusiness = functions.database.ref('users/{uid}/users/invited/{shortEmail}').onWrite( (data, context) => {
//don't run function if data is null
if (!data.after.val()){
console.log('SKIP: newUserRegisteredByBusiness null so skipping')
return null
} else {
let businessUID = context.params.uid
let email = data.after.val()
let shortEmail = context.params.shortEmail
let password // = something I randomly generate
return admin.auth().createUser({ email: email, password: password}).then( (user)=> {
//write new user data
let updates = {}
let userData // = stuff I need for service to run
updates['users/' + user.uid ] = userData;
return admin.database().ref().update(updates)
}).then( () =>{
//email new user about their new account
return emailFunctions.newUserRegisteredByBusiness(email, password)
}).catch( (error) =>{
//if user already exist we will get error here.
if (error.code === 'auth/email-already-exists'){
//email and remind user about account
return emailFunctions.remindUsersAccountWasCreated(email).then( ()=> {
//Once email sends, delete the rtbd invite value that triggered this whole function
//THIS IS WHERE MY CODE FEELS FUNKY! Is it ok to have this chain?
return admin.database().ref('users/' + businessUID + '/users/invited/' + shortEmail).set(null)
})
} else {
//delete the rtbd value that triggered this whole function
return admin.database().ref('users/' + businessUID + '/users/invited/' + shortEmail).set(null)
}
});
}
})
To find if a user account was already created for a given email address, you call admin.auth().getUserByEmail.
admin.auth().getUserByEmail(email).then(user => {
// User already exists
}).catch(err => {
if (err.code === 'auth/user-not-found') {
// User doesn't exist yet, create it...
}
})
While you're still using a catch() it feels like a much less failed operation.
To avoid further implementation in the catch block you can wrap this Firebase function into this code:
async function checkUserInFirebase(email) {
return new Promise((resolve) => {
admin.auth().getUserByEmail(email)
.then((user) => {
resolve({ isError: false, doesExist: true, user });
})
.catch((err) => {
resolve({ isError: true, err });
});
});
}
...
const rFirebase = await checkUserInFirebase('abc#gmail.com');

Resources