From b5b8bebb6949c13020907cb7738b6e8a4a6b3981 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 10 Dec 2025 04:22:09 +0700 Subject: [PATCH] NCBACK-9 work controller for register --- .../no_copy/controller/ApiController.java | 41 ++- .../no_copy/controller/AuthController.java | 8 - .../no_copy/handler/RegRequestValidator.java | 237 ++++++++++++++++++ 3 files changed, 256 insertions(+), 30 deletions(-) diff --git a/src/main/java/ru/soune/no_copy/controller/ApiController.java b/src/main/java/ru/soune/no_copy/controller/ApiController.java index 0e7a207..8f3f734 100644 --- a/src/main/java/ru/soune/no_copy/controller/ApiController.java +++ b/src/main/java/ru/soune/no_copy/controller/ApiController.java @@ -1,7 +1,6 @@ package ru.soune.no_copy.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.MessageCode; 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.handler.RegRequestValidator; import ru.soune.no_copy.repository.UserRepository; import ru.soune.no_copy.service.AuthService; -import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; @Slf4j @@ -42,11 +38,9 @@ public class ApiController { private AuthService authService; @PostMapping("/v{version}/data") - public ResponseEntity handlePostRequest( - @RequestBody BaseRequest request, - @PathVariable("version") int version) { + public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, @PathVariable("version") int version) { Integer msgId = request.getMsgId(); - Map response = new HashMap<>(); + Map response; switch (msgId) { case 20002: @@ -65,30 +59,24 @@ public class ApiController { return createValidationErrorResponse(bindingResult, msgId); } else { authService.register(regRequest); - - response = Map.of( - "msg_id", msgId, - "message_code", MessageCode.SUCCESS.getCode(), - "message_desc", MessageCode.SUCCESS.getDescription()); + response = fillResponseInfo(msgId, MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription()); } break; + case 20001: default: - return ResponseEntity.ok().body(Map.of( - "msg_id", msgId, - "message_code", MessageCode.MSG_ID_NOT_FOUND.getCode(), - "message_desc", MessageCode.MSG_ID_NOT_FOUND.getDescription())); + return ResponseEntity.ok().body(fillResponseInfo(msgId, MessageCode.MSG_ID_NOT_FOUND.getCode(), + MessageCode.MSG_ID_NOT_FOUND.getDescription())); } - return ResponseEntity.status(MessageCode.SUCCESS.getCode()).body(response); + return ResponseEntity.ok().body(response); } private ResponseEntity> createValidationErrorResponse( BindingResult bindingResult, Integer msgId) { - Map response = new HashMap<>(); - response.put("msg_id", msgId); - response.put("message_code", MessageCode.INVALID_FIELD.getCode()); - response.put("message_desc", MessageCode.INVALID_FIELD.getDescription()); + Map response = fillResponseInfo(msgId, MessageCode.INVALID_FIELD.getCode(), + MessageCode.INVALID_FIELD.getDescription()); List> fieldErrors = bindingResult.getFieldErrors() .stream() @@ -113,4 +101,13 @@ public class ApiController { return errorDetail; } + + private Map fillResponseInfo(Integer msgId, Integer messageCode, String messageDesc) { + Map response = new HashMap<>(); + response.put("msg_id", msgId); + response.put("message_code", messageCode); + response.put("message_desc", messageDesc); + + return response; + } } \ No newline at end of file diff --git a/src/main/java/ru/soune/no_copy/controller/AuthController.java b/src/main/java/ru/soune/no_copy/controller/AuthController.java index 3edeb6f..5586e80 100644 --- a/src/main/java/ru/soune/no_copy/controller/AuthController.java +++ b/src/main/java/ru/soune/no_copy/controller/AuthController.java @@ -25,14 +25,6 @@ public class AuthController { private final AuthTokenRepository authTokenRepository; -// @PostMapping("/register") -// public ResponseEntity 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") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { AuthToken login = authService.login(request); diff --git a/src/main/java/ru/soune/no_copy/handler/RegRequestValidator.java b/src/main/java/ru/soune/no_copy/handler/RegRequestValidator.java index 98cf464..f7ea83e 100644 --- a/src/main/java/ru/soune/no_copy/handler/RegRequestValidator.java +++ b/src/main/java/ru/soune/no_copy/handler/RegRequestValidator.java @@ -5,8 +5,13 @@ import org.springframework.validation.Errors; import org.springframework.validation.Validator; import ru.soune.no_copy.dto.RegRequest; +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) { @@ -18,6 +23,150 @@ public class RegRequestValidator implements Validator { 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"); + } + + 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 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) { @@ -42,4 +191,92 @@ public class RegRequestValidator implements Validator { 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 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 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"); + } + } }