El link de reset por mail es inalcanzable para el usuario que lo necesita: proxy.ts no excluye /reset-password del force-change
Medido por audit sobre origin/dev, lectura estatica. NO se ejercito una sesion viva: el orden middleware-vs-hook se lee del codigo, no se observo. CAUSA RAIZ, src/proxy.ts:271-287: forcePasswordChange = (mustChangePassword || passwordExpired) && !isOidcSession. Cuando es true y hay sesion, TODO request que no cuelgue de /auth/change-password, /api/, /user/login o /_next se redirige a /auth/change-password?reason=expired. **/reset-password NO esta en esa lista**, y el matcher de :396-404 solo excluye _next/static|_next/image|favicon.ico. Asi que un usuario logueado con password vencida que hace click en el link del mail (src/lib/email.ts:262) NUNCA llega a la pagina. DOS GUARDAS QUE SE CONTRADICEN: src/lib/password-expiration.ts:90-98 (defaultExcludedPaths) SI incluye /reset-password, y src/hooks/usePasswordExpiration.ts:55-64 tambien lo saltea. Las dos estan bien y NINGUNA corre antes del middleware. Ademas nada llama a enforcePasswordExpiration (password-expiration.ts:81): definicion unica, CERO call sites, o sea esa lista esta muerta en este camino. Y EL DESTINO NO PUEDE SERVIR AL USUARIO: /auth/change-password exige currentPassword (:37, :55, :65) y renderiza el input incondicionalmente (:136-141); POST /api/auth/change-password:101 hace bcrypt.compare y devuelve Contrasena actual incorrecta en :109-114. El que se OLVIDO la password no tiene que tipear. Callejon sin salida. DEFECTO SECUNDARIO: PUT /api/auth/reset-password limpia el token y pone la expiracion de 90 dias pero NUNCA setea mustChangePassword:false -el POST de change-password si lo hace-. Un usuario de primer login que completa el reset por token queda flagueado y lo vuelven a redirigir. RESTRICCION DE AUTORIZACION QUE FIJA EL FIX: NO sacar el requisito de currentPassword en /api/auth/change-password para un llamador meramente autenticado por sesion - eso convierte una sesion robada en toma total de la cuenta sin conocer el secreto viejo. El token del mail ES el factor de posesion, asi que el camino del token es el punto seguro de convergencia. FIX MINIMO: (a) agregar /reset-password a la lista de exclusion de proxy.ts:274-280, una linea; el token sigue siendo la autorizacion. (b) setear mustChangePassword:false en el handler PUT junto con la limpieza del token. (c) en /auth/change-password, cuando el usuario no tiene password actual que ofrecer, linkear a pedir mail de reset en vez de sacar el campo. (a) y (b) son el defecto; (c) es el hueco de UX.
Activity
-
Asignado en firme a coder03-mba-l-cc el 2026-09-07 14:55. Restriccion que fija el fix, repetida aca porque es lo que se puede perder: NO sacar el requisito de currentPassword en /api/auth/change-password para un llamador meramente autenticado por sesion. El token del mail es el factor de posesion y es el punto seguro de convergencia. (a) y (b) son el defecto; (c) es UX y puede ir despues. MOVILBA-327 (estado de exito del formulario de pedido) es de coder-mba-l-ca y no se pisa con este.
-
(a)+(b) shipped a0914729, dev v5.5.40 patch 40. (a) /reset-password added to the forcePasswordChange exclusion list in src/proxy.ts; the page's authorization is the emailed token (possession factor), currentPassword stays required on /api/auth/change-password for a session-only caller. (b) PUT /api/auth/reset-password now sets mustChangePassword:false alongside the token cleanup, matching change-password:145. Verified after deploy: GET /reset-password?token=... on dev01 returns 200 unauthenticated. NOT exercised: a live session with an expired password - the middleware-vs-hook order is read from the code. (c), the change-password link to request a reset mail, is UX and remains OPEN under this item's follow-up, not shipped.