firebase updateProfile() only changes data to firestore after signing in again - javascript

I am trying to save the "score" variable to the "highscore" field in my cloud firestore database for my user but the change only appears after I sign in again using google. Everytime I sign in, there is also a new document being created even though I am signing in with the same google account. I did this while updating a standard collection like "displayName" but the custom collection I have set "highscore" could never be updated, not even after signing in again. I have tried reloading the user but it doesn't do anything. (I am using the namespaced web version 8 in vanilla.js)
This is my code:
function gameOver () {
if (score > highscore) {
GAME_OVER_TEXT.innerText = 'New Highscore:';
HIGHSCORE.innerText = `${score} points.`;
STARS.classList.remove('hide');
SPARKLE.classList.remove('hide');
const user = firebase.auth().currentUser;
user.updateProfile({
highscore: score
}).then(() => {
console.log('Update successful');
}).catch((error) => {
console.log('Update unsuccessful' + error);
});
user.currentUser.reload();
} else {
GAME_OVER_TEXT.innerText = 'Your score:';
HIGHSCORE.innerText = `${score} points.`;
}
}

The call to reload immediately loads the current profile from the server. Since updateProfile is also an asynchronous call, you are now loading the un-updated profile. To fix this, you have to reload the profile after the update has completed. So:
user.updateProfile({
highscore: score
}).then(() => {
console.log('Update successful');
user.currentUser.reload().then(() => {
console.log('Profile reloaded');
});
}).catch((error) => {
console.log('Update unsuccessful' + error);
});
Note that updateProfile only accepts displayName and photoURL as properties, as you can't store other information in the user profile from the Firebase client-side SDKs. You can either use an Admin SDK (on a server or otherwise trusted environment) to set a custom claim with that value, or (more likely here) store the information in a custom database (such as Firestore or Realtime Database, which as also part of Firebase).
This has been covered quite a few times before, so I recommend checking out previous questions on the topic.

Related

How do I fetch user information to display in a feed of posts using firebase storage solutions?

I'm building a forum-style application where users post content that displays on a global feed. I want to display information about the user in posts (photoURL, displayName) similar to Twitter.
I have firebase v9 using the authentication and firestore for the posts. The reason I want to reference the auth is that I can catch changes to the user's information as it happens, this way the feed is up to date.
I save the user's unique ID with the post so I am able to reference who to display. I can successfully reference the post title and description with doc.title & doc.description however I get stuck when retrieving user information. I'm trying doc.UserID.displayName for the display name but I know this is incorrect. I can't find anything in the docs for this specific use case, is this something that I can do with just firestore and auth?
Do I need to create a reference to the auth storage with doc.UserID?
Here is the code:
// add a new post
addPostForm.addEventListener('submit', (e) => {
e.preventDefault();
onAuthStateChanged(auth, (user) => {
const colRef = collection(db, 'Posts');
console.log(hiddenURL.value);
addDoc(colRef, {
UserID: user.uid,
beatURL: hiddenURL.value,
title: addPostForm.postTitle.value,
description: addPostForm.postDescription.value,
})
.then(() => {
console.log("Document written with ID: ", doc.id);
addPostModal.classList.remove('open');
addPostForm.querySelector('.error').textContent = "";
})
.catch(error => {
addPostForm.querySelector('.error').textContent = error.message;
alert(error);
})
})
});
export const initApp = async () => {
initFirebaseAuth;
const posts = await collection(db, 'Posts');
// render data to the page
return renderPosts(posts);
};
const renderPosts = (posts) => {
const main = document.getElementById("feed");
onSnapshot(posts, (snapshot) => {
let cardsArray = []
snapshot.docs.forEach((doc, user) => {
cardsArray.push({ ...doc.data(), id: doc.id })
name.textContent = `${doc.UserID.displayName}`; // users display name
avatar.src = doc.UserID.photoURL; //user's image
description.textContent = `${post.description}`;
title.textContent = `${post.title}`;
});
console.log(cardsArray);
});
};
There are two cases and approaches at first sight:
1. Your users profiles are only available in the Auth Service
In this case, via the JS SDK, a user X cannot "query" the Auth profile of a user Y.
This means that you need to save the author's displayName together with the author uid when the post is created.
2. Your users profiles are also available in a users collection (a common pattern)
In this case, when you display a post, you could fetch the user's document to get the author's displayName.
However, in the NoSQL world, you should not be afraid to duplicate data and denormalize your data model. When designing your data-model you should think about it from a query perspective, trying to minimize the number of queries for a given screen/use case. So approach #1 is recommended, even if you maintain a user's collection.
In case of changes in the user's profile, in order to synchronyse the post documents and user's data a common approach is to use a set of Cloud Functions (which are executed in the back-end) to update the post documents. The link between the posts and the users profile being the user's uid.

