Skip to main content
SnackBase provides a comprehensive security model with role-based access control, field-level permissions, and a powerful rule engine. This guide explains the security architecture, authorization flows, and best practices.

Overview

SnackBase security operates on multiple layers to ensure data protection:
LayerPurposeMechanism
AuthenticationVerify user identityJWT tokens, Argon2id password hashing
Account IsolationSeparate tenant dataRow-level filtering via account_id
AuthorizationControl user actionsRBAC + Permission system
Field-Level SecurityHide sensitive dataField-level access control
Audit LoggingTrack all actionsImmutable audit logs (coming soon)

Authentication vs Authorization

Understanding the distinction is critical:
AspectAuthenticationAuthorization
QuestionWho are you?What can you do?
MechanismJWT tokens, passwordsRoles, permissions, rules
TimingOnce per sessionEvery request
Failure Result401 Unauthorized403 Forbidden

Example Scenario

Authentication (Who are you?):
├── User provides credentials
├── System verifies identity
└── Result: "You are [email protected]"

Authorization (What can you do?):
├── User requests DELETE /api/v1/posts/123
├── System checks permissions
├── User has "editor" role
├── Editor role does NOT have "delete" permission
└── Result: 403 Forbidden - "You cannot delete posts"

Role-Based Access Control (RBAC)

SnackBase uses RBAC as the foundation of authorization.

RBAC Hierarchy

Account (AB1001)

├── Users
│   ├── [email protected]
│   ├── [email protected]
│   └── [email protected]

├── Roles
│   ├── admin
│   │   └── Permissions: All operations on all collections
│   ├── editor
│   │   └── Permissions: Create, Read, Update on posts only
│   └── viewer
│       └── Permissions: Read on posts only

└── Role Assignments
    ├── [email protected] → admin
    ├── [email protected] → editor
    └── [email protected] → viewer

Default Roles

RoleDescriptionTypical Permissions
adminFull administrative accessAll operations on all collections
editorContent creator/managerCreate, Read, Update on specific collections
viewerRead-only accessRead on specific collections

Custom Roles

You can create custom roles for any purpose:
{
  "name": "moderator",
  "description": "Can moderate user-generated content",
  "permissions": [
    {
      "collection": "comments",
      "create": false,
      "read": true,
      "update": true,
      "delete": true
    },
    {
      "collection": "users",
      "create": false,
      "read": true,
      "update": false,
      "delete": false
    }
  ]
}

Permission System

Permissions define what operations a user can perform on which collections.

Permission Matrix

For a role with permissions:
CollectionCreateReadUpdateDelete
posts
comments
users

Permission Structure

{
  "id": "perm_abc123",
  "role_id": "role_editor",
  "collection": "posts",
  "create": true,
  "read": true,
  "update": true,
  "delete": false,
  "fields": ["title", "content", "status"],
  "rules": {
    "create": "@has_role('editor')",
    "update": "@owns_record() or @has_role('admin')"
  }
}

Wildcard Collections

Use * to grant permissions on all collections:
{
  "role": "admin",
  "collection": "*",
  "create": true,
  "read": true,
  "update": true,
  "delete": true
}
This grants admin full access to ALL collections, including future ones.

Permission Caching

Permissions are cached for 5 minutes to improve performance:
┌──────────────────┐
│ First Request    │
│ Check permissions│
│ from database    │
└────────┬─────────┘


┌──────────────────┐
│ Cache for 5 min  │
│ Subsequent       │
│ requests use     │
│ cached perms     │
└────────┬─────────┘


┌──────────────────┐
│ After 5 min or   │
│ permission change│
│ Cache invalidated│
└──────────────────┘

Rule Engine

SnackBase includes a powerful rule engine for fine-grained access control.

Rule Syntax

Rules use a custom DSL (Domain Specific Language):
# Simple comparisons
user.id == "user_abc123"
user.email == "[email protected]"

# Role checks
@has_role("admin")
@has_any_role(["admin", "moderator"])

# Record ownership
@owns_record()

# Field comparisons
status in ["draft", "published"]
priority >= 3

# Logical operators
@has_role("admin") or @owns_record()
@has_role("editor") and status == "draft"
not status == "archived"

# Complex expressions
(@has_role("admin") or @owns_record()) and not status == "locked"

Built-in Functions

FunctionDescriptionExample
@has_role(role)User has specific role@has_role("admin")
@has_any_role([roles])User has any of these roles@has_any_role(["admin", "moderator"])
@owns_record()User created this record@owns_record()
@is_superadmin()User is superadmin@is_superadmin()

Rule Evaluation Context

Rules have access to:
VariableDescriptionExample
userCurrent user objectuser.id, user.email
recordRecord being accessedrecord.created_by, record.status
contextRequest contextcontext.account_id

Permission Rules Example

{
  "collection": "posts",
  "update": true,
  "rules": {
    "update": "(@owns_record() and status in ['draft', 'pending']) or @has_role('admin')"
  }
}
Translation: Users can update posts if:
  • They created the post AND status is draft/pending, OR
  • They have admin role

