Roles y control de acceso
A diferencia del portal del cliente, que usa un único usuario de servicio, aquí cada empleado se autentica con su propia cuenta de Auth0 y el rol de esa cuenta determina qué ve y qué puede hacer.
1. Cliente de Auth0
lib/auth0.ts instancia el SDK de sesión (@auth0/nextjs-auth0 v4):
export const auth0 = new Auth0Client({
authorizationParameters: {
scope: process.env.AUTH0_SCOPE,
audience: process.env.AUTH0_AUDIENCE
}
});
En la v4 del SDK, AUTH0_SCOPE y AUTH0_AUDIENCE ya no se leen automáticamente, de ahí que se pasen de forma explícita. El scope configurado es openid profile email admin atc customer store y el audience es la propia robo-api — el access token que emite Auth0 sirve directamente para llamarla.
El SDK monta las rutas de sesión (/auth/login, /auth/logout, callback) desde el middleware; no hay páginas de login propias.
2. Roles
constants/roles.ts define cuatro roles:
export type Roles = "ADMIN" | "CUSTOMER" | "STORE" | "ATC";
actions/getUserRoles.ts los extrae decodificando el payload del ID token y leyendo el claim personalizado:
robo-hawkers.eu.auth0.com/roles
Si no hay sesión, no hay ID token o falla la decodificación, devuelve [] (y el usuario acaba en la pantalla Unauthorized).
La decodificación es un Buffer.from(...).toString() del segundo segmento del JWT, sin verificar firma. Es aceptable porque el token ya viene de una sesión validada por el SDK, pero conviene tenerlo presente.
3. Rutas por rol
constants/routes.ts:
| Rol | Rutas accesibles |
|---|---|
admin | /returns, /methods, /reasons, /users, /warehouses |
atc | /returns |
store | /returns |
export const PROTECTED_ROUTES = ["/methods", "/reasons", "/users", "/warehouses"];
PROTECTED_ROUTES son las rutas que exigen comprobación de rol; hasAccess(rol, pathname) (en lib/utils.ts) resuelve si el rol tiene ese prefijo entre sus rutas permitidas. Si no, proxy.ts redirige a /returns.
El rol CUSTOMER no aparece en ROLE_ROUTES: un usuario con ese único rol no tiene acceso a ninguna ruta del back-office.
Menú lateral
components/admin/sidebar/ pinta los enlaces de constants/links.ts (Devoluciones, Métodos, Motivos, Usuarios, Almacenes) filtrados por el rol, de modo que un ATC o STORE solo ve Devoluciones.
4. proxy.ts — el punto central
En Next.js 16 el antiguo middleware.ts se llama proxy.ts. Este fichero hace cuatro cosas en cada petición:
flowchart TD
R[Petición] --> T{"pathname === /test"}
T -->|sí| PASS[next]
T -->|no| MW["auth0.middleware(request)"]
MW --> RED{"¿302/307 o /unauthorized?"}
RED -->|sí| OUT[Devuelve la respuesta de Auth0]
RED -->|no| SES{"¿Hay sesión?"}
SES -->|no| ERR[missing_session]
SES -->|sí| TOK["getAccessToken()"]
TOK --> HDR["Inyecta x-internal-access-token<br/>en las cabeceras de la petición"]
HDR --> ROL["getUserRoles()"]
ROL --> CHK{"¿Ruta protegida<br/>y sin acceso?"}
CHK -->|sí| REDIR[Redirige a /returns]
CHK -->|no| NEXT[next con las cabeceras nuevas]
- Excluye
/test— página estática que debe responder sin sesión (validación del certificado de Google). - Ejecuta
auth0.middleware, que gestiona el login, el callback y la renovación de sesión. Si devuelve una redirección (302/307) o la ruta es/unauthorized, se devuelve tal cual. - Propaga el access token: obtiene el token de la sesión y lo inyecta en la cabecera de petición
x-internal-access-token, copiando además al resultado las cabeceras que haya puesto Auth0 (cookies de sesión renovadas). - Comprueba el rol para las rutas de
PROTECTED_ROUTES.
Manejo de errores de sesión
| Situación | Petición de navegación | Petición de API (/api, Accept: application/json o X-Requested-With) |
|---|---|---|
missing_session | Redirige a /auth/login | 401 {"error": "missing_session"} |
missing_refresh_token | Redirige a /auth/logout | 401 {"error": "missing_refresh_token"} |
El fetchWrapper de services/api.ts (lado cliente) reacciona a esos 401 haciendo window.location.href = "/auth/logout" o "/auth/login" según el código, de modo que una sesión caducada durante la navegación del SPA acaba en el flujo de login sin pantallas rotas.
El matcher del proxy cubre todo excepto _next/static, _next/image, favicon.ico, sitemap.xml y robots.txt.
5. Uso del token: fetchAuth
lib/fetchAuth.ts es el único punto por el que se habla con robo-api:
const reqHeaders = await headers();
const token = reqHeaders.get("x-internal-access-token");
newHeaders.set("Authorization", `Bearer ${token}`);
Consecuencias de este diseño:
- Todas las llamadas a robo-api heredan la identidad y los scopes del empleado, no de un usuario de servicio. La autorización fina la aplica robo-api por scope.
fetchAuthsolo funciona dentro del contexto de una petición que haya pasado porproxy.ts(necesitaheaders()), es decir: desde route handlers, server actions y Server Components.- En entornos no productivos añade además la cabecera de modo de pruebas (
X-Test-Mode), que redirige las escrituras de robo-api a tablas sombra.
6. Layout protegido y pantalla Unauthorized
app/(dashboard)/layout.tsx refuerza el control en servidor:
- Si no hay sesión →
redirect("/auth/login"). - Si
userRoles.length <= 0→ renderiza los hijos sin sidebar niRoleProvider, que es cómo se muestra/unauthorized(título Unauthorized y enlace a/auth/logout). - En caso normal, envuelve la página en
RoleProvider(contextoRoleContextcon los roles) y monta elSidebar.
Los componentes de cliente consumen RoleContext para adaptar la UI; el caso más relevante es isStoreRole(), que en el detalle de una devolución impide a una tienda marcar líneas como REFUNDED y es la condición que dispara el journey de aprobación (ver Gestión de devoluciones).