Skip to main content

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 operation
  • get(path): Get data at a specific path
  • getAfter(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