@@ -29,15 +29,13 @@ public class GlobalExceptionHandler {
|
||||
"message" ,message));
|
||||
}
|
||||
|
||||
@ExceptionHandler(UserAlreadyExistsException.class)
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
public ResponseEntity<?> handleUserContainsException(UserAlreadyExistsException ex) {
|
||||
return ResponseEntity.
|
||||
badRequest()
|
||||
.body(Map.of(
|
||||
"success", false,
|
||||
"message" ,ex.getMessage()
|
||||
));
|
||||
@ExceptionHandler(NotValidFieldException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public ResponseEntity<?> handleUserContainsException(NotValidFieldException ex) {
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.body(ex.getBaseResponse());
|
||||
}
|
||||
|
||||
@ExceptionHandler(UserNotFoundException.class)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.entity.AuthToken;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.AuthService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LoginRequestHandler implements RequestHandler {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final AuthService authService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) {
|
||||
LoginRequest loginRequest = objectMapper.convertValue(request.getMessageBody(), LoginRequest.class);
|
||||
|
||||
if (!userRepository.existsByEmail(loginRequest.getEmail())) {
|
||||
LoginAnswer loginAnswer = new LoginAnswer();
|
||||
loginAnswer.setFieldErrors(Arrays.asList(Map.of("email", loginRequest.getEmail())));
|
||||
|
||||
throw new NotValidFieldException("User with email not found: " + loginRequest.getEmail(),
|
||||
new BaseResponse(request.getMsgId(), MessageCode.AUTH_EMAIL_NOT_FOUND.getCode(),
|
||||
MessageCode.AUTH_EMAIL_NOT_FOUND.getDescription(), loginAnswer));
|
||||
}
|
||||
|
||||
AuthToken authToken = authService.login(loginRequest);
|
||||
|
||||
LoginAnswer loginAnswer = new LoginAnswer();
|
||||
loginAnswer.setToken(authToken.getToken());
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), loginAnswer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.entity.AuthToken;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.exception.ValidationException;
|
||||
import ru.soune.nocopy.handler.validator.RegRequestValidator;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.AuthService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RegRequestHandler implements RequestHandler {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final RegRequestValidator regRequestValidator;
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws ValidationException {
|
||||
RegRequest regRequest = objectMapper.convertValue(request.getMessageBody(), RegRequest.class);
|
||||
|
||||
if (userRepository.existsByEmail(regRequest.getEmail()) || userRepository.existsByPhone(regRequest.getPhone())) {
|
||||
RegAnswer regAnswer = new RegAnswer();
|
||||
regAnswer.setFieldErrors(Arrays.asList(Map.of("email", regRequest.getEmail())));
|
||||
regAnswer.setFieldErrors(Arrays.asList(Map.of("phone", regRequest.getPhone())));
|
||||
|
||||
throw new NotValidFieldException("User already exists with email:" + regRequest.getEmail() + " or phone: " +
|
||||
regRequest.getPhone(), new BaseResponse(request.getMsgId(),
|
||||
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getCode(),
|
||||
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
|
||||
}
|
||||
|
||||
BindingResult bindingResult = new BeanPropertyBindingResult(regRequest, "regRequest");
|
||||
regRequestValidator.validate(regRequest, bindingResult);
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new ValidationException(bindingResult, request.getMsgId());
|
||||
}
|
||||
|
||||
AuthToken authToken = authService.register(regRequest);
|
||||
|
||||
RegAnswer regAnswer = new RegAnswer();
|
||||
regAnswer.setToken(authToken.getToken());
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), regAnswer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
|
||||
public interface RequestHandler {
|
||||
BaseResponse handle(BaseRequest request) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package ru.soune.nocopy.handler.validator;
|
||||
|
||||
import org.apache.commons.validator.routines.DomainValidator;
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import ru.soune.nocopy.dto.RegRequest;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
public class RegRequestValidator implements Validator {
|
||||
private static final String COMPANY_REGEX = "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'()]{0,200}$";
|
||||
|
||||
private static final String NAME_REGEX = "^[a-zA-Zа-яА-ЯёЁ\\s\\-'.]{2,100}$";
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return RegRequest.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
RegRequest request = (RegRequest) target;
|
||||
|
||||
validatePhone(request.getPhone(), errors);
|
||||
validatePassword(request.getPassword(), errors);
|
||||
validateEmail(request.getEmail(), errors);
|
||||
validateCompanyName(request.getCompanyName(), errors);
|
||||
validateFullName(request.getFullName(), errors);
|
||||
}
|
||||
|
||||
private void validateFullName(String fullName, Errors errors) {
|
||||
if (fullName == null || fullName.trim().isEmpty()) {
|
||||
errors.rejectValue("fullName", "fullName.required",
|
||||
"Full name is required");
|
||||
return;
|
||||
}
|
||||
|
||||
String trimmedName = fullName.trim();
|
||||
|
||||
if (trimmedName.length() < 2) {
|
||||
errors.rejectValue("fullName", "fullName.too.short",
|
||||
"Full name must be at least 2 characters");
|
||||
}
|
||||
|
||||
if (trimmedName.length() > 100) {
|
||||
errors.rejectValue("fullName", "fullName.too.long",
|
||||
"Full name must be less than 100 characters");
|
||||
}
|
||||
|
||||
if (!trimmedName.matches(NAME_REGEX)) {
|
||||
errors.rejectValue("fullName", "fullName.invalid.chars",
|
||||
"Name can only contain letters, spaces, hyphens and apostrophes");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCompanyName(String companyName, Errors errors) {
|
||||
if (companyName == null || companyName.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String trimmedCompany = companyName.trim();
|
||||
|
||||
if (trimmedCompany.length() > 200) {
|
||||
errors.rejectValue("companyName", "companyName.too.long",
|
||||
"Company name must be less than 200 characters");
|
||||
}
|
||||
|
||||
if (!trimmedCompany.matches(COMPANY_REGEX)) {
|
||||
errors.rejectValue("companyName", "companyName.invalid.chars",
|
||||
"Company name contains invalid characters");
|
||||
}
|
||||
|
||||
validateForbiddenWords(trimmedCompany, "companyName", errors,
|
||||
Arrays.asList("admin", "root", "system", "test"));
|
||||
}
|
||||
|
||||
private void validateForbiddenWords(String text, String fieldName,
|
||||
Errors errors, List<String> forbiddenWords) {
|
||||
String lowerText = text.toLowerCase();
|
||||
for (String word : forbiddenWords) {
|
||||
if (lowerText.contains(word)) {
|
||||
errors.rejectValue(fieldName, fieldName + ".forbidden.word",
|
||||
"Contains forbidden word: " + word);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEmail(String email, Errors errors) {
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
errors.rejectValue("email", "email.is.empty", "Email must not be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
String trimmedEmail = email.trim().toLowerCase();
|
||||
|
||||
if (trimmedEmail.length() > 254) {
|
||||
errors.rejectValue("email", "email.too.long", "Email is too long");
|
||||
return;
|
||||
}
|
||||
|
||||
EmailValidator emailValidator = EmailValidator.getInstance(true, true);
|
||||
if (!emailValidator.isValid(trimmedEmail)) {
|
||||
errors.rejectValue("email", "email.invalid", "Invalid email address");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void validatePhone(String phone, Errors errors) {
|
||||
if (phone == null || phone.trim().isEmpty()) {
|
||||
errors.rejectValue("phone", "phone.empty",
|
||||
"Test phone numbers are not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
String trimmedPhone = phone.trim();
|
||||
String digitsOnly = trimmedPhone.replaceAll("[^0-9+]", "");
|
||||
|
||||
if (digitsOnly.length() < 11 || digitsOnly.length() > 14) {
|
||||
errors.rejectValue("phone", "phone.invalid.length",
|
||||
"Phone number must contain 11-14 digits");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!digitsOnly.matches("^(\\+7|8|7)[0-9]{10}$")) {
|
||||
errors.rejectValue("phone", "phone.invalid.format",
|
||||
"Invalid phone number format");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePassword(String password, Errors errors) {
|
||||
if (password == null || password.isEmpty()) {
|
||||
errors.rejectValue("password", "password.empty",
|
||||
"Password are not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length() < 8) {
|
||||
errors.rejectValue("password", "password.too.short",
|
||||
"Password must be at least 8 characters");
|
||||
}
|
||||
|
||||
if (password.length() > 50) {
|
||||
errors.rejectValue("password", "password.too.long",
|
||||
"Password must be less than 50 characters");
|
||||
}
|
||||
|
||||
if (password.contains(" ")) {
|
||||
errors.rejectValue("password", "password.contains.spaces",
|
||||
"Password cannot contain spaces");
|
||||
}
|
||||
|
||||
// recomment if need complexity
|
||||
// checkPasswordComplexity(password, errors);
|
||||
|
||||
// reccoment if need check simply standart password
|
||||
// checkCommonPasswords(password, errors);
|
||||
|
||||
// reccoment if need check simply standart password
|
||||
// checkForSequences(password, errors);
|
||||
}
|
||||
|
||||
private void checkPasswordComplexity(String password, Errors errors) {
|
||||
boolean hasUpper = false;
|
||||
boolean hasLower = false;
|
||||
boolean hasDigit = false;
|
||||
boolean hasSpecial = false;
|
||||
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) hasUpper = true;
|
||||
if (Character.isLowerCase(c)) hasLower = true;
|
||||
if (Character.isDigit(c)) hasDigit = true;
|
||||
if ("!@#$%^&*()_+-=[]{}|;:,.<>?".indexOf(c) >= 0) hasSpecial = true;
|
||||
}
|
||||
|
||||
List<String> missing = new ArrayList<>();
|
||||
if (!hasUpper) missing.add("uppercase letter");
|
||||
if (!hasLower) missing.add("lowercase letter");
|
||||
if (!hasDigit) missing.add("digit");
|
||||
if (!hasSpecial) missing.add("special character");
|
||||
|
||||
if (!missing.isEmpty()) {
|
||||
String message = "Password must contain: " + String.join(", ", missing);
|
||||
errors.rejectValue("password", "password.complexity.missing", message);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCommonPasswords(String password, Errors errors) {
|
||||
Set<String> commonPasswords = Set.of(
|
||||
"password", "12345678", "qwerty123", "admin123",
|
||||
"welcome1", "password1", "123456789", "qwertyuiop"
|
||||
);
|
||||
|
||||
if (commonPasswords.contains(password.toLowerCase())) {
|
||||
errors.rejectValue("password", "password.too.common",
|
||||
"This password is too common, please choose another");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForSequences(String password, Errors errors) {
|
||||
String lower = password.toLowerCase();
|
||||
|
||||
if (lower.matches(".*123456.*") || lower.matches(".*987654.*")) {
|
||||
errors.rejectValue("password", "password.sequence.numbers",
|
||||
"Password contains predictable number sequence");
|
||||
}
|
||||
|
||||
if (lower.matches(".*abcdef.*") || lower.matches(".*qwerty.*")) {
|
||||
errors.rejectValue("password", "password.sequence.letters",
|
||||
"Password contains predictable letter sequence");
|
||||
}
|
||||
|
||||
if (lower.matches(".*(.)\\1{3,}.*")) {
|
||||
errors.rejectValue("password", "password.repeating.chars",
|
||||
"Password contains too many repeating characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user