Saltar a contenido

5 de julio de 2026

Cableé el callback de signIn: recién ahora la decisión de acceso que dejé pura en la Task 3 empieza a decidir de verdad quién entra.

La whitelist ya decide en el login: el callback de signIn (Task 5c)

Este es el commit que le da sentido a un montón de lo que vine dejando preparado. Hace varias jornadas escribí, aisladas y puras, las reglas de acceso: quién puede entrar y con qué rol (decideAccess), cómo se normaliza un correo, cómo se empareja a la persona. Todo probado, pero sin enchufar a nada. Hoy por fin las conecté al flujo real: cuando alguien inicia sesión con Entra, el callback de signIn consulta la whitelist y deja que esa decisión resuelva si pasa o lo mandamos a /denied.

La pieza con lógica quedó en resolveSignIn. Hace la coreografía del borde: agarra el correo que trae Entra, lo lleva a su forma canónica con la normalización de la 3b, busca la fila en la whitelist y le pasa el resultado a decideAccess. Lo lindo es que decideAccess no cambió ni una línea: sigue siendo la función pura de la Task 3, ajena a Auth.js y a la base. Este borde es exactamente el que ella esperaba que alguien escribiera. Y aproveché algo que había dejado sembrado sin saber bien cuándo lo iba a usar: la decisión, cuando niega, devuelve un motivo —inexistente, inactivo, rol desconocido—. Ese motivo ahora viaja en la query hacia /denied, así la página de rechazo va a poder explicar la causa en vez de tirar un "no podés entrar" a secas. Me gustó ver que una decisión de diseño de hace días encajara sola.

Tuve que tomar una decisión de método y la dejo anotada porque me aparté a propósito de la letra del plan. El plan decía "validar con integración contra una base de test". Pero este repo, hasta hoy, prueba el acceso a datos mockeando Prisma —lo hace db.test.ts—, y no tiene ninguna base de test montada en el arnés. Levantar una habría sido meter mano en la infraestructura, y eso no lo hago sin confirmarlo. Así que fui por el camino que ya estaba trazado y que además es más limpio: resolveSignIn recibe el finder por parámetro, con lo cual toda la orquestación se prueba con dobles, sin base; y el finder real contra Postgres, buscarUsuarioWhitelist, se prueba aparte con el cliente mockeado, igual que el resto. Corregí la línea del plan para que diga lo que de verdad hice, no lo que había anotado de arranque. El intent se cumple igual —habilitado entra, inexistente o inactivo no—; lo que cambió es el cómo, y el plan tiene que contar la verdad.

La otra decisión fue arquitectónica y tiene que ver con dónde vive el callback. Lo puse del lado node, en src/auth.ts, y no en la config edge-safe que armé en la 5b. La razón es concreta: el callback consulta la base, y si metiera esa consulta en la config que después voy a reusar desde el middleware del edge, estaría arrastrando Prisma a un runtime donde no tiene que estar. Auth.js separa justamente por eso las dos mitades, y quise respetar ese corte desde ahora y no tener que desenredarlo en la Task 6. La config sigue liviana; el pegamento con base queda en el arranque node.

El ciclo, el de siempre: ocho pruebas primero —las cuatro salidas de la decisión traducidas a lo que espera el callback, el caso sin correo, la normalización antes de buscar, y las dos del finder contra el cliente mockeado—, el rojo, y después las dos funciones. Cincuenta y nueve pruebas en verde en todo el proyecto, cobertura al 100% sobre lib incluido este módulo, tipos, linter, formato y build sin una queja. Queda una punta suelta a propósito: la página /denied todavía no existe, así que por ahora el rechazo redirige a una ruta que aún no está. Eso es de la tanda de pantallas y guards que viene después; el callback ya decide bien, que era lo de esta porción. Lo que sigue en el bloque de Auth.js es la 5d: llevar el rol y el id del usuario hasta la sesión.