How to update the user object in back4app? - node.js

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.

Related

I need to create a rest API in node that calls another API

I need to create a REST API in my node app, that GET data from an external API - https://newsapi.org/v2/top-headlines?category=%7Bcategoryname%7D&apiKey=APIKEY
The condition is that this rest API should contain id of the user in DB.
Only when we trigger an API with valid userID, it should return response as the data coming from external API.
otherwise show error
Can you help me build a function that would do so?
I am using mongoDB
I am writing few snippets of code i wrote to accomplish this, but i am pretty bad at it. So any help on this will be highly appreciated:
app.js
router.get('/api/Users/news/:id', controller.newsget);
router.get('/api/Users/news/:id', (req, res) => {
const id = req.params.id;
for (let i = 0; i <User.length; i++) {
let user = User[i]
if (user.id === id) {
axios
.get('http://newsapi.org/v2/top-headlines?country=in&category=general&apiKey=36f3e29b704f41339af8439dc1228334')
.then(response => {
let userData = response.data;
res.send(userData);})
}
}
});
controller.js
exports.newsget = (req, res)=>{
if(!req.body){
return res
.status(400)
.send({ message : "Data to update can not be empty"})
}
const id = req.params.id;
User.findByIdAndUpdate(id, req.body, { useFindAndModify: false})
.then(data => {
if(!data){
res.status(404).send({ message : `Cannot Update user with ${id}. Maybe user not found!`})
}else{
res.send(data)
}
})
.catch(err =>{
res.status(500).send({ message : "Error Update user information"})
})
}
I have very little clue on the approach, but i badly need assistance. Please help
I have tried mimicking some online functions to search the user and then try to fetch data from external API if the user's ID was present in my DB. But it failed
First of all, you are writing two GET methods for same route, from what I am being concluding that, the first route should be POST according to what I am suspecting to be your functionality is depicted as follows
router.post('/api/Users/news/:id', controller.newsget);
router.get('/api/Users/news/:id', (req, res) => {
const id = req.params.id;
for (let i = 0; i <User.length; i++) {
let user = User[i]
if (user.id === id) {
axios
.get('http://newsapi.org/v2/top-headlines?country=in&category=general&apiKey=36f3e29b704f41339af8439dc1228334')
.then(response => {
let userData = response.data;
res.send(userData);})
}
}
});
Also if you are picking the logged in user, use req.user.id

How to get userId from Firestore on Node.js backend?

Im new to firebase and I am having trouble setting up the user profile route on my app. Im trying to make it so that the userId stored in Firestore can be retrieved and all the posts made by that specific user will be displayed on the user profile page. Please any help would be appreciated!
This is my Firestore database :
and this is what I have so far but it is not displaying all the posts associated with the user id
const express = require("express");
const router = express.Router();
const firestore = require("firebase/firestore");
const db = firestore.getFirestore();
router.get("/", (req, res) => {
res.send("No user id provided");
});
router.get("/:uid", (req, res) => {
const uid = req.params.uid;
const userPost = firestore.getDoc(firestore.doc(db, "reviews", uid));
userPost(uid, req.query && req.query.dev === "true")
.then((user) => {
return res.send(user);
})
.catch((e) => {
return res.send(e);
});
});
module.exports = router;
This line gets the single (probably non-existant) post at /reviews/:uid:
const userPost = firestore.getDoc(firestore.doc(db, "reviews", uid));
What you are looking to do is build a query for documents in the collection /reviews where docData.userId = uid.
const reviewsColRef = firestore.collection(db, "reviews");
const userPostsQuery = firestore.query(reviewsColRef, firestore.where("userId", "==", uid));
const userPostsPromise = firestore.getDocs(userPostsQuery);
userPostsPromise
.then((querySnapshot) => {
const postsArray = querySnapshot.docs.map(docSnap => {
const docData = { id: docSnap.id, ...docSnap.data() };
delete docData.userId; // it's the same for every post, may as well omit it
delete docData.email; // same as above
return docData;
});
res.json({ posts: postsArray, count: postsArray.length });
})
.catch((err) => {
// don't send the full error object to the client,
// instead you should log the error and send the
// client as little information as possible
console.error(`Failed to get user posts for firebase:${uid}`, err);
res.status(500).json({ error: err.code || err.message }); // Don't forget to use an appropriate status code!
});
Notes:
I recommend using destructuring to import the Firestore lib. Importing it as firestore negates the benefits of the modular SDK of only importing what you need.
import { getFirstore, query, collection, ... } from "firebase/firestore";
If this code is running on a server you control, consider switching to the Firebase Admin SDK instead as this allows you to bypass security rules and has relaxed rate limits.
import { getFirestore, query, collection, ... } from "firebase-admin/firestore";
As a side note, if an import from the SDK conflicts with a name you want to use elsewhere, you can rename it. You might see this when using the RTDB and storage together as they both export ref:
import { getDatabase, ref: rtdbRef } from "firebase/database";
import { getStorage, ref: storageRef } from "firebase/storage";
const userDataRef = rtdbRef(getDatabase(), "users", uid, "data");
const imgStorageRef = storageRef(getStorage(), "path/to/image.png");
Treat emails as sensitive data akin to a phone number. Nobody likes spam and limiting ways to rip emails from your database is good practice. Unless you are working on an internal tool for a corporate network, you should hide the user's email as much as possible.
Email addresses should not be stored with reviews. Instead keep a collection with documents for each user (e.g. document at /userProfile/:uid) and limit access to privileged users.
Consider adding a 404 Not Found error when a user doesn't exist.
Consider adding authentication to restrict access to your API.

Firebase Functions won't read document on Firestore

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.

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