FactuGES_ERP/docs/architecture/identity-auth-api.md
david fbf26d4745 feat: implement account management features
- Added services and use cases for managing account details, including changing passwords and updating profiles.
- Introduced error handling for duplicate emails and password mismatches.
- Created controllers for handling account-related requests in the Express framework.
- Updated API routes to include new account management endpoints.
- Enhanced the account profile schema to support email updates.
- Implemented tests for account management functionalities to ensure reliability and correctness.
2026-08-17 23:30:41 +02:00

302 lines
5.6 KiB
Markdown

# Identity Auth API
## Endpoints activos
```http
GET /identity/account
PATCH /identity/account
POST /identity/account/change-password
POST /identity/auth/login
POST /identity/auth/refresh
POST /identity/auth/logout
GET /identity/auth/session
GET /companies/available
```
## Endpoints que no forman parte del diseño vigente
No documentar ni crear como ruta válida:
```http
GET /identity/companies
GET /account/companies
```
Motivo:
- `identity` no debe devolver fichas completas de empresa.
- `companies` ya depende de `identity` para autenticación.
- Un endpoint de ese tipo introduciría el ciclo `identity -> companies -> identity`.
## POST `/identity/auth/login`
### Request
```ts
export type LoginWithPasswordRequestDTO = {
email: string;
password: string;
};
```
### Response
```ts
export type LoginWithPasswordResponseDTO = {
access_token: string;
refresh_token: string;
account: AuthenticatedAccountDTO;
};
```
```ts
export type AuthenticatedAccountDTO = {
id: string;
email: string;
name: string;
avatar_url: string | null;
language_code: string;
};
```
### Notas
- no requiere `Authorization`
- no requiere `X-Company-Id`
- no devuelve companies accesibles
- no devuelve roles ni permisos
- el access token no contiene `companyId`
## POST `/identity/auth/refresh`
### Request
```ts
export type RefreshSessionRequestDTO = {
refresh_token: string;
};
```
### Response
```ts
export type RefreshSessionResponseDTO = {
access_token: string;
refresh_token: string;
};
```
### Notas
- el refresh token se rota
- el cliente debe reemplazar ambos tokens por los nuevos
- no debe llevar `X-Company-Id`
## POST `/identity/auth/logout`
### Headers
```http
Authorization: Bearer <access_token>
```
### Request
```ts
export type LogoutRequestDTO = {
refresh_token?: string;
all_sessions?: boolean;
};
```
### Response
```ts
export type LogoutResponseDTO = {
success: true;
};
```
### Reglas
- no debe llevar `X-Company-Id`
- para logout normal, enviar el `refresh_token` actual
- si logout falla por expiración de token, el frontend debe limpiar sesión igualmente
## GET `/identity/account`
### Headers
```http
Authorization: Bearer <access_token>
```
### Response
```ts
export type GetAccountProfileResponseDTO = {
id: string;
email: string;
name: string;
avatar_url: string | null;
language_code: string;
status: "active" | "disabled" | "pending_verification";
};
```
### Notas
- no debe llevar `X-Company-Id`
- representa la identidad global del usuario autenticado
- no depende de company activa
## PATCH `/identity/account`
### Headers
```http
Authorization: Bearer <access_token>
```
### Request
```ts
export type UpdateAccountProfileRequestDTO = {
email?: string;
name?: string;
avatar_url?: string | null;
language_code?: string;
};
```
### Response
```ts
export type UpdateAccountProfileResponseDTO = {
id: string;
email: string;
name: string;
avatar_url: string | null;
language_code: string;
status: "active" | "disabled" | "pending_verification";
};
```
### Reglas
- no debe llevar `X-Company-Id`
- PATCH real: campo omitido no modifica
- `avatar_url: null` limpia el avatar
- el cambio de email en V1 es inmediato y no requiere verificación adicional
- el backend valida unicidad de email
- `avatar_url` sigue siendo solo una referencia externa; no existe upload en `identity`
## POST `/identity/account/change-password`
### Headers
```http
Authorization: Bearer <access_token>
```
### Request
```ts
export type ChangeAccountPasswordRequestDTO = {
current_password: string;
new_password: string;
};
```
### Response
```ts
export type ChangeAccountPasswordResponseDTO = {
success: true;
};
```
### Reglas
- no debe llevar `X-Company-Id`
- exige `current_password`
- usa el hasher actual de `identity`
- revoca todos los refresh tokens persistidos de la cuenta
- no invalida de forma inmediata access tokens ya emitidos, porque hoy son stateless y no existe un subsistema de revocación inmediata de access tokens
## GET `/identity/auth/session`
### Headers
```http
Authorization: Bearer <access_token>
```
### Response
```ts
export type CurrentSessionResponseDTO = {
account: AuthenticatedAccountDTO;
};
```
### Notas
- no debe llevar `X-Company-Id`
## GET `/companies/available`
### Headers
```http
Authorization: Bearer <access_token>
```
### Response
```ts
export type AvailableCompaniesResponseDTO = {
companies: AvailableCompanyDTO[];
};
export type AvailableCompanyDTO = {
id: string;
legal_name: string;
trade_name: string | null;
tin: string | null;
slug: string;
email: string | null;
phone: string | null;
website: string | null;
status: "active";
};
```
### Notas
- usa autenticación, no tenant context
- no requiere `X-Company-Id`
- usa `requireIdentityAuthenticated(params)`
- devuelve solo companies activas accesibles para la cuenta autenticada
- no modifica sesión ni access token
## Errores esperados
```txt
400 -> request inválida o UUID de X-Company-Id mal formado
401 -> access token ausente/inválido/expirado o credenciales inválidas
401 -> cuenta no autenticable (`disabled` o `pending_verification`)
403 -> contexto tenant inválido, company no accesible o company no activa
409 -> conflicto de unicidad, por ejemplo email ya existente
500 -> error inesperado
```
## Administración global de cuentas
El dominio ya soporta enable/disable global de `Account`, pero no se documentan todavía endpoints HTTP administrativos operativos porque el runtime nuevo no dispone aún de un mecanismo inequívoco y cableado de autorización de administrador global.
Pendiente explícito:
- definir y cablear la autorización global real
- entonces exponer enable/disable por HTTP