I started this tutorial (https://www.freecodecamp.org/news/react-native-firebase-tutorial/) on Firebase and React Native. Everything is working well overall.
But I have this error: “User does not exist anymore.” for the Login.
However, users are well rooted in Firebase.
const onLoginPress = () => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
.catch(error => {
alert(error)
});
})
.catch(error => {
alert(error)
})
}
With
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
you actually query the user document with the id corresponding to the user's uid in the users collection.
This document is normally created by the onRegisterPress() function in the tutorial. If you get the "User does not exist anymore." message, it means that the user document is not present in the collection.
So you need to check why this is the case: the onRegisterPress() function was not called? The doc was deleted? There are security rules that prevent creating the document? etc...
Related
I have been working on a firebase project in which I created a cloud function that creates documents in firestore. This is the function -
export const createExpenseCategory = functions
.region("europe-west1")
.https.onCall(async (data, context) => { // data is a string
if (!context.auth?.uid) { // check that requesting user is authenticated
throw new functions.https.HttpsError(
"unauthenticated",
"Not Authenticated"
);
}
const res = await admin
.firestore()
.collection("/categories/")
.where("uid", "==", context.auth.uid)
.get();
const categoryExists = res.docs.find((doc) => doc.data().name === data); // check that there are not duplicates.
// doc looks like this -
// {
// "name": "Food",
// "uid": "some_long_uid"
// }
if (categoryExists) {
throw new functions.https.HttpsError(
"already-exists",
`Category ${data} already exists`
);
}
return admin
.firestore()
.collection("/categories/")
.add({ name: data, uid: context.auth.uid });
});
As you can see, at the beginning of the function I check whether the user that sent the request is authenticated with the context parameter. Everything works fine when I play around with it in my web app, but I have been trying to figure out a way to create a unittest for this function. My problem is that I can't really figure out how to create an authenticated request to make sure that my function doesn't fail every time. I tried to look online for any documentation but couldn't seem to find any.
Thanks in advance!
You can unit test your functions using the firebase-functions-test SDK. The guide mentions you can mock the data within the eventContext or context parameter passed to your function. This works for mocking the uid field of the auth object:
// Left out authType as it's only for RTDB
wrapped(data, {
auth: {
uid: 'jckS2Q0'
}
});
The guide uses mocha for testing, but you can use other testing frameworks. I made a simple test to see if it would work and I could send the mock uid to the function, which worked as expected:
index.js
exports.authTest = functions.https.onCall( async (data, context) => {
if(!context.auth.uid){
throw new functions.https.HttpsError('unauthenticated', 'Missing Authentication');
}
const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);
return admin.firestore().collection('users').doc(userDoc.id).update({name: data.name});
});
index.test.js
const test = require('firebase-functions-test')({
projectId: PROJECT_ID
}, SERVICE_ACCTKEY); //Path to service account file
const admin = require('firebase-admin');
describe('Cloud Functions Test', () => {
let myFunction;
before(() => {
myFunction = require('../index.js');
});
describe('AuthTest', () => {
it('Should update user name in UID document', () => {
const wrapped = test.wrap(myFunction.authTest);
const data = {
name: 'FooBar'
}
const context = {
auth: {
uid: "jckS2Q0" //Mocked uid value
}
}
return wrapped(data, context).then(async () => {
//Asserts that the document is updated with expected value, fetches it after update
const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);
assert.equal(userDoc.data().name, 'FooBar');
});
});
});
});
Let me know if this was useful.
I am building an app with firebase .
I had successfully implemented a function that will enable the user to upload a pic to firebase storage
here it is
const uploadImageToBucket = async () => {
let blob;
try {
setUploading(true);
blob = await getPictureBlob(image);
const ref = await storage.ref().child(uuid.v4());
const snapshot = await ref.put(blob);
return await snapshot.ref.getDownloadURL();
} catch (e) {
alert(e.message);
} finally {
blob.close();
setUploading(false);
}
};
the problem is that I want the picture to be uploaded based on certain user and I want to set that pic as user profile pic .any suggestion please!!
here the user sign up function
const handleSignUp = () => {
setErrortext("");
if (!FullName) return alert("Please fill Name");
if (!Email) return alert("Please fill Email");
if (!Password) return alert("Please fill Address");
setIsLogged(true);
firebase
.auth()
.createUserWithEmailAndPassword(Email, Password)
.then((user) => {
alert("Registration Successful. Please Login to proceed");
console.log(user);
if (user) {
firebase
.auth()
.currentUser.updateProfile({
displayName: FullName,
})
.then(() => navigation.replace("Log_In"))
.then(() => {
firebase.auth().onAuthStateChanged((userData) => {
setuserData(userData);
});
})
.catch((error) => {
console.log(error);
setErrortext(error);
});
}
})
.catch((error) => {
if (error.code === "auth/email-already-in-use") {
setErrortext("That email address is already in use!");
setIsLogged(false);
} else {
setErrortext(error.message);
setIsLogged(false);
}
});
};
You can simply use updateProfile method to update currently logged in user's photoURL and set it to the download URL just requested. The uploadImageToBucket function returns that URL back so you can try this:
uploadImageToBucket().then(async (photoURL) => {
const user = firebase.auth().currentUser
await user.updateProfile({ photoURL })
console.log("Photo URL updated")
})
I have an issue and I need help since I'm learning.
I have a flutter app that saves on Firebase/Firestore data, when a user requests a friendship I add it on both sender and target user, changing the IDs of sendTo and sentBy.
My problem is that CloudFunctions detect well that 2 collections from different users have been changed and notify me 2x (target user). So code is fine but should only notify once/target user
I'm using FCM to send local notifications.
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const docReq = snap.data()
/*console.log(docReq)*/
const sentBy = docReq.sentBy
const sentTo = docReq.sentTo
const contentRequest = docReq.code
if(contentRequest !== null){
// Get push token user to (receive)
admin
.firestore()
.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(userTo => {
/*console.log(`Found request user to: ${userTo.data().userId}`)*/
if (userTo.data().pushToken) {
// Get info user from (sent)
admin
.firestore()
.collection('users')
.where('userId', '==', sentBy)
.get()
.then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
/*console.log(`Found request user from: ${userFrom.data().userId}`)*/
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: contentRequest,
badge: '1',
sound: 'default'
}
}
// Let push to the target device
admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload)
.then(response => {
/*console.log('Successfully sent request:', response)*/
})
.catch(error => {
console.log('Error sending request:', error)
})
})
})
} else {
console.log('User request or token not found')
}
})
})
return null
}
})
It is not very clear from your code why it would send the notification twice (since you check that userTo.data().userId !== sentBy). But what is sure is that you are not returning a Promise that resolves when all the asynchronous operations (get() and sendToDevice()) are completed.
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").
In particular, you will see in the videos that if you don't return a Promise, the Cloud Function may terminate before asynchronous operations are completed, potentially resulting in some inconsistent (not logical) results .
So, you should give a try with the following adapted code, which returns the promises chain:
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const db = admin.firestore();
const docReq = snap.data();
/*console.log(docReq)*/
const sentBy = docReq.sentBy;
const sentTo = docReq.sentTo;
// Get push token user to (receive)
return db.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
//We know there is only one document (i.e. one user with this Id), so lets use the docs property
//See https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot.html#docs
const userTo = querySnapshot.docs[0];
if (userTo.data().pushToken && userTo.data().userId !== sentBy) {
// Get info user from (sent)
return db.collection('users')
.where('userId', '==', sentBy)
.get();
} else {
console.log('User request or token not found')
throw new Error('User request or token not found');
}
})
.then(querySnapshot => {
const userFrom = querySnapshot.docs[0];
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: `requestNotify`,
badge: '1',
sound: 'default'
}
}
return admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload);
})
.catch(error => {
console.log('Error:', error);
return false;
})
})
I have this Vuex action that should change a users status on logout but since false values aren't save in Firestore, it isn't working
logoutUser({commit, state}) {
let user = firebase.auth().currentUser;
db
.collection('users')
.where('user_id', '==', user.uid)
.get()
.then(snapshot => {
snapshot.forEach((doc) => {
db
.collection('users')
.doc(doc.id)
.update({
is_online: false
});
localStorage.setItem('firebase_user', null);
firebase
.auth()
.signOut()
.then(() => {
commit('SET_AUTHENTICATED', false);
commit('SET_CURRENT_USER', null);
});
});
});
}
I am just starting with Firestore so I would appreciate any assistance or recommendations ... Thanks!!!
The following should work (not tested). You need to chain the promises returned by the asynchronous methods (update() and signOut()).
I make the assumption that there is only one user with user.uid in the users collection, therefore I use snapshot.docs[0] to get the unique user document, instead of using forEach()
logoutUser({ commit, state }) {
const user = firebase.auth().currentUser;
db.collection('users')
.where('user_id', '==', user.uid)
.get()
.then(snapshot => {
const userDocSnapshot = snapshot.docs[0];
return userDocSnapshot.ref.update({
is_online: false
});
})
.then(() => {
return firebase.auth().signOut();
})
.then(() => {
localStorage.setItem('firebase_user', null);
commit('SET_AUTHENTICATED', false);
commit('SET_CURRENT_USER', null);
});
}
I have set a custom claim using the firebase admin sdk. I have successfully use it to control access in the frontend and even with the RTDB, but I'm not able to use it with the Firestore database. Here is my security rule:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.token.admin == true;
}
}
}
Here is the code in my app:
const users = []
firebase.firestore().collection('users')
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
users.push(doc.data())
})
})
.then(() => {
commit('setUsersList', users)
commit('setLoading', false)
})
.catch(function (error) {
console.log('Error getting documents:', error)
commit('setLoading', false)
})
And here is the error I'm getting:
Error: Missing or insufficient permissions
I have fixed the error. My mistake was that I was setting the permission using the admin sdk but I wasn't passing a boolean but a string.
For example, I was setting the user with uid '1' like this:
axios.post('/admin/setadminprivileges/1/true')
In my firebase functions I was getting:
app.post('/admin/setadminprivileges/:id/:permission', (req, res) => {
const permission = req.params.permission // this is a string "true"
const uid = req.params.id // "1"
const payload = {admin: permission}
admin.auth().setCustomUserClaims(uid, payload)
})
And now with this is working fine:
app.post('/admin/setadminprivileges/:id/:permission', (req, res) => {
const permissionString = req.params.permission
const permission = permissionString === 'true' // this is now a boolean
const uid = req.params.id
const payload = {admin: permission}
admin.auth().setCustomUserClaims(uid, payload)
})
Thanks anyway. I knew it had to be a silly issue of my own, because Firebase is a solid product.