So I 've a application where users are able to write in my database(ONLY during SIGNUP). I want to grant them read/write access to JUST their child node and not my whole database. Currently, an un-authenticated user has full write access to my whole database node. This is my Firebase user node-
"users" : {
"-LAQxFS_GNA-xKewLYgDe" : {
"name" : "XYZ"
"phone" : "988900000"
},
"-LAffLlvl9ZTm-D1FMS8" : {
"name" : "XXYYZZ"
"phone" : "32223333"
},
....and so on
Here, i want this child user "LAQxFS_GNA-xKewLYgDe" to only access his node without any access to other child nodes.
Here's my rules
{
"rules": {
"messages":{
".read":true,
".write":true
}
, "books":{
".read":true,
".write":true
},
"aTime":{
".read":true,
".write":true
},
"users":{
".read":false,
".write":true
}
}
}
PS: I've my own custom signup page where users enter their name and phone number. This info is saved in "users" node.
Related
I want to give .write permission only to those authenticated users with isAdmin:true
Below is the structure of Database.
In the clients(users) node uid is the Firebase generated auth uid.
{
"clients" : {
"-M8bGQaB....." : {
"isAdmin" : true,
"uid" : 5gvR1GzT...
}
},
"products" : {
"-M81GzT....." : {
"productName": "Product 1"
},
"-M81GzT....." : {
"productName": "Product 2"
}
}
With your current data structure there is no way to look up the user profile in the security rules, as security rules can't search across all users.
That's why the proper data structure to store user uses their UID as the key. In your case that'd look like:
"clients" : {
"5gvR1GzT..." : {
"isAdmin" : true
}
},
With this JSON, you can then only allow users to write a product when the isAdmin property for their UID is set to true with:
{
"rules": {
"products": {
"$productid": {
".write": "root.child('clients').child(auth.uid).child('isAdmin').val() == true"
}
}
}
}
A super admin is an user with special uid which is able to access of everyone to edit their profile and publish new content.
How to make a super admin?
Consider using custom user attributes. It is more efficient and cheaper than using Real Time Database to lookup if a user is an Admin on every authenticated request:
https://firebase.google.com/docs/auth/admin/custom-claims
You would set the Admin role on the user upon creation:
admin.auth().setCustomUserClaims(uid, {admin: true})
You can propagate it to the client after ID token refresh.
currentUser.getIdToken(true)
Then you can simply enforce the rule:
{
"rules": {
"adminContent": {
".read": "auth.token.admin === true",
".write": "auth.token.admin === true",
}
}
}
If you don't use rules or Firebase RTDB, then enforce it on your backend by parsing it from the ID token via Firebase Admin SDK:
// Verify the ID token first.
admin.auth().verifyIdToken(idToken).then((claims) => {
if (claims.admin === true) {
// Allow access to requested admin resource.
}
});
Basically, it's all about data structure and the belonging security rules.
To get started, build a data structure where you have some kind of user roles in it.
For example like this:
{
"data" : {
"user1id" : {
"name" : "MisterX"
},
"user2id" : {
"name" : "John Doe"
}
},
"users" : {
"user1id" : {
"role" : "admin"
},
"user2id" : {
"role" : "member"
}
}
}
Each user has a property called role.
Now you can define your security rules and make use of the role property to define the right access rights:
"data" : {
"$userid" : {
".read" : true,
".write" : "root.child('users').child(auth.uid).child('role') === 'admin'",
}
}
In the case above just admins are able to write to the data/userid node. You can apply this to all the different nodes you want to.
I'm really scratching my head here, and I've been reading as much as I can on many other cases. I'm new to Firebase, but got some understanding of it. I was wanting to restrict certain records of the database to certain users. So here's my JSON:
"accounts" : {
"13asdf313" : {
"dog" : "bacon",
"email" : "luis#fakeemail.com",
"first" : "luis",
"last" : "xxxx"
},
"HlELrrGDbiMKgxxxx" : {
"name" : "Luis"
},
"anthony" : {
"email" : "anthony#fakeemail.com",
"last" : "xxxx",
"name" : "anthony"
},
"jpSq6UzX0mcAvExxxx" : {
"name" : "anthony"
}
}
Here are the rules set up based on what I've been reading:
{
"rules": {
"accounts":{
"$uid":{
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
}
},
}
}
In the simulator, I used the bottom condition (I even put /accounts in the location field). I used the UID: HlELrrGDbiMKgxxxx, copied straight from Firebase account list.
This is always coming up as failed
What am I doing wrong?
You have to insert accounts/HlELrrGDbiMKgxxxx into the Location field.
Otherwise you are trying to access the whole database (standard location is root, which covers the whole database). Your rule is just set for the child accounts/$uid and to access it the user id from authentication and the child location in the database must match.
I'm building an application that allows users to stamp their working times. On my application are 2 types of users. One of them are the 'employee' which has only read access and the other is the 'admin' that has read and write access. The employee are allowed to read their own worktimes and the admins are allowed to create new users or add worktimes for each user/employee.
The admin fills the form and push the Data. On firebase, I checked if the user is authenticated andalso runned a validation for each property of the user object. But I'm asking myself, what if somebody copy my firebase url (which are visible in the source code) and tries to push some anonymous objects or properties to my database?
For example, I've SignUp form which requires this fields:
{
"users": {
"-KInd4V0V9k5n6yhAETd": {
"uid": "-KInd4V0V9k5n6yhAETd",
"username": "Haris",
"password": "HelloWolrd123",
"age": "21"
}
}
}
On the firebas side, I've checked with the newData method if the children are passed from the form:
".validate": "newData.hasChildren(['uid','username','password,'age'])"
I've ran already a validation for each property that checks if the data are valid like username.isString() etc.
So what if an admin pushes an user object with the developer-tools like this to my database (email property added)
{
"-KInd4V0VEEEEEEEE": {
"uid": "-KInd4V0VEEEEEEEE",
"username": "Haris",
"password": "HelloWolrd123",
"age": "21",
// anonymous Object pushed from n admin developer-console
"email": "anonymous#object.com"
}
}
This will work because all required fields are valid and the validation passed on firebase. But how can I deny that an admin can't add and pass the 'email' property to my firebase database? Because my user object structure on the Database don't have an email property. I don't wanna allow that an user can push his own anonymous objects/data to my database. Do I need to validate each property or is there a method which I can validate the Object itself? Like newData('users').isValid()? And If a property is append then isValid returns false because the object are not the same like in the database structure?
I'm not entirely sure, but think you are looking for a rule that rejects all other children. If that is indeed what you're looking for, then:
{
"rules": {
"users": {
"$uid": {
// Valid users have these properties
".validate": "newData.hasChildren(['uid','username','password,'age'])",
// The uid property must be a string and refer to an existing noder under /users
"uid": {
".validate": "newData.isString() && root.child('users').child(newData.val()).exists()"
},
// the user name must be a string
"username: {
".validate": "newData.isString()"
},
"password: {
// I really hope you're not storing a password
},
"age: {
// TODO: you're storing age as a string now, might want to fix that
".validate": "newData.isNumber()"
},
// All other properties are rejected
"$other": {
".validate": false
}
}
}
}
}
I'm writing a note sharing app and I'm trying to find the best approach for the data structure to allow adding collaborators to user notes, while at the same time having sensible security rules for the structure in question.
What I have now is the following:
"users": {
"johndoe": {
"notes": {
"note1Key",
"note2Key"
}
},
"jane": {
"notes": {
"note3Key",
"note4Key",
"note5Key"
}
}
...
},
"notes": {
"note1Key": {
// actual note data
},
"note2Key": {
// actual note data
},
"note3Key": {
// actual note data
},
...
},
"shared": {
"johndoe" : {
"note5Key" : true,
"note3Key" : true
},
"jane" : {
"note1Key" : true
}
...
}
When "John Doe" creates a note, the note is stored in notes/noteKey with read/write access granted to owner and collaborators added by the owner. Additionally the note's key is stored in user/johndoe/notes/noteKey, which can be read and written to only by him. When this user wants to add a collaborator ("Jane") to his note, this same note key is stored in shared/jane/noteKey which can be globally read & written to. This way, when listing each user's notes, I have to read from only 2 locations to list all notes a user has access to: user/johndoe/notes and shared/johndoe.
Is there a better approach? I don't like to have the shared index globally accessible, could I somehow limit it? Since one user can potentially collaborate with a big number of different users on different notes, I'm not really sure how to set the security rules, to limit the read/write access to this index.
I was thinking about reversing the shared node logic, to store note key's under it's respectful owners sub-nodes and including a list of collaborators like so: shared/jane/noteKey/collaborators/johndoe. This way I could have a global read rule and a more restrictive write rule (each user can only write in his own shared node), however this would greatly increase the complexity of listing all notes a user has access to.
You wanted to:
allow adding owner & collaborators to user notes.
list all notes a user owned.
list all notes a user has access to.
You should have added collaborators list to each notes as follows:
{"rules":{
"users": {
"$user_id": {
"profile_and_settings":{
".write":"auth != null && auth.uid == $user_id"
},
"owned_notes":{
".write":"auth != null && auth.uid == $user_id",
"$note_id":{}
},
"accesssible_notes": {
".write":"auth != null",
"$note_id":{}
}
}
},
"notes": {
"$note_id": {
// to edit this node: must authenticated, new entry or owner of this node.
".write":"auth != null && ( !data.exists() || data.child('owner').val() == auth.uid )",
"owner":{
".validate":"newData.val() == auth.uid"
},
"collaborators":{
"$user_id":{}
},
// ... other note data
}
//...
}
}}
See related question:
Firebase rule: Do we have better ways to manage object ownership?