NCBACK-9 work controller for register
Test Workflow / test (push) Successful in 7s

This commit is contained in:
vladp
2025-12-10 04:22:09 +07:00
parent c2ffe08e7d
commit b5b8bebb69
3 changed files with 256 additions and 30 deletions
@@ -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");
}
}
} }