What does $uid and auth.uid stand for? - firebase

Was going through some articles for understanding Rules & Permissions in Firebase and then came across conditions like this, for write operation :
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
I went with an understanding that $uid stands for Users Push ID and it applies to all dynamic ID's that are generated for Users node.
Then saw this rule else were :
{
"rules": {
"articles": {
"$article": {
"title": {
".write": "auth != null",
".validate": "newData.isString() && newData.val() != ''"
}
}
}
}
}
If $article stands for Push ID for articles node then Push ID for users node also should have been $user. Isn't it? What is the standard naming convention for declaring Push ID, when configuring rules, so that Firebase parses/understands them correctly.
Lastly, what does auth.uid stands for?

Lets start with auth.uid, this stands for the uid of the authenticated user.
Next up are $user and $article, these wildcard paths so they can be anything, not only push id's. Check out the docs for more info.
In your first example $uid is a wildcard for user id's. And with the write rule you check that the authenticated user can only write to his own location so it will be something like this (using names instead of uid's for clarity):
"users" : {
"Henk": {//Only Henk can write here
},
"John": {//Only John can write here
}
}
As for naming of wildcard paths there is no convention as far as i know. Personally i use descriptive names so i know what it is. Always $uid when using the users uid as a path and for the rest something like $objectID for object id's. (These can be push generated or something homebrew)
For the rest I suggest you take some time to read all the docs about security rules.

Related

Firebase Realtime Database Rules: Validation is cascading to child. How to ignore .validate on parent node?

I'm trying to write firebase realtime database rules that firstly, allow a new game to be created if the $gameID has exactly 6 characters AND the $gameID doesn't already exist on the games node. This rule actually validates perfectly when creating a new game.
The problem comes when a player then tries to join that game: the aforementioned validate rule denies them because it requires the game to not exist - but I only want that rule — "$gameID.length === 6 && !data.exists()" — to validate the writes ONLY on games/$gameID, not on games/$gameID/players.
My understanding was that .validate rules don't cascade down, so why is it not allowing the write to games/$gameID/players?
My current database.rules file is:
{
"rules": {
"games": {
"$gameID": {
".read": "auth !== null",
".write": "auth !== null",
".validate": "$gameID.length === 6 && !data.exists()",
"players": {
".read": "auth !== null",
".write": "auth !== null"
}
}
}
}
}
The javascript code for the players write is:
await push(ref(db, `games/${gameID}/players`), playerName)
set simulation denied with .validate rule ($gameID node ABC123 exists in database):
set simulation allowed without .validate rule ($gameID node ABC123 exists in database):
When security rules talk of "cascading rules", this refers to the behaviour where a higher tier ".read" and ".write" rule will override any nested rules (a higher tier rule succeeding will override a nested rule failing). This is in contrast to ".validate" rules where every rule in the chain must evaluate to true for a write to succeed (a higher tier rule succeeding will not override a nested rule failing).
From your comments your data looks like this:
{
"games": {
"abcdef": {
"players": {
"-MoSHoV6kC4cV_jCdssT": "player A",
"-MoSI4begYNOBYVmiVBi": "not player A",
"-MoSI5nVWYD5VnSaaWP0": "tom"
}
},
/* more rooms */
}
}
The problem with your current rules is that you must allow the room to be created, and you must also allow players to join that room. As you've deduced, your rules permit the first action, but block the second.
To fix this, you'll need to make a slight tweak to your client-based code so that you can differentiate the "I am creating this room" and the "I am joining this room" actions. With the current structure, I do not see how this is possible as the two actions don't have anything different between them - they both look like:
SET /games/abcdef/players/-MoSHoV6kC4cV_jCdssT = "some user"
SET /games/abcdef/players/-MoSI4begYNOBYVmiVBi = "not player A"
Which of these requests should be allowed to create the room?
To help this, we need to introduce a way to tell the database that "I am creating this room". We can do this by adding a new "owner" property which allows us to apply the following rules:
If the owner doesn't exist, allow room creation.
If the owner already exists, deny room creation.
If the room is created without an owner, deny room creation.
If the room is created with an invalid ID, deny room creation.
Players may only be added/removed by the owner.
Because your users are signed in, you should use their user ID instead of a push ID to help with keeping rooms secure later on, although the below setup will still work even if you don't switch.
{
"games": {
"abcdef": {
"players": {
"BzzmNyT7hlMz3ElRLMYS0jaGKgE3": "player A",
"BSfHksARFnYco5LenfxevOpnwe63": "not player A",
"vDnPE4DqE3cz1IYHwXQvDFw3W7r2": "tom"
},
"owner": "BzzmNyT7hlMz3ElRLMYS0jaGKgE3"
},
/* more rooms */
}
}
with the rules:
{
"rules": {
"games": {
"$gameID": {
// any authenticated user may read this entire room's data
// future: restrict to only members?
".read": "auth !== null",
// a room must have an ID of 6 characters
// and must have an owner assigned to it
".validate": "$gameID.length === 6 && newData.hasChild('owner')",
"players": {
// use $pushId here if keeping your "push" method instead of switching to user IDs
"$playerId": {
// only the owner may add/remove players
".write": "auth !== null && newData.parent().child('owner').val() == auth.uid",
// as long as the value is a string
".validate": "newData.isString()"
}
},
"owner": {
// an owner prop can be created, but not edited/deleted
".write": "auth !== null && !data.exists()"
// an owner prop must be the writer's user ID
".validate": "newData.isString() && newData.val() === auth.uid"
}
}
}
}
}
Note: The above rules to nothing to asset that the owner of a room is also a player.

