Saltar a contenido

9 de julio de 2026

El rol y el id del usuario ya viajan hasta la sesión: cableé los callbacks de jwt y session, y de paso me topé con un límite de las sesiones JWT que dejo anotado.

El rol llega a la sesión: callbacks jwt y session (Task 5d)

Con el signIn decidiendo quién entra, faltaba la otra mitad: que una vez adentro, la app sepa quién es y qué puede hacer. Auth.js resuelve eso con dos callbacks encadenados, y hoy los cablié a los dos. El de jwt corre en cada request, pero hay un detalle que ordena todo el diseño: el correo del usuario solo viene en el primer login. Ese es el único momento en que se puede (y se debe) ir a la base. Ahí resolveJwt busca la fila de la whitelist, y si corresponde, estampa el rol y el id en el token. En los requests que siguen no hay correo, no hay consulta, y el token pasa de largo con las estampas que ya traía. El de session es más humilde: resolveSession copia esas dos estampas del token a session.user, que es lo que las pantallas y los guards de la Task 6 van a leer.

La decisión que más me gustó de hoy es que el token no acepta cualquier cosa. Antes de estampar, la fila pasa por decideAccess —sí, otra vez la función pura de la Task 3, intacta—. Si el usuario no figura, está de baja o tiene un rol que no reconocemos, el token queda sin estampas y punto. La consecuencia linda es que el rol del token quedó tipado: no es un texto libre que vino de la base, es el union Rol que la decisión ya validó. La misma regla que decide el login decide qué viaja en el token, sin duplicarla en ningún lado. Para que TypeScript entienda que el token y la sesión cargan estos campos nuestros usé el module augmentation que documenta Auth.js; es de esas cosas que se escriben una vez y después todo el proyecto tipa solo.

Ahora, lo que me hizo frenar un rato: escribiendo los tests me di cuenta de que si a alguien lo dan de baja después de que inició sesión, su token sigue vivo con el rol adentro hasta que expira. La sesión JWT es así: se firma una vez y no se vuelve a consultar la base en cada request —eso es justamente lo que la hace barata—. La baja recién se vuelve a evaluar en el próximo login. Lo dejé escrito como límite asumido del M1, en el código y acá, porque es el tipo de decisión que en la defensa me pueden preguntar y quiero tener la respuesta pensada de antemano: es un intercambio deliberado entre costo por request y frescura de la revocación, no un descuido.

El método, el de siempre pero con más casos que nunca: trece pruebas primero. Las de resolveJwt cubren el primer login del habilitado, que el token original no se mute (devuelvo copia nueva, no toco el que llega), el paso de largo sin correo, los tres rechazos de la decisión y la normalización del correo antes de buscar. Las de resolveSession, la copia de estampas, la inmutabilidad y el token pelado. Y una de punta a punta que encadena las dos funciones y valida literalmente lo que pide el plan: la sesión de un habilitado termina con su rol y su userId. El finder real, buscarUsuarioSesion, es primo hermano del de signin —trae el id además del rol y el estado— y se prueba con Prisma mockeado como todo lo demás. Setenta y dos pruebas en verde, cobertura al 100% sobre lib, tipos, linter, formato y build sin una sola queja. Del bloque de Auth.js queda una sola pieza: la 5e, vincular el ms_oid de Entra en el primer login.