Skip to main content

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).

note

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:

RolRutas 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]
  1. Excluye /test — página estática que debe responder sin sesión (validación del certificado de Google).
  2. 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.
  3. 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).
  4. Comprueba el rol para las rutas de PROTECTED_ROUTES.

Manejo de errores de sesión

SituaciónPetición de navegaciónPetición de API (/api, Accept: application/json o X-Requested-With)
missing_sessionRedirige a /auth/login401 {"error": "missing_session"}
missing_refresh_tokenRedirige a /auth/logout401 {"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.
  • fetchAuth solo funciona dentro del contexto de una petición que haya pasado por proxy.ts (necesita headers()), 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 ni RoleProvider, que es cómo se muestra /unauthorized (título Unauthorized y enlace a /auth/logout).
  • En caso normal, envuelve la página en RoleProvider (contexto RoleContext con los roles) y monta el Sidebar.

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).