firebase realtime database security rules for non user data

So, I have an app where users can order the cakes and do other profile management, the rules looks like below:
{
"rules": {
"cakes" : {
".read": true,
".write": false
},
"users": {
"$user_id": {
".read": "auth != null && $user_id === auth.uid",
".write": "auth != null && $user_id === auth.uid"
}
}
}
}
Simply, they mean any one can read the cakes node (but no one can write). And an authenticated user can see or write to his on node.
This is good so far.
Now, my requirement is: When someone places an order through the app then i need to store it to firebase db in a top level node (lets say it orders). The question is what kind of security would be placed on orders node?
In functional definition: The app should be able to create new orders as user checks out, no one except seller should be able to read those orders, only seller should be able to have update access to a order.
If you want everybody to be able to write orders, and nobody able to read, the rules are simply the inverse of the ones for cakes:
"rules": {
"orders" : {
".read": false,
"$orderId": {
".write": true
}
},
With this anyone can push new data under /orders. And as long as you use push() to generate the new keys, you're guaranteed that they'll never conflict.
With these rules only a system-level admin can read the data. That is: you can read it in the Firebase Console, or someone can read it if they use the Admin SDK.
You might want to open it up for reading a bit more, e.g. by having the concept of an application-level administrator. Say that your UID is uidOfVik, you could model a list of admins in your database:
admins: {
uidOfVik: true
}
Now you can allow only admins to read the orders with:
"rules": {
"orders" : {
".read": "root.child('admins').child(auth.uid).exists()",
"$orderId": {
".write": true
}
},

Search among the documents the user has permission to read in firebase

I have a collection of documents. Each document has child uid which is a reference to the owner. Exemplary document:
{
"uid": "slfjs092320i3jf023jf",
"content": "Example"
}
I want to store them under /documents collection and allow only users with matching uid to store and retrieve them. I created the following rules:
{
"rules": {
"documents": {
"$documentId": {
".read": "data.child('uid').val() === auth.uid",
".write": "newData.exists() && newData.child('uid').val() === auth.uid || data.exists() && data.child('uid').val() === auth.uid",
".indexOn": ["uid"]
}
}
}
}
Unfortunatelly when I want to retrieve user's documents I receive permission denied. To retrieve documents I use:
export default getUserDocuments = () => {
const userUid = firebase.auth().currentUser.uid;
return firebase.database()
.ref('/documents')
.orderByChild('uid')
.equalTo(userUid)
.once('value');
};
How do I query user's documents? I guess the permission denied is related to read restriction required to perform the query.
Firebase Database enforced read permission when you attach a listener. To be able to read from /documents, you will need to have read permission on /documents. Since that isn't the case with your security rules, the listener is rejected.
This may be counter-intuitive initially, and means that security rules cannot be used to filter data in the way you are trying. This is known as rules are not filters in the documentation, and has been the topic of many previous questions. I recommend you check out some of those, and report back if you have more questions.
Have a look on query based rules.
Rules are not filters, as Frank said, and he is right, but you can make some queries to works and achieve something similar to what you were looking for with this kind of stuff :
baskets": {
".read": "auth.uid != null &&
query.orderByChild == 'owner' &&
query.equalTo == auth.uid" // restrict basket access to owner of basket
}
And then, this will work :
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb)
And this, won't :
db.ref("baskets").on("value", cb)

