Add username validation and password breach check to signup

- Enforced alphanumeric-only usernames using regex validation.
- Passwords must be >= 8 chars and checked against haveibeenpwned.com.
- Improved SignupRequest DTO with validation annotations.
- Implemented UserService to handle password validation and encoding.
This commit is contained in:
K
2025-07-03 02:26:30 +05:30
parent 8ae2ced645
commit 39aa31625d
3 changed files with 99 additions and 0 deletions
@@ -0,0 +1,24 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.dto.SignupRequest;
import com.skycrate.backend.skycrateBackend.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class SignupController {
private final UserService userService;
public SignupController(UserService userService) {
this.userService = userService;
}
@PostMapping("/signup")
public ResponseEntity<?> signup(@Valid @RequestBody SignupRequest request) {
userService.registerUser(request);
return ResponseEntity.ok("User registered successfully");
}
}
@@ -0,0 +1,23 @@
package com.skycrate.backend.skycrateBackend.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class SignupRequest {
@NotBlank
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "Username must be alphanumeric only")
private String username;
@NotBlank
@Email
private String email;
@NotBlank
@Size(min = 8, message = "Password must be at least 8 characters long")
private String password;
// Getters and Setters
}
@@ -0,0 +1,52 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.dto.SignupRequest;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.math.BigInteger;
import java.security.MessageDigest;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public void registerUser(SignupRequest request) {
if (isPasswordPwned(request.getPassword())) {
throw new IllegalArgumentException("Password has been compromised in data breaches.");
}
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
userRepository.save(user);
}
private boolean isPasswordPwned(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(password.getBytes());
String fullHash = String.format("%040x", new BigInteger(1, hash)).toUpperCase();
String prefix = fullHash.substring(0, 5);
String suffix = fullHash.substring(5);
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("https://api.pwnedpasswords.com/range/" + prefix, String.class);
return response != null && response.contains(suffix);
} catch (Exception e) {
return false; // If API fails, allow but log in production
}
}
}