I am new to Flutter/Dart but I have been trying to figure out the DB rules for a project that I am working on. I know that there are numerous posts on SO about Firebase DB rules but everything that I have found and tried has not worked. In the particular case below I want anyone to be able to read the data but only the author to be able to write and edit it. I set the Doc ID to be the uid and that works fine with permissions removed but I cannot get anything to work when I add restricted rules. What am I doing wrong?
Stack Trace Message
I/flutter (17900): PlatformException(Error performing setData, PERMISSION_DENIED: Missing or insufficient permissions., null)
E/flutter (17900): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() called after dispose(): _RegisterState#1360e(lifecycle state: defunct, not mounted)
My Firebase Rules:
service cloud.firestore {
match /databases/{database}/documents {
match /brews/{userId} {
allow read: if request.auth.uid != null;
allow write: if request.auth.uid == userId;
}
}
}
Database Code
class DatabaseService {
final String uid;
DatabaseService({this.uid});
//Collection reference
final CollectionReference brewCollection =
Firestore.instance.collection('brews');
Future updateUserData(String sugars, String name, int strength) async {
return await brewCollection.document(uid).setData({
'sugars': sugars,
'name': name,
'strength': strength,
});
}
//brew list from snapshot
List<Brew> _brewListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Brew(
name: doc.data['name'] ?? '',
strength: doc.data['strength'] ?? 0,
sugars: doc.data['sugars'] ?? '0',
);
}).toList();
}
//userData from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(
uid: uid,
name: snapshot.data['name'],
sugars: snapshot.data['sugars'],
strength: snapshot.data['strength'],
);
}
//Get the collection stream
Stream<List<Brew>> get brews {
return brewCollection.snapshots().map(_brewListFromSnapshot);
}
//get user doc stream
Stream<UserData> get userData {
return brewCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
}
Authentication Code:
class Authenticate extends StatefulWidget {
#override
_AuthenticateState createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
bool showSignIn = true;
void toggleView() {
setState(() {
showSignIn = !showSignIn;
});
}
#override
Widget build(BuildContext context) {
if (showSignIn) {
return SignIn(toggleView: toggleView);
} else {
return Register(toggleView: toggleView);
}
}
}
Your rules are correct, and your update function is correct. Your authentication code doesn't show us if you perform a firebase auth login though.
The only thing that could stop it from working is if you are not logged in (authenticated via firebase authentication) OR you passed the wrong uid String to your DatabaseService class. If that's the case you wont have a request.auth.uid so the rule will fail.
You can test your rules in the console, or you can try isolate the exact payload in your code to provide us more info.
Related
currently, the way to check if a user is logged in Flutter Fire as per the documentation (https://firebase.flutter.dev/docs/auth/usage#authentication-state):
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
the way to set up a route guard in Flutter Modular as per the documentation (https://modular.flutterando.com.br/docs/flutter_modular/navegation#route-guard)
class AuthGuard extends RouteGuard {
AuthGuard() : super(redirectTo: '/login');
#override
Future<bool> canActivate(String path, ModularRoute router) {
return Modular.get<AuthStore>().isLogged;
}
}
how do I use this FlutterFire code to create the route guard in Flutter modular? I have trouble coming up with code that will return a Future from the FlutterFire auth code
try use only this:
Future<bool> checkCurrentUser() async {
return FirebaseAuth.instance.currentUser != null;
}
Modular guard required one future of boolean.
class AuthGuard extends RouteGuard {
AuthGuard() : super(redirectTo: '/login/');
#override
Future<bool> canActivate(String path, ModularRoute route) async {
return await Modular.get<AuthStore>().checkCurrentUser;
}
}
resolve to me this.
Whenever the app starts this error comes. i think the error mainly is in the saveUserInfoFirestore function.. i saw that many other people have this same problem cause of their firestore security rules but i tried differenct rules it still shows same error..
final GoogleSignIn gSignIn = GoogleSignIn();
final userReference = Firestore.instance.collection("Users");
void initState(){
super.initState();
pageController=PageController();
gSignIn.onCurrentUserChanged.listen((gSigninAccount) { ControlSingIn(gSigninAccount); },
onError: (gError){
print("Google signin error"+ gError);
});
gSignIn.signInSilently(suppressErrors: false).then((gSigninAccount) { ControlSingIn(gSigninAccount); }).
catchError((onError){
print("Signin silently error"+ onError);
});
}
ControlSingIn(GoogleSignInAccount signInAccount) async{
if(signInAccount != null){
await saveUserInfotoFirestore();
setState(() {
isSignin = true;
});
}else{
setState(() {
isSignin = false;
});
}
}
saveUserInfotoFirestore() async{
final GoogleSignInAccount gCurrentuser = gSignIn.currentUser;
DocumentSnapshot documentSnapshot = await userReference.document(gCurrentuser.id).get();
if(!documentSnapshot.exists){
final username = await Navigator.push(context, MaterialPageRoute(builder: (context)=>CreateAccountPage()));
userReference.document(gCurrentuser.id).setData({
"id": gCurrentuser.id,
"profileName": gCurrentuser.displayName,
"username": username,
"url": gCurrentuser.photoUrl,
"email": gCurrentuser.email,
"bio": "",
"timestamp":timestamp
});
documentSnapshot = await userReference.document(gCurrentuser.id).get();
}
currentUser = User.fromDocument(documentSnapshot);
}
heres the firestore security rule that i am using:
rules_version = '2';
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{document=**} {
allow read, write: if true;
}
}
}
Your security rules only allow access to a single collection called "messages" using this match:
match /messages/{document=**} {
allow read, write: if true;
}
However, your code is trying to write a different collection called "Users". You are seeing a permission error because your rules don't allow any access at all to that collection. You will have to write another rule to allow enough access to that collection as well.
I strongly suggest fully reviewing the documentation for security rules to understand how best to protect your app.
check with your googleservices.json file if it is in the right location. It should be in your project>android folder> app folder>
I'm making an app that needs to display two separate home pages depending on whether or not a counselor value equals true on my Cloud Firestore Database. I am new to Object Oriented Programing, dart, and Firestore so please bear with me.
What I'm trying to do is initialize a variable called counselor and set that equal to the Counselor field value on my database. Then I wish to first check if the user has signed in and if they have signed it that's when I use a series of if statements to see whether or or not the counselor boolean equals true or false. and depending on the result it will display a certain homepage.
I get an error message on my app saying that the I'm returning a null value on my counselor widget. I suspect this is because I'm setting the counselor variable to equal the name of the field Counselor on my database and not it's actual boolean value. Problem is I'm not aware of the syntax to circumvent this problem.
Here is my code
import 'package:strength_together/models/user.dart';
import 'package:strength_together/Screens/authenticate/authenticate.dart';
import 'package:strength_together/Screens/home/home.dart';
import 'package:strength_together/Screens/home/wrapper.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:strength_together/services/database.dart';
import 'package:strength_together/Screens/home/counsHome.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<SUser>(context);
bool counselor = getCounselor();
//return either authenticate or home/couselor home
if (user == null){
return Authenticate();
}else if(counselor == true){
return CounselorHome();
}else if(counselor == false){
return Home();
}
}
}
// Database class
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService {
final String uid;
DatabaseService({this.uid});
//collection reference
final CollectionReference userCollection = FirebaseFirestore.instance
.collection('User Data');
Future updateUserData(String name, bool counselor) async {
return await userCollection.doc(uid).set({
'name': name,
'Counselor': true,
});
}
//get user data stream
Stream<QuerySnapshot> get userData {
return userCollection.snapshots();
}
Future<DocumentSnapshot> getDataSnapshotForCounselor() async
{
return await FirebaseFirestore.instance.collection('User Data')
.doc(uid)
.get();
}
bool getCounselorValue(DocumentSnapshot dataSnapshotForCounselor) {
//modify this by passing proper keyValue to get counselor.
return dataSnapshotForCounselor.data()['Counselor'];
}
}
// auth class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:strength_together/Screens/authenticate/register.dart';
import 'package:strength_together/models/user.dart';
import 'package:strength_together/services/database.dart';
import 'package:flutter/material.dart';
class AuthService{
final FirebaseAuth _auth = FirebaseAuth.instance;
//instance of counselor
bool _counselor;
bool get counselor => counselor;
void setCounselor(bool counselor) {
_counselor = counselor;
}
//create user obj based on firebase user from my code
SUser _userFromFirebaseUser(User user){
return user != null ? SUser(uid: user.uid) : null;
}
//auth change user stream
Stream<SUser> get user {
return _auth.authStateChanges()
.map(_userFromFirebaseUser);
}
//method to sign in anon
Future signInAnon() async{
try{
UserCredential result = await _auth.signInAnonymously();
User user = result.user;
return _userFromFirebaseUser(user);
}catch(e){
print(e.toString());
return null;
}
}
//method to sign in with email/pass
Future signInWithEmailAndPassword(String email, String password) async{
try{
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User user = result.user;
return _userFromFirebaseUser(user);
}catch(e){
print(e.toString());
return null;
}
}
//method to register with email/pass
Future registerWithEmailAndPassword(String email, String password) async{
try{
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User user = result.user;
//create a new document for that user with the uid
await DatabaseService(uid: user.uid).updateUserData('New user', _counselor);
return _userFromFirebaseUser(user);
}catch(e){
print(e.toString());
return null;
}
}
//sign in with google
//sign out
Future signOut() async{
try{
return await _auth.signOut();
}catch(e){
print(e.toString());
return null;
}
}
There are lots of problems with your code . First of all you are using Firestore.instance and .document which are deprecated . Use FirebaseFirestore.instance and FirebaseFirestore.instance.collection(...).doc(...) instead.
Coming to your code , Use a FutureBuilder to decide which page to show , while deciding , you can show a CircularProgressIndicator to the user.
Next , this code
Firestore.instance.collection('User Data').document('Counselor');
returns a DocumentReference and not the actual value that is required .
So after you get the DocumentReference use .get() which returns a Future<DocumentSnapshot> , DocumentSnapshot class has a method data() which returns Map<String,dynamic>. Once you get the Map<> , you can simply get the data you want by passing the key value.
The whole process would look similar to this :
Future<bool> getCounselorValue() async {
return await FirebaseFirestore.instance.collection(...).doc(...).get().data()['keyvalue'];
}
Aagain , remember you are dealing with a Future so you have to use either await , .then() or FutureBuilder . I recommend FutureBuilder for your case , if you are not sure how to use it, refer to this . I also answered this which might be helpful if you want to dynamically decide which page to show to user first (refer to first method).
Edit:
Ok , so there was a little mistake in my code .Now I am sharing with you a little more code which you might find helpful .
make your class Stateful from Stateless and follow this code :
class Wrapper extends StatefulWidget{
WrapperState createState()=> WrapperState();
}
class WrapperState extends State<Wrapper>
{
Future<DocumentSnapshot> documentSnapshot;
User getUser(BuildContext context)
{
return Provider.of<User>(context);
}
dynamic getStartingPage(bool counselor,User user)
{
if (user == null){
return Authenticate();
}else if(counselor == true){
return CounselorHome();
}else if(counselor == false){
return Home();
}
}
Future<DocumentSnapshot> getDocumentSnapshotForCounselor() async
{
return await FirebaseFirestore.instance.collection(...).doc(...).get();
}
bool getCounselorValue(DocumentSnapshot documentSnapshotForCounselor)
{
//modify this by passing proper keyValue to get counselor.
return documentSnapshotForCounselor.data()['keyValue'];
}
#override
void initState()
{
super.initState();
documentSnapshot=getDocumentSnapshotForCounselor();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future:documentSnapshot,
builder: (BuildContext ctx, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Center( child : const CircularProgressIndicator());
}
else
return getStartingPage(getCounselorValue(snapshot.data),getUser(context));
}
);
}
}
Modify getDocumentSnapshotForCounselor and getCounselorValue methods ,so that you can get the value of the field from the database.
Upgrade to latest version of cloud_firestore , visit this to understand how to do it and what to import .
Edit 2:
When you sign in user ,either by email password or using google sign in etc. It returns a Future<UserCredential> , UserCredential has a property user , which returns User. Now what we want is, a unique uid of the user which we can get using User.uid :
UserCredential userCredential =await _auth.createUserWithEmailAndPassWord();
String uid= userCredential.user.uid;
Now create an entry for the user in the database using this uid:
FirebaseFirestore.instance.collection("User Data").doc(uid).set({"Counselor":true,"name":name});
Afterwards , to get the counselor value , :
FirebaseFirestore.instance.collection("User Data").doc(uid).get();
and
documentSnapshotForCounselor.data()['Counselor'];
The above code is just for an explanation , make appropriate changes to your code.
I want to get Products added to cart by user.So i am trying to get documents based on uid(Documents named as uid)
But i am getting error
Only static members can be accessed in initializer
Code:
class CartDataBase{
FirebaseUser user;
final FirebaseAuth _auth=FirebaseAuth.instance;
void getUid()async
{
user=await _auth.currentUser();
}
final CollectionReference cart1 = Firestore.instance.collection('users').document(user.uid)//Only static members can be accessed in initializer
.collection('CartProducts');
List<Checkout> _checkout(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Checkout(
original: doc.data['original'] ?? 999,
image: doc.data['image'] ?? 'Error',
name: doc.data['name'] ?? 'Error',
quantity: doc.data['Quantity'] ?? 'N/A',
identifier: doc.data['identifier'] ?? 'qwerty',
price: doc.data['price'] ?? 999,
iPP: doc.data['IPP'] ?? 999,
uPQ: doc.data['UPQ'] ?? 999,
);
}).toList();
}
Stream<List<Checkout>> get CartProducts {
return cart1.snapshots().map(_checkout);
}
}
Screenshot for reference
The problem in your code can be found here:
final CollectionReference cart1 = Firestore.instance.collection('users').document(user.uid).collection('CartProducts');
There is no way you can assign that to the variable without waiting for it to finish fetching the document. You have to call await method first before you access it. We can solve this with the await keyword
final CollectionReference cart1 = await Firestore.instance.collection('users').document(user.uid).collection('CartProducts');
Since you cannot call async in your class, you have to convert it into a function as well as call that function in initstate() which gets called before your ui builds.
getDetails() async {
final CollectionReference cart1 = await Firestore.instance.collection('users').document(user.uid).collection('CartProducts');
//other things here
}
//place this below your widget build
#override
void initState() {
getDetails();
super.initState();
}
I try to compare the request.auth.token.name with the document name in the Firestore Security Rules, to give only the user access to his specific document. Unfortunately, it still doesn't work out. I tried a bit and it seems that the name is null. But why? Can anybody relate or have an answer for this?
The problem:
W/Firestore(30226): (21.3.0) [Firestore]: Write failed at users/johndoe: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
The security rule:
match /users/{username} {
allow create: if true;
allow update: if request.auth.token.name == username;
allow read: if true;
The client code (written in Dart for Flutter):
#override
Future<void> uploadProfileData(ProfileSetupDataModel data) async {
// Checks if username is already in use and creates a document (works perfectly)
final userDocReference = await _createDocIfUsernameAvailable(data.username);
// Here the username is saved in the display name of the FirebaseUser
await _saveUsernameInAuthUser(data.username);
// Write all the other information into the user document
await _fillProfileInformation(userDocReference, data); // -> here the problem occurs
}
Future<void> _saveUsernameInAuthUser(String username) async {
try {
final user = await auth.currentUser();
final profileUpdate = UserUpdateInfo();
profileUpdate.displayName = username;
await user.updateProfile(profileUpdate);
} catch (e) {
throw GeneralAuthException();
}
}
Future<void> _fillProfileInformation(
DocumentReference userDocReference, ProfileSetupDataModel data) async {
try {
await userDocReference.setData(
{
FirestoreUserKeys.birthday: data.birthday,
FirestoreUserKeys.sex: data.sex,
FirestoreUserKeys.country: data.country,
FirestoreUserKeys.storiesCount: 0,
FirestoreUserKeys.subscribersCount: 0,
FirestoreUserKeys.subscriptionsCount: 0,
FirestoreUserKeys.totalLikes: 0,
},
merge: true,
);
} on PlatformException {
throw GeneralCloudFirestoreException();
}
}