2026-07-06 12:04:47 +00:00
|
|
|
# Tenant Context
|
|
|
|
|
|
|
|
|
|
## Objetivo
|
|
|
|
|
|
|
|
|
|
Documentar cómo se resuelve el contexto tenant actual del ERP mediante `X-Company-Id` y `requireIdentityTenant(params)`.
|
|
|
|
|
|
|
|
|
|
## Header tenant
|
|
|
|
|
|
|
|
|
|
Las rutas tenant-scoped deben recibir:
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
Authorization: Bearer <access_token>
|
|
|
|
|
X-Company-Id: <uuid-company>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`X-Company-Id` es el identificador de la company activa en la request.
|
|
|
|
|
|
|
|
|
|
No usar:
|
|
|
|
|
|
|
|
|
|
- `X-Company-Slug`
|
|
|
|
|
- `companySlug` en token
|
|
|
|
|
- `req.user.companySlug`
|
|
|
|
|
|
|
|
|
|
## Middleware público
|
|
|
|
|
|
|
|
|
|
Para rutas tenant-scoped:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { requireIdentityTenant } from "@erp/identity/api";
|
|
|
|
|
|
|
|
|
|
router.use(...requireIdentityTenant(params));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Para rutas solo autenticadas:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { requireIdentityAuthenticated } from "@erp/identity/api";
|
|
|
|
|
|
|
|
|
|
router.use(...requireIdentityAuthenticated(params));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Validación real de `requireIdentityTenant(params)`
|
|
|
|
|
|
|
|
|
|
Actualmente ya valida:
|
|
|
|
|
|
|
|
|
|
1. access token válido
|
|
|
|
|
2. account autenticable
|
|
|
|
|
3. `X-Company-Id` presente
|
|
|
|
|
4. `X-Company-Id` con formato UUID válido
|
|
|
|
|
5. membership `active` en `identity_company_memberships`
|
|
|
|
|
6. company `active` en `companies`
|
|
|
|
|
7. solo entonces rellena `req.user.companyId`
|
|
|
|
|
|
2026-08-17 13:49:11 +00:00
|
|
|
Esto significa que hoy el contexto tenant garantiza:
|
|
|
|
|
|
|
|
|
|
```txt
|
|
|
|
|
membership activa
|
|
|
|
|
+ company activa
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
pero no enforcement de permisos granulares por operación.
|
|
|
|
|
|
2026-07-06 12:04:47 +00:00
|
|
|
## Semántica esperada de errores
|
|
|
|
|
|
|
|
|
|
```txt
|
|
|
|
|
sin Authorization -> 401
|
|
|
|
|
token inválido -> 401
|
|
|
|
|
cuenta inexistente/no autenticable -> 401
|
|
|
|
|
sin X-Company-Id -> 403
|
|
|
|
|
X-Company-Id inválido -> 400
|
|
|
|
|
company inexistente -> 403
|
|
|
|
|
company disabled -> 403
|
|
|
|
|
membership inexistente -> 403
|
|
|
|
|
membership disabled/invited -> 403
|
|
|
|
|
membership active + company active -> entra al controller
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Shape mínimo de `req.user`
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
{
|
|
|
|
|
userId: UniqueID;
|
|
|
|
|
email?: EmailAddress;
|
|
|
|
|
companyId?: UniqueID;
|
|
|
|
|
roles?: string[];
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
No añadir:
|
|
|
|
|
|
|
|
|
|
- `companySlug`
|
|
|
|
|
- `company`
|
|
|
|
|
- `permissions` enriquecidos
|
|
|
|
|
- `companyStatus`
|
|
|
|
|
|
|
|
|
|
## Módulos tenant-scoped migrados
|
|
|
|
|
|
|
|
|
|
Actualmente usan `requireIdentityTenant(params)`:
|
|
|
|
|
|
2026-08-17 13:49:11 +00:00
|
|
|
- `modules/companies` para `GET /companies/current`, `PATCH /companies/current` y endpoints company-scoped legacy
|
2026-07-06 12:04:47 +00:00
|
|
|
- `modules/catalogs`
|
|
|
|
|
- `modules/customers`
|
|
|
|
|
- `modules/customer-invoices`
|
|
|
|
|
- `modules/factuges`
|
|
|
|
|
|
|
|
|
|
`modules/supplier` queda fuera de esta migración por ahora.
|
|
|
|
|
|
|
|
|
|
## Qué no debe ir en el token
|
|
|
|
|
|
|
|
|
|
El access token no debe incluir:
|
|
|
|
|
|
|
|
|
|
- `companyId`
|
|
|
|
|
- `companySlug`
|
|
|
|
|
- roles
|
|
|
|
|
- permisos
|
|
|
|
|
|
|
|
|
|
El contexto tenant se resuelve por request con `X-Company-Id`.
|
|
|
|
|
|
2026-08-17 13:49:11 +00:00
|
|
|
## Caso actual: company settings
|
|
|
|
|
|
|
|
|
|
`/settings/company/details` opera siempre sobre la company activa.
|
|
|
|
|
|
|
|
|
|
Endpoints:
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
GET /companies/current
|
|
|
|
|
PATCH /companies/current
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Política V1:
|
|
|
|
|
|
|
|
|
|
```txt
|
|
|
|
|
usuario autenticado
|
|
|
|
|
+ X-Company-Id válido
|
|
|
|
|
+ membership activa en esa company
|
|
|
|
|
+ company activa
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
No se aplica todavía un permiso específico como `identity:companies:update`.
|
|
|
|
|
|
2026-07-06 12:04:47 +00:00
|
|
|
## Document context
|
|
|
|
|
|
|
|
|
|
Si un flujo documental necesita `companySlug`, debe resolverlo desde `companies`, por ejemplo:
|
|
|
|
|
|
|
|
|
|
```txt
|
|
|
|
|
companyId -> companies:general.finder.findById(...) -> slug
|
|
|
|
|
```
|