Firebase Database Rules for Unique Usernames - firebase

I'm trying to create a website that uses Firebase's databases to store user information. I want to use unique usernames. I have two indexes, one for users and another for usernames.
My database is structured like this:
users {
$uid {
username: "username1",
gender: "xyz"
email: "xyz"
}
},
usernames {
"username1": $uid"
}
The users claim a username with their $uid.
These are my rules:
{
"rules": {
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.uid === $uid",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
}
}
},
"usernames" : {
".write": "!data.exists() && auth!= null",
".validate": "newData.val() == auth.uid" <---- I can't get this too work
}
}
}
When setting username under $uid it checks the usernames index so username can only be written with a username not in use or one that has it's own $uid.
I only want data in which the value is the authenticated users uid and the key is the username. I can't quite get this to work. I suspect that I am using newData().val() incorrectly. My validate statement is failing.
I'd like to avoid using custom tokens, but I'm open to any suggestions. Thanks in advance.
Sorry if this explanation is too drawn out, this is my second post on StackOverflow.
Edit #2
I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.

The accepted answer will cause problems as soon as you want to allow people to change their usernames.
By slightly changing your database structure you can easily manage usernames in firebase. You can also allow users to change their username.
Database:
usernames: {
username1: {
owner: "userId1"
},
username2: {
owner: "userId2"
},
...
}
The following rules will:
stop any user changing/deleting another user's username
let a new user create a username
let a user change their username by deleting their old one and creating a new one
(split on newlines only for readability)
"usernames": {
"$username": {
".read": "true",
".write": "auth != null &&
(
\\ Allow user to delete only their own username
(data.exists() && data.child('owner').val() === auth.uid && !newData.child('owner').exists())
||
\\ Allow user to create a new username if the username is not taken
(!data.exists() && newData.child('owner').val() === auth.uid)
)"
}
}
This update will create a user:
const toAdd = {}
toAdd[`usernames/${username}`] = { owner: userId };
firebase.database().ref().set(toAdd)
This update will change a users username:
const update = {}
update[`usernames/${previousUsername.toLowerCase()}`] = null;
update[`usernames/${newUsername.toLowerCase()}`] = { owner: userId };
firebase.database().ref().update(update)
In your code you should also query the database to check if a username exists before sending the update.
Why I avoided the username: userId approach:
You say
Edit #2 I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.
I believe this is correct. For your usernames setup:
usernames {
"username1": userId1,
"username2": userId2,
...
}
This answer describes a method which sends the key information in the request:
ref.update({
_username: "username3",
username3: "userId3",
})
Then you can check if the username is in the database with a rule like
data.child(newData.child('_username').val()).exists()
However in your usernames setup, you would then overwrite the other usernames with this update. To avoid that you would need to set the new data at the path of the username, usernames/username3 = userId3. But you cannot set it like this as you are then back at the problem of not having the key to reference in the rule.
You would need to create a nonsensical structure something like:
usernames: {
username1: {
_username: "username1",
userId: "userId1"
}
}
So I chose the simpler, but unfortunately a slightly less beautiful username database setup I describe at the start.

Sorry if i'm late but i ran into a similar problem, i changed my usernames rule to the following which did the trick:
"usernames" : {
"$username": {
".write": "!data.exists() && auth!= null && newData.val() == auth.uid"
}
},
}

Related

Firebase rules for comment on a post

What should be the firebase rules for comment on post which is similar to facebook.
There are two things:
first, only authenticated user can comment.
Second, only the user who has commented can delete the comment. The user who has commented his id is saved in username.
I strongly suggest using Firebase Bolt for writing/compiling Firebase Database Security rules. Data structure can get big and complicated. Using Bolt language you'll be able to easily write complex access and structure rules that can be re-used for other db patterns.
Your rules would look something like this:
path /comment/{postUid}/{commentUid} is Comment {
read() { true }
write() { isAuthor(this) || isAuthor(prior(this)) }
}
type Comment {
text : String,
username : String
}
isAuthor(value) { auth != null && value.username == auth.uid }
Pay attention to isAuthor(prior(this)) call. This is the way to make sure only author can delete a comment. prior function returns data as it was saved before current event (create, update or delete).
After using firebase-bolt tool to compile rules to JSON format you'll get:
{
"rules": {
"comment": {
"$postUid": {
"$commentUid": {
".validate": "newData.hasChildren(['text', 'username'])",
"text": {
".validate": "newData.isString()"
},
"username": {
".validate": "newData.isString()"
},
"$other": {
".validate": "false"
},
".read": "true",
".write": "auth != null && newData.child('username').val() == auth.uid || auth != null && data.child('username').val() == auth.uid"
}
}
}
}
}

Firebase: Cannot add user entry (`set` permission denied)

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.

Adding Security to Firebase to prevent the insertion of additional data

I have a field within Firebase called 'pending_members' which contains a list of members pending permission to be granted by an 'owner', as such, 'pending_members' requires the following rules:
The current user can only add themselves (uid)
The current user can remove only themselves (uid) from the list
The 'owner' can remove any member from the list
Only the 'owner' can read the list
I've tried various security rules but seem to miss many corner cases, for example, a user is given write access because the data contains their uid but they can then submit someone else's uid along with this.
Can anyone suggest appropriate rules for this situation? Many thanks
"pending_members" : {
".write" : "auth !== null &&
// The user is authenticated AND
(newData.child(auth.uid).exists() ||
// The new data contains either the current user's id OR
(!newData.exists() &&
// There's no new data (a delete operation) AND
data === auth.uid))",
// The old data is the current user's id
"$member" : {
".validate" : "newData.isString()",
"$other": { ".write": false, ".read": false }
}
}
Edit:
Structure Example:
users ->
personal_data ->
email (user email address)
first_name (user first name)
last_name (user last name)
networks_index ->
networks ->
members (list of uids of users linked to the network)
owner (uid of the owner/primary user)
pending_members (list of uids of users wishing to link to the network)
Data Example (image)
Complicated structure you have but i will give it a try:
Keep in mind standard value for read and write is false.
{
"rules": {
"networks": {
"$networkid": {
//Give read and write access to the owner of the network
".read": "auth != null && "root.child('networks').child($networkid).child('owner').val() == auth.uid",
".write": "auth != null && "root.child('networks').child($networkid).child('owner').val() == auth.uid",
"pending_members": {
"$uid": {
//Give members write access to their own node inside pending_members
".write": "auth != null && auth.uid == $uid",
//Use validate to check if the value is a bool or emty(removal)
".validate": newData.isBoolean() || !newData.exists()
}
}
}
}
I have only concentrated on the pending_members here, I hope that is enough and it is clear enough. If it doesn't work i suggest testing each rule seperatly to see which one is causing a ploblem so I (or someone else) can help fix it.

Firebase private chat schema and rules

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.

excluding children from a firebase node with read access

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>"
}
}
}

Resources