Managing team permissions and allowing two users from the same team to access a node

I allow login with Google to my Firebase web app.
I control access to the database by the auth.uid:
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid === $uid",
".write":"auth.uid !== null",
"images": {
".read": "auth.uid === $uid",
".write":"auth.uid === $uid",
},
...
I want to enable other users in the user's team to access his/her images. I went through the docs but couldn't find a way to accomplish that. Any ideas?
Security rules are able to read data from other keys, so it's possible to construct rules that are based upon the existence of a key (i.e. membership of a team).
What's below is a small fragment of the Bolt rules that I've used for a Firebase database:
path /teams/{$teamKey}/members/{$userId} is Boolean {
...
}
path /users/{$userId}/shares/{$teamKey} {
...
read() { root.teams[$teamKey].members[auth.uid] !== null }
}
The JSON would look something like this:
...
"users": {
"$userId": {
...
"shares": {
"$teamKey": {
...
".read": "root.child('teams').child($teamKey).child('members').child(auth.uid).val() != null",
...
Hopefully, that will make some sense. Basically, there is a key for a team and it contains user ids (with boolean values). And read access to shared information under a user's key is granted to other team members by verifying their membership - that is, by checking for the existence of a user id key under the team key. Essentially, you store the data that drives the security rules in the database itself.
You don't have to use Bolt, but I find it much easier to manage than the JSON representation. The Bolt language documentation contains information on the RuleDataSnapshot Methods for both the Bolt definitions and the JSON definitions.

How to set firebase rules when I use user email as the unique ID?

I'm working on an app using Ionic Framework and Firebase. I have the following data structure on Firebase:
Users {
mary#fb,com: {
Group: {
group123: {
Contacts: {email1#gmail.com, email2#gmail.com, etc. }
}
group456: {
Contacts: {email3#gmail.com, email4#gmail.com, etc. }
}
}
}
leo#fb.com: {}
wayne#fb.com: {}
etc.
}
Users on the app can create groups and invite their friends to a group.
I'm trying to figure out how to give "email1#gmail.com", "email2#gmail.com" etc. access to the path Users/mary#fb,com/Group/group123 using Firebase rules. I'm also having trouble giving mary#fb,com permissions to read and write. How do I use rules like below for using a custom Unique ID like the the User's email?
{
"rules": {
"Users": {
"$user_id": {
".read": "$user_id === auth.uid",
".write": "$user_id === auth.uid"
}
}
}
}
Circumventing use of the user's auth uid as the unique identifier should be discouraged and probably is only going to make you sad. I'd rethink this approach and encourage others not to follow suit.
Assuming you can't avoid this, then the following will be necessary:
implement your own auth schema
sign your own tokens
include email as part of the token data or, depending on your use case (it helps a great deal to share this in the question, see XY Problem) maybe just use an escaped email as the uid
refer to auth.email in place of auth.uid in your security rules
Thus, in a server/node/etc script:
// after some auth process to verify
// the user and obtain the email
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator("<YOUR_FIREBASE_SECRET>");
var token = tokenGenerator.createToken({uid: uniqueIdOrMaybeEscapedEmail, email: escapedEmailAddress});
And in your rules:
".read": "$user_id === auth.email",

Resources