Uecko_ERP/docs/frontend/auth-and-company-selection.md

216 lines
5.1 KiB
Markdown
Raw Permalink Normal View History

2026-07-06 12:04:47 +00:00
# Frontend Auth And Company Selection
## Runtime actual
El frontend ERP usa el runtime nuevo:
```txt
@erp/identity/client
IdentityAuthSessionProvider
DataSourceProvider
Axios compartido
```
No debe usarse `@erp/auth/client` como runtime nuevo.
## Flujo actual
```txt
login
-> GET /identity/auth/session
-> GET /companies/available
-> resolver empresa activa
-> auto-selección si hay una sola empresa
-> selector si hay varias
-> /no-companies si no hay empresas
-> X-Company-Id en rutas tenant-scoped
```
## Login
El login usa:
```http
POST /identity/auth/login
```
Tras login correcto:
- se guardan `accessToken` y `refreshToken`
- se limpia cualquier `activeCompanyId` previo
- se carga `GET /companies/available`
- se resuelve el redirect autenticado según companies disponibles
## Session restore
Si existe `accessToken` persistido:
1. se llama `GET /identity/auth/session`
2. se hidrata la sesión autenticada
3. se llama `GET /companies/available`
4. se resuelve `activeCompanyId` persistido o auto-selección
Si la restauración falla:
- se limpia la sesión
- se limpia `activeCompanyId`
## Selección de empresa
### Cero empresas
Si `companies.length === 0`:
- se muestra `/no-companies`
- no existe `activeCompanyId`
- no se envía `X-Company-Id`
- sigue disponible el logout
### Una empresa
Si `companies.length === 1`:
- se selecciona automáticamente
- se guarda `activeCompanyId`
- no se muestra `/company-selection`
- se continúa al área privada
### Varias empresas
Si `companies.length > 1`:
- se muestra `/company-selection`
- no existe `activeCompanyId` hasta elegir
- el usuario selecciona company
- se guarda `activeCompanyId`
- se continúa al área privada
### `activeCompanyId` persistido ya no disponible
Si el `activeCompanyId` persistido ya no aparece en `GET /companies/available`:
- con `0` companies -> `/no-companies`
- con `1` company -> auto-selección de la disponible
- con varias companies -> `/company-selection`
## `GET /companies/available`
Endpoint actual:
```http
GET /companies/available
Authorization: Bearer <access_token>
```
No requiere:
- `X-Company-Id`
No usa:
- `requireIdentityTenant(params)`
Devuelve:
```ts
export type AvailableCompaniesResponseDTO = {
companies: AvailableCompanyDTO[];
};
```
2026-07-06 15:47:05 +00:00
## App company switcher
Componente documentado:
```txt
apps/web/src/layout/app-company-switcher.tsx
```
Propósito:
- cambiar de empresa activa una vez iniciada la sesión
- mostrar la empresa activa actual dentro del layout autenticado
Reglas:
- consume `availableCompanies` y `activeCompany` desde `useIdentityAuthSession()`
- usa `changeActiveCompany()` o `selectActiveCompany()` del contexto, no requests directas
- no llama directamente a `GET /companies/available` si el contexto ya está hidratado
- no maneja tokens
- no usa `@erp/auth/client`
- no usa `companySlug` para autenticación
- no modifica el access token
- al cambiar empresa, las siguientes requests tenant-scoped deben salir con el nuevo `X-Company-Id`
Comportamiento esperado:
- `0` empresas: no rompe; render deshabilitado o equivalente
- `1` empresa: muestra la empresa activa; no hace falta selector operativo
- `N` empresas: permite cambiar entre las permitidas y marca la activa
2026-07-06 12:04:47 +00:00
## Headers
### No deben llevar `X-Company-Id`
```txt
POST /identity/auth/login
POST /identity/auth/refresh
POST /identity/auth/logout
GET /identity/auth/session
GET /companies/available
```
### Sí deben llevar `X-Company-Id`
Rutas tenant-scoped como:
- `/customers`
- `/catalogs/*`
- `/customer-invoices/*`
- `/factuges`
siempre que exista `activeCompanyId`.
## Refresh automático sobre `401`
El frontend implementa refresh automático global sobre Axios.
Comportamiento:
1. una request autenticada recibe `401`
2. si existe `refreshToken`, se llama `POST /identity/auth/refresh`
3. se guardan los nuevos tokens
4. se reintenta una única vez la request original
5. si refresh falla, se limpia la sesión y se redirige a `/login`
## Reglas implementadas
- retry único por request con `_retry`
- refresh concurrente único con `refreshPromise` compartida
- varias requests `401` esperan el mismo refresh
- login `401` no dispara refresh
- refresh `401` no intenta refrescarse a sí mismo
- `/identity/auth/refresh` no lleva `X-Company-Id`
- la request tenant-scoped reintentada conserva `X-Company-Id`
## Endpoints excluidos de tenant header y refresh
```txt
/identity/auth/login
/identity/auth/refresh
/identity/auth/logout
/identity/auth/session
/companies/available
```
2026-07-06 15:47:05 +00:00
## Pruebas manuales recomendadas
1. Login con usuario sin empresas: debe ir a `/no-companies`.
2. Login con usuario con una empresa: debe auto-seleccionarse y entrar al área privada.
3. Login con usuario con varias empresas: debe mostrarse `/company-selection`.
4. Cambio de empresa desde `app-company-switcher`: la request tenant-scoped siguiente debe llevar el nuevo `X-Company-Id`.
5. Access token expirado con refresh válido: debe ejecutarse refresh + retry único.
6. Refresh token inválido: debe limpiarse la sesión y redirigir a `/login`.
7. Endpoints auth y `GET /companies/available`: no deben llevar `X-Company-Id`.
8. Rutas tenant-scoped: deben llevar `X-Company-Id` cuando exista `activeCompanyId`.