Best practice: Nested subscriptions to receive user data? - javascript

I'm developing an Ionic 2 mobile app using Firebase as backend. With Firebase, some authentication data is stored with firebase automatically (currently logged in user's id, email and password). The other user data (name, address, ...) is stored in my Firebase database.
To get the currently logged in user's data, I have to subscribe to Firebase's auth methode first. Using angularfire2 I do the following:
this.af.auth.subscribe(data => {
... // data.uid gives me the user's id
}
Now I know the user's id and can subscribe to my Firebase database to get the other user data by doing:
this.af.database.object('/users/'+userId).subscribe( userData => {
...
}
If I combine both it's a nested subscription looking like this:
this.af.auth.subscribe(data => {
this.af.database.object('/users/'+user.uid).subscribe( userData => {
...
}
}
This works, however, it doesn't feel right to have two nested subscriptions. How do you guys deal with this? What would be a "best practice approach" here?

This can be done by Rxjs mergeMap operator
this.af.auth.subscribe(user=> {
this.af.database.object('/users/'+user.uid).subscribe( userData => {
...
}
}
to
this.af.auth.mergeMap(user=>this.af.database.object('/users/'+user.uid))
.subscribe(userData){
...
}
To answer your question, mergeMap is used to avoid nested subscription and remaining stuff is your app specific logic.

Related

Is there a better way of implementing this without multiple fetch request?

This is login component from a react application and I am trying to do a simple authentication login with Firebase as my real time database.
My first approach is to execute a fetch request (GET) to see if there is any existing user. Afterwhich, if the password do match with the user, I want to update the "isLoggedIn" field to true. In order to achieve the following, another fetch request (PATCH) was made.
Therefore, I am wondering if it is a bad practice to have multiple fetch request in one function and would like to know if there is a more efficient or better way of implementing this simple application?
const loginHandler = async (userName, password) => {
const url = `insert_url_link_here/users/${userName}.json`;
try {
const response = await fetch(url);
const user = await response.json();
if (user.password === password) {
await fetch(url, {
method: "PATCH",
body: JSON.stringify({ isLoggedIn: true }),
});
}
} catch (error) {
console.log(error);
}
This is the schema of my database.
--users:
eric:
isLoggedIn: false
password: "321"
test:
isLoggedIn: false
password: "222"
You're accessing the Firebase Realtime Database through its REST API, which implements simple CRUD operations, and there's no way to combine a read and a write operation into a single operation there.
The only other relevant operation is the conditional write, which you can use to only write data if it hasn't been modified since you read it. You can use this to implement an optimistic compare-and-set like transaction mechanism, but given that you're toggling a boolean it doesn't seem to apply here.
If you're on an environment that supports it, you may want to consider using the Firebase SDK for that platform, as the SDKs typically use a web socket to communicate with the server, which often ends up being a lot more efficient than performing multiple individual HTTP requests.
I suggest using a Firebase client implementation and looking at the Firebase documentation. There are well described and explicit examples in various languages:
https://firebase.google.com/docs/auth/web/start
Besides, I don't see the point of storing passwords or login state on your side since Firebase Auth already does this for you in a secure way.

Create multiple Firebase Instances for the same project in Node.js

I have a Node.js server, inside which I want to have two firebase instances.
One instance should use the JavaScript SDK and will be used to provide authentication - login/register. The other instance should use the Admin SDK and will be used to read/write from the Realtime Database. I want to use this approach, so that I don't have to authenticate the user before each request to the Realtime DB.
I've read how we're supposed to initialize Firebase instances for multiple projects, but I'm not sure if my issue isn't coming from the fact that both instances are for the same project.
My issue is that I can use the JS SDK without any issue and I can login/register the user, but for some reason I can't get the Admin SDK to work.
Here's how I'm instantiating the apps:
const admin = require("firebase-admin");
const { applicationDefault } = require('firebase-admin/app');
admin.initializeApp({
credential: applicationDefault(),
databaseURL: 'my-database-url'
}, 'adminApp');
const firebase = require("firebase/app");
firebase.initializeApp(my-config);
Now I can use the JS SDK without an issue, but not the Admin SDK. I've created a test endpoint to just get data from my Realtime DB:
app.get("/api/test", (req, res) => {
const uid = 'my-user-UID';
admin.database().ref(`users/${uid}`)
.once('value', (snapshot) => {
if(snapshot) {
console.log('data');
} else {
console.log('no data');
}
});
});
Now here as an approach to getting the data from the Realtime DB, I tried all possible described approaches. Using get with child and all sorts of possible combinations. Here's an example of another approach I used:
get(child(ref(admin.database()), `users/${uid}`)).then((snapshot) => {
if (snapshot.exists()) {
// retrieved data
} else {
// No data
}
}).catch((error) => {
console.error(error);
});
For the first approach I wasn't getting any response at all, like the once wasn't executing. For the second one I think I was getting - typeerror: pathstring.replace is not a function firebase. At some point I was getting a no firebase app '[default]' has been created . These errors don't worry me as much, but since I saw the last error I moved my focus to the initialization of the apps, but still to no avail.
I just need a direction of where my issue might be coming from.
Update:
The solution is to not pass a second argument (app name) to any of the Firebase initializations. Looks like it's not needed in case you're referencing the same project.

How To Setup Custom Claims In My React Website For a Login Page

I want to set up custom claims to a certain number of users let's say 5 users would be admins on my website. I want these 5 users to be able to log in through the login page which would redirect them to the dashboard.
but I still don't fully understand the concept of the custom claims and how to use them and firebase documentation is limited with examples.
In their example they show that I can pass a uid that I want to assign a custom claim to, but how is this supposed to be a variable when i want certain users uid's from my firestore database Users collection to be admins and have a custom claim, in other words, where would I put this code or how would I assign a custom claim to more than one user at a time and how and where would this code be executed.
if anyone can give me an example of how I would make this work.
here is what I did:
created a firebaseAdmin.js file:
var admin = require("firebase-admin");
// lets say for instance i want these two users to be admins
//2jfow4fd3H2ZqYLWZI2s1YdqOPB42
//2jfow4vad2ZqYLWZI2s1YdqOPB42 what am i supposed to do?
admin
.auth()
.setCustomUserClaims(uid, { admin: true })
.then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
I honestly don't know what to do from here.
Custom Claims can only be set from a privileged server environment via the Firebase Admin SDK. The easiest ways are either using a Node.js script (running the Admin SDK) or a Cloud Function (which also uses the Admin SDK).
Let's look at the example of a Callable Cloud Function that you call from your front-end (and in which you could check the UID of the user who is calling it, i.e. a Super Admin).
exports.setAdminClaims = functions.https.onCall(async (data, context) => {
// If necessary check the uid of the caller, via the context object
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
await Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })));
return { result: "Operation completed" }
});
A Node.js script would be similar:
#!/usr/bin/node
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(".....json") // See remark on the private key below
});
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })))
.then(() => {
console.log("Operation completed")
})
You must generate a private key file in JSON format for your service account , as detailed in the doc.
Then, when the Claims are set, you can access these Claims in your web app, and adapt the UI (or the navigation flow) based on the fact the user has (or not) the admin claim. More detail here in the doc.

