Permission denied while configuring firestore in flutter - firebase

I am trying to add security rules to my app have firebase firestore feature so the rules are
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
function isSignedIn() {
return request.auth.uid != null;
}
allow read, write: if isSignedIn() && request.auth.uid == resource.data.uid
}
}
}
and this is the function for getting query snapshot
Stream<QuerySnapshot> diarySnapshot(String uid){
return _firestore
.collection(uid)
.where('uid', isEqualTo:uid)
.snapshots();
}
but I am getting an error
[Firestore]: Write failed at 2nVrS92SbOZocu2CJY3Tj68er1n2/rXZAWPxEqhtqwmOrtZfo: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
One more thing each user would have their own collection named on their id.
EDIT
** DatabaseService.dart**
class DatabaseService{
Firestore _firestore=Firestore.instance;
/*Future<void> configure() async{
final FirebaseApp app=await FirebaseApp.configure(name:'notes',
options: FirebaseOptions(
googleAppID: '1:1051484666895:android:7e879575351f8463b2f77a',
projectID: 'notes-e2864',
gcmSenderID:'1051484666895',
apiKey: 'AIzaSyBrCie0qxxwn0KwSLJ1wwTxnVZ7nLz-QkY'
),
);
_firestore = Firestore(app: app);
}
*/
Future setNewData(String uid,String title,String diary,DateTime dateTime) async{
await _firestore.collection(uid).document().setData({
'title':title,
'diary':diary,
'dateTime':dateTime.toString()
});
}
Stream<QuerySnapshot> diarySnapshot(String uid){
return _firestore
.collection(uid)
.where('uid', isEqualTo:uid)
.snapshots();
}
}
I tried both commented and uncommented code, the commented was being first configured and and then program was run.
ServiceViewModel
class ServiceViewModel extends ChangeNotifier {
final AuthService _authService=AuthService();
final DatabaseService _databaseService=DatabaseService();
String _uid;
/*ServiceViewModel(){
_databaseService.configure();
}*/
void signInWithEmailAndPassword(String email,String password) async {
await _authService.signInWithEmailAndPassword(email, password);
}
void registerInWithEmailAndPassword(String email,String password) async {
await _authService.registerWithEmailAndPassword(email, password);
}
void signInWithGoogle()async {
await _authService.signInWithGoogle();
print('Sign In With Google');
}
void signOut(){
_authService.signOut();
_uid=null;
}
/* void setUid() async{
_uid=await _authService.getCurrentUser();
}*/
void setData(String title, String diary, DateTime dateTime) async{
await _authService.getCurrentUser().then((value) async {
return await _databaseService.setNewData(value.toString(), title, diary, dateTime);
});
}
Stream<QuerySnapshot> getSnapShot(String uid) {
return _databaseService.diarySnapshot(uid);
}
}

resource.data does not exist on create, hence you error.
Try this instead:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
function isSignedIn() {
return request.auth.uid != null;
}
allow create: if isSignedIn() && request.auth.uid == request.resource.data.uid;
allow update: if isSignedIn() && request.auth.uid == resource.data.uid
&& request.auth.uid == request.resource.data.uid;
allow read, delete: if isSignedIn() && request.auth.uid == resource.data.uid
}
}
}
EDIT: special case for update not to get stucked

Related

Firebase Storage Exception when fetching downloadUrl [Flutter]

When trying to use FireBase Cloud Storage to get a video downloadUrl
final storage = FirebaseStorage.instance;
downloadUrl() async {
final downloadUrl =
await storage.ref("User_uploadVideo/videoplayback.mp4").getDownloadURL();
return downloadUrl;
}
An exception is thrown saying Exception: [firebase_storage/unauthenticated] User is unauthenticated. Authenticate and try again.
Even tho I have opened the security rules to public for development.
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}
Try this:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
...
Also, make sure to have AppCheck disabled.

Firebase Rules and flutter : How to check for username availability

