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.
Related
I have an object called 'Service' This can be read by anyone but changes can only be made by the owner. My firebase rule for this is:-
"Service": {
".read": true,
"$uid": {
".write": data.child('owner').val() == auth.uid"
}
}
Service has a child called 'owner' which is == to the users UI when logging in through firebase Auth.
I also have a User object which can make a Service as a favourite. I'm saving these in the user object as an array of [businessKey:true].
However, I've been asked to also save in the Service object the reverse relation of what users have favourited it. So an array However as a user isn't always the owner of a service I come into a permissions error. I'm trying to write rules that allow anyone to write to one child but not the others.
I have tried...
"Service": {
".read": true,
"$uid": {
".write": data.child('owner').val() == auth.uid" || data.child('users').val() != root.child('users').val()"
}
}
this is would allow a write if the user was the Service owner or just changing the child 'users' The effect was that any user was able to write to anything.
I've also tried
"Service": {
".read": true,
"$uid": {
"$users": {
".write" : true,
},
".write": "data.child('owner').val() == auth.uid"
}
}
thinking this would always allow a write to 'users' and any other child if user was owner.
I'm pretty new to firebase and this rules syntax in general so I'm probably making some glaring error! If what I want to achieve possible? what have I got wrong?
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 am trying to setup private chat abilities in an app that I am working on and I'm having a bit of trouble wrapping my head around denormalizing the data/setting up the rules properly.
After doing some reading, I realize that rules are all or nothing so using rules to filter is not an option.
I've sketched out my basic idea on paper, and have pasted it below. Basically there would be two main routes, users and chats.
Users would just be a keyed list, which each key matching an authenticated user. Then inside each member of the list I would just have each chat that the said user is in listed as a key.
For the chats route I'd have a list of all of the chats.
Now for the rules.
Users would only be able to read their data in the list where the key matched their uid. For the write i'm less confident. I'm thinking I have to let anyone with authentication write, otherwise the user starting the chat could not notify others of the new chat by playing the chat id in their chat list in the users route.
For the chats rules both read and write would only be allowed if the user is authenticated and the chat key is located inside their data in the user route.
Does the seem like I'm going in the right direction?
users:{
user1:{
chat1: true,
chat2: true
...
},
user2:{
chat1: true,
chat3: true
....
}
}
chats:{
chat1:{
lastUpdate: timestamp,
messages:{
0:{
from: user1
to: user2,
message: some message
}
...
}
}
}
rules:{
.read: false,
.write: false,
users:{
$user_id:{
.read: auth != null && $user_id == auth.uid,
.write: auth != null //not sure here as other users need to write here if the start a new chat
}
},
chats:{
$chat_id: {
.read: auth != null && root.child('users').child($chat_id).contains(auth.id),
.write: auth != null && root.child('users').child($chat_id).contains(auth.id)
}
}
}
I've been playing with this more, so here is one option (by no means am I suggesting this is the best way to do it)
Rules:
{
"rules":{
".read": false,
".write": false,
"users":{
"$user_id":{
".read": "auth != null && $user_id == auth.uid",
".write": "auth != null" //not sure here as other users need to write here if the start a new chat
}
},
"chats":{
"$chat_id": {
".read": "auth != null && root.child('users').child(auth.uid).child('chats').hasChild($chat_id)",
".write": "auth != null && (root.child('users').child(auth.uid).child('chats').hasChild($chat_id) || !data.exists())"
}
}
}
}
then for users I have a structure like this:
users:{
someUserId:{
chats:{ //embedded a second level so I can save firebaseObj.someUserId to get the keys more easily
someChatId: true //and repeat for each chat
}
}
}
Chats are like this:
chats:{
someChatId:{
//chat data
}
//more chat objects
}
I wouldn't be surprised if there is a much better way to do this, but at least this may be a start for those who are stuck. I'll try to remember to update this if/when I get a better solution.
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 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"
}
}
}
}
}