Skip to content

Firewall - Data Isolation

The firewall generates WHERE clauses automatically to isolate data by user, organization, or team.

Configuration Options

typescript
firewall: {
  // User-level ownership (personal data)
  owner?: {
    column?: string;              // Default from auth provider
    source?: string;              // e.g., 'ctx.userId'
    mode?: 'required' | 'optional';  // Default: 'required'
  };

  // Organization-level ownership
  organization?: {
    column?: string;
    source?: string;
  };

  // Team-level ownership
  team?: {
    column?: string;
    source?: string;
  };

  // Soft delete filtering
  softDelete?: {
    column?: string;  // Default: 'deletedAt'
  };

  // Opt-out for public tables (cannot combine with ownership)
  exception?: boolean;
}

Common Patterns

typescript
// Public/system table - no filtering
firewall: { exception: true }

// Organization-scoped data
firewall: { organization: {} }
// Generated: WHERE organizationId = ctx.activeOrgId

// Personal user data
firewall: { owner: {} }
// Generated: WHERE ownerId = ctx.userId

// Org data with optional owner filtering
firewall: {
  organization: {},
  owner: { mode: 'optional' }
}

// With soft delete
firewall: {
  organization: {},
  softDelete: {}
}

Rules

  • Every resource MUST have at least one ownership scope OR exception: true
  • Cannot mix exception: true with ownership scopes

Why Can't You Mix exception with Ownership?

They represent opposite intentions:

  • exception: true = "Generate NO WHERE clauses, data is public/global"
  • Ownership scopes = "Generate WHERE clauses to filter data"

Combining them would be contradictory - you can't both filter and not filter.

Handling "Some Public, Some Private" Data

If you need records that are sometimes public and sometimes scoped, you have two options:

Option 1: Two Separate Resources

Split into two tables - one public, one scoped:

typescript
// Public templates anyone can see
defineResource(templateLibrary, {
  firewall: { exception: true }
});

// User's custom templates
defineResource(userTemplates, {
  firewall: { owner: {} }
});

Option 2: Use Access Control Instead

Keep ownership scope but make access permissive, then control visibility via access:

typescript
defineResource(documents, {
  firewall: {
    organization: {},  // Still scoped to org
  },
  crud: {
    list: {
      // Anyone in the org can list, but they see different things
      // based on a "visibility" field you check in your app logic
      access: { roles: ["member", "admin"] },
    },
    get: {
      // Use record conditions to allow public docs OR owned docs
      access: {
        or: [
          { record: { visibility: { equals: "public" } } },
          { record: { ownerId: { equals: "$ctx.userId" } } },
          { roles: ["admin"] }
        ]
      }
    },
  }
});

Which to Choose?

ScenarioRecommendation
Truly global data (app config, public templates)exception: true in separate resource
"Public within org" but still org-isolatedOwnership scope + permissive access rules
User can toggle their own data public/privateOwnership scope + visibility field + access conditions

Backend security, simplified.