From e5c33c4c42563ba415cec0f7513f1203a5a89862 Mon Sep 17 00:00:00 2001 From: VadimasikKPI Date: Tue, 19 Dec 2023 20:53:50 +0200 Subject: [PATCH] add find user by author id into send activation token, add service class for verification token and write tests --- docker-compose.yml | 2 +- .../dokazovi/entity/VerificationToken.java | 7 +- .../VerificationTokenRepository.java | 12 ++- .../VerificationTokenCleanupScheduler.java | 17 +++++ .../dokazovi/service/UserService.java | 5 -- .../service/VerificationTokenService.java | 16 ++++ .../service/impl/UserServiceImpl.java | 61 +++++---------- .../impl/VerificationTokenServiceImpl.java | 62 ++++++++++++++++ ...ion_column_to_verification_token_table.sql | 2 + .../service/impl/UserServiceImplTest.java | 30 -------- .../VerificationTokenServiceImplTest.java | 74 +++++++++++++++++++ 11 files changed, 208 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/softserveinc/dokazovi/scheduler/VerificationTokenCleanupScheduler.java create mode 100644 src/main/java/com/softserveinc/dokazovi/service/VerificationTokenService.java create mode 100644 src/main/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImpl.java create mode 100644 src/main/resources/db/migration/V34__add_time_expiration_column_to_verification_token_table.sql create mode 100644 src/test/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImplTest.java diff --git a/docker-compose.yml b/docker-compose.yml index e2bf5156..6e14e986 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: postgres: image: postgres ports: - - "5432:5432" + - "7000:5432" environment: POSTGRES_DB: dokazovi POSTGRES_USER: dokazovi diff --git a/src/main/java/com/softserveinc/dokazovi/entity/VerificationToken.java b/src/main/java/com/softserveinc/dokazovi/entity/VerificationToken.java index 7ddc5935..97f999a0 100644 --- a/src/main/java/com/softserveinc/dokazovi/entity/VerificationToken.java +++ b/src/main/java/com/softserveinc/dokazovi/entity/VerificationToken.java @@ -8,6 +8,7 @@ import lombok.NoArgsConstructor; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -16,6 +17,7 @@ import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; +import java.time.LocalDateTime; @Data @Builder @@ -24,7 +26,7 @@ @Entity(name = "verification_token_entity") @Table(name = "verification_tokens") public class VerificationToken { - + public static final int EXPIRATION = 1440; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @@ -37,4 +39,7 @@ public class VerificationToken { property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) private UserEntity user; + + @Column(name = "date_expiration") + private LocalDateTime dateExpiration; } diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/VerificationTokenRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/VerificationTokenRepository.java index a71b9e23..24906132 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/VerificationTokenRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/VerificationTokenRepository.java @@ -2,8 +2,18 @@ import com.softserveinc.dokazovi.entity.VerificationToken; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository public interface VerificationTokenRepository extends JpaRepository { + Optional findByToken(String token); - VerificationToken findByToken(String token); + @Query(nativeQuery = true, + value = "SELECT vt FROM VerificationToken vt WHERE vt.dateExpiration < :currentTime") + List findAllExpritedTokens(LocalDateTime currentTime); } diff --git a/src/main/java/com/softserveinc/dokazovi/scheduler/VerificationTokenCleanupScheduler.java b/src/main/java/com/softserveinc/dokazovi/scheduler/VerificationTokenCleanupScheduler.java new file mode 100644 index 00000000..e83e261f --- /dev/null +++ b/src/main/java/com/softserveinc/dokazovi/scheduler/VerificationTokenCleanupScheduler.java @@ -0,0 +1,17 @@ +package com.softserveinc.dokazovi.scheduler; + +import com.softserveinc.dokazovi.service.VerificationTokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class VerificationTokenCleanupScheduler { + private final VerificationTokenService verificationTokenService; + + @Scheduled(cron = "0 0 0 * * ?") + public void deleteExpiredTokens() { + verificationTokenService.deleteExpiredTokens(); + } +} diff --git a/src/main/java/com/softserveinc/dokazovi/service/UserService.java b/src/main/java/com/softserveinc/dokazovi/service/UserService.java index 7c86c9be..24e575fe 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/UserService.java +++ b/src/main/java/com/softserveinc/dokazovi/service/UserService.java @@ -6,7 +6,6 @@ import com.softserveinc.dokazovi.dto.user.UserStatusDTO; import com.softserveinc.dokazovi.entity.PasswordResetTokenEntity; import com.softserveinc.dokazovi.entity.UserEntity; -import com.softserveinc.dokazovi.entity.VerificationToken; import com.softserveinc.dokazovi.pojo.UserSearchCriteria; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -33,10 +32,6 @@ public interface UserService { void setEnabled(Integer authorId, boolean isEnabled); - void createVerificationToken(UserEntity user, String token); - - VerificationToken getVerificationToken(String verificationToken); - UserEntity getById(Integer authorId); UserEntity getByUserId(Integer userId); diff --git a/src/main/java/com/softserveinc/dokazovi/service/VerificationTokenService.java b/src/main/java/com/softserveinc/dokazovi/service/VerificationTokenService.java new file mode 100644 index 00000000..413a0b09 --- /dev/null +++ b/src/main/java/com/softserveinc/dokazovi/service/VerificationTokenService.java @@ -0,0 +1,16 @@ +package com.softserveinc.dokazovi.service; + +import com.softserveinc.dokazovi.entity.UserEntity; +import com.softserveinc.dokazovi.entity.VerificationToken; + +public interface VerificationTokenService { + VerificationToken createVerificationTokenForUser(UserEntity user, String token); + + VerificationToken getByToken(String token); + + boolean validateVerificationToken(String token); + + void delete (VerificationToken passwordResetTokenEntity); + + void deleteExpiredTokens(); +} diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/UserServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/UserServiceImpl.java index f7476b04..6d771570 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/UserServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/UserServiceImpl.java @@ -15,10 +15,11 @@ import com.softserveinc.dokazovi.pojo.UserSearchCriteria; import com.softserveinc.dokazovi.repositories.AuthorRepository; import com.softserveinc.dokazovi.repositories.UserRepository; -import com.softserveinc.dokazovi.repositories.VerificationTokenRepository; import com.softserveinc.dokazovi.service.MailSenderService; import com.softserveinc.dokazovi.service.PasswordResetTokenService; +import com.softserveinc.dokazovi.service.ProviderService; import com.softserveinc.dokazovi.service.UserService; +import com.softserveinc.dokazovi.service.VerificationTokenService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -45,11 +46,12 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final UserMapper userMapper; - private final VerificationTokenRepository tokenRepository; + private final VerificationTokenService tokenService; private final PasswordEncoder passwordEncoder; private final PasswordResetTokenService passwordResetTokenService; private final MailSenderService mailSenderService; private final AuthorRepository authorRepository; + private final ProviderService providerService; private static final String HAS_NO_DIRECTIONS = "hasNoDirections"; private static final String HAS_NO_REGIONS = "hasNoRegions"; @@ -111,7 +113,7 @@ public UserDTO findExpertByUserId(Integer userId) { } /** - * Gets doctors by search criteria. For example, if directions, regions and user name fields are empty, the + * Gets doctors by search criteria. For example, if directions, regions and username fields are empty, the * findDoctorsProfiles method without parameters is called * * @param userSearchCriteria received from User controller @@ -207,11 +209,7 @@ public Page findRandomExpertPreview(Set directionsIds, Pageabl */ @Override public void setEnabled(Integer authorId, boolean isEnabled) { - AuthorEntity author = authorRepository.findById(authorId).orElse(null); - if (author == null) { - throw new EntityNotFoundException("Author not found"); - } - UserEntity userEntity = userRepository.findById(author.getProfile().getId()).orElse(null); + UserEntity userEntity = getById(authorId); if (userEntity == null) { throw new EntityNotFoundException("User not found"); } @@ -219,32 +217,6 @@ public void setEnabled(Integer authorId, boolean isEnabled) { userRepository.save(userEntity); } - /** - * Gets the verification token received from tokenRepository. - * - * @param verificationToken received from Auth controller - * @return found VerificationToken - */ - @Override - public VerificationToken getVerificationToken(String verificationToken) { - return tokenRepository.findByToken(verificationToken); - } - - /** - * Gets the verification token received from tokenRepository. - * - * @param user user received from Mail Sender - * @param token token received from Mail Sender - */ - @Override - public void createVerificationToken(UserEntity user, String token) { - VerificationToken myToken = VerificationToken.builder() - .user(user) - .token(token) - .build(); - tokenRepository.save(myToken); - } - @Override public UserEntity getById(Integer authorId) { AuthorEntity author = authorRepository.findById(authorId).orElse(null); @@ -288,35 +260,41 @@ public boolean isPasswordMatches(UserEntity user, String password) { @Override public void sendActivationToken(Integer userId, String email, String origin) { - UserEntity user = userRepository.findById(userId).orElse(null); + UserEntity user = getById(userId); if (user == null) { throw new EntityNotFoundException("User not found"); } - String token = UUID.randomUUID().toString(); user.setEmail(email); user.setStatus(UserStatus.NEW); - createVerificationToken(user, token); + user.setEnabled(false); + update(user); + String token = UUID.randomUUID().toString(); + tokenService.createVerificationTokenForUser(user, token); mailSenderService.sendEmailWithActivationToken(origin, token, user); } @Override public void activateUser(UserPasswordDTO userPasswordDTO) { - VerificationToken token = getVerificationToken(userPasswordDTO.getToken()); + VerificationToken token = tokenService.getByToken(userPasswordDTO.getToken()); + if (!tokenService.validateVerificationToken(userPasswordDTO.getToken())) { + throw new BadRequestException("Token is not valid"); + } UserEntity user = token.getUser(); if (user == null) { - throw new BadRequestException("User not found"); + throw new EntityNotFoundException("User not found"); } user.setEnabled(true); user.setStatus(UserStatus.ACTIVE); user.setPassword(passwordEncoder.encode(userPasswordDTO.getNewPassword())); update(user); - tokenRepository.delete(token); + providerService.createLocalProviderEntityForUser(user, user.getEmail()); + tokenService.delete(token); } @Override public UserPublicAndPrivateEmailDTO getAllPublicAndPrivateEmails() { List users = userRepository.findAll(); - UserPublicAndPrivateEmailDTO userPublicAndPrivateEmailDTO = UserPublicAndPrivateEmailDTO.builder() + return UserPublicAndPrivateEmailDTO.builder() .publicEmail(Arrays.stream(users.toArray()) .map(user -> ((UserEntity) user).getPublicEmail()) .collect(Collectors.toList())) @@ -324,7 +302,6 @@ public UserPublicAndPrivateEmailDTO getAllPublicAndPrivateEmails() { .map(user -> ((UserEntity) user).getEmail()) .collect(Collectors.toList())) .build(); - return userPublicAndPrivateEmailDTO; } @Override diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImpl.java new file mode 100644 index 00000000..2e2de7b2 --- /dev/null +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImpl.java @@ -0,0 +1,62 @@ +package com.softserveinc.dokazovi.service.impl; + +import com.softserveinc.dokazovi.entity.UserEntity; +import com.softserveinc.dokazovi.entity.VerificationToken; +import com.softserveinc.dokazovi.repositories.VerificationTokenRepository; +import com.softserveinc.dokazovi.service.VerificationTokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class VerificationTokenServiceImpl implements VerificationTokenService { + + private final VerificationTokenRepository verificationTokenRepository; + + @Override + public VerificationToken createVerificationTokenForUser(UserEntity user, String token) { + VerificationToken myToken = VerificationToken.builder() + .token(token) + .user(user) + .dateExpiration(LocalDateTime.now().plusMinutes(VerificationToken.EXPIRATION)) + .build(); + return verificationTokenRepository.save(myToken); + } + + @Override + public VerificationToken getByToken(String token) { + return verificationTokenRepository.findByToken(token).orElse(null); + } + + @Override + public boolean validateVerificationToken(String token) { + VerificationToken verificationToken = getByToken(token); + return isAvailable(verificationToken) && !isExpired(verificationToken); + } + + + @Override + public void delete(VerificationToken verificationToken) { + if (isAvailable(verificationToken)) { + verificationTokenRepository.delete(verificationToken); + } + } + + @Override + public void deleteExpiredTokens() { + LocalDateTime currentTime = LocalDateTime.now(); + List expiredTokens = verificationTokenRepository.findAllExpritedTokens(currentTime); + verificationTokenRepository.deleteAll(expiredTokens); + } + + private boolean isAvailable(VerificationToken verificationToken) { + return verificationToken != null; + } + + private boolean isExpired(VerificationToken verificationToken) { + return verificationToken.getDateExpiration().isBefore(LocalDateTime.now()); + } +} diff --git a/src/main/resources/db/migration/V34__add_time_expiration_column_to_verification_token_table.sql b/src/main/resources/db/migration/V34__add_time_expiration_column_to_verification_token_table.sql new file mode 100644 index 00000000..a7c06c33 --- /dev/null +++ b/src/main/resources/db/migration/V34__add_time_expiration_column_to_verification_token_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE verification_tokens +ADD COLUMN date_expiration TIMESTAMP; \ No newline at end of file diff --git a/src/test/java/com/softserveinc/dokazovi/service/impl/UserServiceImplTest.java b/src/test/java/com/softserveinc/dokazovi/service/impl/UserServiceImplTest.java index f6d8887e..778d2c1a 100644 --- a/src/test/java/com/softserveinc/dokazovi/service/impl/UserServiceImplTest.java +++ b/src/test/java/com/softserveinc/dokazovi/service/impl/UserServiceImplTest.java @@ -5,7 +5,6 @@ import com.softserveinc.dokazovi.entity.InstitutionEntity; import com.softserveinc.dokazovi.entity.PasswordResetTokenEntity; import com.softserveinc.dokazovi.entity.UserEntity; -import com.softserveinc.dokazovi.entity.VerificationToken; import com.softserveinc.dokazovi.exception.BadRequestException; import com.softserveinc.dokazovi.exception.EntityNotFoundException; import com.softserveinc.dokazovi.mapper.UserMapper; @@ -388,35 +387,6 @@ void findAll() { // .findById(any(Integer.class)); // } - @Test - void getVerificationToken() { - String token = "950c9760-805e-449c-a966-2d0d5ebd86f4"; - VerificationToken verificationToken = VerificationToken.builder() - .token(token) - .build(); - when(tokenRepository.findByToken(any(String.class))).thenReturn(verificationToken); - verificationToken = userService.getVerificationToken(token); - assertEquals(token, verificationToken.getToken()); - verify(tokenRepository, times(1)) - .findByToken(any(String.class)); - } - - @Test - void createVerificationToken() { - String token = "950c9760-805e-449c-a966-2d0d5ebd86f4"; - UserEntity userEntity = UserEntity.builder().build(); - VerificationToken verificationToken = VerificationToken.builder() - .token(token) - .user(userEntity) - .build(); - when(tokenRepository.save(any(VerificationToken.class))).thenReturn(verificationToken); - userService.createVerificationToken(userEntity, token); - verify(tokenRepository, times(1)) - .save(any(VerificationToken.class)); - assertEquals(token, verificationToken.getToken()); - assertEquals(userEntity, verificationToken.getUser()); - } - @Test void findUserByEmail() { String email = "some@some.com"; diff --git a/src/test/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImplTest.java b/src/test/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImplTest.java new file mode 100644 index 00000000..ebaba70f --- /dev/null +++ b/src/test/java/com/softserveinc/dokazovi/service/impl/VerificationTokenServiceImplTest.java @@ -0,0 +1,74 @@ +package com.softserveinc.dokazovi.service.impl; + +import com.softserveinc.dokazovi.entity.UserEntity; +import com.softserveinc.dokazovi.entity.VerificationToken; +import com.softserveinc.dokazovi.repositories.VerificationTokenRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class VerificationTokenServiceImplTest { + @Mock + private VerificationTokenRepository verificationTokenRepository; + + @InjectMocks + private VerificationTokenServiceImpl verificationTokenService; + + VerificationToken expected; + private final Integer id = 1; + private final String token = "ef590bd8-e993-4153-8206-b963732bfeb9"; + private final UserEntity user = UserEntity.builder().id(1).build(); + + @BeforeEach + public void setUp() { + expected = VerificationToken.builder() + .id(id) + .token(token) + .user(user) + .dateExpiration(LocalDateTime.now().plusMinutes(60)) + .build(); + } + + @Test + void createPasswordResetTokenForUserTest () { + when(verificationTokenRepository.save(any(VerificationToken.class))).thenReturn(expected); + VerificationToken actual = verificationTokenService.createVerificationTokenForUser(user, token); + verify(verificationTokenRepository).save(any(VerificationToken.class)); + Assertions.assertEquals(expected, actual); + } + + @Test + void getPasswordResetTokenByTokenTest() { + when(verificationTokenRepository.findByToken(token)).thenReturn(Optional.of(expected)); + VerificationToken actual = verificationTokenService.getByToken(token); + verify(verificationTokenRepository).findByToken(token); + Assertions.assertEquals(expected, actual); + } + + @Test + void deletePasswordResetTokenTest() { + when(verificationTokenRepository.findByToken(token)).thenReturn(Optional.of(expected)); + VerificationToken passwordResetTokenEntity = verificationTokenService.getByToken(token); + verificationTokenService.delete(passwordResetTokenEntity); + verify(verificationTokenRepository, times(1)).delete(passwordResetTokenEntity); + } + + @Test + void validatePasswordResetTokenTest_isOk() { + when(verificationTokenRepository.findByToken(token)).thenReturn(Optional.of(expected)); + Assertions.assertEquals(true, verificationTokenService.validateVerificationToken(token)); + } +}