# Tenant Filtering {#tenant-filtering}


Documentation of how tenant-scoped data isolation is enforced at the ORM level, including intentional bypasses and their justifications.


## How Tenant Filtering Works {#how-tenant-filtering-works}


All entities extending `TenantBaseEntity` or `TenantOrganizationBaseEntity` are automatically scoped by `tenantId`:

```typescript
// TenantAwareCrudService automatically adds:
// WHERE tenant_id = :currentTenantId
const employees = await this.employeeService.findAll();
// Returns only employees for the authenticated user's tenant
```


### Automatic Filtering {#automatic-filtering}


Services that extend `TenantAwareCrudService` automatically inject the current user's `tenantId` into:

- `find()` / `findAll()` — SELECT queries
- `findOne()` — single record queries
- `create()` — INSERT operations (sets tenantId)
- `update()` — UPDATE operations (scoped by tenantId)
- `delete()` — DELETE operations (scoped by tenantId)


## Intentional Bypasses {#intentional-bypasses}


Some scenarios intentionally bypass tenant filtering. Every bypass must be documented and justified.


### Category 1: Cross-Tenant by Design {#category-1-cross-tenant-by-design}


These services **must** query across all tenants as part of their core functionality:

| Service                      | Justification                           |
| ---------------------------- | --------------------------------------- |
| `TenantService.findAll()`    | Super Admin listing all tenants         |
| `UserService.findByEmail()`  | Email lookup during login (pre-auth)    |
| `AuthService.validateUser()` | Authentication (no tenant context yet)  |
| `HealthService.check()`      | System health check (no tenant context) |


### Category 2: Manual QueryBuilder Usage {#category-2-manual-querybuilder-usage}


Services using `createQueryBuilder()` bypass automatic tenant scoping and **must manually include** `tenantId`:

```typescript
// ✅ CORRECT — includes tenant_id filter
const result = await this.repository
  .createQueryBuilder("employee")
  .where("employee.tenantId = :tenantId", {
    tenantId: RequestContext.currentTenantId(),
  })
  .getMany();

// ❌ WRONG — missing tenant_id filter
const result = await this.repository.createQueryBuilder("employee").getMany();
```


### Category 3: Plugin Services {#category-3-plugin-services}


Integration plugins that sync external data may operate outside standard tenant context:

| Plugin             | Justification                                      |
| ------------------ | -------------------------------------------------- |
| GitHub Integration | Webhook handlers receive data without user context |
| Upwork Integration | Background sync jobs with stored credentials       |
| Job Search         | Cross-tenant job matching                          |


### Category 4: Base Class Internal {#category-4-base-class-internal}


Internal methods in `CrudService` and `TenantAwareCrudService` that implement the filtering logic itself:

| Method                  | Justification                            |
| ----------------------- | ---------------------------------------- |
| `CrudService.findAll()` | Base implementation without tenant scope |
| `CrudService.findOne()` | Overridden by TenantAwareCrudService     |


## Audit Rules {#audit-rules}



### New Services {#new-services}


All new services **must**:

1. Extend `TenantAwareCrudService` (default)
2. Or document the bypass with justification
3. Never use raw `Repository` without tenant filtering


### Manual QueryBuilder {#manual-querybuilder}


All `createQueryBuilder` usages **must**:

1. Include `.where('entity.tenantId = :tenantId', { tenantId })` clause
2. Use `RequestContext.currentTenantId()` for the tenant ID
3. Be audited for tenant isolation compliance


### Code Review Checklist {#code-review-checklist}


- ✅ Does the service extend `TenantAwareCrudService`?
- ✅ If using `createQueryBuilder`, is `tenantId` in the WHERE clause?
- ✅ If bypassing tenant filtering, is there a documented justification?
- ✅ Are plugin services properly scoped when handling multi-tenant data?
- ✅ Do background jobs use stored tenant context?


## Configuration {#configuration}



### Super Admin Cross-Tenant Access {#super-admin-cross-tenant-access}


```bash
# Allow Super Admin to query across tenants
ALLOW_SUPER_ADMIN_ROLE=true
```

Super Admin users can access the `TenantController` to list and manage all tenants.


## Maintenance Guidelines {#maintenance-guidelines}



### Adding a New Bypass {#adding-a-new-bypass}


1. Document the service, method, and justification in this page
2. Add a code comment explaining the bypass
3. Ensure Super Admin or system-level authorization is in place
4. Submit for code review with explicit security review


### Auditing Existing Bypasses {#auditing-existing-bypasses}


Periodically audit all services for:

- Unauthorized `Repository.find()` usage (bypasses tenant filter)
- Missing `tenantId` in `createQueryBuilder` queries
- New plugin services without documentation
- Deprecated bypasses that can be removed


## Related Pages {#related-pages}


- [Multi-Tenancy](https://sourcey-gauzy.pages.dev/architecture/multi-tenancy/index.md) — tenant architecture
- [Roles & Permissions](https://sourcey-gauzy.pages.dev/authentication/roles-and-permissions/index.md) — RBAC
- [Database Overview](https://sourcey-gauzy.pages.dev/database/database-overview/index.md) — general database info
