directlx-claude-config/plans/enchanted-meandering-aho.md

3.8 KiB

Plan: Admin API Endpoint — Reset User Password

Context

hiveops-auth has no password reset functionality. The login page has a dead "Forgot Password" link. The admin role and /auth/api/admin/** route prefix already exist. Adding an admin password reset endpoint gives operators a way to reset user passwords without direct DB access. The PASSWORD_CHANGED audit event type and logPasswordChanged() method are already defined but never called.

Files to Create / Modify

Action File
CREATE src/main/java/com/hiveops/auth/dto/request/AdminPasswordResetRequest.java
MODIFY src/main/java/com/hiveops/auth/service/AuthService.java
CREATE src/main/java/com/hiveops/auth/controller/UserAdminController.java

Implementation Steps

1. DTO — AdminPasswordResetRequest.java

  • Single field: newPassword (String)
  • Jakarta validation: @NotBlank, @Size(min=12) — mirrors the existing RegisterRequest pattern
  • Package: com.hiveops.auth.dto.request

2. AuthService — resetUserPassword(String email, String newPassword)

  • Lookup user with userRepository.findByEmail(email) → throw UsernameNotFoundException (Spring Security) if absent (already handled by GlobalExceptionHandler returning 404-ish; check and use an appropriate existing exception or plain RuntimeException)
  • Validate password strength via PasswordValidator.validatePassword(newPassword) — throw IllegalArgumentException with the error message if invalid
  • Encode: passwordEncoder.encode(newPassword)user.setPasswordHash(...)
  • Side effects for security:
    • user.resetFailedLoginAttempts() — clears lock + counter
    • Revoke all existing refresh tokens via refreshTokenRepository.revokeAllByUser(user) (already exists — sets revoked=true, preserving audit history)
  • userRepository.save(user)
  • authAuditService.logPasswordChanged(user) — already implemented, just unused

3. Controller — UserAdminController.java

  • Pattern mirrors RateLimitAdminController exactly:
    • @RestController, @RequestMapping("/auth/api/admin/users"), @RequiredArgsConstructor, @Slf4j
    • @Tag(name = "User Admin", description = "User management (Admin only)")
    • All endpoints: @PreAuthorize("hasRole('ADMIN')")
  • Single endpoint for now:
    POST /auth/api/admin/users/{email}/reset-password
    Body: { "newPassword": "..." }
    Response 200: { "message": "Password reset successfully", "email": "..." }
    Response 404: { "message": "User not found", "email": "..." }
    Response 400: { "message": "<validation error>", "email": "..." }
    
  • Log at INFO level: "Admin password reset for user: {}" (email only, never the password)

Key Reused Components

  • PasswordEncoder bean (BCrypt strength 12) — injected into AuthService
  • PasswordValidator.validatePassword()util/PasswordValidator.java
  • authAuditService.logPasswordChanged(user)service/AuthAuditService.java
  • userRepository.findByEmail()repository/UserRepository.java
  • refreshTokenRepository.revokeAllByUser(user)repository/RefreshTokenRepository.java
  • user.resetFailedLoginAttempts()entity/User.java
  • @PreAuthorize("hasRole('ADMIN')") pattern — RateLimitAdminController.java

Verification

  1. Build: ./mvnw compile — must be clean
  2. Start service (dev profile, H2 in-memory)
  3. Register a test user, obtain an admin JWT
  4. POST /auth/api/admin/users/{email}/reset-password with valid new password → 200
  5. POST /auth/api/login with old password → 401
  6. POST /auth/api/login with new password → 200
  7. Attempt same endpoint without ADMIN role → 403
  8. Attempt with unknown email → 404
  9. Attempt with weak password (< 12 chars) → 400
  10. Check audit log table for PASSWORD_CHANGED event