El API no tiene ningún guard. POST /auth/login solo devuelve el usuario, que el front guarda en sessionStorage (apps/web/src/app/_core/hooks/use-session-user.ts). El rol se revisa únicamente en el cliente (user.role === "admin" en page.tsx, pos-shell.tsx, user-login-screen.tsx).
Además, el usuario que ejecuta cada acción viaja en el body (userId, openedById, closedById) y el servidor lo acepta tal cual.
Qué estamos pagando hoy
Cualquiera con acceso a la red puede llamar GET /api/users, DELETE /api/payments/:id, etc., sin iniciar sesión.
Un cajero puede hacer operaciones de administrador llamando al API directamente.
Los registros de quién abrió o cerró una caja o un día no son confiables.
Confirmado en caja negra (2026-10-02)
Contra localhost:3000, sin ninguna sesión ni token, respondieron 200 con datos: GET /api/users (incluye email), /api/payments, /api/cash-movements, /api/cash-register-sessions, /api/operating-days, /api/shop-carts, /api/customers, /api/suppliers, /api/products. El login no devuelve cookie ni token (Set-Cookie/Authorization ausentes): la "sesión" vive solo en sessionStorage del cliente. Ver también #26 (crear admin sin sesión) y la falta de rate limiting en /api/auth/login (no se probó a fondo).
Arreglo propuesto
Emitir una sesión (cookie httpOnly) o JWT en el login.
AuthGuard global y RolesGuard con decorador @Roles(UserRole.admin).
Tomar el usuario actual del token en el servidor en vez del body.
GET /users para el selector de login devuelve solo id y name.
Criterios de aceptación:
Ningún endpoint (salvo login y health) responde sin autenticación
Los endpoints de admin rechazan a cajeros
El servidor determina el usuario que ejecuta la acción
Techo de esta ronda
Autenticación por sesión o JWT y dos roles (admin, cashier). Fuera: permisos finos por sucursal, recuperación de contraseña y bloqueo por intentos fallidos.
Superficie
API (apps/api)
Módulos
Auth / sesión / roles, API / backend, Web / frontend, Usuarios
Esfuerzo
L — partir; este issue solo cubre el primer corte
Riesgo de no tocarlo
Alto (seguridad, datos, o bloquea el próximo corte)
### Qué es la deuda
El API no tiene ningún guard. `POST /auth/login` solo devuelve el usuario, que el front guarda en `sessionStorage` (`apps/web/src/app/_core/hooks/use-session-user.ts`). El rol se revisa únicamente en el cliente (`user.role === "admin"` en `page.tsx`, `pos-shell.tsx`, `user-login-screen.tsx`).
Además, el usuario que ejecuta cada acción viaja en el body (`userId`, `openedById`, `closedById`) y el servidor lo acepta tal cual.
### Qué estamos pagando hoy
- Cualquiera con acceso a la red puede llamar `GET /api/users`, `DELETE /api/payments/:id`, etc., sin iniciar sesión.
- Un cajero puede hacer operaciones de administrador llamando al API directamente.
- Los registros de quién abrió o cerró una caja o un día no son confiables.
### Confirmado en caja negra (2026-10-02)
Contra `localhost:3000`, sin ninguna sesión ni token, respondieron `200` con datos: `GET /api/users` (incluye email), `/api/payments`, `/api/cash-movements`, `/api/cash-register-sessions`, `/api/operating-days`, `/api/shop-carts`, `/api/customers`, `/api/suppliers`, `/api/products`. El login no devuelve cookie ni token (`Set-Cookie`/`Authorization` ausentes): la "sesión" vive solo en `sessionStorage` del cliente. Ver también #26 (crear admin sin sesión) y la falta de rate limiting en `/api/auth/login` (no se probó a fondo).
### Arreglo propuesto
- Emitir una sesión (cookie httpOnly) o JWT en el login.
- `AuthGuard` global y `RolesGuard` con decorador `@Roles(UserRole.admin)`.
- Tomar el usuario actual del token en el servidor en vez del body.
- `GET /users` para el selector de login devuelve solo `id` y `name`.
Criterios de aceptación:
- [ ] Ningún endpoint (salvo login y health) responde sin autenticación
- [ ] Los endpoints de admin rechazan a cajeros
- [ ] El servidor determina el usuario que ejecuta la acción
### Techo de esta ronda
Autenticación por sesión o JWT y dos roles (`admin`, `cashier`). Fuera: permisos finos por sucursal, recuperación de contraseña y bloqueo por intentos fallidos.
### Superficie
API (apps/api)
### Módulos
Auth / sesión / roles, API / backend, Web / frontend, Usuarios
### Esfuerzo
L — partir; este issue solo cubre el primer corte
### Riesgo de no tocarlo
Alto (seguridad, datos, o bloquea el próximo corte)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Qué es la deuda
El API no tiene ningún guard.
POST /auth/loginsolo devuelve el usuario, que el front guarda ensessionStorage(apps/web/src/app/_core/hooks/use-session-user.ts). El rol se revisa únicamente en el cliente (user.role === "admin"enpage.tsx,pos-shell.tsx,user-login-screen.tsx).Además, el usuario que ejecuta cada acción viaja en el body (
userId,openedById,closedById) y el servidor lo acepta tal cual.Qué estamos pagando hoy
GET /api/users,DELETE /api/payments/:id, etc., sin iniciar sesión.Confirmado en caja negra (2026-10-02)
Contra
localhost:3000, sin ninguna sesión ni token, respondieron200con datos:GET /api/users(incluye email),/api/payments,/api/cash-movements,/api/cash-register-sessions,/api/operating-days,/api/shop-carts,/api/customers,/api/suppliers,/api/products. El login no devuelve cookie ni token (Set-Cookie/Authorizationausentes): la "sesión" vive solo ensessionStoragedel cliente. Ver también #26 (crear admin sin sesión) y la falta de rate limiting en/api/auth/login(no se probó a fondo).Arreglo propuesto
AuthGuardglobal yRolesGuardcon decorador@Roles(UserRole.admin).GET /userspara el selector de login devuelve soloidyname.Criterios de aceptación:
Techo de esta ronda
Autenticación por sesión o JWT y dos roles (
admin,cashier). Fuera: permisos finos por sucursal, recuperación de contraseña y bloqueo por intentos fallidos.Superficie
API (apps/api)
Módulos
Auth / sesión / roles, API / backend, Web / frontend, Usuarios
Esfuerzo
L — partir; este issue solo cubre el primer corte
Riesgo de no tocarlo
Alto (seguridad, datos, o bloquea el próximo corte)
Autenticación y guards por rol en el APIto [debt] Autenticación y guards por rol en el API