Search code examples
firebasereact-nativegoogle-cloud-firestorefirebase-securityformik

React + Formik + Firebase/Firestore Uncaught Error in snapshot listener:, [FirebaseError: Missing or insufficient permissions.]


Making an instagram clone and it won't let me post a picture. I keep getting this error 'Uncaught Error in snapshot listener:, [FirebaseError: Missing or insufficient permissions.]' whenever I enter the page to upload a picture. My guess is that it's coming from the onSnapshot function. I also get the 'Warning: An unhandled error was caught from submitForm(), [TypeError: null is not an object (evaluating 'currentLoggedInUser.username')]' when I press the share button. Nothing happens beyond that point. Its supposed to take me back to the homescreen afterwards:

import { View, Text, Image, TextInput, Button } from 'react-native'
import React, { useState, useEffect } from 'react'
import * as Yup from 'yup'
import { Formik } from 'formik'
import { Divider } from 'react-native-elements'
import validUrl from 'valid-url'
import {db, firebase} from '../../firebase'

const PLACEHOLDER_IMG = 'https://pacificpatiostructures.com/wp-content/uploads/2016/06/import_placeholder.png'

const uploadPostSchema = Yup.object().shape({
    imageUrl: Yup.string().url().required('A URL is required'),
    caption: Yup.string().max(2200, 'Caption has reached the character limit.')
})

const FormikPostUploader = ({ navigation }) => {
    const [thumbnailUrl, setThumbnailUrl] = useState(PLACEHOLDER_IMG)
    const [currentLoggedInUser, setCurrentLoggedInUser] = useState(null)
    
    const getUsername = () => {
        const user = firebase.auth().currentUser
        const unsubscribe = db
        .collection('user')
        .where('owner_uid', '==', 'user.uid').limit(1).onSnapshot(
            snapshot => snapshot.docs.map(doc => {
                setCurrentLoggedInUser({
                    username: doc.data().username,
                    profilePicture: doc.data().profile_picture,
                })
            }) 
        )
        return unsubscribe
    }

    useEffect(() => {
        getUsername()
    }, [])

    const uploadPostToFirebase = (imageUrl, caption) => {
        const unsubscribe = db
            .collection('users')
            .doc(firebase.auth().currentUser.email).collection('posts')
            .add({
                imageUrl: imageUrl,
                user: currentLoggedInUser.username,
                profile_picture: currentLoggedInUser.profilePicture,
                owner_uid: firebase.auth().currentUser.uid,
                caption: caption,
                createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                likes: 0,
                likes_by_users: [],
                comments: [],
            })
            .then(() => navigation.goBack())
        return unsubscribe
    }



  return (
      <Formik
        initialValues={{caption: '', imageUrl: ''}}
        onSubmit={values => {
            uploadPostToFirebase(values.imageUrl, values.caption)
        }}
        validationSchema={uploadPostSchema}
        validateOnMount={true}
        >

            {({ 
                handleBlur, 
                handleChange, 
                handleSubmit, 
                values, 
                errors, 
                isValid 
            }) => (
                <>
                <View 
                    style={{ 
                        margin: 20, 
                        justifyContent: 'space-between', 
                        flexDirection: 'row', 
                    }}>
                    <Image source={{ uri: validUrl.isUri(thumbnailUrl) ? thumbnailUrl : PLACEHOLDER_IMG}} 
                            style={{ width: 100, height: 100 }}/>

                    <View style={{ flex: 1, marginLeft: 12 }}>
                        <TextInput 
                            style={{ color: 'white', fontSize: 20 }}
                            placeholder='Write a caption...' 
                            placeholderTextColor='gray'
                            multiline={true}
                            onChangeText={handleChange('caption')}
                            onBlur={handleBlur('caption')}
                            value={values.caption}
                        />
                    </View>
                </View>
                <Divider width = {0.2} orientation='vertical' />
                <TextInput 
                    onChange={(e) => setThumbnailUrl(e.nativeEvent.text)}
                    style={{ color: 'white', fontSize: 18 }}
                    placeholder='Enter Image Url' 
                    placeholderTextColor='gray'
                    onChangeText={handleChange('imageUrl')}
                    onBlur={handleBlur('imageUrl')}
                    value={values.imageUrl}
                />
                {errors.imageUrl &&(
                    <Text style={{ fontSize: 10, color: 'red' }}>
                        {errors.imageUrl}
                    </Text>
                )}

                <Button onPress={handleSubmit} title='Share' disabled={!isValid}/>
                </>
            )}
          
      </Formik>
  )
}

export default FormikPostUploader

Here are my security rules that I used in Cloud Firestore:

rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {
  function userIsAuthenticated() {
        return request.auth != null;
    }
  // security rule for group collection ('post')
    match /{path=**}/posts/{postId} {
        allow read, write: if userIsAuthenticated();
    }
    
    match /users/{userId} {
        allow read, write: if userIsAuthenticated();
    }
  }
}

Solution

  • Your getUsername() function queries user collection which is not included in your Firestore Rules which returns Missing or insufficient permissions.

    As per this documentation:

    Cloud Firestore security rules evaluate each query against its potential result and fails the request if it could return a document that the client does not have permission to read. Queries must follow the constraints set by your security rules.

    You should also add necessary permissions to the user collection. See sample rule below:

    rules_version = '2';
    service cloud.firestore {
        match /databases/{database}/documents {
        
        function userIsAuthenticated() {
            return request.auth != null;
        }
    
        // security rule for group collection ('post')
        match /{path=**}/posts/{postId} {
            allow read, write: if userIsAuthenticated();
        }
        
        match /users/{usersId} {
            allow read, write: if userIsAuthenticated();
        }
    
        // Security Rule for `user` collection.
        match /user/{userId} {
            allow read, write: if userIsAuthenticated();
        }
      }
    }