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!
Related
As per documentation written in FlutterFire about Firestore bundles, I was unable to create such a thing. It seems documentation lacks something as all I get was error codes: Permission is denied
static const bucket = "felix-3610.appspot.com";
static const storage = "https://storage.cloud.google.com";
final Message msg = Get.find();
Future<QuerySnapshot<Map<String, Object?>>> getEpic() async {
QuerySnapshot<Map<String, Object?>>? snapshot;
const path = "$storage/$bucket/music/bundles/epic_bundles.txt";
Reference httpsReference = FirebaseStorage.instance.refFromURL(path);
try {
String url = await httpsReference.getDownloadURL();
final response = await http.get(Uri.https(url));
Uint8List buffer = Uint8List.fromList(response.body.codeUnits);
LoadBundleTask task = FirebaseFirestore.instance.loadBundle(buffer);
await task.stream.last.then((value) async {
snapshot = await FirebaseFirestore.instance.collection("latest-epic").get(const GetOptions(source: Source.cache));
});
} on FirebaseException catch (e) {
log(e.code); //unauthorized
}
return snapshot!;
}
I could not know where I went wrong. Have people tried to code and load Firestore bundles in Flutter? Please, I do need a good documentation of how to do it correctly.
E/StorageException(24282): StorageException has occurred.
E/StorageException(24282): User does not have permission to access
this object. E/StorageException(24282): Code: -13021 HttpResult: 403
E/StorageException(24282): { "error": { "code": 403, "message":
"Permission denied." }} E/StorageException(24282):
java.io.IOException: { "error": { "code": 403, "message":
"Permission denied." }} E/StorageException(24282): at
com.google.firebase.storage.network.NetworkRequest.parseResponse(NetworkRequest.java:445)
E/StorageException(24282): at
com.google.firebase.storage.network.NetworkRequest.parseErrorResponse(NetworkRequest.java:462)
E/StorageException(24282): at
com.google.firebase.storage.network.NetworkRequest.processResponseStream(NetworkRequest.java:453)
E/StorageException(24282): at
com.google.firebase.storage.network.NetworkRequest.performRequest(NetworkRequest.java:272)
E/StorageException(24282): at
com.google.firebase.storage.network.NetworkRequest.performRequest(NetworkRequest.java:289)
E/StorageException(24282): at
com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(ExponentialBackoffSender.java:76)
E/StorageException(24282): at
com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(ExponentialBackoffSender.java:68)
E/StorageException(24282): at
com.google.firebase.storage.GetDownloadUrlTask.run(GetDownloadUrlTask.java:77)
E/StorageException(24282): at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/StorageException(24282): at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/StorageException(24282): at java.lang.Thread.run(Thread.java:764)
The way I generated bundle is using Cloud Scheduler, like in this example below:
// #ts-check
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const firestore = admin.firestore();
const fs = require("fs");
// This will create it every month
exports.BundleRelax = functions.pubsub.schedule("0 0 1 * *").retryConfig({
retryCount: 3,
maxRetryDuration: "30s",
}).onRun(async (context) => {
const bundleId = "latest-relax";
const bundle = firestore.bundle(bundleId);
const querySnapshot = await firestore.collection("Music_Relax").where("status.state", "==", 1).get();
// Build the bundle
const bundleBuffer = bundle.add("latest-relax-query", querySnapshot).build();
// Create tmp local file to upload
const bundledFilePath = "/tmp/bundle.txt";
fs.writeFileSync(bundledFilePath, bundleBuffer);
// Upload bundle file to Storage
const bucket = "felix-366510.appspot.com";
const destination = "relax_bundle.txt";
await admin.storage().bucket(bucket).upload(bundledFilePath, {
destination,
public: true,
metadata: {
cacheControl: "public, max-age=3110400",
},
}).then((value) => {
return Promise.resolve("success");
});
});
Security Rules for Cloud Storage:
rules_version = '2';
//Read operation: get, list
//Write operation: create, update, delete
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if isAdmin();
}
match /music {
allow read: if isIDN();
allow get, list, create, update: if isPublisher();
}
match /server {
allow read, create, update: if isUser();
}
match /publishers {
allow read: if isUser();
allow get, list, create, update: if isPublisher();
}
match /clients {
allow read, write: if isFelix();
}
//This is for any subcription inside Felix Apps
//Value 0 is equal to FALSE
//Value 1 is equal to TRUE
//This is for all versions
function isMT() {
return mtIDN() || mtEN() || mtCHN();
}
function isMTtrial() {
return trMT_IDN() || trMT_EN() || trMT_CHN();
}
//IDN version code 1
//Trials here
function trMT_IDN(){
return isIDN() && (request.auth.token.T11 == 1);
}
//Subs here
function mtIDN(){
return isIDN() && (request.auth.token.MT1 == 1);
}
//EN version code 2
//Trials here
function trMT_EN(){
return isEN() && (request.auth.token.T21 == 1);
}
//Subs here
function mtEN(){
return isEN() && (request.auth.token.MT2 == 1);
}
//CHN version code 3
//Trials here
function trMT_CHN(){
return isCHN() && (request.auth.token.T31 == 1);
}
//Subs here
function mtCHN(){
return isCHN() && (request.auth.token.MT3 == 1);
}
function isFelix(){
return isIDN() || isEN() || isCHN();
}
function isIDN(){
return isUser() && request.auth.token.IDN == true;
}
function isEN(){
return isUser() && request.auth.token.EN == true;
}
function isCHN(){
return isUser() && request.auth.token.CHN == true;
}
function isUser() {
return (isClient() || isServer());
}
function isPublisher(){
return (isMusician() || isGross() || isNet() || isFree());
}
function isMusician(){
return isUser() && roleNew();
}
function isNet(){
return isUser() && roleNet();
}
function isGross(){
return isUser() && roleGross();
}
function isFree(){
return isUser() && roleFree();
}
function isAdmin(){
return request.auth.uid != null && request.auth.token.Admin == true;
}
function isServer(){
return request.auth.uid != null && request.auth.token.HaD == true;
}
function isClient(){
return request.auth.uid != null && request.auth.token.Felix == true;
}
//This is for roles claim inside HaD App
function roleNew(){
return request.auth.token.role == "Unregistered";
}
function roleNet(){
return request.auth.token.role == "Net";
}
function roleGross(){
return request.auth.token.role == "Gross";
}
function roleFree(){
return request.auth.token.role == "Free";
}
}
}
What I'm trying to achieve is to only show the documents that match request.auth.uid. I've seen these examples on a lot of website but none of them seems to work for me. No matter what article I read I see these examples there but none of them seems to work for me. I have a (posts) collection with bunch of documents with auto-generated (ids).
// I cannot attach more than 2 images it gives me formatting error that's why.
https://i.stack.imgur.com/M84P5.png
https://i.stack.imgur.com/hbBii.png
https://i.stack.imgur.com/lm4HH.png
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == resource.data.userId; // This doesn't work
}
}
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == postId; // This also doesn't work
}
}
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid != null; // even though this works fine but it is general.
}
}
}
Below I've shared my code.
class App extends React.Component {
constructor() {
super();
this.state = { social: [], loggedIn: "false" };
}
componentDidMount = () => {
firestore.collection("posts").onSnapshot((snapshot) => {
const social = snapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() };
});
this.setState({
social
});
});
};
// clickHandle(id, likes) {
// firestore
// .collection("posts")
// .doc(id)
// .update({ likes: likes + 1 });
// }
handleCreate() {
var obj = { title: "Never Ever GIve Up" };
firestore.collection("posts").add(obj);
}
handleDelete(id) {
firestore.collection("posts").doc(id).delete();
}
handleLogin() {
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then((result) => {
console.log(result);
});
}
handleLogout() {
firebase.auth().onAuthStateChanged((user) => {
auth.signOut();
});
}
render() {
return (
<div>
<button onClick={this.handleLogin.bind(this)}>Login to Google</button>
<button onClick={this.handleLogout.bind(this)}> Logout from Google</button>
{this.state.social.map((obj) => {
return (
<h1>{obj.title} </h1>
);
})}
<br />
<button id="create" onClick={this.handleCreate.bind(this)}>
Create
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
The problem is that Firestore security rules are not filters. Please read that documentation carefully, and also this blog.
Your query is asking for all documents in the posts collection. However, your rules do not allow that. The rules will not filter the documents that match the rules. Instead, your client app needs to filter for the documents that the user should be able to read, and your rules need to validate that filter.
If you want to require the userId field to be the same as the authenticated user ID for the purpose of reading the document, your query needs to add a filter to ensure they aren't asking for anything more than they have permission to read:
firestore.collection("posts").where("userId", "==", uid)
Where uid is the user ID that you got from the Firebase Auth SDK.
Then your rules can check to see that the filter is correct:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == resource.data.userId;
}
}
}
I bet you're doing something funky with your requests.
resource.data will return the attributes of the document accessed. In your first example, this will work if the document at path posts/{post_id} has the attribute userId, and the userId attribute matches the uid of the incoming request context. I would verify the structure of each document in the posts collection to check that it matches.
The first example should work, I would double-check the user id matches of the sender. Check the "Authentication" tab of your Firebase console. (In the document shown, looks like the userId is the id of another post, which might indicate some misbehavior?)
The second example won't work because the document doesn't have a postId attribute.
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;
}
}
In my application, once the user is logged in via Firebase auth, I want to fetch additional data from my firestore database.
I do not want users to be able to look at other user's documents, therefore I created a rule for this:
match /users/{userId} {
function isAuthenticated() {
return request.auth != null;
}
function userIsSelf() {
return request.auth.uid == userId;
}
allow read: if
isAuthenticated()
&& userIsSelf();
}
In my head, what I wrote should in theory allow the current logged in user to see data only about himself, and this worked fine in the "rules playground".
However, when I try this code in in the app, I get an error: FirebaseError: Missing or insufficient permissions.
I think this has something to do with the way firestore fetches the data?
The way I query this is by fetching the collection users with a where that only returns users that has uid same as logged in user:
const querySnapshot = await firebase.firestore().collection("users").where("uid", "==", uid).get().catch(err => {
console.error('could not fetch user', err)
})
if (!querySnapshot || querySnapshot.empty) {
dispatch('logout')
throw new Error('Cannot find logged in user\'s data in database')
}
const userData = querySnapshot.docs[0].data()
commit('setUser', userData)
I created my firestore rules to look like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAuthenticated() {
return request.auth != null;
}
function isAdmin(uid) {
return get(/databases/$(database)/documents/users/$(uid)).data.role == 'admin'
}
// Allow admin to do everything
match /{document=**} {
allow read, write: if isAdmin(request.auth.uid)
}
// Only allow users to read/write to themselfs
match /users/{userId} {
function userIsSelf() {
return request.auth.uid == userId;
}
function roleIsUserOrSameAsDocument() {
return request.resource.data.role == 'user' || request.resource.data.role == resource.data.role;
}
allow read: if
isAuthenticated()
&& userIsSelf();
allow write: if
isAuthenticated()
&& userIsSelf()
&& roleIsUserOrSameAsDocument();
}
}
}
Your query doesn't match your rules. Your query is attempting to get all documents where the uid field matches the provided uid:
firebase.firestore()
.collection("users")
.where("uid", "==", uid)
But your rules are saying that users may only access the individual document with the ID that matches their UID. The rule is going to reject this query every time, because it's not looking at the document ID at all, just a field.
Your rules allow this query instead:
firebase.firestore()
.collection("users")
.doc(uid)
If you actually do want to allow the user to access any document where their UID matches the uid field in the document, you will need to adjust them like this:
match /users/{userId} {
function isAuthenticated() {
return request.auth != null;
}
function checkDocUid() {
return request.resource.data.uid == request.auth.uid;
}
allow read: if isAuthenticated() && checkDocUid();
}
Note that request.auth.uid is the currenetly auth'd user's uid and request.resource.data.uid is the value of the uid field in the document.
Always remember that your query must match the rules exactly, and that security rules are not filters.
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;
}
}
}