Policies
Overview
Policies in Tarobase are the backbone of your application's security and data integrity. They define the rules for data access, storage, and manipulation within your app. By carefully crafting policies, you can control who can read or write data, enforce data structures, decide whether data is stored on-chain or off-chain, and automate actions based on data changes.
This page provides an in-depth understanding of how policies work in Tarobase and offers best practices for creating secure, scalable policies for your applications. For a quick reference while writing your policies, visit our Policy Cheatsheet.
Understanding Policies
What Are Policies?
A policy in Tarobase is a JSON object that defines rules and configurations for a specific path or set of documents within your application's data structure. Policies allow you to:
- Control Access: Define who can read or write to specific parts of your data
- Enforce Data Structure: Specify the expected fields and their data types
- Manage Storage Location: Decide whether data should be stored on-chain or off-chain
- Automate Actions: Trigger automatic actions (like token transfers) when certain conditions are met
Policy Structure
A typical policy consists of:
- Path Definitions: The location in your data hierarchy that the policy applies to
- Rules: Conditions under which read and write operations are permitted
- Fields: Definitions of data fields and their types (required for on-chain storage)
- On-Chain Storage Flag: Indicates whether the data should be stored on-chain
- Hooks: Actions that occur after a successful write operation
- Plugins: Extensions that provide additional functionalities within policies
Basic Policy Structure Example:
{
"path": {
"rules": {
"read": "<condition>",
"write": "<condition>"
},
"fields": {
"requiredField": "String",
"optionalField": "UInt?"
},
"onchain": true | false,
"hooks": {
"onchain": {
"create": "<action>",
"update": "<action>",
"delete": "<action>"
}
}
}
}
Writing Effective Policies
Defining Paths with Dynamic Segments
Use dynamic segments (prefixed with $
) to create flexible and reusable policies that apply to multiple documents or collections.
Example:
{
"users/$userId": { ... },
"posts/$postId/comments/$commentId": { ... }
}
In this example, $userId
, $postId
, and $commentId
are placeholders that match any value at that position in the path.
Rules: Controlling Access
Rules determine whether a read or write operation is permitted based on specified conditions.
- Read Rule (
"read"
): Must evaluate to true for a read operation to succeed - Write Rule (
"write"
): Must evaluate to true for a write operation to succeed - Create Rule (
"create"
): Specific rule for document creation - Update Rule (
"update"
): Specific rule for document updates - Delete Rule (
"delete"
): Specific rule for document deletion
Special Variables Available in Rules:
@user.address
: The wallet address of the user making the request@data
: The current state of the document before the write operation@newData
: The proposed state of the document after the write operationget(path)
: Get data at a specific pathgetAfter(path)
: Get data at a specific path after all pending changes in the batch are applied
Operators:
Logical Operators:
&&
(and)||
(or)!
(not)
Comparison Operators:
==
,!=
<
,>
<=
,>=
Null Checks:
variable != null
variable == null
Example Rule:
"create": "@newData.admin == @user.address"
This rule allows a create operation only if the admin field in the new state matches the user's wallet address.
Fields: Enforcing Data Structure
When storing data on-chain, you must define the fields and their data types explicitly.
Supported Data Types:
String
Address
Int
UInt
Bool
Add ?
after the type to make it optional, e.g., "UInt?"
.
Example Fields Definition:
"fields": {
"createdBy": "Address",
"text": "String",
"amount": "UInt",
"maxMembers": "UInt?"
}
On-Chain Storage
To specify that data should be stored on-chain, set the "onchain"
property to true
. This will deploy or update a smart contract on the blockchain to handle the data storage.
Example:
"onchain": true
Important Notes
- All fields to be stored on-chain must be defined in the "fields" section
- Optional fields can be marked with a
?
suffix - On-chain storage is ideal for data that requires immutability and transparency
Hooks: Automating Actions
Hooks allow you to automate actions after a write operation passes the rules.
Syntax
"hooks": {
"onchain": {
"create": "<action>",
"update": "<action>",
"delete": "<action>"
}
}
Example Hook:
"hooks": {
"onchain": {
"create": "@TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @user.address, get(/chatrooms/$roomId).feeReceiverAddress, get(/chatrooms/$roomId).feeAmount)"
}
}
This hook transfers USDC tokens from the user's wallet to a specified recipient when a new document is created.
Plugins: Extending Functionality
Plugins provide additional functionality that can be used within policies. The @TokenPlugin
is a built-in plugin for token-related operations.
TokenPlugin Actions:
@TokenPlugin.transferWholeTokens(tokenAddress, fromAddress, toAddress, amount)
@TokenPlugin.transfer(tokenAddress, fromAddress, toAddress, amountInWei)
Example Usage:
"hooks": {
"onchain": {
"create": "@TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @user.address, recipientAddress, amount)"
}
}
Best Practices for Secure and Scalable Policies
Principle of Least Privilege
- Start with the most restrictive permissions and only grant additional access as needed
- This minimizes potential security risks
Use Dynamic Segments Wisely
- Leverage dynamic segments (
$variableName
) to generalize policies for similar data structures - Ensure that conditions within rules properly handle these dynamic segments
Explicitly Define Fields for On-Chain Data
- Always specify fields and their data types when using on-chain storage
- Mark optional fields with a
?
suffix - This ensures that your smart contract accurately reflects your data schema
Validate Data Integrity
- Use
@data
and@newData
to enforce data integrity rules - For example, prevent fields from being modified after creation or ensure certain fields are not null
Consider On-Chain Costs
- Be mindful of the costs associated with on-chain storage and operations
- Only store essential data on-chain and keep hooks efficient
Test Policies Thoroughly
- Use Tarobase's simulation tools to test policies before deploying
- Check that your policies enforce the intended access controls and data validations
Document Your Policies
- Maintain clear documentation or comments within your policies
- This aids in future maintenance and helps team members understand policy logic
Limit Hooks to Essential Actions
- Overusing hooks can lead to complex policies and potential performance issues
- Only include hooks that are necessary for your application's functionality
Use Batch Operations Wisely
- Leverage
getAfter()
for referencing pending changes in batch operations - Ensure your rules properly validate the entire batch of changes