I'm just making a simple single-page app with react js and firebase. in the authentication, part users can create a new account with email and password but to login first they need to verify their email.
My code.
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authuser) => {
if (authuser){
setuser(authuser);
}
else{
setuser(null);
}
});
return () => {
unsubscribe();
}
},[user,username]);
const signup = (event) => {
event.preventDefault();
var user = auth.createUserWithEmailAndPassword(email,password)
.then((authuser) => {
authuser.user.updateProfile({
displayName : username
})
})
.catch((error) => {
alert(error.message);
}
);
user.then((authuser) => {
authuser.user.sendEmailVerification();
})
.then(() => {alert("We have send you an confirmation email to verify your account")})
.catch((error) => { return; })
.then(() => {handleClose()});
}
const signin = (event) =>{
event.preventDefault();
auth
.signInWithEmailAndPassword(email,password)
.then(() => {loghandleClose()})
.catch((error) => {alert(error.message)})
}
After creating new user signup method skipping the send confirmation email part why and user get automatically login when they create new account.
I think onAuthStateChanged() function run automatically when new user created and skip the send confirmation email part of signup method.
how I can fix it please.
i solve it by myself this how I changed my code.
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authuser) => {
if (authuser && authuser.emailVerified === true){
setuser(authuser);
}
else{
auth.signOut();
setuser(null);
}
});
return () => {
unsubscribe();
}
},[user,username]);
const signup = (event) => {
event.preventDefault();
auth.createUserWithEmailAndPassword(email,password)
.catch((error) => {
alert(error.message);
}
)
.then((authuser) => {
authuser.user.updateProfile({
displayName : username
})
authuser.user.sendEmailVerification();
})
.then(() => {alert("We have send you an confirmation email to verify your account")})
.catch((error) => { alert(error.message) })
.then(() => {handleClose()});
}
const signin = (event) =>{
event.preventDefault();
auth
.signInWithEmailAndPassword(email,password)
.catch((error) => {alert(error.message)})
.then((user) => {
if (!user.user.emailVerified){
auth.signOut();
alert("Please verify your email first");
}
})
.then(() => {loghandleClose()})
}
Related
I have a react native application. I want to be able to reauthenticate a user before submitting the updated email. I am running into the issue of reautneticaing the user.
tried
const saveProfile = () => {
let credential = EmailAuthProvider.credential(user.email, 'hellohello')
user.reauthenticateWithCredential(credential)
.then(() => {
updateEmail(auth.currentUser, newEmail.toString())
.then(() => {
console.log('updated email')
setUser(auth.currentUser)
})
.catch((error) => {
console.log(error)
})
})
.catch((error) => {
console.log(error)
})
}
and
const saveProfile = () => {
let credential = EmailAuthProvider.credential(user.email, 'hellohello')
reauthenticateWithCredential(credential)
.then(() => {
updateEmail(auth.currentUser, newEmail.toString())
.then(() => {
console.log('updated email')
setUser(auth.currentUser)
})
.catch((error) => {
console.log(error)
})
})
.catch((error) => {
console.log(error)
})
}
When i try to update the email with firebase, it says I have to Firebase: Error (auth/requires-recent-login).
error:
undefined is not an object (evaluating 'credential._getReauthenticationResolver')
at node_modules/#firebase/auth/dist/rn/phone-eec7f987.js:5048:20 in _processCredentialSavingMfaContextIfNecessary
at node_modules/#firebase/auth/dist/rn/phone-eec7f987.js:5212:113 in tslib.__generator$argument_1
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:144:21 in step
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:125:60 in
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:118:17 in
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:114:15 in __awaiter
at node_modules/#firebase/auth/dist/rn/phone-eec7f987.js:5333:49 in tslib.__generator$argument_1
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:144:21 in step
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:125:60 in
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:118:17 in
at node_modules/#firebase/auth/node_modules/tslib/tslib.js:114:15 in __awaiter
at src/screens/SettingsScreen.js:158:4 in saveProfile
at src/screens/SettingsScreen.js:186:81 in TouchableOpacity.props.onPress
at node_modules/react-native/Libraries/Pressability/Pressability.js:702:17 in _performTransitionSideEffects
at node_modules/react-native/Libraries/Pressability/Pressability.js:639:6 in _receiveSignal
at node_modules/react-native/Libraries/Pressability/Pressability.js:520:8 in responderEventHandlers.onResponderRelease
The reauthenticateWithCredential() function takes currentUser as first parameter. Try refactoring the code as shown below:
const saveProfile = async () => {
const credential = EmailAuthProvider.credential(user.email, 'hellohello')
// auth.currentUser as first param
await reauthenticateWithCredential(auth.currentUser, credential);
await updateEmail(auth.currentUser, newEmail.toString())
console.log('updated email')
setUser(auth.currentUser)
}
export const requestFederatedSignOn = async (type) => {
return new Promise((resolve, reject) => {
Auth.federatedSignIn({ provider: type })
.then(() => {
console.log("you have signed in");
authenticatedUserCred()
.then((userData) => {
console.log("you have been authenticated");
resolve(userData);
})
.catch((err) => console.error(err));
})
.catch((error) => reject(error));
});
};
export const authenticatedUserCred = async () => {
return new Promise((resolve, reject) => {
Auth.currentAuthenticatedUser()
.then((user) => {
const userAttributes = user.signInUserSession.idToken.payload;
const tokens = {
accessToken: user.signInUserSession.accessToken.jwtToken,
idToken: user.signInUserSession.idToken.jwtToken,
refreshToken: user.signInUserSession.refreshToken.token,
};
const userData = {
tokens: tokens,
userProfile: userAttributes,
};
resolve(userData);
})
.catch((err) => reject(err));
});
};
Basically, I am able to sign in using federatedSignIn and console log "you have signed in" is outputted but then authenticatedUserCred function for some reason is rejecting the promise. Please someone help me out!!!
Evening All
Having a few problems performing two actions when a document is created
the below worked until i added the last "then" in the createDocument function where i attempt to send a notification to inform the use via fcm.
exports.createRequest = functions.firestore
.document('requests/{requestId}')
.onCreate((snap, context) => {
var email = snap.data().requestedFromEmail;
checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
)
can anyone see where im going wrong, all the examples im finding send the fcm explicitly from using a exports. Ideally id like to pass the userProfile object through to the send notification function but um not sure how to do that and still set the changes to the document. Full code is below
async function checkUserInFirebase(email) {
return new Promise((resolve) => {
admin.auth().getUserByEmail(email)
.then((user) => {
return resolve({ isError: false, doesExist: true, user });
})
.catch((err) => {
return resolve({ isError: true, err });
});
});
}
async function getUserProfile(uid) {
return admin.firestore()
.collection("users")
.doc(uid)
.get();
}
async function sendNotification(uid, requestedByName) {
const querySnapshot = await db
.collection('users')
.doc(uid)
.collection('tokens')
.get();
const tokens = querySnapshot.docs.map(snap => snap.token);
console.info(tokens);
const payload = {
notification: {
title: 'New Request!',
body: `You received a new request from ${requestedByName}`,
icon: 'your-icon-url',
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(tokens, payload);
}
exports.createRequest = functions.firestore
.document('requests/{requestId}')
.onCreate((snap, context) => {
var email = snap.data().requestedFromEmail;
checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
)
Your funciton needs to return a promise that resolves when all the async work is complete. Right now it returns nothing, which means that Cloud Functions might terminate it up before the work is done. You should return the promise chain:
return checkUserInFirebase(email)
.then((user) => {
//get user profile
return getUserProfile(user.user.uid);
})
.then(userProfile => {
return snap.ref.set({ requestedFromName: userProfile.data().fullName, requestedFromId: userProfile.id }, {merge:true});
})
.then(value=> {
return sendNotification(snap.data().requestedFromId, snap.data().requestedByName);
})
.catch(error => {return error;})
}
Note the return at the start of the whole thing.
See the documentation for more information.
This works fine in development, but when I deploy many of my functions break. E.g., I have a form that adds data to the database but when I submit it, it just refreshes the page but the data never makes it to the database.
onSubmit = () => {
firebase.auth().onAuthStateChanged((user) => {
const { phoneNumber, points } = this.state;
let pointsToAdd = Number(points);
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(phoneNumber);
docRef.set({
points: pointsToAdd
})
.then(() => {
console.log('success');
})
.catch((error) => {
console.log(error);
});
} else {
window.location.href = '/';
}
});
}
If it's refreshing, then the user variable is null. So, you must not be signed in on your deployed version. Just add a firebase signin function first before calling that one!
https://firebase.google.com/docs/auth/web/password-auth
I guess you're not logged in on the non-dev environment, the current implementation will simply redirect when no user is found.
As feedback on your current submit-handler: I think you want to listen to authStateChanged when your components mounts instead of when the submit handler is called. Update the state accordingly and use the user from the state when reaching for the correct document.
For example:
componentDidMount () {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) this.setState({ user })
else window.location.href = '/'
})
}
componentWillUnmount () {
this.unsubscribe() // don't forget to unsubscribe when unmounting!
}
onSubmit = () => {
const { phoneNumber, points, user } = this.state;
let pointsToAdd = Number(points);
database.collection('users').doc(user.uid).collection('customers').doc(phoneNumber)
.set({
points: pointsToAdd
})
.then(() => {
console.log('success');
})
.catch((error) => {
console.log(error);
});
}
I have a passing test now thanks to the answer here: How to test is chained promises in a jest test?
However I'm still getting an error in the catch part of my test.
I seem to not be able to correctly mock or spy this part in the actions file: .then(res => res.getIdToken())
TEST signIn ERROR => TypeError: res.getIdToken is not a function
The Test
jest.mock('services/firebase', () => new Promise(resolve => resolve({
signInWithEmailAndPassword: () => Promise.resolve({ getIdToken: 'abc123' }),
getIdToken: () => jest.fn(),
signOut: () => jest.fn()
})));
describe('login actions', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
it('signIn should call firebase', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
return store.dispatch(signIn(user.email, user.password))
.then(() => {
console.log('TEST signIn SUCCESS');
expect(mockSignIn).toHaveBeenCalled();
expect(store.getActions()).toEqual({
type: USER_ON_LOGGED_IN
});
})
.catch((err) => {
console.log('TEST signIn ERROR =>', err);
});
});
The SignIn actions/Login
// Sign in action
export const signIn = (email, password, redirectUrl = ROUTEPATH_DEFAULT_PAGE) => (dispatch) => {
dispatch({ type: USER_LOGIN_PENDING });
return firebase
.then((auth) => {
console.log('auth =>', auth);
return auth.signInWithEmailAndPassword(email, password);
})
.catch((e) => {
console.error('actions/Login/signIn', e);
// Register a new user
if (e.code === LOGIN_USER_NOT_FOUND) {
dispatch(push(ROUTEPATH_FORBIDDEN));
dispatch(toggleNotification(true, e.message, 'error'));
} else {
dispatch(displayError(true, e.message));
setTimeout(() => {
dispatch(displayError(false, ''));
}, 5000);
throw e;
}
})
// I can't seem to mock this correctly
.then(res => res.getIdToken())
.then((idToken) => {
if (!idToken) {
dispatch(displayError(true, 'Sorry, there was an issue with getting your token.'));
}
dispatch(onCheckAuth(email));
dispatch(push(redirectUrl));
});
};
It looks like the reason why you're getting this error has to do with the data you're mocking through Jest.
Try using jest.fn() to mock your getIdToken as a function, rather than a string:
const mockGetIdToken = jest.fn(() => 'abc123');
jest.mock('services/firebase', () => new Promise(resolve => resolve({
signInWithEmailAndPassword: () => Promise.resolve({ getIdToken: mockGetIdToken }),
getIdToken: mockGetIdToken,
signOut: () => jest.fn()
})));
describe('login actions', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
it('signIn should call firebase', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
return store.dispatch(signIn(user.email, user.password))
.then(() => {
console.log('TEST signIn SUCCESS');
expect(mockSignIn).toHaveBeenCalled();
expect(store.getActions()).toEqual({
type: USER_ON_LOGGED_IN
});
})
.catch((err) => {
console.log('TEST signIn ERROR =>', err);
});
});