Butane is a simplified version of the official Firebase Blaze Compiler
See https://github.com/firebase/bolt
npm install -g butane
Create a rules.yaml
file containing the following code:
.functions:
isAuthed(): auth !== null
createOnly(): next.exists() && !prev.exists()
rules:
chats:
$chat:
.read: true
.write: isAuthed() && createOnly()
Now compile it from the command line:
butane rules.yaml rules.json
# or copy the output to the clipboard
butane rules.yaml | pbcopy
{
"rules": {
"chats": {
"$chat": {
".read": true,
".write": "auth !== null && (newData.exists() && !data.exists())"
}
}
}
}
Security expressions are the strings that go in .write/.read/.validate
values of security rules.
Butane expressions have similar semantics, but shorter syntax.
Some predefined variables have been renamed for clarity:
Old Name | New Name |
---|---|
data | prev |
newData | next |
The expression for selecting a child is now an array-like syntax:
// Old
root.child('users')
// New
root['users']
In the common case that you are selecting a child using a single literal, you can select the child as if it were a property.
root.users
In the new syntax, .val()
is inserted if the expression is next to an operator
or in an array like child selector. You only need to use .val()
if you
are using a method of a value type like .length
, .beginsWith()
, .contains(...)
.
// Old
newData.child('counter').val() === data.child('counter').val() + 1
// New
next.counter === prev.counter + 1
Commonly used expressions are defined in the .functions
map.
.functions:
isLoggedIn(): auth.uid !== null
isUser(user): auth.uid === user
rules:
users:
$user:
.write: isLoggedIn() && isUser($user)
You can then use them anywhere a security expression would be expected.
Butane includes a few predefined functions:
Return an expression that requires snapshot
to equal one of the provided keys
Arguments
An array of possible values
The snapshot to check against
Example
rules:
colors:
#.write: next.val() === 'red' || next.val() === 'blue' || next.val() === 'green'
.write: oneOf(['red', 'blue', 'green'])
shapes:
#.write: root.child('colors').val() === 'red' || root.child('colors').val() === 'blue' || root.child('colors').val() === 'green'
.write: oneOf(['red', 'blue', 'green'], root.colors)
Shorthand version that uses the function arguments as keys
and defaults
to "next" as the snapshot
Example
rules:
colors:
#.write: next.val() === 'red' || next.val() === 'blue' || next.val() === 'green'
.write: oneOf('red', 'blue', 'green')
References to commonly used nodes are defined in the .refs
map.
They can be accessed using the ^
symbol.
rules:
messages:
$message:
.refs:
myMessageRef: prev
title:
#.read: data.parent().child('settings').child('private').val() === false
.read: ^myMessageRef.settings.private === false
meta:
info:
#.read: data.parent().parent().child('settings').child('private').val() === false
.read: ^myMessageRef.settings.private === false
settings:
private:
.validate: next.isBoolean()
Because it's common to reference $wildcard
paths, they will automatically
be inserted as a .ref
with the name of the wildcard (including the $
)
rules:
messages:
$message:
# no need to specify .refs
title:
.read: ^$message.settings.private === false
settings:
private:
.validate: next.isBoolean()
References can also override the provided value:
rules:
messages:
$message:
title:
#.read: data.parent().child('settings').child('private').val() === false
.read: ^$message.settings.private === false
#.write: newData.parent().child('settings').child('private').val() === false
.write: ^$message(next).settings.private === false
settings:
private:
.validate: next.isBoolean()
Convert a string of YAML Butane rules to a JSON string of Firebase rules
Arguments
A valid YAML string
Returns
An JSON string representing the converted rules
Example
import {convert} from 'butane'
const rulesYamlString = `
rules:
.read: true
`
const rulesJsonString = convert(rulesYamlString)
const rulesJson = JSON.parse(rulesJsonString)
Convert a file of YAML Butane rules to JSON Firebase rules and optionally write the file to disk.
Arguments
The path of the input file
The path of the output file
Returns
The converted rules as a JSON object
Example
import {convertFile} from 'butane'
convertFile('./rules.yaml', './rules.json')
Register more complex functions that can't be defined in the .functions
map.
This will override any existing registered functions with the same name.
NOTE: Functions defined in the .functions
map will take precedence over
registered functions with the same name.
Arguments
The name used to reference the function inside a rule expression
A function that should return a Butane expression string
Example
Here is a simplified example of how oneOf()
is implemented:
import {registerFunction} from 'butane'
registerFunction('oneOf', function(...keys) {
return keys.map(key => {
if (typeof key === 'string') key = `'${key}'`
return `next === ${key}`
}).join('||')
})
rules:
colors:
#.write: next.val() === 'red' || next.val() === 'blue' || next.val() === 'green'
.write: oneOf('red', 'blue', 'green')