Files
gh-whamp-whamp-claude-tools…/skills/pocketbase/references/security_rules.md
2025-11-30 09:06:02 +08:00

13 KiB

PocketBase Security Rules

Comprehensive guide to implementing security and access control in PocketBase collections.

Table of Contents

  1. Understanding Security Rules
  2. Rule Types
  3. Common Patterns
  4. Role-Based Access Control
  5. Field-Level Security
  6. File Security
  7. Examples by Use Case
  8. Testing Rules

Understanding Security Rules

PocketBase uses four types of security rules per collection:

  1. listRule - Who can view the list of records
  2. viewRule - Who can view individual records
  3. createRule - Who can create new records
  4. updateRule - Who can update existing records
  5. deleteRule - Who can delete records

Rule Context Variables

  • @request.auth.id - ID of the authenticated user making the request
  • @request.auth - The full authenticated user record
  • @request.method - HTTP method (GET, POST, PATCH, DELETE)
  • id - ID of the current record being accessed

Common Comparison Operators

  • = - Equals
  • != - Not equals
  • <, <=, >, >= - Numeric comparisons
  • ~ - Contains/in (for arrays and relations)
  • !~ - Not contains
  • ~ with regex - Pattern matching (e.g., name ~ "test")

Rule Types

Public Access (No Authentication)

// Anyone can read, only authenticated can write
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"

Authenticated Users Only

// Only authenticated users can access
listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id != ''"
deleteRule: "@request.auth.id != ''"

Owner-Based Access Control

// Only the record owner can modify
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id"
deleteRule: "user_id = @request.auth.id"

Admin-Only Access

// Only admins can access (requires user role field)
listRule: "@request.auth.role = 'admin'"
viewRule: "@request.auth.role = 'admin'"
createRule: "@request.auth.role = 'admin'"
updateRule: "@request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'admin'"

Read Public, Write Owner

// Public can read, only owner can write
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

Common Patterns

Pattern 1: User Profile (User Can Only Modify Their Own)

listRule: "@request.auth.id = user_id"
viewRule: "@request.auth.id = user_id"
createRule: "@request.auth.id = user_id"
updateRule: "@request.auth.id = user_id"
deleteRule: "@request.auth.id = user_id"

// Where user_id is a field that stores the record owner's ID

Pattern 2: Posts/Articles (Public Read, Owner Write)

listRule: "status = 'published'"
viewRule: "status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

Pattern 3: Comments (Nested Under Parent)

listRule: "post.status = 'published'"
viewRule: "post.status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

// Assuming 'post' is a relation field

Pattern 4: Team Projects (Team Members Only)

listRule: "@request.auth.id ~ members"
viewRule: "@request.auth.id ~ members"
createRule: "@request.auth.id ~ members"
updateRule: "creator = @request.auth.id || @request.auth.id ~ members"
deleteRule: "creator = @request.auth.id"

// Where 'members' is an array of user IDs

Pattern 5: E-commerce Orders

// Customers can see their own orders
listRule: "customer = @request.auth.id"
viewRule: "customer = @request.auth.id"
createRule: "customer = @request.auth.id"
updateRule: "@request.auth.id != ''"  // Only admins/staff can update
deleteRule: "@request.auth.id != ''"  // Only admins can delete

Role-Based Access Control

Basic RBAC with User Roles

First, add a role field to your users collection:

{
  "id": "role",
  "name": "role",
  "type": "select",
  "required": true,
  "options": {
    "values": ["user", "moderator", "admin"]
  }
}

Now use the role in security rules:

Regular Collection (User/Moderator/Admin)

listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id || @request.auth.role = 'moderator' || @request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'moderator' || @request.auth.role = 'admin'"

Admin-Only Collection

listRule: "@request.auth.role = 'admin'"
viewRule: "@request.auth.role = 'admin'"
createRule: "@request.auth.role = 'admin'"
updateRule: "@request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'admin'"

Moderator+ Collection

listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id || @request.auth.role != 'user'"
deleteRule: "@request.auth.role != 'user'"

Advanced RBAC: Permission Matrix

// Roles: user, author, editor, admin
// Permissions: read, write, delete, publish

// For 'posts' collection:
createRule: "@request.auth.role = 'author' || @request.auth.role = 'editor' || @request.auth.role = 'admin'"
updateRule: "author = @request.auth.id || @request.auth.role = 'editor' || @request.auth.role = 'admin'"
deleteRule: "author = @request.auth.id || @request.auth.role = 'admin'"
updateRule: "status = 'draft' && (author = @request.auth.id || @request.auth.role = 'editor' || @request.auth.role = 'admin')"

// Only editors and admins can publish
updateRule: "if(status != 'published'){ author = @request.auth.id } else { @request.auth.role = 'editor' || @request.auth.role = 'admin' }"

Field-Level Security

Restrict access to specific fields using the options parameter in the schema.

Read-Only Fields

{
  "id": "created_by",
  "name": "created_by",
  "type": "relation",
  "required": true,
  "options": {
    "collectionId": "users",
    "cascadeDelete": false,
    "maxSelect": 1
  },
  "presentable": false  // Don't show in public APIs
}

Admin-Only Fields

{
  "id": "internal_notes",
  "name": "internal_notes",
  "type": "text",
  "options": {},
  "onlyAllow": ["@request.auth.role = 'admin'"]  // Only admins can set
}

User-Owned Fields

{
  "id": "private_data",
  "name": "private_data",
  "type": "json",
  "options": {},
  "onlyAllow": ["user_id = @request.auth.id || @request.auth.role = 'admin'"]
}

