Firebase rules - read only value which was in query - javascript

I am storing usernames separately in validation section in the db. There are only usernames, so that when user is registering, he can enter the username and see if the username is available before he registers.
The rules for that are now ".read": true , so that basically allows anyone to get easy access to all usernames in the db. I would want to prevent that, but i am not sure how to do that, since i cant use auth != null rule, because user was not created yet.
My only idea was to allow reading only the username which is requested, ie something
firebase.database().ref('Validations/Usernames')
.orderByChild('name')
.equalTo(username)
.once('value')
.then(snapshot => ....
and then using rule like this:
"$name": {
".read": "query.equalTo == $name"
}
ie allow the read only for username which was checked from the client. Now, this fails because for all others its checking with orderByChild it does not have access to those.
Is there some similar way to allow to read only the value, which was requested to read from the client? Or perhaps there is some completely other way i should approach username validation before he created an account?

If you only want to allow the user to check if the username they typed is already claimed, they don't read access on the entire Validations/Usernames node. Instead they only need read access on a specific child under that.
To allow this, make sure that you use the user names as the keys under Validations/Usernames, so something like:
"Validations": {
"Usernames": {
"CosmicSeizure": "uidOfCosmicSeizure",
"puf": "uidOfPuf"
}
}
With the above your rules can be:
"Validations": {
"Usernames": {
"$name": {
".read": true
}
}
}
And then you can check if a username exists with:
firebase.database().ref('Validations/Usernames')
.child(username)
.once('value')
.then(snapshot => ....
And then you check if snapshot.exists().

Related

Firebase rule for checking user is in channel is not working with .exists or .val

Hello i am trying to create a firebase rule to check weather or not the user is in the message channel that they are sending a message to. Here is my firebase database structure
and my rule is
"messages": {
"$room_id": {
".read": "root.child('channels').child($room_id).child('joined').child(auth.uid).val() === true",
".validate": "root.child('channels').child($room_id).exists()",
the .validate rule works, while the .read does not. It works if i delete the entry from joined or not, I have also tried replacing .val() === true with .exists(). I need it to only work if the uid is in joined and set to true.
This is the JS code i am using
const itemsRef = firebase.database().ref('messages').child(this.selectedChannel);
itemsRef.on('value', snapshot => {
let data = snapshot.val();
this.messages = Object.entries((data || {})).map(m => {return {id: m[0], user: m[1].user, message: m[1].message}});
})
In cases like this, usually, the approach used is to set values for variables - for chat rooms applications, usually the variables used are user, timestamp of access and room_id - so you can validate if they are not empty and add the message altogheter. In your case, you will be using a third different variable to the timestamp, when validating.
Considering that, I believe a good configuration for your verification would be the following - this code is untested and you will need to modify more it for your usage, but I believe it's a good starting point, as a basic model for you.
".read": "root.child('members/'+$room_id+'/'+auth.uid+joined).exists() && !data.exists() && newData.exists()",
// the object to write must have a name, message, and be in the room
".validate": "newData.hasChildren(['user', 'message', 'joined'])",
As in the above code sample, the validation will be done once it has all the values you want to verify.
A example of this code in full version can be access in the official documentation.
Another example of a simple chat room with Firebase that I would recommend you to access, it's in this following repository: katowulf/gist:4741111
Let me know if the information helped you!

#AskFirebase: Is there a built-in method for securely checking for and existing User e-mail in Firebase?

Is there a built-in method to check for the existence of a user without exposing your 'user' collection to unauthenticated users? (Read-only access will still expose the entire collection of users.) Below is the traditional solution:
usersRef.child('users').child(userId).once('value', function(snapshot) {
var exists = (snapshot.val() !== null);
});
My use-case is a registration flow with multiple steps. I want to check for the existence of a user before the candidate arrives at the final step.
My initial thought on how to resolve this issue is to create an Admin service that can return the result while limiting queries from individual users (similar to how the built-in sign-in logic restricts multiple invalid retries).
I am aware of the "auth/email-already-in-use" error code returned from createUserWithEmailAndPassword. However, the check needs to be performed before the password is provided.
Just making sure it doesn't exist before I start hammering out the service ;)
To be able to check whether a certain node exists, you must be able to read that node. If we look specifically at your code:
usersRef.child('users').child(userId).once('value', ...
This code requires that the user has read access to /users/$userId. It does not require that they have access to all of /users.
Same if you'd create a top-level emails node, where you keep the emails that are already in use. To check if a specific email is already in use, all the user needs is access to /emails/$email, they don't need to have access to /emails.
So:
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid !== null"
}
},
"emails": {
"$email": {
".read": "auth.uid !== null"
}
}
}
}
The big deal about these rules is that anyone trying to read from /users or /emails will be rejected, so that prevents them from getting access to a list of all users or emails. But given an individual UID or email address, they can check whether that is already claimed or not.
Note that hammering out your own service is fine too. But if the basic use-case is to securely check whether a specific UID/email is already claimed, you can probably get by with just the database.

firebase json db security rule using fb authentication

I am using ionic 3 angular for my mobile app and fb native cordova plugin is used to login.
The firebase db security documentation uses the syntax like
{
"rules":{
"users":{
"$user_id":{
".write":"$user_id === auth.id"
}
}
}
}
the fb authentication looks like below in my app
doLogin(){
if (this.platform.is('cordova')) {
return this.fb.login(['email', 'public_profile']).then(res => {
const facebookCredential = firebase.auth.FacebookAuthProvider.credential(res.authResponse.accessToken);
firebase.auth().signInWithCredential(facebookCredential);
this.navCtrl.setRoot(TabsPage);
})
}
}
my question is the auth firebase variable is taken care with above code or i need to something extra for auth to get required uid etc. ?
The auth firebase variable is token care of in theory: assuming you have the Facebook sign-in method enabled already. However, the database rules you are showing are not necessarily related.
These rules (the same as above):
"rules": {
"users":{
"$variable":{ ".write": "$variable=== auth.uid" }
Dictate that users can only write to a child node with the same uid. I changed $user_id to $variable to highlight that the $ simply denotes a variable that represents the child node's name.
(I should probably mention that it should be auth.uid not auth.id)
This is used to save user specific data. So, when they signup you could have a function that says
firebase.database().ref('users').child(firebase.auth().currentUser.uid).update(<your custom data here>);
*please note how the child of users is the "firebase.auth().currentUser.uid" which can optionally be retrieved from the firebase.auth().signInWithCredential() promise.
Sorry if the explanation was more that necessary. In short. the uid is always present with firebase.auth().currentUser.uid after login and that uid is what the database rules are referring to in auth.uid and last, the auth/uid/etc is pretty much 100% taken care of with firebase.

Cloud Functions for Firebase - Knowing which user has written the data in onWrite

I'm trying to make a simple messaging app. In the process, I want to use firebase's cloud function feature for functions that should not be carried out client-side for the sake of security.
Many of the functions I want to be called whenever the user wants. The way I'm trying to do it now is by having clients push little 'function request' objects under a single parent so that the onWrite function will fire off and I can parse the request object to execute the function (The reason I'm not using http functions is because I want some way to securely know which user has made the request).
The problem is I can't find a way in firebase's documentation to know which user wrote the data.
in index.js I have
exports.requestFunction = functions.database.ref('/function-
requests/{pushId}')
.onWrite(event => {
// Parse event.data.val() for things like the function name and its
// parameters. Then actually call the function.
// Ideally I would also be able to parse event somehow to find the user
// who saved the data.
});
Please note that I have considered including the user id as a parameter, but that's too unsafe as any user can pretend to be another user by giving a different uid.
You could write a Firebase Database Rule that only allows the user to write their own user id in a specific field, then pass it up as a parameter.
ex.
"user": {
".validate": "newData.val() === auth.uid"
},
You have two choices. First, you can make the UID part of the database write path, the use a security rule to ensure that only an authenticated rule can write to that path. Consider the Database trigger wildcard path "/commands/{uid}/{pushid}" along with the following security rule:
"commands": {
"$uid": {
".read": false,
".write": "$uid === auth.uid"
}
}
Only an authenticated user can effectively write to their own area of the database under /commands/{uid}. You can grab the matched UID wildcard in the function like this: const uid = event.params.uid
Consider watching my talk at Google I/O 2017 where I use this strategy to build a turn-based game.
You can also ask the database trigger event the UID that generated the write, as described in this other question, though it is not documented (or supported).
First you can read more about security rules in here, (different with other links "security/database")
Make sure in the data that you want to save to the database contains field for example "user_id", this field will be checked with the auth.uid whether it's same or not.
make your database rule like this :
{
"rules": {
".read": true,
"function-requests": {
"$pushId" :{
".write": "newData.child('user_id').val() == auth.uid"
}
}
}
}
After that you can proceed your cloud function without worries

In a noSQL setting, what is an efficient way to search for child nodes?

In my noSQL database, I have a tree users which looks as follows:
users
/simplelogin:1
/email: a#a.com
/username: albert
/simplelogin:2
/email: b#b.com
/username: benihana
If I now wish to check whether the username benihana is taken, I would need to extract all the data from the tree users and then iterate over the childs child(uid).child(username) to see whether I can find a match.
This seems to me a very slow way as it can take some time to retrieve the data when you have a tree of let's say 10000 users.
What are therefore good practices to structure the data to make a query like the one above more efficient?
One option that I was thinking of is to create another tree usernames and the fill them with all the current usernames; e.g.:
usernames
/benihana
/uid: simplelogin:2
/...
Then looking up the username requires checking whether the node usernames/benihana exists.
However, this requires many functions to be built in like "add/edit/remove username in users" and also in "add/edit/remove username in usernames", which I would like to avoid.
You don't need to get all users to check their user name. Instead you can use a Firebase query to get the user you can looking for:
ref.orderByChild('username').equalTo('albert').on('value', ...
Creating a separate section in your tree for the user names is a common pattern in Firebase (and other NoSQL databases). In your case you're creating a map from the user's name to their uid, which is commonly called an index.
You'll probably want to secure/validate the data being written, so that only simplelogin:1 can write /usernames/albert.
{ rules: {
"usernames": {
"$username": {
// can only write if there is no data in this place yet AND
// the user can only write their own data
".write": "!data.exists() && newData.val() == auth.uid",
}
},
"users": {
"$uid": {
"username": {
// validate that the /username/$username has out $uid associated with it
".validate": "root.child('usernames').child(newData.val()).val() == $uid"
}
}
}
}}
Note that I didn't test the above, so it is possible there are some problems with it. But this approach shows how I would secure this operation in Firebase.

Categories

Resources