I am trying to update my firebase realtime database with user info when I am creating new user through firebase password authentication. And on successful sign in I am moving to another page. The problem is that my database is not getting updated in the above scenario however if stay in the login page and don't change to any other url; the database gets updated.
Here's my create user code
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
if (user !== null) {
const db = firebase.database();
db.ref("/").set({
uid: user.uid,
});
}
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
// ..
});
And here's where I'm switching to another page,
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
console.log(user);
//If I remove the below line, database is updated
window.location.href = "../html/home.html";
// ...
} else {
// User is signed out
// ...
console.log("not logged in");
}
});
This calls is asynchronous:
db.ref("/").set({
uid: user.uid,
});
That means that the code continues to run after the set() function returns, and asynchronously sends the data to the database. But when you change window.location, it interrupts this write operation. That's also why it works when you don't send the user to a new location: the write operation can then complete without interruption.
A quick simple fix is to flag when you're updating the database:
isCreatingUser = true; // 👈 Flag that we're creating a user
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
if (user !== null) {
const db = firebase.database();
db.ref("/").set({
uid: user.uid,
}).then(() => {
isCreatingUser = false; // 👈 We're done
window.location.href = "../html/home.html"; // 👈 Navigate away
}).catch((e) => {
isCreatingUser = false; // 👈 We're done
throw e;
})
}
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
// ..
isCreatingUser = false; // 👈 We're done
});
And then:
firebase.auth().onAuthStateChanged((user) => {
if (user && !isCreatingUser) {
...
You'll probably need some more synchronization, but that's the gist of it.
Related
please help.
I'm trying to ensure that the user does not log out after reloading the page.
const signUp = async (loginEmail, loginPassword) => {
const auth = getAuth()
setPersistence(auth, browserSessionPersistence)
.then(({ user }) => {
dispatch(
setUser({
email: user.email,
id: user.uid,
token: user.accessToken,
})
)
return signInWithEmailAndPassword(auth, loginEmail, loginPassword)
})
.catch((error) => {
console.log(error.message)
})
}
This is what the example looks like in the official documentation
const auth = getAuth();
setPersistence(auth, browserSessionPersistence)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// if a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return signInWithEmailAndPassword(auth, email, password);
})
.catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
});
The recommended way to get the current user is by setting an observer on the Auth object:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
documentation
When the user clicks on the "sign-in" button and if user.challangeName === 'NEW_PASSWORD_REQUIRED'is true, I redirect the user to a page (form screen) where he can provide the input for required attributes and a new password. Even tho the user object is getting populated upon clicking on the sign-in button, using Auth.currentsession on the form screen will print "No user found"
Can someone let me know why I'm seeing no user? What am I doing wrong here?
Here's my login function (triggered when clicked on sign-in button) where I direct the user to the change password screen (form screen) if user.challangeName === 'NEW_PASSWORD_REQUIRED' is true.
const login = async (email, password) => {
try {
const user = await Auth.signIn(email, password);
if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
navigate('/change-password');
return;
}
if (user) {
setToken(user.signInUserSession.idToken.jwtToken);
const userDetails = await getAccountDetails();
dispatch({
type: LOGIN,
payload: {
user: {
attributes: user.attributes,
username: user.username
},
client: userDetails
}
});
}
} catch (error) {
await logout();
throw error;
}
};
Here's my onSubmit function on the change password screen where eventually I want to use Auth.completeNewPassword to update the user's password in Cognito
const onSubmitClick = (e) => {
e.preventDefault();
if (validateFields());
Auth.currentSession()
.then((user) => console.log(user))
.catch((err) => console.log(err));
};
Here's the documentation provided by AWS https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#forgot-password, and the code provided by AWS to update password
Auth.signIn(username, password)
.then(user => {
if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
const { requiredAttributes } = user.challengeParam; // the array of required attributes, e.g ['email', 'phone_number']
Auth.completeNewPassword(
user, // the Cognito User Object
newPassword, // the new password
// OPTIONAL, the required attributes
{
email: 'xxxx#example.com',
phone_number: '1234567890'
}
).then(user => {
// at this time the user is logged in if no MFA required
console.log(user);
}).catch(e => {
console.log(e);
});
} else {
// other situations
}
}).catch(e => {
console.log(e);
});
New updated answer to reflect your updated post, change the onSubmitClick to the following:
const onSubmitClick = (e) => {
e.preventDefault();
if (validateFields());
Auth.currentAuthenticatedUser()
.then(user => {
console.log(user))
})
.then((data) => console.log(data))
.catch((err) => console.log(err));
};
You are looking at the documentation which shows how to use the user after Auth.signIn() (which was my previous answer), compared to what you use: Auth.currentAuthenticatedUser(). The proper documentation example you have to look at is this one: https://www.docs.amplify.aws/lib/auth/manageusers/q/platform/js
So I have a functionality in my app wherein a user can post or delete any alert. If a new alert is posted other users must get a notification regarding it. Firebase push notification works good when a new data is added to the database but if a post has been deleted (dataRef.child(root_child).removeValue();) it still sends notification to the user which is not required. How to handle this situation?
index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationAlert = functions.database.ref(`AlertPost/{pushId}`).onWrite(event => {
const getDeviceTokensPromise = admin.database().ref(`/Token/token_no`).once('value');
const getBody=admin.database().ref(`/AlertPost`).once('value');
var title_input='You have a new Alert';
var contentAlert = event.data.val();
var body_input=contentAlert.description;
//const tokensSnapshot = results[0];
return Promise.all([getDeviceTokensPromise,getBody]).then(results => {
const tokensSnapshot = results[0];
const notify=results[1];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
var contentAlert = event.data.val();
// Notification details.
const payload = {
data: {
title: title_input,
body: body_input
//icon: follower.photoURL
},
notification: {
title: title_input,
body: body_input
}
};
const tokens = Object.keys(tokensSnapshot.val());
//token_send(admin,tokensSnapshot,tokens,payload,title_input);
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
console.log("Successfully sent message:", response);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
You could check if the previous value of the event DataSnapshot is already deleted. Check this documentation for more information.
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original').onWrite((event) => {
// Only edit data when it is first created.
if (event.data.previous.exists()) {
return;
}
// Exit when the data is deleted.
if (!event.data.exists()) {
return;
}
});
You could also check this SO post for reference.
I have a simple login page built with React that uses the AWS Cognito API to authenticate the user. There are some authentication scenarios (password needs to be updated, need to enter an MFA code, etc.) that require me to get user input mid-execution of the authenticateUser workflow. I'm trying to find a way to get the user input dynamically without using the built-in prompt() method, especially when a user is entering a new password. Based on how the authenticateUser workflow is structured, I'm trying to get all user input within the workflow.
Perhaps I'm not thinking about this problem in the right way, but how can I have another React component dynamically render, get user input (new password, MFA code, etc), and then use that input within the authenticateUser workflow?
The main Login component has a form that upon clicking the Submit button triggers the following function:
handleSubmit = async (event) => {
event.preventDefault();
this.setState({ isLoading: true, loginError: null });
try {
await this.login(this.state.username, this.state.password);
this.props.userHasAuthenticated(true);
}
catch(e) {
//alert(e);
this.setState({ isLoading: false, loginError: e.toString() });
}
}
And then we have the login function that goes through the authenticateUser workflow:
login(username, password) {
const userPool = new CognitoUserPool({
UserPoolId: config.cognito.USER_POOL_ID,
ClientId: config.cognito.APP_CLIENT_ID
});
const authenticationData = {
Username: username,
Password: password
};
const user = new CognitoUser({ Username: username, Pool: userPool });
const authenticationDetails = new AuthenticationDetails(authenticationData);
return new Promise((resolve, reject) => (
user.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
// User authentication was successful
resolve();
},
onFailure: (err) => {
var error = err.toString();
// If password expired
if (error.includes('Password reset required for the user')) {
var verificationCode = prompt('Password reset required. Please enter the verification code sent to your trusted device.' ,'');
var newPassword1 = '';
var newPassword2 = '';
while (newPassword1 !== newPassword2 || newPassword1.length < 8) {
newPassword1 = prompt('Please enter a new password.','');
newPassword2 = prompt('Please confirm your new password','');
}
user.confirmPassword(verificationCode, newPassword1, {
onSuccess: (result) => {
//Not sure if this handleSubmit does anything
//this.handleSubmit;
this.setState({ loginError: 'Password updated successfully! Please login with new password.', loginAlert: "success", updatePasswordUpdateAttribute: true });
return;
},
onFailure: (err) => {
this.setState({ loginError: err.toString() });
reject(err)
}
});
}
// User authentication was not successful
console.error(err);
reject(err)
},
mfaRequired: (codeDeliveryDetails) => {
// MFA is required to complete user authentication.
// Get the code from user and call
var verificationCode = prompt('Please enter the multi-factor code sent to your trusted device.' ,'');
user.sendMFACode(verificationCode, this);
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// Get these details and call
// newPassword: password that user has given
// attributesData: object with key as attribute name and value that the user has given.
user.completeNewPasswordChallenge(newPassword1, null, {
onSuccess: (result) => {
this.updatePasswordAttribute(user);
resolve()
},
onFailure: (err) => {
this.setState({ loginError: err.toString() });
reject(err)
}
})
}
})
));
}
I have done authentication trigger, it's working fine. If someone delete their account I need to send to notification "this user deleted his account (email)" like that. Here is my code
const functions = require('firebase-functions')
//initialize the app
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const ref = admin.database().ref()
//create user account function
exports.createUserAccount = functions.auth.user().onCreate(event => {
const uid = event.data.uid
const email = event.data.email
const newUserRef = ref.child(`/UserNotify/${uid}`)
return newUserRef.set({
email: email
})
})
//delete user account function
exports.cleanupUserData = functions.auth.user().onDelete(event => {
const uid = event.data.uid
const userRef = ref.child(`/UserNotify/${uid}`)
return userRef.update({isDeleted: true})
})
function sendNotification() {
console.log("Successfully sent");
var payload = {
notification: {
title: "User get deleted",
body: "sample#gmail.com"
}
};
admin.messaging().sendToDeveice(payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
})
}
You may have a typing error
admin.messaging().sendToDevice() and not sendToDeveice
check: https://firebase.google.com/docs/cloud-messaging/admin/send-messages