I develop web app using Angular 8 and connect to firebase using #angular/fire v5.4.2 and firebase js SDK v7.8.0. Every time I want to get a document in firestore it always shows error
FirebaseError: Missing or insufficient permissions.
at new FirestoreError (https://localhost:4200/vendor.js:130146:28)
at JsonProtoSerializer.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.JsonProtoSerializer.fromRpcStatus (https://localhost:4200/vendor.js:145520:16)
at JsonProtoSerializer.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.JsonProtoSerializer.fromWatchChange (https://localhost:4200/vendor.js:146033:44)
at PersistentListenStream.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.PersistentListenStream.onMessage (https://localhost:4200/vendor.js:142655:43)
at https://localhost:4200/vendor.js:142584:30
at https://localhost:4200/vendor.js:142624:28
at https://localhost:4200/vendor.js:131493:20
at ZoneDelegate.invoke (https://localhost:4200/polyfills.js:3690:26)
at Object.onInvoke (https://localhost:4200/vendor.js:83071:33)
at ZoneDelegate.invoke (https://localhost:4200/polyfills.js:3689:52)
Here is my code when trying to get the document
loginFirebase(): Promise<any> {
return new Promise((resolve, reject) => {
firebase.auth().signInAnonymously().then(res => {
resolve(res);
}).catch(err => {
reject(err);
});
});
}
login(username: string, password: string): Promise<IUsers> {
return new Promise((resolve, reject) => {
this.loginFirebase().then(userLogin => {
this.setUser(res).then(() => {
resolve();
}).catch(err => {
reject(err);
});
}).catch(errorFirebase => {
reject(errorFirebase);
});
}
}
setUser(data: UserLogin): Promise<any> {
return new Promise((resolve, reject) => {
const userData: IUsers = {
userId: data.doctor.id.toString(),
userFullName: `${data.doctor.firstName} ${data.doctor.lastName}`,
userPhoto: data.doctor.profileImage || '',
userStatus: UserStatus.ACTIVE,
userType: data.roles[0].role,
token: data.token,
sex: data.doctor.sex,
email: data.name
};
try {
this.afStore.collection(`${environment.firestoreCollection}users`, ref => ref.where('userId', '==', userData.userId.toString()))
.get()
.subscribe(doc => {
resolve(doc)
}, err => {
console.error(err);
});
} catch (error) {
this.translate.get('error_message', {error: 'catch saveUser'}).subscribe(err => {
if (confirm(err)) {
this.setUser(data);
} else {
reject(error);
}
});
}
And here is the rules for the firestore
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isAuth(request);
}
function isAuth(req) {
return req.auth != null;
}
}
}
When the web app signed in anonymously, I check the uid it's already in firebase authentication. What possibly I get wrong here? Anyone can help me, please?
You should use an auth state observer to determine when the user is actually signed in and able to make authenticated queries. It turns out that the promise returned by signInAnonymously isn't actually an indicator if the user is fully signed in.
Related
I have login code in react native using firebase and google signin auth.
So when new user sign in using google account, I set new data. And if user has signed in before, user go to main page.
My problem is when new user sign in > code start to get signInWithCredential > set new data user, before set data finish, onAuthStateChanged was detect there is change in auth and start to get user document / data. But because it's not finish yet, it throw error 'Can Not Get UID / Undefined UID'.
This is my login page code:
const _signIn = async () => {
setInitializing(true);
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(
userInfo.idToken,
userInfo.accessToken,
);
await auth()
.signInWithCredential(credential)
.then(response => {
const uid = response.user.uid;
const data = {
uid: uid,
email: userInfo.user.email,
fullname: userInfo.user.name,
bio: 'Halo!! ..',
username: uid.substring(0, 8),
};
const usersRef = firestore().collection('users');
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
usersRef
.doc(data.uid)
.set(data)
.then(() => {
setInitializing(false); return;
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
return;
}
})
.catch(error => {
Alert.alert(JSON.stringify(error.message));
console.log('Error getting document:', error);
return;
});
});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
setInitializing(false);
Alert.alert('Sign in canceled');
} else if (error.code === statusCodes.IN_PROGRESS) {
setInitializing(false);
Alert.alert('Signin in progress');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
setInitializing(false);
Alert.alert('PLAY_SERVICES_NOT_AVAILABLE');
} else {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
}
}};
And this is my index page code to check auth user:
useEffect(() => {
try {
NetInfo.fetch().then(state => {
if(state.isConnected === false){
Alert.alert('No Internet Connection Detected');
setInitializing(false);
return;
}
});
setInitializing(true);
await auth().onAuthStateChanged(user => {
if (user) {
const usersRef = firestore().collection('users');
usersRef
.doc(user.uid)
.get()
.then(document => {
const userData = document.data().uid;
setisLogin(userData);
})
.then(() => {
setInitializing(false);
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
}
});
} catch (error) {
Alert.alert(error);
} }, []);
How to wait auth().signInWithCredential finish? Thankyou.
If you need to perform more actions such read data from database or so after the user logs in, you should ideally unsubscribe from onAuthStateChanged. Essentially it won't trigger when the auth state changes (i.e. user logs in) and let you do your own custom actions. Once your processing is done, then you manually redirect the user to where the onAuthStateChange would have redirected is the user wa s logged in.
const authStateListenter = await auth().onAuthStateChanged(user => {
//...
})
// Unsubscribe auth state observer when _signIn function runs
const _signIn = async () => {
setInitializing(true);
authStateListenter()
}
Calling authStateListener will disable the auth state observer. It's similar to detaching Firestore's listeners.
For some reason this code fail to sign up on firebase,
app.post('/signup', (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassword: req.body.confirmPassword,
handle: req.body.handle
}
let token, userId;
db.doc(`/users/${newUser.handle}`).get().then(doc => {
if (doc.exists) {
return res.status(400).json({ handle: 'this handle already exists' })
} else {
return firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password)
}
})
.then(data => {
userId = data.user.uid;
return data.user.getIdToken();
})
.then(idToken => {
token = idToken;
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
createdAt: new date().toISOString(),
userId
}
return db.doc(`/users/${newUser.handle}`).set(userCredentials)
})
.then(() => {
return res.status(201).json({ token });
})
.catch(err => {
if (err.code === "auth/email-already-in-use") {
return res.status(400).json({ email: 'Email is alrready in use' })
} else {
return res.status(500).json({ general: 'something went wrong, please try again' })
}
})
});
I always get { general: 'something went wrong, please try again' } , i am using postman to mimic the request to firebase,
This code works perfectly:
firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password)
.then(data => {
return data.user.getIdToken();
})
.then(token => {
return res.status(201).json({token})
})
.catch(err => {
console.error(err);
return res.status(500).json({error: 'something went wrongly'})
})
The first trial is from a tutorial i am following in Youtube and sadly it doesn't work
On the backend, you can use Firebase Admin SDK to get details about any user but it does not have any ability to login on behalf of user.
I'm just trying to have the rules be that if the user is authenticated then they can write. When I try to upload an image to the gallery from the website I get back an error saying that I'm not authorized. I have authentication set up and working. Here are my rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
And here is my image upload code:
imgUploadHandler = () => {
const image = this.props.imageUpload;
const uploadTask = storage.ref(`/Gallery/${image.name}`).put(image);
uploadTask.on('state_changed',
(snapshot) => {
// Progress function
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({ progress });
},
(error) => {
// Error function
console.log(error);
},
() => {
// Complete function
storage.ref('Gallery').child(image.name).getDownloadURL()
.then(URL => {
const imageData = { imgURL: URL, name: image.name, width: "400"}
this.props.onImageUploaded(URL);
this.imgSelectedHandler(URL);
Axios.post(`/Gallery.json?auth=${this.props.token}`, imageData);
})
})
}
My use case is fairly simple, i want to create a new user (username,email,password) but first check that the username/email doesn't exist already.
After checking, i use bcrypt to hash the password and create/store the user in my database
Here is the code i'm actually using, it works but i think it's a little too complicated so i'm wondering if there is anything i can do to make it a bit more readable/optimized
ipcMain.on('register', (e, newUser) => {
userRepo.findByUsername(newUser.username).then(
(user) => {
if (user)
e.sender.send('register-failed', "Username already exists!");
else {
userRepo.findByEmail(newUser.email).then(
(user) => {
if (user)
e.sender.send('register-failed', "Email already exists!");
else {
bcrypt.hash(newUser.password, saltRounds).then(
(hasedPassword) => {
newUser.password = hasedPassword;
userRepo.create(newUser).then(
(user) => {
e.sender.send('register-success', user.get({plain:true}));
},
(error) => {
e.sender.send('register-failed', "Unexpected Error");
}
)
}
)
}
}
)
}
},
(error) => { e.sender.send('register-failed', "Unexpected Error"); }
).catch(error => e.sender.send('register-failed', "Unexpected Error"));
});
userRepo module :
const db = require('../db.js');
const findByUsername = function (username) {
return db.models.User.findOne({
where: {
username: username
}
});
}
const findByEmail = function (email) {
return db.models.User.findOne({
where: {
email: email
}
});
}
const create = function (newUser) {
return db.models.User.create({
username: newUser.username,
email: newUser.email,
password: newUser.password
});
}
module.exports = { findByUsername, findByEmail, create }
Thanks for the help.
EDIT:
Here's a much more readable code (that may be optimized more, but i find it readable enough)
ipcMain.on('register', (e, newUser) => {
Promise.all([userRepo.isUsernameAvailable(newUser.username), userRepo.isEmailAvailable(newUser.email)])
.then(creation => {
bcrypt.hash(newUser.password, saltRounds).then(
(hashedPassword) => {
newUser.password = hashedPassword;
userRepo.create(newUser).then(
(user) => {
e.sender.send('register-success', user.get({ plain: true }));
}
).catch(error => e.sender.send('register-failed', "Unexpected internal error!"))
}
).catch(error => e.sender.send('register-failed', "Unexpected internal error!"));
}) // User already exists
.catch((exists) => e.sender.send('register-failed', exists))
})
Using the two functions to check availability of username and email
async function isUsernameAvailable(username){
const user = await findByUsername(username);
if(!user)
return Promise.resolve(`Username : "${username}" is available`)
return Promise.reject(`Username : "${username}" is already taken !`)
}
async function isEmailAvailable(email){
const user = await findByEmail(email);
if(!user)
return Promise.resolve(`Email : "${email}" is available`)
return Promise.reject(`Email : "${email}" is already taken !`)
}
You are first checking whether username exists and then checking whether email exists. However, both of them can be checked asynchronously using Promise.all.
If anyone of the username or email already exists then you can return an error message.
You have used a promise chain however code looks much cleaner and easy to read if you implement the same code using async-await.
Please refer the below blog for how to implement Promise.all using async-await.
https://www.taniarascia.com/promise-all-with-async-await/
I'm using vuex and firebase to implement user authentication following the instruction of vuegram. I tried many ways to detach firebase listeners, the only one that stop warning error is the following:
var unsubscribe=fb.auth.onAuthStateChanged(user=>{
if(user){
store.commit('setCurrentUser',user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
}
})
unsubscribe();
However, the code above just stop warning on signOut(), I can't update data anymore.
My store.js file:
var unsubscribe=fb.auth.onAuthStateChanged(user=>{
if(user){
store.commit('setCurrentUser',user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
}
})
export const store=new Vuex.Store({
state:{
currentUser:null,
userProfile:{}
},
actions:{
clearData({commit}){
commit('setCurrentUser',null)
commit('setUserProfile', {})
},
fetchUserProfile({ commit, state }) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', res.data())
}).catch(err => {
console.log(err)
})
},
updateProfile({ commit, state }, data) {
let displayName = data.displayName
fb.usersCollection.doc(state.currentUser.uid).set({
displayName: displayName
}, {merge:true}).then(function() {
alert("Document successfully written!");
})
.catch(function(error) {
alert("Error writing document: ", error);
});
}
},
mutations:{
setCurrentUser(state, val) {
state.currentUser = val
},
setUserProfile(state, val) {
state.userProfile = val
}
}
})
The signOut method:
signOut: function(){
fb.auth.signOut().then(()=> {
this.$store.dispatch('clearData')
this.$router.push('login')
}).catch(function(error) {
console.log(error);
});
}
My firebase rule:
allow read, write: if request.auth.uid!=null;
Since you still have an active listener when you log out, the system detects that the client has lost permission to read that data and rejects the listener. This means you need to remove the listener before signing out to prevent the error message.
See the documentation on detaching listeners, you first get a reference to the unsubscribe function when you attach the listener:
unsubscribe = fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
And then call that function before signing out:
signOut: function(){
unsubscribe();
fb.auth.signOut().then(()=> {
this.$store.dispatch('clearData')
this.$router.push('login')
}).catch(function(error) {
console.log(error);
});
}
If you have a logout button on many pages just navigate to a different page and call logout from there. Then you don't have to litter your code with unsubscribes.
You can also check your Firestore rules.
If this is what is there:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Change to:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Please note that this is insecure and for dev purposes only. To understand how Firestore rules work, check out this youtube video.