Field-Level Security

SnackBase supports field-level access control to hide sensitive data.

Field Visibility

Restrict which fields a role can see:
{
  "role": "viewer",
  "collection": "users",
  "read": true,
  "fields": ["name", "email"],
  "excluded_fields": ["phone", "ssn", "salary"]
}
Users with this role will receive:
// Response (excluded fields filtered out)
{
  "id": "user_abc123",
  "name": "Alice Johnson",
  "email": "[email protected]"
  // phone, ssn, salary NOT included
}

Field-Level Rules

Apply rules to specific fields:
{
  "collection": "users",
  "field_rules": {
    "salary": {
      "read": "@has_role('admin') or @owns_record()",
      "write": "@has_role('hr') or @is_superadmin()"
    },
    "email": {
      "read": "true",
      "write": "@has_role('admin')"
    }
  }
}

Account Isolation

Account isolation is the foundation of SnackBase security.

Multi-Tenant Isolation

All data is automatically isolated by account_id:
-- User from AB1001 requests posts
SELECT * FROM posts WHERE account_id = 'AB1001';

-- User from XY2048 requests posts
SELECT * FROM posts WHERE account_id = 'XY2048';
Users cannot see or access data from other accounts.

Enforcement Layers

Account isolation is enforced at multiple layers:
LayerMechanismExample
Databaseaccount_id column in WHERE clauseWHERE account_id = ?
RepositoryAutomatic filtering in queriesposts.find_all(context)
API MiddlewareValidates account in tokenToken contains account_id
HooksBuilt-in account_isolation_hookCannot be disabled

Cross-Account Access Prevention

Attempting to access another account’s data:
# User from AB1001 tries to access XY2048 data
GET /api/v1/posts?account_id=XY2048

# Result: 403 Forbidden
# The account_id filter is overridden and reset to AB1001
The system ignores malicious account_id parameters.

Security Best Practices

1. Principle of Least Privilege

Grant minimum required permissions:
// ❌ Too permissive
{
  "role": "viewer",
  "collection": "*",
  "delete": true  // Viewers shouldn't delete!
}

// ✅ Correct
{
  "role": "viewer",
  "collection": "posts",
  "read": true,
  "create": false,
  "update": false,
  "delete": false
}

2. Use Rules for Fine-Grained Control

Leverage the rule engine for complex scenarios:
{
  "rules": {
    "update": "@owns_record() or @has_role('admin')",
    "delete": "@has_role('admin') and not record.status == 'locked'"
  }
}

3. Implement Field-Level Security

Hide sensitive fields by default:
{
  "collection": "users",
  "excluded_fields": ["password_hash", "ssn", "salary"]
}

4. Regular Permission Audits

Periodically review and update permissions:
  • Remove unused roles
  • Tighten overly permissive rules
  • Document permission rationale
  • Use audit logs (when available) to track access

5. Use Wildcards Carefully

Wildcard permissions (*) are powerful but dangerous:
// ⚠️ Use with caution
{
  "collection": "*",
  "delete": true  // Can delete from ALL collections!
}

// ✅ Prefer explicit collections
{
  "collection": "posts",
  "delete": true
}

6. Test Permission Changes

Always test permission changes in development:
def test_editor_cannot_delete_posts():
    editor_user = create_user(role="editor")
    client = login_as(editor_user)

    response = client.delete("/api/v1/posts/123")

    assert response.status_code == 403

7. Monitor and Alert

Monitor for suspicious activity:
  • Repeated failed authorization attempts
  • Unusual access patterns
  • Permission escalation attempts
  • Cross-account access attempts

Common Security Scenarios

Scenario 1: User Can Only Edit Their Own Posts

{
  "role": "author",
  "collection": "posts",
  "create": true,
  "read": true,
  "update": true,
  "delete": true,
  "rules": {
    "update": "@owns_record()",
    "delete": "@owns_record() and not status == 'published'"
  }
}

Scenario 2: Moderators Can Edit All Comments

{
  "role": "moderator",
  "collection": "comments",
  "create": false,
  "read": true,
  "update": true,
  "delete": true,
  "field_rules": {
    "author_ip": {
      "read": "@has_role('admin')"
    }
  }
}

Scenario 3: Public Read, Private Write

{
  "role": "anonymous",
  "collection": "posts",
  "read": true,
  "create": false,
  "update": false,
  "delete": false,
  "excluded_fields": ["draft_notes", "internal_status"]
}

Summary

ConceptKey Takeaway
Security LayersAuthentication → Account Isolation → Authorization → Field-Level Security → Audit
Authentication vs AuthorizationAuthentication = Who are you? Authorization = What can you do?
RBACUsers → Roles → Permissions → Collections
Permission SystemCRUD permissions per collection, wildcard support, 5-minute cache
Rule EngineCustom DSL for fine-grained control with built-in functions
Field-Level SecurityHide sensitive fields, field-specific rules
Account IsolationAutomatic via account_id, enforced at multiple layers
Best PracticesLeast privilege, use rules, hide sensitive data, audit permissions