File Security

Control who can upload, view, and delete files.

Private Files (Owner Only)

// User avatars - only owner can upload
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id = user_id"

// File access rule (in file field options):
// This controls who can access the file URL
"options": {
  "maxSelect": 1,
  "maxSize": 5242880,
  "thumbs": ["100x100", "300x300"],
  "filterSelect": "user_id = @request.auth.id"
}

Public Files (Viewable by All)

// Blog post images - authenticated users can upload
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"

// File is publicly viewable

Members-Only Files

// Team documents - only team members can access
createRule: "@request.auth.id ~ team_members"
updateRule: "@request.auth.id ~ team_members"

// File access filter
"filterSelect": "@request.auth.id ~ team_members"

Examples by Use Case

Blog Platform

// Posts
listRule: "status = 'published'"
viewRule: "status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

// Comments (must be authenticated)
listRule: "is_approved = true"
viewRule: "is_approved = true"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

Social Network

// Posts (public)
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"

// Private Messages
listRule: "sender = @request.auth.id || receiver = @request.auth.id"
viewRule: "sender = @request.auth.id || receiver = @request.auth.id"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id != ''"  // Only mark as read
deleteRule: "sender = @request.auth.id || receiver = @request.auth.id"

SaaS Application

// Workspaces
listRule: "@request.auth.id ~ members"
viewRule: "@request.auth.id ~ members"
createRule: "@request.auth.id ~ members"
updateRule: "@request.auth.id ~ owners"
deleteRule: "@request.auth.id ~ owners"

// Workspace Records
listRule: "workspace.members.id ?= @request.auth.id"
viewRule: "workspace.members.id ?= @request.auth.id"
createRule: "workspace.members.id ?= @request.auth.id"
updateRule: "workspace.members.id ?= @request.auth.id"
deleteRule: "workspace.owners.id ?= @request.auth.id"

E-commerce

// Products
listRule: "is_active = true"
viewRule: "is_active = true"
createRule: "@request.auth.id != ''"  // Staff only in real app
updateRule: "@request.auth.id != ''"  // Staff only
deleteRule: "@request.auth.id != ''"  // Staff only

// Orders
listRule: "customer = @request.auth.id"
viewRule: "customer = @request.auth.id"
createRule: "customer = @request.auth.id"
updateRule: "@request.auth.id != ''"  // Staff can update status
deleteRule: "@request.auth.id != ''"  // Staff only

Project Management

// Projects
listRule: "@request.auth.id ~ members || @request.auth.id = owner"
viewRule: "@request.auth.id ~ members || @request.auth.id = owner"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id = owner || @request.auth.id ~ managers"
deleteRule: "@request.auth.id = owner"

// Tasks
listRule: "project.members.id ?= @request.auth.id"
viewRule: "project.members.id ?= @request.auth.id"
createRule: "project.members.id ?= @request.auth.id"
updateRule: "assignee = @request.auth.id || @request.auth.id ~ project.managers"
deleteRule: "@request.auth.id ~ project.managers"

Testing Rules

Manual Testing

  1. Create a test user account
  2. Create records with different ownership
  3. Test each rule (list, view, create, update, delete)
  4. Test with different user roles
  5. Test edge cases (null values, missing relations, etc.)

Programmatic Testing

// Test with authenticated user
const pb = new PocketBase('http://127.0.0.1:8090')

// Login
await pb.collection('users').authWithPassword('test@example.com', 'password')

try {
  // Try to create record
  const record = await pb.collection('posts').create({
    title: 'Test',
    content: 'Content'
  })
  console.log('✓ Create successful')
} catch (e) {
  console.log('✗ Create failed:', e.data)
}

try {
  // Try to get list
  const records = await pb.collection('posts').getList(1, 50)
  console.log('✓ List successful:', records.items.length, 'records')
} catch (e) {
  console.log('✗ List failed:', e.message)
}

Common Pitfalls

  1. Forgetting != '' check

    // Wrong - allows anonymous users
    createRule: "user_id = user_id"
    
    // Correct - requires authentication
    createRule: "@request.auth.id != '' && user_id = @request.auth.id"
    
  2. Incorrect relation syntax

    // Wrong
    listRule: "user.id = @request.auth.id"
    
    // Correct
    listRule: "user = @request.auth.id"
    
  3. Not handling null values

    // If field can be null, add explicit check
    listRule: "status != null && status = 'published'"
    
  4. Over-restrictive rules

    // This prevents admins from accessing
    updateRule: "author = @request.auth.id"
    
    // Better - allows admin override
    updateRule: "author = @request.auth.id || @request.auth.role = 'admin'"
    

Best Practices

  1. Use the principle of least privilege - Start restrictive, add permissions as needed
  2. Test with multiple user roles - Don't just test with admin users
  3. Document your rules - Add comments explaining complex rules
  4. Use consistent naming - Name fields clearly (e.g., author instead of user)
  5. Validate on the client - Don't rely solely on server-side validation
  6. Use indexes - Add database indexes for fields used in rules
  7. Monitor access - Log security events and failed attempts
  8. Regular audits - Review rules periodically for security issues

Security Checklist

  • All sensitive collections require authentication
  • Users can only access their own data
  • Admin-only collections are properly protected
  • File uploads have size and type restrictions
  • Delete operations are properly restricted
  • Rules handle edge cases (null values, empty arrays)
  • Public data is explicitly marked as public
  • Internal data is never exposed via rules
  • Rules are tested with multiple user types
  • Complex rules are documented and reviewed