I'm new to Firebase and really struggling with the security rules.
I'm trying to store data for anonymous user sessions that can only be accessed by that user but as soon as I tighten up the security rules I get permission denied and can't work out why.
The structure I have is:
/userSessions/$user/user_id
The application pushes to user sessions and writes the user_id as returned by the anonymous login.
I have the following security rules:
{
"rules": {
".read": true,
"userSessions": {
"$user": {
".write": "newData.child('user_id').val() == auth.id",
"user_id": {
".validate": "newData.isString()"
}
}
}
}
}
I'm sure I'm doing something silly, but can't work out how to use the security simulator either so don't know how to go about troubleshooting this
Any help greatly appreciated
EDIT -
Problem occurs when trying to create a new entry under userSessions after authenticating anonymously.
Code:
var userSessionsRef = new Firebase('https://xxxxxx.firebaseio.com/userSessions');
var userSession;
var auth = new FirebaseSimpleLogin( userSessionsRef, function(error,user) {
if (error) {
console.log(error);
} else if (user) {
userSessionsRef.child(user.id).set({
user_id: user.id,
provider: user.provider,
created: Firebase.ServerValue.TIMESTAMP
});
userSession = new Firebase('https://xxxxx.firebaseio.com/userSessions/'+user.id);
userSession.onDisconnect().remove();
}
The set() operation you've provided runs great; no errors; there must be another issue at play here that you haven't captured in your repro (did you enable anonymous auth in Forge?). Here's a fiddle running on your security rules, copied line-for-line (just moved down one child path): http://jsfiddle.net/katowulf/wxn5J/
On a related note, your current security rules will not allow the userSession.onDisconnect().remove(); op, since records can only be written if newData() contains a user_id value (i.e. it can't be null). A better way to structure these security rules would be as follows:
{
"rules": {
".read": true,
"userSessions": {
"$user": {
".write": "$user === auth.id", // delete allowed
"user_id": {
".validate": "newData.isString() && newData.val() === $user"
}
}
}
}
}
Related
I tried to set some important write persmissions but I can't solve my problem. I got told that, if I add a write-rule to room, then I overwrite my room/$roomID/ingame rule.
What I'm trying to do is
Creating a room by auth users.
Set/update ingame of a room only by the creator of the room. (That works)
Rules:
{
"rules": {
".read": true,
"user": {
".indexOn": "displayname"
},
"room": {
"$roomID": {
"ingame":{
".write": "data.parent().child('creatorUid').val() == auth.uid"
}
}
}
}
}
How I call to create a new room:
let user = firebase.auth().currentUser
dbRoomRef.push().then((room) => {
room.set({
creatorUid: user.uid,
ingame: false,
})
}).catch(function(err) {
console.log(err.message)
}
)
Error message (as expected):
FIREBASE WARNING: set at /room/-L572bnuRv0_vntko-Bd failed: permission_denied
Thank you.
The error messages says that you're trying to write /room/-L572bnuRv0_vntko-Bd and have no permission to write there. That is correct, since your rules only give permission to write to /room/-L572bnuRv0_vntko-Bd/ingame.
If creatorUid is already set when you create the room, you don't have to include it in your write statement and can just do:
room.child("ingame").set(false);
If you're trying to allow everyone to create a new room (or write to an existing room) as long as they are the owner, you need to set your rules one level higher:
"room": {
"$roomID": {
".write": "newData.child('creatorUid').val() == auth.uid"
}
}
I want to ensure that a new user can be created (from the client), but only an authenticated user can read or write an existing object.
I have a simple rule set:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid === $uid",
".write": "!data.exists() || auth.uid === $uid"
}
}
}
}
I am calling createUser and then in the callback I'm trying to add an entry to my own users object:
const usersRef = ref.child('users');
const userEntry = {
[userData.uid]: {
created: new Date().getTime()
}
};
usersRef.set(userEntry)
I would have thought that even though the user is not yet logged in, they should have write permission because of !data.exists(). Yet I am getting a PERMISSION_DENIED error.
If I set ".write": true on the users level then it will cascade (and override?) my inner rules won't it?
Edit:
This fails even with:
"users": {
"$uid": {
".read": true,
".write": true
}
}
Thanks.
I think I initially misunderstood the problem. In your current callback, you are trying to overwrite the entire users level because of how set works.
You would really want to set only the thing that doesn't exist:
const userRef = ref.child('users').child(userData.uid);
const userEntry = {
created: new Date().getTime()
};
userRef.set(userEntry);
Then, I think that your existing rules would work.
I think this a confusing question because creating a user and writing to the database are completely different things. So i will just show how i do it in my app.
First step is creating the user
Next log the user in because creating doesn't automaticly log the user in (I do this in the callback function of create user)
Last step is writing the user data to firebase
I use the following rule to make sure each user can only write to his own node in firebase (documentation):
{
"rules": {
"users": {
"$user_id": {
".write": "$user_id === auth.uid"
}
}
}
}
And one thing to keep in mind is that set() will replace any existing data at that path. So make sure you use the uid of the user and not the users node.
Finally i want to point out a huge flaw in the rules you posted in your question:
".write": "!data.exists() || auth.uid === $uid"
This rule states you can write if there isn't any data yet OR you have the correct uid. The first part of this statement is the problem because ANYONE can write to this locaion when there isn't any data. Also because $uid is a dynamic path you can add anything there like:
"users": {
"WoWIjustMadeThisUp": {
"nice": "Some huge value making you go over your limit"
}
}
If you want users to only write an initial value and after that won't be able to edit it just use a validate rule to check if there is already data at that location.
I have an authenticated user, which is given authentication through using FirebaseSimpleLogin. This user has the token:
'user':{
email: "myemail#internets.com",
firebaseAuthToken: SOME TOKEN,
"id: "4",
isTemporaryPassword: true,
md5_hash: "aHash123",
provider: "password",
uid: "simplelogin:4"
}
I have given the authenticated user read permission under the 'rules and security' tab in firebase using:
{
"rules": {
"SOME DATA": {
".read": "auth.email == 'myemail#internets.com'",
".write": true
}
}
}
However, when it comes to accessing the data, I get the error:
Error: permission_denied: Client doesn't have permission to access the desired data.
Am I missing something fundamental as to why I do not have read permission?
In the security rules section of firebase, I had made an error.
{
"rules": {
"SOMEDATA": { //NOTE: removal of space character
".read": "auth.email == 'myemail#internets.com'",
".write": true
}
}
}
Lesson learnt: be very specific with rules, and always follow some convention.
Try This and Like-Up if its worked:
service firebase.storage {
match /b/<url data>/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
I'm creating a simple chat application using firebase and am running into some issues with the available security settings.
The data model for this application is very simple and is as follows
rooms:[
people:[
{
name: //string
status: // what the user is doing, typing, still connected etc.
secret: // the problem is with this
}
],
messages:[
{/* message to and payload*/}
]
]
the issue is that I only want the user that created the rooms[i].people[j] to be able to update the status of that person.
Being new to firebase I though I would be able to use the update function as follows
personRef.update({
'status': // newStatus
'secret': // used to authorize the update
})
the problem with this is I can't find any way to make the secret write only and give access to the people at the same time. That is I need anyone to be able to pull the data located at rooms[i].people - meaning rooms[i].people would have to have ".read":true (in firebases security rules). But this would give read access to every child and anyone in the room would be able to see any one else's update secret. I'm I thinking of this problem incorrectly?
Is there a way to give read access to a parent but exclude some of the children from the results?
Thanks!
It depends a bit on how you're using the secret to implement authorization, but I suspect denormalizing your data is going to do the trick. Try something like this:
people-secrets:[
<user's ID>: {
secret:
}, ...
],
rooms:[
people:[
{
name: //string
status: // what the user is doing, typing, still connected etc.
}
],
messages:[
{/* message to and payload*/}
]
]
That would allow you to segment the security rules:
{
"rules": {
"people-secrets": {
"$user_id": {
".read": "$user_id === auth.uid",
".write": "$user_id === auth.uid"
}
},
"rooms": {
"$room_id": {
"$user_id": {
".read": "auth.uid != null",
".write": "$user_id === auth.uid && root.child('people-secrets/' + auth.uid + "/secret") === <that token>"
}
}
}
I'm struggling to set the proper security rules for my application.
An overview of the application I'm writing is that users can register themselves using email and password (I'm using Firebase Simple Login for this which works perfectly). Once logged in, user can add their todos.
angularFire('https://<firebase>/firebaseio.com/todos', $scope, 'todos');
And to add a new todo against any user, I simply update the todos model.
$scope.todos.push({
user: 'a#b.com',
todo: 'What to do?'
});
This security rules I'm using to restrict non-registered user to add any todo:
{
"rules": {
".read": true,
"todos": {
".write": "auth != null",
".validate": "auth.email == newData.child('user').val()"
}
}
}
But it does not allow even an authenticated user to write any data and throwing an error,
"FIREBASE WARNING: on() or once() for /todos failed: Error: permission_denied."
But If I add the following data in simulator then it works as expected.
{user: "a#b.com", todo: 'What to do?'}
Here is the log:
/todos:.write: "auth != null"
=> true
/todos:.validate: "auth.email == newData.child('user').val()"
=> true
/todos:.validate: "auth.email == newData.child('user').val()"
=> true
Write was allowed.
push adds a new child with a randomly generated ID (in chronological order) to /todos. So, newData isn't pointing to what you think it is pointing to. Change your rules to:
{
"rules": {
".read": true,
"todos": {
"$todoid": {
".write": "auth != null",
".validate": "auth.email == newData.child('user').val()"
}
}
}
}
Update: Above rule is valid but angularFire currently writes the whole array back to the server causing the auth to fail. You can use angularFireCollection instead, to only write the new TODO back, like so:
$scope.todos = angularFireCollection(new Firebase(URL));
$scope.todos.add({user: 'a#b.com', todo: 'What to do?'});
There's an open issue to optimize angularFire's behavior when new items are added to the list, but in the meantime you can use angularFireCollection to get the right behavior.