I am using firebase authentication with phone number, and that requires Recaptcha verification. I had initially used the normal visible recaptcha, and that worked fine, but a need came in to make the recaptcha an invisible one.
I used Firebase's documentations on this and here's my code below:
const generateRecaptcha = () => {
window.recaptchaVerifier = new RecaptchaVerifier(
"signin_btn",
{
size: "invisible",
callback: (response) => {
onSignInSubmit();
},
},
auth
);
};
const onSignInSubmit = async () => {
setLoading(true);
const q = query(doc(db, "users", phone));
const querySnapshot = await getDoc(q);
console.log("Snapshot", querySnapshot.exists());
if (
phone === "" ||
password === "" ||
repeatPassword === "" ||
lastname === "" ||
firstname === ""
) {
setLoading(false);
setErrors("Please fill the needed fields.");
} else if (password !== repeatPassword) {
setErrors("Passwords do not match.");
setLoading(false);
} else if (querySnapshot.exists()) {
setErrors(
"This phone number already exists on the server. Please try another number."
);
setLoading(false);
} else {
setErrors("Complete ReCaptcha to get verification code");
generateRecaptcha();
let appVerifier = window.recaptchaVerifier;
console.log("Did we get here?");
signInWithPhoneNumber(auth, phone, appVerifier)
.then((confirmationResult) => {
console.log("We got here..!!");
window.confirmationResult = confirmationResult;
setLoading(false);
})
.catch((err) => {
console.error(err);
});
}
};
I end up getting this error though
Please I need help on this issue.
Related
let me first note that I am new to firebase and authentication matters ... for several days I am facing a problem with updating email and password functionality using firebase... I want to know if this problem have something with using React and its way of working like re-rendering components ..etc. or is it the use of promises the way i've done in the code below.. I really don't know.
This is the error that I am facing.
Uncaught (in promise) FirebaseError: Firebase: Error (auth/user-token-expired).
Here is the code involved in the situation
firebase.js
const updateUserEmail = (newEmail) => {
return updateEmail(auth.currentUser, newEmail);
}
const updateUserPassword = (newPassword) => {
return updatePassword(auth.currentUser, newPassword);
}
update_profile.jsx
async function handleUpdateProfile() {
setErrorMessage('');
setIsLoading(true);
// check for password equality
if (passwordInputRef.current.value !== confirmPasswordInputRef.current.value) {
setErrorMessage('Passwords do not match');
return;
}
const promises = [];
if (emailInputRef.current.value !== currentUser?.email) {
promises.push(updateUserEmail(emailInputRef.current.value));
}
if (passwordInputRef) {
promises.push(updateUserPassword(passwordInputRef.current.value);
}
Promise.all(promises).then(() => {
navigate('/');
}).catch((err) => {
console.log(currentUser);
console.log(err)
setErrorMessage('Couldn\'t Update Your Profile');
}).finally(() => {
setIsLoading(false);
})
}
I tried auth.currentUser.reload()
like that
auth.currentUser.reload().then(() => {
updateEmail(auth.currentUser, newEmail);
})
I tried Re-authenticate the user using the doc guide here
firebaseDocs
but nothing solved the error.
Finally I have come to an answer for my question that has worked
I am not quite sure, but I believe that the root of my problem returns to two things
Reauthentication of a user
Async matters
Here is how I solved the error
firebase.js
const updateUserEmail = async (newEmail) => {
const email = prompt('Please Enter Your Email');
const password = prompt('Please enter your password');
let credential = EmailAuthProvider.credential(email, password);
await reauthenticateWithCredential(auth.currentUser, credential).then(() => {
updateEmail(auth.currentUser, newEmail);
})
}
const updateUserPassword = async (newPassword) => {
const email = prompt('Please Enter Your Email');
const password = prompt('Please enter your password');
let credential = EmailAuthProvider.credential(email, password);
await reauthenticateWithCredential(auth.currentUser, credential).then(() => {
updatePassword(auth.currentUser, newPassword);
})
}
for each update I prompted the user to give me his email and pass, and then used it for reauthenticating matters.
to fix the async issue.. i made the function to wait for the first update (email) before proceeding to the next update (password) and the next lines of code clarify it more.
update_profile.jsx
let newEmail = false;
let newPassword = false;
if (emailInputRef.current.value !== currentUser?.email) {
newEmail = true;
}
if (passwordInputRef) {
newPassword = true;
}
if (newEmail) {
await updateUserEmail(emailInputRef.current.value);
if (newPassword) {
await updateUserPassword(passwordInputRef.current.value);
}
}
I bet there are better solutions than mine and I'll be happy to read it, but for now this will do the trick.
i am using nodeJS with mongoDB.
i am using a get request to load the checkout form and then a post request to create a paymentIntent and load the stripe checkout form. then the form is submitted using a javascript file on the checkout page which then sends the user to a success page if it was successful, where would be the best place to save the purchase to the database and update the user to add it to their purchases, if i do it on the post request, it is too early and saves it before the user has a chance to pay.
routes
router.get('/pay/:id', catchAsync (async(req, res, next) => {
if (!req.isAuthenticated()){
req.flash('sir', 'you must be signed in')
return res.redirect('/account/sign-in')
}
try{
const { id } = req.params
const user = req.user._id
const concert = await Concert.findById(id).populate('artist')
const artistId = concert.artist
const artist = await Artist.findById(artistId)
const foundUser = await User.findById(user)
const user_purchases = foundUser.purchased_content.toString()
const concert_id = concert.id
if(!concert || concert.artist.banned === 'true' || concert.visibility === 'private') {
// return next(new AppError('page not found'))
req.flash('paynf', 'sorry, the concert you are looking for could not be found')
return res.redirect('/posts')
}
console.log(artist.stripe_id)
res.render('pay', { concert, foundUser})
}catch(e){
console.log(e.name)
req.flash('paynf', 'sorry, the concert you are looking for could not be found')
res.redirect('/posts')
}
}))
router.post('/pay/:id', catchAsync (async(req, res, next) => {
if (!req.isAuthenticated()){
req.flash('sir', 'you must be signed in')
return res.redirect('/account/sign-in')
}
try{
const { id } = req.params;
const user = req.user._id
const concert = await Concert.findById(id).populate('artist')
const concert_id = concert.id
const artistId = concert.artist
const artist = await Artist.findById(artistId)
const stripe_id = artist.stripe_id
const foundUser = await User.findById(user)
if(!concert || concert.artist.banned === 'true' || concert.visibility === 'private') {
// return next(new AppError('page not found'))
req.flash('paynf', 'sorry, the concert you are looking for could not be found')
return res.redirect('/posts')
}
const purchase = new Purchase(req.body)
const customer = ({
id: foundUser.cus_id,
name: 'john',
email: foundUser.email
});
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
customer: customer.id,
amount: concert.price*100,
description: `${concert.title} by ${concert.artName}`,
currency: "gbp",
receipt_email: customer.email,
automatic_payment_methods: {
enabled: true,
},
application_fee_amount: Math.round(concert.price*100*0.35),
transfer_data: {
destination: artist.stripe_id,
},
});
res.send({
clientSecret: paymentIntent.client_secret,
});
}
catch(e){
console.log(e)
return res.send('/posts')
}}))
javascript for checkout page:
const stripe = Stripe("pk_test_51KJaaVEnftAKbusC8A9kTrtzrLKklZDHQdserQ2ZrYMHRqFRfbMk9SrGVnQlLoSjIfqmOCOEsDmcsTnO0evWY2Pr00nNd02KLv");
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const response = await fetch(window.location.href, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
});
const { clientSecret } = await response.json();
const appearance = {
theme: 'stripe'
}
elements = stripe.elements({ appearance, clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000/success",
receipt_email: 'virtconcerts#gmail.com'
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}}```
You shouldn't rely on your customers being returned to the return_url following a successful payment to handle any fulfilment or post-payment requirements. Instead you should implement a webhook to handle these actions.
Specifically, listen for the payment_intent.succeeeded event. Details here.
I am currently just doing a very simple and basic login system with either a email address or a phone number. No password.
Nothing major to hide, just an information hub.
What i want to achieve:
Check if user entered email exists within users array.
Check if users entered phone exists within users array. Check if both email and phone match the same user.
Check if email is wrong and phone is drop (or vice versa) - it still finds the right user.
Whats currently happening:
It finds the right user if the email is entered in.
If email is entered in wrong and phone number is correct, i get a undefined error.
If both are wrong i get a server error stating email is undefined.
The code:
Users object
const users = [
{
email: "jon#doe.com",
phone: "0798888888",
name: "John Doe",
access: ["home", "blah", "etc"]
},
{
email: "fakedoe#john.com",
phone: "079000000",
name: "Fake Doe",
access: ["home", "etc"]
}
];
Main code:
app.post("/in", async (req, res) => {
let email = req.body.email,
phone = req.body.phone;
let conditions = !!email ? { email } : { phone };
let data = users.find((x) => x.email === conditions.email || x.phone === conditions.phone);
let pass = data.email || data.phone !== undefined;
console.log(pass);
if (pass) {
if (conditions.email && conditions.email === data.email) {
pass = true;
}
if (conditions.phone && conditions.phone === data.phone) {
pass = true;
}
}
if (pass) {
res.cookie("JWT", jwtSign(req.body.email, data.name, data.access));
res.status(200);
res.send("OK");
} else {
res.status(200);
res.send("Invalid user/password");
}
});
login system with either a email address or a phone number
Had to find a way to play around with the code. Here is a working version!
const users = [{
email: "fakedoe#john.com",
phone: "079000000",
name: "Fake Doe",
access: ["home", "etc"]
}
];
function checkUserExists(email, phone) {
return !!users.find((x) => x.email === email || x.phone === phone);
}
console.log(checkUserExists('fakedoe#john.com', '0490')); //email
console.log(checkUserExists('fakedoe#joh.com', '079000000')); //phone
console.log(checkUserExists('fakedoe#john.com', '079000000')); //both
console.log(checkUserExists('fakedoe#joh.com', '0490')); //none
So replace this:
app.post("/in", async (req, res) => {
let email = req.body.email,
phone = req.body.phone;
let conditions = !!email ? { email } : { phone };
let data = users.find((x) => x.email === conditions.email || x.phone === conditions.phone);
let pass = data.email || data.phone !== undefined;
console.log(pass);
if (pass) {
if (conditions.email && conditions.email === data.email) {
pass = true;
}
if (conditions.phone && conditions.phone === data.phone) {
pass = true;
}
}
if (pass) {
res.cookie("JWT", jwtSign(req.body.email, data.name, data.access));
res.status(200);
res.send("OK");
} else {
res.status(200);
res.send("Invalid user/password");
}
});
with this:
function userExists(email, phone) {
return !!users.find((x) => x.email === email || x.phone === phone);
}
app.post("/in", async (req, res) => {
if (userExists(req.body.email, req.body.phone)) {
res.cookie("JWT", jwtSign(req.body.email, data.name, data.access));
res.status(200);
res.send("OK");
} else {
res.status(200);
res.send("Invalid user/password");
}
});
In order to create a transaction the user must first be authenticated(password) in the confirm dialog modal. Once the user is authenticated the modal is closed and the transaction is shown. If a user is not authenticated correctly the modal still closes and a toast message is displayed with the error. I would like to change this logic so that the user must re-enter the password if their attempt was not authenticated.
setting the state inside createTransaction is done in an async way. this.closeModal in newTransactionModal is whats causing the modal to be closed and the state to be reset.
NewTransactionModal.js
this.state.showConfirmDialog ? (
<ConfirmDialog
type="New Transaction"
onPasswordChange={this.changePassword}
onConfirm={this.handleCreateClick}
onCancel={this.handleClose}
errMsg = {this.props.passwordErrMsg}
/>
) : null
NewTransactionModal.js
handleCreateClick = () => {
if (this.formIsValid()) {
let path = '/transaction',
transaction = {
type: this.state.transactionType.id,
amount: this.state.transactionAmount,
internalComment: this.state.comment,
userPassword: this.state.password
},
extraDataForError = {
typeName: this.state.transactionType.name,
advertiserName: this.state.advertiser.name,
fundingType: this.state.advertiser.fundingType,
financialDocumentId: this.state.documentId,
financialDocumentType: this.state.document && this.state.document.documentMetadata.documentType
};
if (this.state.transactionType.creditType) {
path += '/transfer';
transaction.debitAdvertiserId = this.state.advertiserId;
transaction.creditAdvertiserId = this.state.transferAdvertiserId;
transaction.debitFinancialDocumentId = this.state.documentId;
transaction.creditFinancialDocumentId = this.state.documentId;
} else {
transaction.advertiserId = this.state.advertiserId;
transaction.financialDocumentId = this.state.documentId;
}
this.props.createTransaction(path, transaction, extraDataForError);
this.closeModal();
}
};
ListTransaction.js
createTransaction = (path, data, extra) => {
const failureMsg = 'Failed to create transaction';
if (!path || !data) {
this.setState({
toastMessage: failureMsg,
toastType: 'error'
});
return;
}
const getErrTransactions = res => {
return [{ transaction: {...data, ...extra}, validations: res.validations, result: res.result }];
};
this.setState({ toastType: 'pending' }, async () => {
try {
const res = await ApiService.post(path, data, this.abortController.signal);
if (res && res.result !== 'FAILURE') {
this.setState({
toastMessage: 'Transaction created',
toastType: 'success',
selected: []
}, () => this.loadData(1));
} else if (res) {
this.handleError(getErrTransactions(res), failureMsg);
} else {
this.setState({toastType: null})
}
} catch (err) {
if (err) {
if (err.name === 'AbortError') {
return false;
} else if (err.json) {
this.setState({
passwordErrMsg: "Please enter a valid password"
})
const jsonErr = await err.json();
this.handleError(jsonErr.result ? getErrTransactions(jsonErr) : jsonErr, failureMsg);
} else {
this.handleError(err, failureMsg);
}
} else {
this.setState({ toastType: null });
}
}
});
};
ListTransactions.js
canCreate ? (
<NewTransactionModal
show={showNewTransactionModal}
types={allowedTransactionTypes}
createTransaction={this.createTransaction}
passwordErrMsg = {this.state.passwordErrMsg}
handlePasswordAttempt = {this.handlePasswordAttempt}
handleRecent={this.handleRecent}
handleClose={this.hideModal}
/>
) : null
I expect that that the user must still enter their password after an invalid attempt and that the modal does not go away. Also after the user has entered the correct password than the modal is closed.
I'm running into an async issue I hope I can get help in. The error I'm getting is
index.js:2178 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in CreateRoomPage (created by Route)
The location of this error is within this function. The two location are found where it says loginPromise.then and the setState which is at the bottom of the function. I'm currently calling this function when a person clicks on .
createUser(e) {
if (this.state.username === "") {
e.preventDefault();
this.setState({
errors: "Username can't be blank"
});
return;
}
const loginPromise = new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
window.user = user;
resolve(user.uid);
} else {
firebase
.auth()
.signInAnonymously()
.then(user => {
resolve(user.uid);
})
.catch(err => {
console.log(err);
});
}
});
});
loginPromise.then(id => {
let db = firebase.database();
let playersRef = db.ref(`Room/${this.state.roomId}/players`);
playersRef.child(`${id}`).set(`${this.state.username}`);
let player = db.ref(`Room/${this.state.roomId}/players/${id}`);
player.onDisconnect().remove();
let allPlayers = db.ref(`Room/${this.state.roomId}/all-players`);
allPlayers.child(`${id}`).set(true);
let allPlayer = db.ref(`Room/${this.state.roomId}/all-players/${id}`);
allPlayer.onDisconnect().remove();
let scoreBoard = db.ref(`Room/${this.state.roomId}/scoreBoard`);
scoreBoard.child(`${this.state.username}`).set(0);
let playerScore = db.ref(
`Room/${this.state.roomId}/scoreBoard/${this.state.username}`
);
playerScore.onDisconnect().remove();
let creator = db.ref(`Room/${this.state.roomId}`);
creator.child("creator").set(`${id}`);
db.ref(`Room/${this.state.roomId}`)
.child("gameStarted")
.set(false);
this.setState({
username: "",
errors: ""
});
});
I've spend nearly 3 hours trying to figure this out. I'm hoping someone can teach me where I'm making this error. I've tried to use a local state where once the componentDidMount it'll change local state to true and back to false once it unmounted like so:
componentDidMount() {
this.setState({ isMounted: true }, () => {
if (this.state.isMounted) {
let db = firebase.database();
let roomRefKey = db.ref("Room").push().key;
this.setState({
roomId: roomRefKey
});
}
});
}
Below is another place where it throws such an error
createUser(e) {
e.preventDefault();
if (
this.state.username.length === 0 &&
this.state.accesscode.length === 0
) {
this.setState({
errors: {
username: "Username can't be blank",
accesscode: "Access Code can't be blank"
}
});
return;
}
if (this.state.username.length === 0) {
this.setState({
errors: { username: "Username can't be blank", accesscode: "" }
});
return;
}
if (this.state.accesscode.length === 0) {
this.setState({
errors: { username: "", accesscode: "Access Code can't be blank" }
});
return;
}
const loginPromise = new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
window.user = user;
resolve(user.uid);
} else {
firebase
.auth()
.signInAnonymously()
.then(user => {
resolve(user.uid);
})
.catch(err => {
console.log(err);
});
}
});
});
loginPromise.then(id => {
let db = firebase.database();
let playersRef = db.ref(`Room/${this.state.accesscode}/players`);
playersRef.child(`${id}`).set(`${this.state.username}`);
let player = db.ref(`Room/${this.state.accesscode}/players/${id}`);
player.onDisconnect().remove();
let allPlayers = db.ref(`Room/${this.state.accesscode}/all-players`);
allPlayers.child(`${id}`).set(true);
let allPlayer = db.ref(`Room/${this.state.accesscode}/all-players/${id}`);
allPlayer.onDisconnect().remove();
let scoreBoard = db.ref(`Room/${this.state.accesscode}/scoreBoard`);
scoreBoard.child(`${this.state.username}`).set(0);
let playerScore = db.ref(
`Room/${this.state.accesscode}/scoreBoard/${this.state.username}`
);
playerScore.onDisconnect().remove();
this.props.history.push({
pathname: `/waiting-room/${this.state.accesscode}`
});
});
}