Firebase - check if user created with Google Account is signing up or logging in?

I'm trying to create a web application, where you can log-in and register an account with a google account. I have managed to make it so they can log-in with the signInWithPopup(provider), but not sure how to Implement the sign-up. Any suggestions? or know of any functions in firebase i can use?
There aren't any separate methods to login and sign up when using Google Sign-In or any other provider. If a user with that credential exists then user will be logged in else a new user account will be created. You can then use additionalUserInfo.isNewUser property from the result received from signInWithPopUp method to check if the user is new.
firebase.auth().signInWithPopup(provider).then(function (result) {
const {additionalUserInfo: {isNewUser}} = result;
console.log(isNewUser ? "This user just registered" : "Existing User")
})
For the new Modular SDK (V9.0.0+), the same can be written as:
import { signInWithPopup, getAdditionalUserInfo } from "firebase/auth"
const result = await signInWithPopup(auth, googleProvider);
// Pass the UserCredential
const { isNewUser } = getAdditionalUserInfo(result)
So far, as I understood, you have two options to log in to your website: one is to make a local username/password account on your website, and the other option is to use your google account. I suppose the best way would be to check if the Google account is linked to any existing user using additionalUserInfo.isNewUser, and then linking up your google account with the account that is created via your website locally by following this article: https://firebase.google.com/docs/auth/web/account-linking?hl=lt
Once you have Firebase dependency inside your application. You can use createUserWithEmailAndPassword method to do that.
firebase
.auth()
.createUserWithEmailAndPassword("email#domain.com", "123123")
.then(data => {
data.user.updateProfile({
displayName: this.form.name
}).then(() => {});
})
.catch(err => {
this.error = err.message;
});

Firebase Web SDK: refreshing auth token so that email_verified is updated in firestore rules

Using Firebase Web SDK, I'm requiring users to verify their email before accessing Firestore documents. I have a Firestore rule that gates the document like this:
allow read: if request.auth != null && request.auth.token.email_verified;
I'd like the email verification to be reflected as soon as the user verifies his/her email without requiring the user to sign out and sign back in. Unfortunately onAuthStateChanged() doesn't fire when emailVerified changes, so I'm refreshing the client token by polling for changes to emailVerified. Something like this:
Note: My examples use the new beta Firebase Web SDK V9 (Modular Web SDK) in case the syntax is unfamiliar.
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
});
}, 2000);
With the code above, I can get emailVerified to be reflected property inside my web app, but the problem arises when I try to make a request to Firestore:
const unsubscribe = onSnapshot(
doc(db, 'widgets', 'widget1'),
snap => {
console.log(snap);
},
);
That request results in a Firestore permission error. Once the user signs out and signs back in, the Firestore request is accepted.
How can I get the auth token that gets sent to Firestore to be updated with the latest email_verified without the user to sign out and and sign back in?
It turns out that a series of steps need to happen to refresh the token. After email verification, you need to reload the user AND explicitly get a new id token with getIdToken(user, true) after you reload the user. Only after those 2 steps will an updated token be sent to Firestore for queries. You also need to unsubscribe and re-subscribe to onAuthStateChanged manually, as that doesn't get triggered on token change. The modified version of my example is:
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
getIdToken(auth.currentUser, true).then(() => {
// now the new token will be sent to Firestore, yay!
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
})
});
}, 2000);
Please post your answer if there's an easier way. I especially don't like the polling part.

