UDT-008: Gestión completa de usuarios #11

Merged
dmolinari merged 16 commits from feature/UDT-008 into main 2026-04-16 00:01:36 +00:00
Owner

Summary

Cierre definitivo del módulo Auth. CRUD completo de usuarios + passwords + guard anti-lockout.

Backend (.NET 10)

  • Migración V008: columna MustChangePassword BIT + índice IX_Usuario_Activo_Rol
  • 7 handlers nuevos: List / GetById / Update / Deactivate / Reactivate / ChangeMyPassword / ResetPassword
  • DomainException hierarchy + LastAdminLockoutException + UsuarioNotFoundException + CannotSelfResetException
  • Anti-lockout guard: bloquea desactivar o cambiar rol del último admin activo
  • Revoca refresh tokens en Update (cambio rol/activo), Deactivate, ResetPassword. NO en ChangeMyPassword (acción voluntaria)
  • TempPasswordGenerator con RNG seguro (12 chars, sin ambiguos)
  • LoginResponse extiende con username y mustChangePassword; UltimoLogin se actualiza best-effort
  • Entidad Usuario inmutable con factories With…

Frontend (React 19)

  • authStore.AuthUser += username + mustChangePassword + acción updateUser
  • MustChangePasswordGate → redirect forzoso a /perfil/contrasena si flag activo
  • UsersListPage (filtros rol/activo/search + paginación), UserDetailPage, UserEditPage
  • ChangeMyPasswordPage (con confirm match client-side), ResetPasswordModal (copy + warning única vez)
  • Router wireado + nav link "Usuarios"

Out of scope (diferido)

  • UDT-009 nueva: activación de Usuario.PermisosJson overrides — catalogada en STATUS
  • ADM-004: auditoría centralizada (Issue #6 sigue abierto)

SDD Artifacts (engram)

sdd/udt-008-gestion-usuarios/* — explore / proposal / spec / design / tasks / apply-progress / verify-report

Test plan

  • dotnet test tests/SIGCM2.Application.Tests — 287/287
  • dotnet test tests/SIGCM2.Api.Tests — 100/100
  • cd src/web && npx vitest run — 117/117
  • Correr V008 en SIGCM2 (dev) antes de probar manualmente — ODBC Driver 17 no está instalado en la workstation, aplicar desde SSMS/Azure Data Studio
  • Smoke test manual: login → forzar mustChangePassword → reset por admin → cambio → verify
## Summary Cierre definitivo del módulo Auth. CRUD completo de usuarios + passwords + guard anti-lockout. **Backend (.NET 10)** - Migración **V008**: columna `MustChangePassword BIT` + índice `IX_Usuario_Activo_Rol` - 7 handlers nuevos: List / GetById / Update / Deactivate / Reactivate / ChangeMyPassword / ResetPassword - `DomainException` hierarchy + `LastAdminLockoutException` + `UsuarioNotFoundException` + `CannotSelfResetException` - Anti-lockout guard: bloquea desactivar o cambiar rol del último admin activo - Revoca refresh tokens en Update (cambio rol/activo), Deactivate, ResetPassword. NO en ChangeMyPassword (acción voluntaria) - `TempPasswordGenerator` con RNG seguro (12 chars, sin ambiguos) - `LoginResponse` extiende con `username` y `mustChangePassword`; `UltimoLogin` se actualiza best-effort - Entidad `Usuario` inmutable con factories `With…` **Frontend (React 19)** - `authStore.AuthUser` += `username` + `mustChangePassword` + acción `updateUser` - `MustChangePasswordGate` → redirect forzoso a `/perfil/contrasena` si flag activo - `UsersListPage` (filtros rol/activo/search + paginación), `UserDetailPage`, `UserEditPage` - `ChangeMyPasswordPage` (con confirm match client-side), `ResetPasswordModal` (copy + warning única vez) - Router wireado + nav link "Usuarios" ## Out of scope (diferido) - **UDT-009 nueva**: activación de `Usuario.PermisosJson` overrides — catalogada en STATUS - **ADM-004**: auditoría centralizada (Issue #6 sigue abierto) ## SDD Artifacts (engram) `sdd/udt-008-gestion-usuarios/*` — explore / proposal / spec / design / tasks / apply-progress / verify-report ## Test plan - [x] `dotnet test tests/SIGCM2.Application.Tests` — 287/287 - [x] `dotnet test tests/SIGCM2.Api.Tests` — 100/100 - [x] `cd src/web && npx vitest run` — 117/117 - [x] **Correr V008 en `SIGCM2` (dev)** antes de probar manualmente — ODBC Driver 17 no está instalado en la workstation, aplicar desde SSMS/Azure Data Studio - [x] Smoke test manual: login → forzar mustChangePassword → reset por admin → cambio → verify
dmolinari added 13 commits 2026-04-15 23:46:37 +00:00
Batch 7: POST /api/v1/users/{id}/password/reset (admin only).
- TempPasswordGenerator: RandomNumberGenerator.Fill, 12-char min, full charset diversity, never logs result
- ResetUsuarioPasswordCommandHandler: self-reset guard, 404, hash, mustChangePassword=true, revoke all tokens
- ExceptionFilter: CannotSelfResetException → 400 {error: cannot-self-reset}
- Unit tests: TempPasswordGeneratorTests (8), ResetUsuarioPasswordCommandHandlerTests (5)
- Integration tests: ResetPasswordEndpointTests (6) — 200/length/self-reset/404/401/403
- Agrega ProtectedPage helper que combina ProtectedRoute + MustChangePasswordGate + ProtectedLayout
- Rutas nuevas: /usuarios, /usuarios/:id, /usuarios/:id/editar con permisos RBAC
- /perfil/contrasena sin MustChangePasswordGate (evita redirect loop)
- Sidebar: sección "Mi cuenta" con cambio de contraseña; link Usuarios en sección admin
dmolinari added 1 commit 2026-04-15 23:54:28 +00:00
El componente ResetPasswordModal estaba implementado pero nunca montado en una pagina.
Ahora se renderiza en UserDetailPage, oculto cuando el target es el usuario logueado
(evita hit de cannot-self-reset en backend).
dmolinari added 1 commit 2026-04-15 23:55:27 +00:00
La seccion Mi cuenta en el sidebar quedaba desprolija con un unico item.
Se movio Cambiar contraseña al dropdown del avatar en AppHeader donde
pertenece semanticamente.
dmolinari added 1 commit 2026-04-16 00:00:10 +00:00
El row click de UsersListPage navega directo a /usuarios/:id/editar,
por lo que el modal montado solo en UserDetailPage no era alcanzable
desde el flujo real. Ahora tambien esta en el header del EditPage,
al lado del boton Volver, oculto cuando el target es el user logueado.
dmolinari merged commit 68897f446b into main 2026-04-16 00:01:36 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dmolinari/SIG-CM2.0#11