Reloading current user doesn't refresh emailVerified state in Firebase Authentication - javascript

I've been trying to fix the problem for a whole day and couldn't make it work. I'm getting irritated, firebase docs are such a mess it's insane..
So I'm trying to implement email verification on my React app. I wen't with the docs, and google do send the email, I can click it, it's all good.
But, the email verified state doesn't change at all, and believe me I've went through all the stackoverflow topics.
firebase.auth().doSignInWithEmailAndPassword(email, password)
.then(() => {
console.log(firebase.auth().currentUser);
firebase.auth().currentUser.reload()
.then(() => {
console.log(firebase.auth().currentUser);
})
})
So I've found i need to reload the user to see the changes applied, tho they just won't work no matter what I do.
Both console logs return email.verified = false at all times.
I'm hopeless, anyone have any idea on how to make this work?
I was wondering whether me setting up custom domain as a verification link has to do anything with that? I'm testing on localhost, link is linking to live website.
Please help :(

The method you should call is signInWithEmailAndPassword, so remove the "do" in the function call:
firebase.auth().signInWithEmailAndPassword(email, password)
.then((credential) => {
const currentUser = credential.user;
console.log(firebase.auth().currentUser);
firebase.auth().currentUser.reload()
.then(() => {
console.log(firebase.auth().currentUser);
})
}).catch((err) => {console.error("Problem with sign in ", err);}
Use the credential returned from this function and assign its user value to currentUser to validate that the user has been authenticated.
Lastly, remember to add a catch at the end whenever you use then. In this case, err will contain a property err.code that will tell you why the user could not be authenticated.

I had the same problem. You need to reload your current user using a timer and then listen to user changes (not auth changes) in a Streambuilder. As soon as the user clicks the link they will be redirected to your main page.
Use a Streambuilder to listen to changes to the user:
StreamBuilder<User?>(
stream: FirebaseAuth.instance
.userChanges(), // monitor the changes to the user
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return LoadingScreen(); // show loading screen while loading
if (snapshot.hasData && !snapshot.data!.emailVerified) {
return VerifyEmailScreen();
} // if not verified show the verify email screen
if (snapshot.hasData) {
return MainScreen();
} // if logged in already show main screen
return OnboardingScreen(); // if not logged in already show the auth screen
},
),
In your VerifyEmailScreen() create a timer in it's initState like this:
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
FirebaseAuth.instance.currentUser?.reload();
});
super.initState();
}
I hope it helps your problem.
Cheers

Related

How to find original email redirect link from landing page for firebase email link sign in option