Hello I am working with Firestore and flutter. I need to check the username availability when someone creates a new account.
I want to make that when the user is not connected in the app, the field 'username' of the collection "User Data" can be access with get().
However, the code in rules return several errors of 'expected {' but even if I add the '{', it stills does not accept it.
The code in rule that doesn't work and firebase won't allow me to install this rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
match /User Data/{User Data} {
allow read: true;
}
}
What I've tried so far :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
match /User Data/{User Data} {
allow read: request.resource.data == resource.data.username;
}
}
The code in flutter :
Future<bool> checkUsernameAvailability(String val) async {
final result = await Firestore.instance.collection("User Data").where('username', isEqualTo: val).getDocuments();
return result.documents.isEmpty;
}
onPressed: () async {
final valid = await checkUsernameAvailability(_usernameController.text);
if (!valid) {
error = AppLocalizations.of(context)
.translate('this_username_is_not_available');
} else if (_formKey.currentState.validate()) {
setState(() => loading = true);
dynamic result =
await _auth.registerWithEmailAndPassword(
_emailController.text,
_passwordController.text,
_nameController.text,
_usernameController.text);
if (result == null) {
setState(() {
loading = false;
error = AppLocalizations.of(context)
.translate('please_enter_email');
});
}
}
}
All help is welcomed thanks!
You can seperately write security rules for all collections. When you use match /{document=**} expression to allow read and write for authenticated users, it overrides other rules.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /User Data/{User Data} {
allow read: request.resource.data == resource.data.username
allow write: if request.auth.uid != null;
}
}

Firestore query Can't getDocuments

so I have an array in a collection called 'Authors'. I want to check if a certain string (mail) is there, but it says permission denied on the getDocuments.
Here's a little snippet:
List<String> authors;
_getAuthors(DocumentSnapshot doc) async {
if (await FirebaseAuth.instance.currentUser() != null) {
var query = Firestore.instance
.collection('mealList')
.where('Authors', arrayContains: mail);
query.getDocuments().then((value) => print(value));
}
}
Widget buildItem(DocumentSnapshot doc) {
DateTime now = doc.data['Date'].toDate();
DateFormat formatter = DateFormat('dd-MM-yyyy');
String formatted = formatter.format(now);
_getUserId();
_getMail(doc);
if (doc.data['Authors'] != null) {
_getAuthors(doc);
}
And here's my database rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /mealList/{uid} {
allow read, write: if request.auth == null;
}
match /shoppingList/{uid} {
allow read, write: if request.auth != null;
}
}
}
match /mealList/{uid} {
allow read, write: if request.auth == null;
}
should be
match /mealList/{uid} {
allow read, write: if request.auth != null;
}

Firestore security rules email_verified not working

If I create a new user with createUserWithEmailAndPassword, even though I didn't verify the mail yet, that user is already logged in. And his .emailVerified === false, and until here all good.
Now, I go to the mail, verify it using the link, go back to the web app, it is still .emailVerified === false so I refresh the page, now .emailVerified === true.
So I try to reach this doc:
public async getPublicUserDetails() {
const currentUserId = this._angularFireAuth.auth.currentUser.uid;
try {
const docRef = this._angularFirestore.collection("users").doc(currentUserId).ref;
const doc = await docRef.get();
if (!doc.exists) {
return null;
}
return doc.data() as IPublicUserDetailsDto;
}
catch (error) {
console.error("User " + currentUserId + " details get failed! " + JSON.stringify(error));
throw error;
}
}
It catches an exception, saying I don't have the required permissions to access the doc.
The Firestore rules I'm using are:
rules_version = '2';
service cloud.firestore {
function dbDocs() { return /databases/$(database)/documents; }
function isSignedIn() { return request.auth != null && request.auth.uid != null; }
function isEmailVerified() { return isSignedIn() && request.auth.token.email_verified; }
function isCurrUser(uid) { return isSignedIn() && request.auth.uid == uid; }
function userExists(uid) { return exists(/databases/$(database)/documents/users/$(uid)); }
match /databases/{database}/documents {
match /users {
match /{userId} {
allow read: if isEmailVerified();
allow write: if isEmailVerified() && isCurrUser(userId);
}
}
}
}
I can refresh the page infinite times, but it will work only if I signOut & signIn again OR if I replace the allow read line with
match /{userId} {
allow read: if isSignedIn(); // replace this
allow write: if isEmailVerified() && isCurrUser(userId);
}
Conclusion: it seems like the request.auth.token.email_verified does not reflect the value provided inside the FirebaseAuth service, as it seems to get refreshed only if I log out and back in.
Can someone help me, please? Thank you all in advance!

