> ## Documentation Index
> Fetch the complete documentation index at: https://docs.veloiq.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# VeloIQ Access Control: RBAC and ReBAC Guide

> VeloIQ provides three-layer role-based access control (RBAC) and row-level relationship-based access control (ReBAC) for fine-grained permissions.

VeloIQ ships with four layers of access control that go from coarse to fine: global role permissions, model-level exceptions, field-level exceptions, and row-level filtering. All four layers are opt-in and can be combined freely on any model.

## RBAC — role-based access control

The three RBAC layers work together as a funnel. Each layer can only narrow the access that the layer above it grants — it can never expand it.

### Layer 1 — Global role permissions

Define your roles in `VeloIQConfig` and pass them to `create_veloiq_app()`. Each `RoleDef` maps a role name to a set of allowed HTTP methods. VeloIQ seeds these roles to the database on startup and makes them editable at runtime through the admin UI.

Use the built-in constants to assign method sets:

| Constant        | Allowed methods                              |
| --------------- | -------------------------------------------- |
| `ALL_METHODS`   | GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD |
| `WRITE_METHODS` | GET, POST, PUT, PATCH, OPTIONS, HEAD         |
| `READ_METHODS`  | GET, OPTIONS, HEAD                           |

```python theme={null}
# main.py
from veloiq_framework import (
    create_veloiq_app, VeloIQConfig,
    RoleDef, ALL_METHODS, WRITE_METHODS, READ_METHODS,
)

app = create_veloiq_app(VeloIQConfig(
    roles=[
        RoleDef("Admin",   ALL_METHODS,   "Full access",               is_preset=True),
        RoleDef("Manager", WRITE_METHODS, "Create/edit, no delete",    is_preset=True),
        RoleDef("Viewer",  READ_METHODS,  "Read-only",                 is_preset=True),
        # Add as many custom roles as needed:
        RoleDef("Auditor", READ_METHODS,  "External auditor",          is_preset=True),
    ],
))
```

### Layer 2 — Model-level exceptions

Use the `@model_access` decorator to override which actions a role may perform on a specific model. Roles not listed in `@model_access` continue to inherit their global permissions unchanged.

```python theme={null}
from veloiq_framework import model_access, TimestampedModel

@model_access(Viewer=["list", "show"])   # Viewer is read-only on Invoice
class Invoice(TimestampedModel, table=True):
    __tablename__ = "invoice"
    amount: float
    status: str = "draft"
```

<Note>Exceptions are restrictive only. `@model_access` can reduce what a role may do on a model — it cannot grant actions that the role's global permissions don't already include.</Note>

### Layer 3 — Field-level exceptions

Use `veloiq_field()` to restrict read and write access per field. The backend CRUD router enforces these at runtime, and `veloiq generate` emits `readRoles` and `writeRoles` into the TypeScript schema so the frontend can hide or disable restricted inputs automatically.

```python theme={null}
from veloiq_framework import veloiq_field, TimestampedModel

class Employee(TimestampedModel, table=True):
    __tablename__ = "employee"
    name: str
    department: str
    # Only Admins can see or change salary
    salary: float = veloiq_field(
        default=0.0,
        read_roles=["Admin"],
        write_roles=["Admin"],
    )
    # Viewers can read notes, but only Managers and Admins can write them
    notes: str = veloiq_field(
        default="",
        write_roles=["Admin", "Manager"],
    )
```

`veloiq_field()` accepts all the same keyword arguments as `pydantic.Field` and is fully compatible with `TimestampedModel` and `FrameworkModel`.

## ReBAC — relationship-based access control

ReBAC applies row-level filtering based on the data itself. Use it when access depends on ownership, tenant membership, or any relationship between a user and a record. Apply the `@rebac` decorator to a model class; the framework then filters every query for that model before returning results.

### `@rebac(owner_field=...)`

Restrict each user to only the rows they created. Pass the name of the column that holds the owner's user ID.

```python theme={null}
from sqlmodel import Field
from veloiq_framework import rebac, TimestampedModel

@rebac(owner_field="created_by")
class Note(TimestampedModel, table=True):
    __tablename__ = "note"
    created_by: int = Field(foreign_key="veloiq_user.id")
    body: str
```

### `@rebac(tenant_field=...)`

Isolate rows by tenant. Each user sees only rows whose `tenant_id` matches a tenant they belong to.

```python theme={null}
from sqlmodel import Field
from veloiq_framework import rebac, TimestampedModel

@rebac(tenant_field="tenant_id")
class Contract(TimestampedModel, table=True):
    __tablename__ = "contract"
    tenant_id: int = Field(foreign_key="veloiq_tenant.id")
    title: str
```

### `@rebac(filter=...)`

For custom access logic, pass a lambda that receives the current user, the model class, and the database session and returns a SQLAlchemy WHERE clause. Use `rebac_subquery()` to inherit access from a parent model through a relationship.

```python theme={null}
from veloiq_framework import rebac, rebac_subquery, TimestampedModel

@rebac(filter=lambda user, cls, session:
           cls.folder_id.in_(rebac_subquery(Folder, user, session)))
class Document(TimestampedModel, table=True):
    __tablename__ = "document"
    folder_id: int
    name: str
```

### `rebac_subquery()`

`rebac_subquery(ModelClass, user, session)` returns a subquery of primary keys from `ModelClass` that the current user may access. The target model must itself carry a `@rebac` decorator. Circular dependencies between models raise a `ValueError` at runtime.

Use it to chain access through a relationship: if a user can access a `Folder`, they can access the `Document` rows inside it.

### Key behaviors

<Warning>`@rebac` applies to all roles, including Admin. To exempt Admins, return `True` from the filter lambda when the user has the Admin role.</Warning>

* **Inaccessible rows return 404, not 403.** This prevents leaking information about which IDs exist.
* **Multiple patterns are OR-combined.** If you pass more than one of `filter`, `owner_field`, and `tenant_field` to the same `@rebac` call, a row is visible if any single pattern allows it.

## After changing models

Run `veloiq generate` after adding or changing any `veloiq_field()`, `@model_access`, or `@rebac` annotation. This updates the TypeScript schemas so the frontend enforces the same restrictions as the backend.

```bash theme={null}
veloiq generate
```

<CardGroup cols={2}>
  <Card title="Models" href="/concepts/models">
    Define fields and use veloiq\_field() for field-level access
  </Card>

  <Card title="Modules" href="/concepts/modules">
    Understand how modules and auto-loading work
  </Card>
</CardGroup>
