UDT-003: Registro de Usuarios (admin-only) + fix JWT claim mapping #4
Reference in New Issue
Block a user
Delete Branch "feature/UDT-003"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implementa UDT-003: Registro de Usuarios siguiendo flujo SDD completo (explore → proposal → spec → design → tasks → apply → verify → archive) en modo Strict TDD.
POST /api/v1/usersadmin-only ([Authorize(Roles="admin")]) con BCrypt cost 12, password policy configurable, mapeo UQ 2627 +UsernameAlreadyExistsException→ 409./usuarios/nuevocon guard admin,UserForm(RHF + Zod),useCreateUser(TanStack Query), nav link admin-only.JwtBearerOptions.MapInboundClaims=false— desbloqueaLogin_Refresh_Logout_FullFlowque quedó rojo desde UDT-002 (middleware mapeabasub → ClaimTypes.NameIdentifiery rompíaUser.FindFirst("sub")).Commits
3d598fafeat(api): UDT-003 registro de usuarios — backend completo (Phases 1-6)dd99e5cfeat(web): UDT-003 formulario de alta de usuarios (admin)bce591efix(auth): preserve JWT claim names in bearer middlewareRequirements cubiertos (5/5)
Test plan
dotnet build src/src.sln→ 0 errores, 0 warningsdotnet test tests/SIGCM2.Application.Tests→ 107/107 PASSEDdotnet test tests/SIGCM2.Api.Tests→ 15/15 PASSED (incluye 7 nuevos UDT-003 + el flow completo login/refresh/logout ya en verde)cd src/web && npx tsc --noEmit→ cleannpx vitest run→ 39/39 PASSED (12 nuevos UDT-003)/usuarios/nuevo→ login con el usuario recién creado ✅Archivos destacados
Backend nuevos
src/api/SIGCM2.Application/Usuarios/Create/— Command + Handler + Validator + DTOsrc/api/SIGCM2.Domain/Exceptions/UsernameAlreadyExistsException.cssrc/api/SIGCM2.Api/Controllers/UsuariosController.cstests/SIGCM2.Api.Tests/Usuarios/CreateUsuarioEndpointTests.csBackend modificados
src/api/SIGCM2.Domain/Entities/Usuario.cs— factoryForCreationsrc/api/SIGCM2.Application/Abstractions/Persistence/IUsuarioRepository.cs—+AddAsync,+ExistsByUsernameAsyncsrc/api/SIGCM2.Infrastructure/Persistence/UsuarioRepository.cs— impl Dappersrc/api/SIGCM2.Infrastructure/DependencyInjection.cs—RoleClaimType="rol",MapInboundClaims=false,NameClaimType="name"src/api/SIGCM2.Api/Filters/ExceptionFilter.cs— +409 handlerstests/SIGCM2.TestSupport/TestWebAppFactory.cs— overrideSqlConnectionFactorysingleton conConfigureTestServicesFrontend nuevos
src/web/src/features/users/{api,hooks,components,pages}/src/web/src/tests/features/users/Frontend modificados
src/web/src/router.tsx— ruta/usuarios/nuevosrc/web/src/components/layout/AppSidebar.tsx— link admin-onlyDecisiones arquitecturales
[Authorize(Roles="admin")]; RBAC granular se difiere a UDT-005/006.Usuarios/separada deAuth/(alta es gestión de usuarios, no auth).ExceptionFiltermapeaSqlException 2627+UsernameAlreadyExistsException→ 409.AuthOptions(min 8, letra + dígito por default).Deuda / follow-ups
Registrados como issues con label
followup(no dependen de este PR body para sobrevivir):[UDT-005/006] Crear ProtectedRoute reutilizable con rol-check en frontend[Auditoría] Registrar admin creador en alta de usuariosGap de roadmap detectado: agregado
UDT-008: Gestión completa de usuariosenSTATUS.md(list / edit / deactivate / password-change / reset; requiere UDT-006 cerrado).Engram artifact trail
sdd/udt-003-registro-usuarios/{explore,proposal,spec,design,tasks,apply-progress,verify-report,archive-report}(IDs enarchive-report).JwtBearerOptions.MapInboundClaims defaulted to true, which mapped the 'sub' claim to ClaimTypes.NameIdentifier in HttpContext.User. Logout endpoint read User.FindFirst("sub") and got null, returning 401 for any authenticated caller. Fix: set MapInboundClaims=false and pin NameClaimType="name" so the JWT claims land in the principal with their original names, aligning with how JwtService.GetPrincipalFromExpiredToken (used by refresh) already consumes them. Unblocks Login_Refresh_Logout_FullFlow integration test (15/15 green).