How to delete authenticated user from firebase in angular

when clicking on a button i called a function,
onDelete(id:string){ this.db.collection('Students').doc(id).delete(); }
Here, id is a name of document that i want to delete, db is a property of type AngularFireStore, 'Students' is a name of collection.
Structure of document:
enter image description here
In the above image, collection name is Students, under which multiple documents exist, since document name must be unique so i given that name a number of type string which acts as id. In every document, there is email field, i want to delete that email from authentication when i delete the same document.
code to sign up users:
this.afAuth.auth.createUserWithEmailAndPassword(email:string,password:string).then(res=>{})
If you want to delete a user existing in Firebase authentication you have two possibilities:
1/ Using the JavaScript SDK (since your app is made with angular)
You call the delete() method, as follows:
const user = firebase.auth().currentUser;
user.delete()
.then(() => {
//....
})
.catch(err => {
if (err.code === "auth/requires-recent-login") {
//Re-authenticate the user
} else {
//....
}
})
Note however, that this method "requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call firebase.User.reauthenticateWithCredential". An error with the auth/requires-recent-login code is "thrown if the user's last sign-in time does not meet the security threshold".
So, only the logged-in user can call this method from a front-end, in order to delete his/her own account.
2/ Using the Admin SDK
You can use the Admin SDK's deleteUser() method, for example within a Cloud Function.
In this case, there is no need to have the user logged-in since this is executed in the back-end and it is therefore possible to delete any user.
For example, you could have a Callable Cloud Function triggered by an admin user.
Another possibility, is to trigger a Cloud Function upon the Firestore user's document deletion.
Update based on your Question update:
I understand that you want to delete the user record in the Auth service upon deletion. For that you can write a Cloud Function as follows:
exports.deleteUser = functions.firestore
.document('Students/{studentID}')
.onDelete((snap, context) => {
const deletedValue = snap.data();
const userEmail = deletedValue.Email;
return admin.auth().getUserByEmail(userEmail)
.then(userRecord => {
const userID = userRecord.uid;
return admin.auth().deleteUser(userID)
})
.catch(error => {
console.log(error.message);
return null;
})
});

firebase - Firebase Current User not storing the correct user

I have a project using Firebase for the authentication. When I log into my project, sometimes the firebase.auth().currentUser method returns info from some other user. Sometimes I get the info from the actual current user and sometimes the previously signed in user - when this happens, the only way to correct the problem and get data for the correct user is to logout and sign back in again.
My login method:
onLoginClick = () => {
firebase.auth().signInWithEmailAndPassword(this.state.email.trim(), this.state.password.trim())
.then((user) => {
//Some redirects depending on my users role
})
.catch((error) => {
console.log(error, error.message);
if (error.code == "auth/user-not-found" || error.code == "auth/wrong-password") {
//Handle error
}
});
}
And my Logout method:
onLogoutClick = () => {
firebase.auth().signOut().then(function () {
this.props.history.replace("/")
}).catch(function (error) {
console.log(error);
});
}
They're pretty standard but seems like I'm doing something wrong. I've tried clearing the sessionStorage and the localStorage on the logout method but that didn't help.
UPDATE:
I've tried with some suggestions like doing:
firebase.auth().signOut().then(() => {
firebase.auth().signInWithEmailAndPassword(email, password).then((user) => {
//My redirects and everything
})
})
But this doesn't work.
Also, I've noticed that when I log in and use wrong credentials, the sessionStorage gets an entry with the previously signed in user, even though I signed him out.
I'm pretty sure that during the initialization of a new login session, until it is complete, currentUser hasn't been updated yet; use the user parameter when one is passed in on those methods. Because the login is still in progress, there is not yet a currentUser, so it passes the user in progress via the user parameter. (I think this means it is likely that it will be null the first time someone logs in, and it will be the previous one thereafter, unless it's cleared on logout by some code.)
Use the user parameter when it is provided, such as in the onAuthStateChanged handler. See the first example here, and the starred note in the blue box farther down that same page.

Categories

Resources