Firestore: userId rule

I cannot get this firestore rule to work.
I want to write/read to user-read-only/USER-ID-HERE/business/settings
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
match /{document=**} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
}
I continue to get the message
FirebaseError: Missing or insufficient permissions.
I have tried many different approaches with the simulator and they are all successful, but I can’t repro from my app.
Does anything look incorrect above?
Can the above be simplified? I would like the user to be able to control everything beyond {userId}
How do I know if request.auth.uid and userId are populating properly?
This works
service cloud.firestore {
match /databases/{database}/documents {
match /{userId}/{document=**} {
allow read, write;
}
}
}
This does not work
service cloud.firestore {
match /databases/{database}/documents {
match /{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
Update following your comment "The intent is to expand the rule so that anything beyond {userId} can be managed by the user":
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId}/{document=**} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
Just note that the create rule (copied from your question) allows any authenticated user to write under any {userId} folder.
(On the opposite if you just want to declare a rule for business/settings sub-collection and doc) the following should do the trick:
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId}/business/settings {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
In order to be sure that userId is populated properly, you could add it as a field to the document when created and check in the rules for create that it is correct, as follows:
allow create: if request.auth.uid != null && request.auth.uid == request.resource.data.userId;
On the other hand, Firebase Auth will automatically ensure that request.auth.uid is correctly populated.
Finally, you may watch this very good video from the Firebase team about Security Rules : https://www.youtube.com/watch?v=eW5MdE3ZcAw
Here is the HTML page used for testing. Just change the value of userId with the different user's ID.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://www.gstatic.com/firebasejs/5.9.3/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: 'xxxxx',
authDomain: 'xxxxx',
databaseURL: 'xxxxx',
projectId: 'xxxxx'
};
firebase.initializeApp(config);
firebase
.auth()
.signInWithEmailAndPassword('xxxxxx#gmail.com', 'yyyyyyy')
.then(userCredential => {
const userId = userCredential.user.uid;
// Replace with another userId to test
//e.g. const userId = 'l5Wk7UQGRCkdu1OILxHG6MksUUn2';
firebase
.firestore()
.doc('user-read-only/' + userId + '/business/settings4')
.set({ tempo: 'aaaaaaa' })
.then(() => {
return firebase
.firestore()
.doc(
'user-read-only/' + userId + '/testC/1/collec/2'
)
.get();
})
.then(function(doc) {
if (doc.exists) {
console.log('Document data:', doc.data());
} else {
// doc.data() will be undefined in this case
console.log('No such document!');
}
})
.catch(function(error) {
console.log('Error getting document:', error);
});
});
</script>
</head>
<body>
</body>
</html>
Did you deploy security rules?
See: https://firebase.google.com/docs/firestore/security/get-started#deploying_rules
Before you can start using Cloud Firestore from your mobile app, you will need to deploy security rules. You can deploy rules in the Firebase console or using the Firebase CLI.
Did you have loggedin using Firebase Authentication?
See: https://firebase.google.com/docs/firestore/security/rules-conditions
If your app uses Firebase Authentication, the request.auth variable contains the authentication information for the client requesting data. For more information about request.auth, see the reference documentation.
How do you call Firestore method?
See:
https://firebase.google.com/docs/firestore/data-model
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#currentuser
https://firebase.google.com/docs/reference/js/firebase.User
Like this?
var userId = firebase.auth().currentUser.uid
var docRef = db.doc(`user-read-only/${userId}/business/settings`);
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
I think you should change structure data.
A structure data should be like db.collection('coll').doc('doc').collection('subcoll').doc('subdoc').
(Collections->doc->SubCollections->SubDoc->SubSubCollections->SubSubDoc)
So {userId} should be docId. Not collections.
The security rules should be the this.
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
match /settings/{setting} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
The settings collection ref is db.collection('users').doc(userId).collection('settings').
If does not work then you should try basic rule sets.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}

Resources