How to password protect certain nodes in Firebase Realtime Database - firebase

I'm creating a chat room with a Fireabse Realtime Database backend and I want some of the rooms to be password protected. I have a rough idea of how that could work but I'm having trouble fully fleshing it out. Say my structure is like this:
Chatrooms : {
-randomID : {
roomName: 'Chatroom 1',
password: 'foobar'
messages: {
...
}
}
}
I could set up security structure as such for rooms that need a passwords:
'Chatrooms' : {
-randomID : {
'roomName': 'Chatroom 1',
'password': 'foobar',
'.read': "data.child('validatedUsers/*user's uid*').val() === true",
'validatedUsers' : {
*user's uid* : true,
*other user's uid* : true,
...
}
'messages': {
'.read': false
...
}
}
}
This way (as I understand it) I can add user's who have been granted access to the validatedUsers object and if they are in the list then the top level .read will override the 'messages' read.
My problem is I can't figure out a secure way to get the password, check it against what the user entered, and then enter them into the validatedUsers object. Since it wouldn't be secure to do so no the client would I need a cloud function of some sort to do the checks? Now that I think about it I would likely need a cloud function to hash the password anyway?

Related

Acessing adjacent data in firebase

Let's imagine a simple scenario: A simple mail system in firebase in a tree like this:
{
"users":{
"user-0001":{
"mailbox":{
"user-0002":{
"DSdljkdkd333klll":{
"message":"Hi ! I am user-0002 and I sent to user-0001 a message"
},
"JjkJHHH8888Gggg2":{
"message":"Hi ! It's Me, user-0002 again !"
}
}
},
"secretStuff":"Something Private - nobody can write anything here",
"myContacts":"This is my contact list. Obviously just me can access"
},
"user-0002":{
"mailbox":{
"user-0056":{
"DSdljkdkd333klll":{
"message":"Party tonight ! Don't forget !"
}
},
"user-0282":{
"3893NJJj33333eddf":{
"message":"How are you ?"
}
}
},
"secretStuff":"Something Private - nobody can write anything here",
"myContacts":"This is my contact list. Obviously just me can access"
}
}
}
Me as user-0002 , I can write on my own tree.
Ok, but I should be capable to write at user-*/mailbox/user-0002 because I can send a message to any user that I want. And of course: I can't have access to any other key.
So, how to archive this : A rule where I can write on my tree and in an adjacent tree like the example above ?
You should keep public data and private data in separate top-level lists. So:
mailboxes
user-0001
user-0002:{
"DSdljkdkd333klll":{
"message":"Hi ! I am user-0002 and I sent to user-0001 a message"
},
"JjkJHHH8888Gggg2":{
"message":"Hi ! It's Me, user-0002 again !"
}
}
user-0002
user-0056:{
"DSdljkdkd333klll":{
"message":"Party tonight ! Don't forget !"
}
},
user-0282:{
"3893NJJj33333eddf":{
"message":"How are you ?"
}
}
contacts
user-0001: "This is my contact list. Obviously just me can access"
user-0002: "This is my contact list. Obviously just me can access"
secrets
user-0001: "Something Private - nobody can write anything here"
user-0002: "Something Private - nobody can write anything here"
Now you can secure access based on the type of information, and load specific types of information.
If you need all information for a specific user, you will need to read from all three places. But given that you'll usually do this for a specific user, those reads are not a scalability concern. Even if you need all info for multiple users, it's usually quite fast since Firebase pipelines the requests over a single connection.
Also see:
Firebase: How to structure public/private user data
How to create public/private user profile with Firebase security rules?
Make a public profile and a private profile in angular 4+
Firebase: Allow Read of property names, but not content
How to store public/private data under a node, and still query the entire node
After researching more and testing I conclude that this should work...
{
"rules":{
"users":{
"$uid":{
".write":"$uid === auth.uid",
".read":"$uid === auth.uid",
"mailbox":{
"$uid":{
".write":" $uid === auth.uid"
}
}
}
}
}
}
This means:
If I'm user-0001, I can read/write in anything at /users/user-0001, however I just can write in elsewhere if the path is /users/*/mailbox/user-0001

Restrict Firebase users by email

