Search code examples
fluttergoogle-cloud-firestorefirebase-authenticationfirebase-securityflutter-getx

permission-denied when i log in or log out


I check "is user logged in or logged out" with Getx. I got permission-denied error when I log in or log out to my app with verify phone number. but I restart my App then It works without error when I got:

Exception has occurred. FirebaseException ([cloud_firestore/permission-denied] The caller does not have permission to execute the specified operation.)

here my phone authentication dart

class PhoneAuthController extends GetxController {

  static PhoneAuthController instance = Get.find();
  late Rx<User?> _user;
  FirebaseAuth auth = FirebaseAuth.instance;

  CollectionReference subscribers = FirebaseFirestore.instance.collection("subscribers");
  
  @override
  void onReady(){
    super.onReady();
    _user = Rx<User?>(auth.currentUser);
    _user.bindStream(auth.userChanges());
    ever(_user, _initialScreen);
  }

  _initialScreen(User? user){
    if(user==null){
      Get.offAll(()=> const VerifyByNumber());
    } else {
      String number = user.phoneNumber.toString().substring(1);
      var subscriber = subscribers.doc(number);
      subscriber.update({"userId": user.uid});
      Get.offAll(()=>HomePage(phoneNumber: number,));
    }
  }

  void verifyPhoneNumber(String uPhoneNumber) {
    String number = uPhoneNumber.substring(1);
      auth.verifyPhoneNumber(
          timeout: const Duration(seconds: 120),
          phoneNumber: uPhoneNumber,
          verificationCompleted: (PhoneAuthCredential credential) {
            auth.signInWithCredential(credential).then((value) {});
          },
          verificationFailed: (FirebaseAuthException e) {
          },
          codeSent: (String id, int? token) {
            Get.off(() =>
                VerifyToCode(
                    phoneNumber: number,
                    verifyId: id,));
          },
          codeAutoRetrievalTimeout: (String verificationID) {});
  }

  void verifyCode(String uPhoneNumber, String verifyId, String smsCode) {
    Map<String, dynamic> subscriberToSave = {
      "phone": "+$uPhoneNumber",
      "name": null,
      "email": null,
      "password": null,
      "fcmToken": null,
      "date": DateTime.now()
    };
    try {
      FirebaseFirestore.instance.collection("subscribers").doc(uPhoneNumber).set(subscriberToSave);
      PhoneAuthCredential credential = PhoneAuthProvider.credential(verificationId: verifyId, smsCode: smsCode);
      auth.signInWithCredential(credential);   
      Get.offAll(VerificationSuccessfulScreen(number: uPhoneNumber));      
    } catch (e) {
      Get.snackbar("verifyCode, ERROR:", ">$e");
    }
  }

  void quitFirebaseAuth() {
    FirebaseAuth.instance.signOut();
  }
}

here my firestore rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /subscribers/{subscriber} {
    
        allow get, write: if isSignIn() && 
        request.auth.uid == resource.data.userId && 
        request.auth.token.phone_number == resource.data.phone;
        
            match /sites/{site} {
            allow read, write: if isSignIn();
          match /messages/{message}{
            allow read, write: if isSignIn();
          }
        }
    }
    match /sites/{site} {
        allow read, write: if isSignIn();
    }
  } 
  
  function isSignIn(){
    return request.auth.uid != null;
  }
}

Solution

  • Your code to write the user document does:

    Map<String, dynamic> subscriberToSave = {
      "phone": "+$uPhoneNumber",
      "name": null,
      "email": null,
      "password": null,
      "fcmToken": null,
      "date": DateTime.now()
    };
    FirebaseFirestore.instance.collection("subscribers").doc(uPhoneNumber).set(subscriberToSave);
    

    Your rules that allow/deny this write are:

    match /subscribers/{subscriber} {    
        allow get, write: if isSignIn() && 
        request.auth.uid == resource.data.userId && 
        request.auth.token.phone_number == resource.data.phone;
    

    You don't set a userId field anywhere in the code, so this check will fail request.auth.uid == resource.data.userId and the write will be rejected.

    You should either add a userId field with the correct value to the data, or remove that check from your rules.


    In general, you'll want to use request.resource rather than resource when validating data in write rules. The resource variable contains the resource as it exists before the operation, while request.resource is the resource as it'll exist after the operation (assuming the operation is allowed).