NCBACK-33 add verification logic
Test Workflow / test (push) Successful in 3s

This commit is contained in:
vladp
2026-01-20 15:12:24 +07:00
parent 55fa8b84e2
commit f9accd7af1
14 changed files with 137 additions and 44 deletions
@@ -34,7 +34,7 @@ public class UserController {
@GetMapping("/all") @GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() { public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream() List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(), .map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(), u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType())) u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType()))
.toList(); .toList();
@@ -52,19 +52,19 @@ public class UserController {
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
if (authToken != null) { if (authToken != null) {
Optional<User> user = userRepository.findByEmail(email); User user = userRepository.findByEmail(email);
//TODO add mapper //TODO add mapper
if (user.isPresent()) { if (user != null) {
UserDTO userDTO = userMapper.toDTO(user.get()); UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email); userDTO.setEmail(email);
userDTO.setFullName(user.get().getFullName()); userDTO.setFullName(user.getFullName());
userDTO.setCompany(user.get().getCompany()); userDTO.setCompany(user.getCompany());
userDTO.setPhone(user.get().getPhone()); userDTO.setPhone(user.getPhone());
userDTO.setGenderType(user.get().getGenderType()); userDTO.setGenderType(user.getGenderType());
userDTO.setBirthday(user.get().getBirthday()); userDTO.setBirthday(user.getBirthday());
userDTO.setCreatedAt(user.get().getCreatedAt()); userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setSubscriptionType(user.get().getSubscriptionType()); userDTO.setSubscriptionType(user.getSubscriptionType());
userDTO.setActive(user.get().getIsActive()); userDTO.setActive(user.isActive());
return ResponseEntity.ok(userDTO); return ResponseEntity.ok(userDTO);
} }
@@ -6,9 +6,11 @@ public enum MessageCode {
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"), REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
INVALID_FIELD(2, "Invalid field"), INVALID_FIELD(2, "Invalid field"),
INVALID_TOKEN(2, "Invalid token"), INVALID_TOKEN(2, "Invalid token"),
TOKEN_IS_ALIVE(2, "Token is alive"),
INVALID_ACTION(2, "Invalid action"), INVALID_ACTION(2, "Invalid action"),
FILE_UPLOAD_ERROR(2, "File upload error"), FILE_UPLOAD_ERROR(2, "File upload error"),
FILE_DOWNLOAD_ERROR(2, "File download error"), FILE_DOWNLOAD_ERROR(2, "File download error"),
USER_NOT_VERIFIED(2, "User not verified"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"), FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
IMAGE_FOUND_ERROR(2, "Image found error"), IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
@@ -13,5 +13,9 @@ import java.util.Map;
public class LoginAnswer { public class LoginAnswer {
private String Token; private String Token;
private boolean isActive;
private boolean isVerified;
private List<Map<String, String>> fieldErrors; private List<Map<String, String>> fieldErrors;
} }
@@ -13,5 +13,9 @@ import java.util.Map;
public class RegAnswer { public class RegAnswer {
private String Token; private String Token;
private boolean isActive;
private boolean isVerified;
private List<Map<String, String>> fieldErrors; private List<Map<String, String>> fieldErrors;
} }
@@ -14,4 +14,7 @@ public class VerifyUserRequest {
@JsonProperty("user_id") @JsonProperty("user_id")
private Long userId; private Long userId;
@JsonProperty("resend")
private Integer resend;
} }
@@ -28,4 +28,8 @@ public class EmailVerificationToken {
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
public boolean isExpired() {
return LocalDateTime.now().isAfter(expiresAt);
}
} }
@@ -69,7 +69,7 @@ public class User {
private LocalDateTime lastLoginAt; private LocalDateTime lastLoginAt;
@Column(name = "is_active") @Column(name = "is_active")
private Boolean isActive = false; private boolean isActive = false;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore @JsonIgnore
@@ -7,12 +7,14 @@ import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.register.LoginAnswer; import ru.soune.nocopy.dto.register.LoginAnswer;
import ru.soune.nocopy.dto.register.LoginRequest; import ru.soune.nocopy.dto.register.LoginRequest;
import ru.soune.nocopy.entity.user.AuthToken; import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Optional;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -25,8 +27,9 @@ public class LoginRequestHandler implements RequestHandler {
@Override @Override
public BaseResponse handle(BaseRequest request) { public BaseResponse handle(BaseRequest request) {
LoginRequest loginRequest = objectMapper.convertValue(request.getMessageBody(), LoginRequest.class); LoginRequest loginRequest = objectMapper.convertValue(request.getMessageBody(), LoginRequest.class);
User user = userRepository.findByEmail(loginRequest.getEmail());
if (!userRepository.existsByEmail(loginRequest.getEmail())) { if (user != null) {
LoginAnswer loginAnswer = new LoginAnswer(); LoginAnswer loginAnswer = new LoginAnswer();
loginAnswer.setFieldErrors(Arrays.asList(Map.of("email", loginRequest.getEmail()))); loginAnswer.setFieldErrors(Arrays.asList(Map.of("email", loginRequest.getEmail())));
@@ -35,6 +38,17 @@ public class LoginRequestHandler implements RequestHandler {
MessageCode.AUTH_EMAIL_NOT_FOUND.getDescription(), loginAnswer)); MessageCode.AUTH_EMAIL_NOT_FOUND.getDescription(), loginAnswer));
} }
if (!user.isActive() && !user.isEmailVerified()) {
LoginAnswer loginAnswer = new LoginAnswer();
loginAnswer.setFieldErrors(Arrays.asList(Map.of(
"verified", String.valueOf(user.isEmailVerified()),
"isActive", String.valueOf(user.isActive()))));
return new BaseResponse(request.getMsgId(), MessageCode.USER_NOT_VERIFIED.getCode(),
MessageCode.USER_NOT_VERIFIED.getDescription(), loginAnswer);
}
AuthToken authToken = authService.login(loginRequest); AuthToken authToken = authService.login(loginRequest);
LoginAnswer loginAnswer = new LoginAnswer(); LoginAnswer loginAnswer = new LoginAnswer();
@@ -65,6 +65,8 @@ public class RegRequestHandler implements RequestHandler {
RegAnswer regAnswer = new RegAnswer(); RegAnswer regAnswer = new RegAnswer();
regAnswer.setToken(authToken.getToken()); regAnswer.setToken(authToken.getToken());
regAnswer.setVerified(false);
regAnswer.setActive(false);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), regAnswer); MessageCode.SUCCESS.getDescription(), regAnswer);
@@ -11,10 +11,14 @@ import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.register.VerifyUserRequest; import ru.soune.nocopy.dto.register.VerifyUserRequest;
import ru.soune.nocopy.entity.user.AuthToken; import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.EmailVerificationToken; import ru.soune.nocopy.entity.user.EmailVerificationToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.TokenNotFoundException; import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.EmailVerificationTokenRepository; import ru.soune.nocopy.repository.EmailVerificationTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.mail.EmailService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.user.UserService; import ru.soune.nocopy.service.user.UserService;
import java.util.Map; import java.util.Map;
@@ -24,45 +28,91 @@ import java.util.Optional;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class VerifyRegisterUserHandler implements RequestHandler { public class VerifyRegisterUserHandler implements RequestHandler {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final EmailVerificationTokenRepository emailVerificationTokenRepository; private final EmailVerificationTokenRepository emailVerificationTokenRepository;
private final EmailService emailService;
private final UserRepository userRepository;
private final UserService userService; private final UserService userService;
private final AuthTokenRepository authTokenRepository; private final AuthTokenRepository authTokenRepository;
private final AuthService authService;
@Override @Override
public BaseResponse handle(BaseRequest request) { public BaseResponse handle(BaseRequest request) {
VerifyUserRequest verifyUserRequest = objectMapper.convertValue(request.getMessageBody(), VerifyUserRequest verifyUserRequest = objectMapper.convertValue(request.getMessageBody(), VerifyUserRequest.class);
VerifyUserRequest.class);
String verifyToken = verifyUserRequest.getVerifyToken();
Long userId = verifyUserRequest.getUserId(); Long userId = verifyUserRequest.getUserId();
EmailVerificationToken emailVerificationToken = User user = userRepository.findById(userId)
emailVerificationTokenRepository.findByUserIdAndToken(userId, verifyToken); .orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));
if (emailVerificationToken == null) { Optional<AuthToken> userAuthTokens = authTokenRepository.findByUserIdAndIsActive(userId, false);
throw new NotValidFieldException("Token was incorrected:" + verifyToken,
new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), AuthToken authToken = userAuthTokens.isEmpty() ? authService.generateAuthToken(user): userAuthTokens.get();
MessageCode.INVALID_TOKEN.getDescription(), Map.of(
"userId", userId, if (verifyUserRequest.getResend() != null && verifyUserRequest.getResend() == 1) {
"token", verifyToken))); return handleResend(request, user, authToken);
} }
userService.activateAndVerifyUser(userId); return handleVerify(request, user, authToken, verifyUserRequest.getVerifyToken());
emailVerificationTokenRepository.delete(emailVerificationToken); }
Optional<AuthToken> byUserIdAndIsActive = authTokenRepository.findByUserIdAndIsActive(userId, true); private BaseResponse handleResend(BaseRequest request, User user, AuthToken authToken) {
AuthToken authToken = byUserIdAndIsActive.orElseThrow(() ->
new TokenNotFoundException("Token not found with user id: " + userId)); EmailVerificationToken token = emailVerificationTokenRepository.findByUserId(user.getId());
if (token != null && !token.isExpired()) {
emailService.sendVerificationEmail(user, token.getToken());
log.info("Verification token resent for user {}", user.getEmail());
return successResponse(request, authToken, "Verification code has been resent", false, false);
}
if (token != null) {
emailVerificationTokenRepository.delete(token);
}
EmailVerificationToken newToken = emailService.createEmailVerificationToken(authToken);
emailService.sendVerificationEmail(user, newToken.getToken());
log.info("New verification token generated for user {}", user.getEmail());
return successResponse(request, authToken, "New verification code has been sent",
false, false);
}
private BaseResponse handleVerify(BaseRequest request, User user, AuthToken authToken, String providedToken) {
EmailVerificationToken token = emailVerificationTokenRepository.findByUserIdAndToken(user.getId(), providedToken);
if (token == null || token.isExpired()) {
throw new NotValidFieldException("Invalid or expired token",
new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
MessageCode.INVALID_TOKEN.getDescription(), Map.of("userId", user.getId())));
}
User updateUser = userService.activateAndVerifyUser(user.getId());
emailVerificationTokenRepository.delete(token);
return successResponse(request, authToken, MessageCode.SUCCESS.getDescription(), updateUser.isActive(),
updateUser.isEmailVerified());
}
private BaseResponse successResponse(BaseRequest request, AuthToken authToken, String message, boolean isActive,
boolean isVerified) {
RegAnswer regAnswer = new RegAnswer(); RegAnswer regAnswer = new RegAnswer();
regAnswer.setToken(authToken.getToken()); regAnswer.setToken(authToken.getToken());
regAnswer.setVerified(isVerified);
regAnswer.setActive(isActive);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), message, regAnswer);
MessageCode.SUCCESS.getDescription(), regAnswer);
} }
} }
@@ -11,4 +11,5 @@ public interface EmailVerificationTokenRepository extends JpaRepository<EmailVer
Optional<EmailVerificationToken> findByToken(String token); Optional<EmailVerificationToken> findByToken(String token);
EmailVerificationToken findByUserIdAndToken(Long userId, String token); EmailVerificationToken findByUserIdAndToken(Long userId, String token);
List<EmailVerificationToken> findByExpiresAtBefore(LocalDateTime expiresAtBefore); List<EmailVerificationToken> findByExpiresAtBefore(LocalDateTime expiresAtBefore);
EmailVerificationToken findByUserId(Long userId);
} }
@@ -7,7 +7,7 @@ import ru.soune.nocopy.entity.user.User;
import java.util.Optional; import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); User findByEmail(String email);
boolean existsByEmail(String email); boolean existsByEmail(String email);
boolean existsByPhone(@Size(min = 11, max = 14) String phone); boolean existsByPhone(@Size(min = 11, max = 14) String phone);
} }
@@ -50,17 +50,14 @@ public class AuthService {
User savedUser = userRepository.save(user); User savedUser = userRepository.save(user);
AuthToken authToken = new AuthToken(); AuthToken authToken = genereateAuthToken(savedUser);
authToken.setToken(generateAuthToken());
authToken.setUser(savedUser);
return authTokenRepository.save(authToken); return authTokenRepository.save(authToken);
} }
@Transactional @Transactional
public AuthToken login(LoginRequest request) { public AuthToken login(LoginRequest request) {
Optional<User> userOpt = userRepository.findByEmail(request.getEmail()); User user = userRepository.findByEmail(request.getEmail());
User user = userOpt.get();
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
LoginAnswer loginAnswer = new LoginAnswer(); LoginAnswer loginAnswer = new LoginAnswer();
@@ -87,6 +84,18 @@ public class AuthService {
.ifPresent(authTokenRepository::delete); .ifPresent(authTokenRepository::delete);
} }
public AuthToken generateAuthToken(User user) {
return authTokenRepository.save(genereateAuthToken(user));
}
private AuthToken genereateAuthToken(User user) {
AuthToken authToken = new AuthToken();
authToken.setToken(generateAuthToken());
authToken.setUser(user);
return authToken;
}
private String generateAuthToken() { private String generateAuthToken() {
byte[] bytes = new byte[32]; byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes); secureRandom.nextBytes(bytes);
@@ -47,13 +47,13 @@ public class UserService {
} }
@Transactional @Transactional
public void activateAndVerifyUser(long userId) { public User activateAndVerifyUser(long userId) {
Optional<User> users = userRepository.findById(userId); Optional<User> users = userRepository.findById(userId);
User user = users.orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId)); User user = users.orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));
user.setIsActive(true); user.setActive(true);
user.setEmailVerified(true); user.setEmailVerified(true);
userRepository.save(user); return userRepository.save(user);
} }
private void updateUserFromRequest(User user, UserRequest request) { private void updateUserFromRequest(User user, UserRequest request) {