Firestore not reflecting changes in firestore after deployed

I have a web app written in NUXT that makes use of Firebase's Hosting, Firestore, Authentication and Storage.
Its a simple blog layout that has all the usual CRUD functions for its blog posts. It is loosely bases on Quick Nuxt.js SSR prototyping with Firebase Cloud Functions and Nuxt.js Firebase Auth.
In the development environment it runs perfectly but when I deploy it, the Firestore specifically, behaves unexpectedly.
So after the project has been deployed I can CRUD documents that reflect as expected in the Firebase Console Firestore viewer, but when I read the data again it will load the same data. In other words if I delete a document it will disappear in the Firestore viewer but when I refresh my NUXT website it loads that document again even though it's no longer present in the Firebase console. I get the same result on different computers/devices, so not a local caching issue.
I noticed that the changes in the Firestore viewer will only reflect in my website after I re-deploy my project. But any changes I make will not show after I refresh the website even though they have changed permanently in the Firestore viewer.
When in development it works perfectly, I can manipulate the database, refresh and it will load exactly what’s reflected in Firestore viewer.
Sorry for repeating it so much but I’m having an existential crisis here, lol.
So below is a sample of the NUXT's Store's index.js file, where you would have all your data stored for your app. It works perfectly at manipulating the data on Firestore but once in production the website gets served the same data over and over.
import { firestore } from '~/plugins/fireinit.js' // the part where `firebase.initializeApp` happens
Decare my array state: posts.
export const state = () => ({
posts: []
})
Mutations for manipulating the posts array.
export const mutations = {
addP (state, payload) { // Gets run for each documents from collection on first load.
state.posts.push(payload);
},
delP (state, payload) { // Deletes a post from the posts state.
state.posts = state.posts.filter(p => {
return p.id != payload.id;
});
},
}
The nuxtServerInit() runs on the server to make it Server Side Rendered when the website first loads.
export const actions = {
async nuxtServerInit({commit}, context) {
await firestore.collection('posts').get().then((querySnapshot) => {
querySnapshot.forEach(function(doc) {
var obj = doc.data()
obj.id = doc.id;
commit('posts/addP', obj)
})
})
},
The deletePost() action deletes a file on Firebase Storage then deletes the document on Firestore. Then finally removes the item from the posts state.
deletePost ({commit}, payload) {
storage.ref().child(payload.fullPath).delete().then(function() {
firestore.collection('posts').doc(payload.id).delete().then(()=>{
commit('delP', payload);
})
.catch((error)=>{
console.error(error);
});
})
}
}
This is what my Firestore Rules look like
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if request.auth != null;
}
}
}
What am I doing wrong :/
So after losing some hair I finally figured it out!
So in NUXT you have two options for deploying your project, nuxt build or nuxt generate.
The generate option reads the database and then builds your static files from the firestore, which is then deployed. This is why when I reloaded my page it had all the old entried in the DB.
After switching to the build option and deploying that instead it all works perfectly.

Firebase Database - Login

I have a register page working well, this is my database.
enter image description here
Each one has an ID and you Login using Username and password,
And I am using this code, to verify if the username is in database.
ref.child("accounts").orderByChild("lowerun").equalTo(username.toLowerCase()).once("value",snapshot => {
if (snapshot.exists()){
const userData = snapshot.val();
console.log(userData)
}
})
But how do I get the password for the username?
Dudis! Welcome to SO! It looks like you're using Firebase Realtime Database? I think you're using the database API incorrectly if you're trying to fetch user number 1 in your accounts. Here is something you could try:
database
.ref("main/accounts")
.child(userId)
.once("value", snapshot => {
// check if snapshot exists
// log your snapshot.val()
})
Also, as Doug suggested, I would strongly suggest using Firebase Authentication.

Categories

Resources