This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package ru.soune.no_copy.controller;
|
package ru.soune.no_copy.controller;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -13,17 +12,14 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import ru.soune.no_copy.dto.BaseRequest;
|
import ru.soune.no_copy.dto.BaseRequest;
|
||||||
import ru.soune.no_copy.dto.MessageCode;
|
import ru.soune.no_copy.dto.MessageCode;
|
||||||
import ru.soune.no_copy.dto.RegRequest;
|
import ru.soune.no_copy.dto.RegRequest;
|
||||||
import ru.soune.no_copy.entity.AuthToken;
|
|
||||||
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
||||||
import ru.soune.no_copy.handler.RegRequestValidator;
|
import ru.soune.no_copy.handler.RegRequestValidator;
|
||||||
import ru.soune.no_copy.repository.UserRepository;
|
import ru.soune.no_copy.repository.UserRepository;
|
||||||
import ru.soune.no_copy.service.AuthService;
|
import ru.soune.no_copy.service.AuthService;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -42,11 +38,9 @@ public class ApiController {
|
|||||||
private AuthService authService;
|
private AuthService authService;
|
||||||
|
|
||||||
@PostMapping("/v{version}/data")
|
@PostMapping("/v{version}/data")
|
||||||
public ResponseEntity<?> handlePostRequest(
|
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, @PathVariable("version") int version) {
|
||||||
@RequestBody BaseRequest request,
|
|
||||||
@PathVariable("version") int version) {
|
|
||||||
Integer msgId = request.getMsgId();
|
Integer msgId = request.getMsgId();
|
||||||
Map<String, ? extends Serializable> response = new HashMap<>();
|
Map<String, Object> response;
|
||||||
|
|
||||||
switch (msgId) {
|
switch (msgId) {
|
||||||
case 20002:
|
case 20002:
|
||||||
@@ -65,30 +59,24 @@ public class ApiController {
|
|||||||
return createValidationErrorResponse(bindingResult, msgId);
|
return createValidationErrorResponse(bindingResult, msgId);
|
||||||
} else {
|
} else {
|
||||||
authService.register(regRequest);
|
authService.register(regRequest);
|
||||||
|
response = fillResponseInfo(msgId, MessageCode.SUCCESS.getCode(),
|
||||||
response = Map.of(
|
MessageCode.SUCCESS.getDescription());
|
||||||
"msg_id", msgId,
|
|
||||||
"message_code", MessageCode.SUCCESS.getCode(),
|
|
||||||
"message_desc", MessageCode.SUCCESS.getDescription());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 20001:
|
||||||
default:
|
default:
|
||||||
return ResponseEntity.ok().body(Map.of(
|
return ResponseEntity.ok().body(fillResponseInfo(msgId, MessageCode.MSG_ID_NOT_FOUND.getCode(),
|
||||||
"msg_id", msgId,
|
MessageCode.MSG_ID_NOT_FOUND.getDescription()));
|
||||||
"message_code", MessageCode.MSG_ID_NOT_FOUND.getCode(),
|
|
||||||
"message_desc", MessageCode.MSG_ID_NOT_FOUND.getDescription()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.status(MessageCode.SUCCESS.getCode()).body(response);
|
return ResponseEntity.ok().body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<Map<String, Object>> createValidationErrorResponse(
|
private ResponseEntity<Map<String, Object>> createValidationErrorResponse(
|
||||||
BindingResult bindingResult, Integer msgId) {
|
BindingResult bindingResult, Integer msgId) {
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = fillResponseInfo(msgId, MessageCode.INVALID_FIELD.getCode(),
|
||||||
response.put("msg_id", msgId);
|
MessageCode.INVALID_FIELD.getDescription());
|
||||||
response.put("message_code", MessageCode.INVALID_FIELD.getCode());
|
|
||||||
response.put("message_desc", MessageCode.INVALID_FIELD.getDescription());
|
|
||||||
|
|
||||||
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
|
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
|
||||||
.stream()
|
.stream()
|
||||||
@@ -113,4 +101,13 @@ public class ApiController {
|
|||||||
|
|
||||||
return errorDetail;
|
return errorDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> fillResponseInfo(Integer msgId, Integer messageCode, String messageDesc) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("msg_id", msgId);
|
||||||
|
response.put("message_code", messageCode);
|
||||||
|
response.put("message_desc", messageDesc);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,14 +25,6 @@ public class AuthController {
|
|||||||
|
|
||||||
private final AuthTokenRepository authTokenRepository;
|
private final AuthTokenRepository authTokenRepository;
|
||||||
|
|
||||||
// @PostMapping("/register")
|
|
||||||
// public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest registerRequest) {
|
|
||||||
// AuthToken authToken = authService.register(registerRequest);
|
|
||||||
//
|
|
||||||
// return ResponseEntity.ok(new AuthResponse(true, "success.user.register", authToken.getToken(),
|
|
||||||
// authToken.getExpiresAt()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
AuthToken login = authService.login(request);
|
AuthToken login = authService.login(request);
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import org.springframework.validation.Errors;
|
|||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
import ru.soune.no_copy.dto.RegRequest;
|
import ru.soune.no_copy.dto.RegRequest;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class RegRequestValidator implements Validator {
|
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
|
@Override
|
||||||
public boolean supports(Class<?> clazz) {
|
public boolean supports(Class<?> clazz) {
|
||||||
@@ -18,6 +23,150 @@ public class RegRequestValidator implements Validator {
|
|||||||
RegRequest request = (RegRequest) target;
|
RegRequest request = (RegRequest) target;
|
||||||
|
|
||||||
validatePhone(request.getPhone(), errors);
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] nameParts = trimmedName.split("\\s+");
|
||||||
|
if (nameParts.length < 2) {
|
||||||
|
errors.rejectValue("fullName", "fullName.missing.parts",
|
||||||
|
"Please enter both first and last name");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nameParts.length; i++) {
|
||||||
|
String part = nameParts[i];
|
||||||
|
if (part.length() < 2 && part.length() > 0) {
|
||||||
|
errors.rejectValue("fullName", "fullName.part.too.short",
|
||||||
|
"Each name part must be at least 2 characters");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Character.isUpperCase(part.charAt(0))) {
|
||||||
|
errors.rejectValue("fullName", "fullName.capitalization",
|
||||||
|
"Each name part should start with capital letter");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() > 128) {
|
||||||
|
errors.rejectValue("email", "email.too.long",
|
||||||
|
"Email must be less than 128 characters");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trimmedEmail.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
|
||||||
|
errors.rejectValue("email", "email.invalid.format",
|
||||||
|
"Invalid email format");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = trimmedEmail.split("@");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String localPart = parts[0];
|
||||||
|
String domain = parts[1];
|
||||||
|
|
||||||
|
if (localPart.length() > 64) {
|
||||||
|
errors.rejectValue("email", "email.local.too.long",
|
||||||
|
"Email username too long");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localPart.matches("^[a-z0-9+_.-]+$")) {
|
||||||
|
errors.rejectValue("email", "email.local.invalid.chars",
|
||||||
|
"Email contains invalid characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEmailDomain(domain, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEmailDomain(String domain, Errors errors) {
|
||||||
|
if (domain.length() > 255) {
|
||||||
|
errors.rejectValue("email", "email.domain.too.long",
|
||||||
|
"Email domain too long");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] domainParts = domain.split("\\.");
|
||||||
|
if (domainParts.length < 2) {
|
||||||
|
errors.rejectValue("email", "email.domain.invalid",
|
||||||
|
"Invalid domain name");
|
||||||
|
}
|
||||||
|
|
||||||
|
String tld = domainParts[domainParts.length - 1];
|
||||||
|
if (tld.length() < 2) {
|
||||||
|
errors.rejectValue("email", "email.tld.invalid",
|
||||||
|
"Invalid domain extension");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePhone(String phone, Errors errors) {
|
private void validatePhone(String phone, Errors errors) {
|
||||||
@@ -42,4 +191,92 @@ public class RegRequestValidator implements Validator {
|
|||||||
return;
|
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