I have a client that would like to be able to make a list of restricted emails that can access the data. So anyone else coming to the app can't read/write any data at all ( ideally can't even log in but I don't think that's possible with Firebase? ). Any ideas on how to go about this? I had thought of having an array of accepted emails and checking whether their email existed in the security rules but that didn't seem to work. I had the following in the database:
"validEmails": ["test#test.com"]
and then in the security rules:
".read": "root.child('validEmails').val().indexOf(auth.token.email) > -1"
But it looks like you can't use indexOf in those security rules.
Maybe I need to have a list of acceptable emails, and then when a user signs up it checks whether they're in that list and adds their UID to an accepted list? I guess I could do this through a cloud function or something?
Any help would be much appreciated.
Cheers
Have the list of allowed user's emails in the database:
"whitelist": {
"fred#gmail%2Ecom": true,
"barney#aol%2Ecom": true
}
Since periods are not allowed in keys, you need to escape strings with periods before storing them.
Then in the database rules:
{
"rules": {
"whitelist": {
".read": false,
".write": false
},
".read": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()",
".write": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()"
}
}
User's email is accessible via auth.token.email. You need to escape the dots (. -> %2E) and check if the key exists on the whitelist.
These rules don't allow anyone read or write access to the /whitelist section of the database. Modification is only possible via firebase console.
Thanks guys, what I ended up doing was having a list of acceptable emails:
{
"validEmails": ["test#test.com"],
"validUsers": {}
}
and then have a cloud function run to check when a user signed up if their email was in the valid email list. If it was then it added them to the valid users list and if not it deleted the newly created user. I also set up data rules so that only users within validUsers could access the data.
The front-end then handled the redirection etc for invalid users.
Once you enable the authentication module of Firebase I believe you can't restrict it to email addresses or domains. However you could secure your database another way. If your users are already registered and you know their uid, then you can restrict read and write access based on these.
Lets pretend you have an acl object in the database, you can list the users and their uid with their read/write permissions.
These rules will check each request and only allow authorised users to access the data.
{
"acl": {
[
{
"uid: "abc123"
"canRead": true,
"canWrite": true
},
{
"uid": "def456",
"canRead": true,
"canWrite": false
}
},
"secure": {
".read": { root.child('acl').child(auth.uid).child('canRead').val() == true }
".write": { root.child('acl').child(auth.uid).child('canWrite').val() == true }
}
}

Setting hierarchical rules in firebase by comparing array to an array in firebase security rules

I am making realtime chat of my app using firebase. The login is userId based. so the auth.uid is this userId. A user is owner of many profiles(say of his family members), and a profile can belong to multiple users(say a child profile can belong to his father and mother). Now a chat is always on behalf of a profile(i.e. a father and wife can send chatMessage on behalf of their son). I want to set security rules for this chat.
The issues which I am facing are
Only the users whose profile has created a chat-message can edit that message. I am unable to set this security as I would have to compare an array to an array for which I have to write a function in security rules which firebase does not allow.
Users:{
userIdOfVed:{
profiles:{
userProfileIdOfVedSon:{
profileId:userProfileIdOfVedSon,
relation:son
},
__:{...},
__:{...},
...
}
},
__:{...},
__:{...},
...
}
Profiles:{
userProfileIdOfVedSon:{
chats:{
chatRoom1Id:{
caseId:case32Id,
chatRoomId:chatRoom1Id
},
chatRoom3Id:{
caseId:case42Id,
chatRoomId:chatRoom3Id
}
}
//...Other user data(like email, phone no.) which might be required
},
__:{...},
__:{...},
...
}
ChatMetadata:{
chatRoom1Id:{
createdOn:ISODate("2017-04-13T11:25:35.668Z"),
members:{
userProfileIdOfVedSon:{
userProfileId:userProfileIdOfVedSon
},
__:{...},
__:{...},
...
},
users:{},//I want to avoid putting this field as least as possible, but if its very critical for setting security, then there is no avoiding it.
caseId:case32Id,
lastMessage:"hello world"
//...Other chat meta data(like last message,etc) which might be required
},
__:{...},
__:{...},
...
}
Chats:{
chatRoom1Id:{
message1Id:{ //these are randomly generated messageIds by firebase
userId:userIdOfVed,
userProfileId:1,
message:"hello world",
timestamp:1459361875337 //can be replaced by a standard date time format
},
message2Id:{...},
message3Id:{...}
},
chatRoom2Id:{
message34Id:{...},
message69Id:{...}
}
}
//Rules for reading the realtime database
//The list of profiles can be put in the payload of the auth token (auth.profiles) which is not implemented yet
Users:{
$userId:{ //A user can only read/write/modify his $userId key-value object
"./read": "$userId == auth.uid"
}
}
Profiles:{
$profileId:{ //Can be read/write/modified by Users who have access to this profiles.
".read": "root.child('Users').child(auth.uid).child('profiles').child($profileId).exists()"
}
}
ChatMetaData:{
$chatRoomId:{ //Only the profiles who are present in its "members" keys can read it and a profile can only modify his $profileId entry in "members".
".read": "data.child('users').child(auth.uid).exists()"
}
}
Chats:{
$chatRoomId:{ //Only the profiles who are present in "ChatMetadata.$chatRoodId.members" keys can read it and push new values in it.(optional: modification to a child can be done only if that profile belongs to "ChatMetadata.$chatRoodId.members" & his profileId==the child.profileId)
".read":"I AM UNABLE TO FIGURE OUT THIS RULE, AS FIREBASE DOES NOT ALLOW FUNCTIONS INSIDE THIS."
}
}
TL;DR: Can I compare array to an array in firebase security rules to set hierarchical rules

Firebase permissions based on user's role

In my application I am supposed to have three kinds of roles:
Store: Can do whatever kind of reads and writes on anything bellow their owned key;
Customers: Can create their chat keys only underneath their designated store; Once a chat is created they can only push to the message child, on their own chats;
Attendants: Can only update chats which are designated to them, and insert under their messages tag.
The database is something similar to:
"$store_id": {
"owner": STORE_ID,
"chats": {
...
"$chat_id": {
"owner": COSTUMER,
"attendant": ATTENDANT,
"messages": {
"$message_id": {
"owner": CUSTOMER_ID
}
}
}
...
}
}
I can't figure out how I can achieve this behavior because:
If a permission is applied to a top level node, their children can't override it;
If someone crack into the database with valid credentials (i.e.: customers'), they can assume whatever role they want;
How this kind of issue is managed in Firebase?

How do you delete user accounts in Meteor?

The only way I have found to delete user accounts in meteor (other than emptying the database with mrt reset), is by actually logging into that specific user account, and deleting the account from the console, using:
Meteor.users.remove('the user id');
But like I said, I need to be logged in as that specific user, and have not been able to find a solution which enables me to delete any user from the db. I'm sure it has something to do with permissions or roles, but I am not sure how to proceed / what is the best solution / how to set an administrative role for a particular user, so that I can delete different user accounts.
You could do
meteor mongo
or
meteor mongo myapp.meteor.com for a deployed app
Then
db.users.remove({_id:<user id>});
I wouldn't recommend it but if you want to delete any user without being logged in from meteor you would need to modify the allow rules. But deleting a user is a very unlikely event hence the above might be the best way to do it.
Anyway if you do want, modify the Meteor.users.allow({remove:function() { return true }); property. See http://docs.meteor.com/#allow. You could add in some custom logic there so it'll only let you do so if you're the admin
I was having trouble doing this on nitrous.io because I couldn't open both Meteor and Mongo. I put:
Meteor.users.remove(' the _id of the user ');
in the isServer section to remove the user.
If anyone is still looking for an answer to this question, I have outlined my solution below.
When I create a new user, I add a field called role in my user document. If I want a user to be able to remove other users from the Meteor.users collection, I give him a role of administrator. If not, I give him a role of member. So, my user document looks something like this -
{
"_id" : ...,
"createdAt" : ...,
"services" : {...},
"username" : "test",
"profile" : {
"name" : "Test Name",
"role" : "administrator"
}
}
On the client, I have a list of users (added using a #each template tag) with a remove button next to each user. A user has to login to see this list. I defined an event handler for the remove button -
'click #remove-user-btn': function () {
Meteor.users.remove({ _id: this._id }, function (error, result) {
if (error) {
console.log("Error removing user: ", error);
} else {
console.log("Number of users removed: " + result);
}
})
}
However, Meteor.users does not allow remove operations from the client by default. So, you have to edit the Meteor.users.allow callback in the server as shown below to allow the users to be removed from the client side. But we need to make sure that only a user with an administrator role is allowed this privilege.
Meteor.users.allow({
remove: function (userId, doc) {
var currentUser, userRole;
currentUser = Meteor.users.findOne({ _id: userId }, { fields: { 'profile.role': 1 } });
userRole = currentUser.profile && currentUser.profile.role;
if (userRole === "administrator" && userId !== doc._id) {
console.log("Access granted. You are an administrator and you are not trying to delete your own document.");
return true;
} else {
console.log("Access denied. You are not an administrator or you are trying to delete your own document.");
return false;
}
},
fetch: []
});
This is the general idea. You can build upon this to suit your needs.
Here are the steps to delete user from mongo through console:
step 1: open new console
step 2: change diretory to your app such as (cd myapp)
step 3 : enter command meteor mongo
step 4: make sure there exists a table called users, db.users.find({});
step 5: find the userid of the user you wish to delete and type :
db.users.remove({_id:"nRXJCC9wTx5x6wSP2"}); // id should be within quotes

Resources