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)
}
Related
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.
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()})
}
so I'm using a popup to log my users in with firebase:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
await setNewUserInformation(result.user.uid)
}
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
so if I remove window.location.href = 'visited' this all works fine and it sets in firebase. I'm probably doing something stupid but I cant figure out how to wait for this function to fire setNewUserInformation and to complete before I move to the new page?
function code:
export const setNewUserInformation = (userId) => {
return {
type: 'SET_NEW_USER_INFORMATION',
userId,
}
}
this then has a redux observable epic listening to it:
return action$.pipe(
ofType('SET_NEW_USER_INFORMATION'),
mergeMap((action) => {
return from(
firebaseApp.database().ref(firebaseRef).update(userInformation),
).pipe(
mergeMap(() => {
return [updatedUserInformationSuccess()]
}),
catchError((error) => of(updatedUserInformationFailure(error))),
)
}),
)
setNewUserInformation() is an action creator, which is sync. You do not need to wait for it as it does not return anything useful to you logic. What you need to do, is move window.location.href = 'newRoute' to separate logic, and make it depend on state returned from action creators updatedUserInformationSuccess() and updatedUserInformationFailure(error). If your component is functional, put this logic in a useEffect. If it is a class component, use ComponentDidUpdate lifecycle method.
Use it like below
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
new Promise((resolve, reject) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
setNewUserInformation(result.user.uid)
}
const { user } = result
resolve(user)
}).then((user)=>{
setUser(user)
// and this line
window.location.href = 'newRoute'
})
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Because on then You can returned a Promise and resolve later. We could re-write the code above like this below:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then((result) => {
if (result.additionalUserInfo.isNewUser) {
// return for next resolve function
return setNewUserInformation(result.user.uid).then(() => result);
}
return result;
})
.then((result) => {
// after all above promises resolve
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Are you using React?
If yes, then you can simply use didUpdate Cycle to route to new url after successful action dispatched. Move your "window.location.href = 'newRoute'" under the ComponentDidUpdate with props check.
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);
});
});