In firebase, in order to authenticate a use after they click an email link, firebase needs the original clicked email link.
Example
This link redirects you a few times, and brings you to the page needed (currently set to localhost for dev purposes)
Firebase will only accept the email link, and only for one test per email which makes it difficult to diagnose this.
I need a way to fetch the first link clicked (the email link) from the landing page.
I apolagise if this has been answered anywhere else but I tried several combinations of keywords that did not work
export default function Home() {
return (
<>
boilerplate
<button onClick={bigFunction}>Press me to check if the browser has the email saved</button>
</>
)
}
let signinwithemaillink = "https://ticketme.page.link/qbvQ"
function bigFunction()
{
console.log(window.location.href)
if (isSignInWithEmailLink(auth, signinwithemaillink)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
let email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = 'example#gmail.com';
}
// The client SDK will parse the code from the link for you.
signInWithEmailLink(auth, email, signinwithemaillink)
.then((result) => {
alert("all good")
console.log(result.user)
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
})
.catch((error) => {
console.log(error.code)
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
}
As you can tell there is still an incredible amount of comments from the firebase docs, Im just trying to get this to work.
*Amended - I cut out the dynamic link, so the current redirect cycle is as follows:
Email -> Firebase Redirect Link -> Desired Page

How to pass post submit callback to authentication method

I'm implementing a user comments feature, and I'd like to allow users to start writing before asking for authentication to reduce UX friction.
The user flow would be:
The user clicks on the input field, and start typing.
Once done, the user clicks on the submit button, then the system will check if the user is authenticated.
(A) If the user is logged
in, then it will submit the content.
(B) If the user is not logged
in, open a login/signup modal.
(A) If the user has an account, they
choose login methods either email or social login. (B) If the user
is new, they signup for a new account with either email or a social
account.
Once the user is authenticated, the authentication modal will close, and the content will then be submitted. (the user does not need to click on the submit button once again)
I think this is a very common use case, but I cannot find anything on Stackoverflow, or on Google search.
I'm using React with Firebase authentication. My first attempt is to check every second if the user is authenticated.
const submit = async (data) => {
if (!isAuthenticated) {
setOpenAuthDialog(true);
while (!isAuthenticated)
await new Promise((resolve) => setTimeout(resolve, 1000));
}
setDoc(doc(firestore, "comments", postId), data);
setInputField("");
};
However, I don't feel this is the best practice, because it will continue to check even when the user abandons the authentication flow.
I think using callbacks might be the better approach, but the authentication modal and comments are sibling components. I'm not sure if passing the callback function to the sign-in methods is possible.
Please let me know if there is an any better approach to this problem.
One way to do it with callbacks could be:
const submit = async (data) => {
if (!isAuthenticated) {
setOpenAuthDialog(true);
setDataWaitingForSubmission(data)
return;
}
setDoc(doc(firestore, "comments", postId), data);
setInputField("");
}
// This is passed as callback to auth modal
const onAuthSuccess = () => {
if (dataWaitingForSubmission && isAuthenticated) {
submit(dataWaitingForSubmission)
}
}

Data from firestore it's being fetched multiple times when login and logout (vanilla JS)

Well I made this Library app, where an user can login and add books. So, when a user login the app fetch data from a firestore collection, cool. The problem exists when the user login once, logout and then login again without refreshing the app. If the user do this twice, the fetch twice, if thrice, the fetch thrice. The code that executes multiple times its the fetchBooks(), the signInWithGoogle() only executes once. Here's the code involved:
function signInWithGoogle(){
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(result => {
// Create the new user document in firestore
createNewUserDocument(result.user)
// fetch feed data
auth.onAuthStateChanged(user =>{
user? fetchBooks() : null
})
}).catch(err => {
console.log(err)
})
signUpForm.reset()
signUpModal.hide()
signInForm.reset()
signInModal.hide()
}
function fetchBooks() {
const docRef = db.collection('users').doc(auth.currentUser.uid).collection('books')
docRef.get().then(querySnapshot =>{
console.log(querySnapshot)
querySnapshot.forEach(doc => {
const data = doc.data()
console.log(doc.data());
addCardToHTML(data.title, data.author, data.pages, data.description, data.read)
})
})
}
onAuthStateChanged is a subscription that triggers itself when there's a change in the user's authentication state.
So it will trigger when you log in, when you log out, etc.
So ideally you'd want to wait until the user logs in, and then call the fetchBooks() function, but if you keep doing it inside of the subscriber the function will trigger any time the subscriber emits a new value.
I would recommend starting with a restructure of your code to have functions that do individual things. Right now, you have a function signInWithGoogle. That function should only sign the user in with Google and return a promise with the result of that sign in. Instead, you have it signing in the user, fetching books (which itself is also fetching books AND modifying the DOM), and calling methods on your signUp elements.
Restructuring this to have some other top-level function would likely help you handle your problem easier. Specifically, try something like this:
function handleSignIn() {
signInWithGoogle()
.then(fetchBooks)
.then(books => {
books.forEach(book => addCardToHTML(...))
})
}
This is a good start because now it's clear what each individual function is doing. So now to handle your specific issue, I'll assume that the problem you're facing is that you're seeing the books be added multiple times. In that case, I would think what you'd want to happen is that:
When a user is signed in, you want to load their books and display them on the page.
When they log out, you want the books to be unloaded from the screen
When they log back in, the books are re-loaded and displayed.
If all of those assumptions are correct, then your problem wouldn't be with the code you have, but rather the signout functionality. When the user signs out, you need to add a function that will remove the books from the HTML. That way, when they sign back in after signing out, the handleSignIn function will kick off again and the addCardToHTML function will be running on a blank HTML page rather than a page that already has the cards.
Example:
function handleSignOut() {
signOut()
.then(clearBookCards)
}
function clearBookCards() {
// Manipulate DOM to remove all of the card HTML nodes
}

How can a user's displayName be added to a Firestore database collection entry?

I am setting up a simple webpage with Firebase Authentication and a Firestore database which takes user inputs from a form, adds the inputs as a collection document in Firestore, and then also outputs the whole collection. Each document in the collection has two fields, the Name and the Body of the document. The goal is to allow users to make posts on the website using the input form. Everything I described is working, but now I would like to display the user.displayName with the post, to show who exactly created the user input, and that's what I can't figure out how to do. Here's the relevant code, from the script.js of the website:
const createForm = document.querySelector('#create-form');
createForm.addEventListener('submit', (e) => {
e.preventDefault();
db.collection('forumposts').add({
Body: createForm['body'].value,
//the line below is what I cannot figure out how to set up
Name: string(user.displayName)
}).then(() => {
//reset the form
createForm.reset();
}).catch(err => {
console.log(err.message)
})
});
I'm still learning about JavaScript, so I apologize if I am missing something obvious. I know the user's displayName (which is collected upon sign up) is being collected properly, as I can log it to the console and it shows up correctly. I just cannot figure out how to then add it as a field in this database collection input. I have tried searching here on SO for related questions, but am only getting questions related to how the user can add a display name on sign-up. I already have the display name, I just need to input it into the database. Any help would be greatly appreciated!
Where is the "user" defined? That could be null if no user is logged in so it's better to check for the user.
const createForm = document.querySelector('#create-form');
createForm.addEventListener('submit', (e) => {
e.preventDefault();
const user = firebase.auth().currentUser
if (user) {
db.collection('forumposts').add({
Body: createForm['body'].value,
Name: user.displayName || "No username"
}).then(() => {
//reset the form
createForm.reset();
}).catch(err => {
console.log(err.message)
})
} else {
console.log("NO user logged in")
}
});
You don't need the String() constructor. If displayName is defined it'll be a string. You can use if (user.displayName) before adding the post to check if user has a display name.
It's String(), not string(). As long as displayName is a property of user, it will be added to the database.

query to firestore to check collection and document

i am a little bit new to firebase / firestore. I am using the stripe api.
Once the user hits start trial on the prebuilt stripe checkout page, then it should go to firestore and create a new collection called subscriptions with all the users information. It seems to be doing this, however, I created a page called successPage, and it basically checks to make sure that it created it.
please find the code below:
const successPage = props => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
console.log("calling success page : " + user.uid)
//checking if user is paying for subscription
firestore.collection('customers').doc(user.uid).collection('subscriptions')
.where('status', 'in', ['trialing', 'active']).get()
.then(activeSubscriptions => {
// if this is true, the user has no active subscription.
if (activeSubscriptions.empty === true) {
console.log(user.uid)
firestore.collection('customers').doc(user.uid)
.get().then(
doc => {
if (doc.exists) {
firestore.collection('customers').doc(user.uid).collection('subscriptions').get().
then(sub => {
if (sub.docs.length > 0) {
var activityStatus = "canceled"
createCheckoutSession(activityStatus)
console.log('subcollection exists');
} else {
alert("Your account has been created, but your payment details we're not successfully created. You will now be redirected to the checkout page")
createCheckoutSession()
console.log(user.uid)
console.log("does not exist!")
}
});
}
});
} else if (activeSubscriptions.size > 1){
alert("you have more then one active subscription. please manage your subscriptions and cancel one of your subscriptions to access the application")
} else {
firestore.collection("profiledata").doc(user.uid).update({
accountStatus: "active"
}).then (() => {
firestore
.collection("roger#x.ca")
.add({
to: user.email,
message: {
},
})
.then(() => console.log("email out for delivery!"));
props.history.push('/clients')
})
}
});
}
})
return (
<input type="hidden"></input>
)
}
it checks the subscriptions collection where status = to either trialing, or active, and then it checks everything inside subscriptions to see what is going on, but it for some reason it keeps redirecting to the stripe page (createCheckoutSession) even though the subscriptions collection has been created. is this a timing issue?
Stripe triggers a Webhook to your server/cloud functions when a new subscription is created and after that the document is created in Firestore. This process might take some time and meanwhile your user may have been redirected to the success page. If the document has not been created yet then you won't be able to show the transaction status.
Here's a workaround that you can do:
While creating a Stripe Checkout session on your server, you can actually create the subscriptions document but set a field called "stripe_response" to false and also add the new subscription document ID as a query parameter in the stripe success_url. So you url maybe looks something like: https://domain.ext/paymentSuccess?id=<that_document_id>,
Now when the user is on the success page, look for that specific subscription document with the ID mentioned in the query parameter. If the "stripe_response" is still false, then maybe the webhook has not done it's job yet. Just retry the request after 5 seconds and then show the status to user. Make sure you set the stripe_response to true in the webhook.
To simply step 2, you can just attach a realtime listener on the document so when the status is updated you get the response asap and don't need to rely on polling.
Coming to the 'keeps redirecting' part, can you please specify what the exact redirect situation? Where is it redirecting and all that?
It's definitely a race condition but if you follow the steps above it should take care of it. Please let me know if you need more assistance fixing this issue.

Categories

Resources