Reputation: 1287
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):
Upvotes: 4
Views: 869
Reputation: 26246
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:
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.
Upvotes: 2