I am creating a complete user login and registration back-end system with email verification and usage of PostgreSQL to store the user's credentials. I am sending the user's credentials and e-mail address via Postman in POST request, and then I am generating an e-mail with a link for activation that lasts 15 minutes.
I've come to a point where I am dealing with a bug, in which I am posting my payload via Postman, the e-mail gets registered but if the time limit expires I can't send another e-mail since the latter has already been registered in my DB. To debug this, I am currently modifying my service class as follows:
Registration Service Class
package com.andrekreou.iot.registration;
import com.andrekreou.iot.auth.ApplicationUser;
import com.andrekreou.iot.auth.ApplicationUserService;
import com.andrekreou.iot.email.EmailSender;
import com.andrekreou.iot.registration.token.ConfirmationToken;
import com.andrekreou.iot.registration.token.ConfirmationTokenService;
import com.andrekreou.iot.security.ApplicationUserRole;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
@AllArgsConstructor
public class RegistrationService {
private final ApplicationUserService applicationUserService;
private final EmailValidator emailValidator;
private final ConfirmationTokenService confirmationTokenService;
private final EmailSender emailSender;
public String register(RegistrationRequest request) {
boolean isValidEmail = emailValidator.
test(request.getEmail());
if (!isValidEmail) {
throw new IllegalStateException("Email not valid");
}
String token = applicationUserService.signUpUser(
new ApplicationUser(
request.getFirstName(),
request.getLastName(),
request.getEmail(),
request.getPassword(),
ApplicationUserRole.USER
)
);
String link = "https://localhost:8443/api/v1/registration/confirm?token=" + token;
emailSender.send(
request.getEmail(),
buildEmail(request.getFirstName(),
link));
return token;
}
@Transactional
public String confirmToken(String token) {
ConfirmationToken confirmationToken = confirmationTokenService
.getToken(token)
.orElseThrow(() ->
new IllegalStateException("token not found"));
if (confirmationToken.getConfirmedAt() != null) {
throw new IllegalStateException("email already confirmed");
}
LocalDateTime expiredAt = confirmationToken.getExpiresAt();
if (expiredAt.isBefore(LocalDateTime.now())) {
throw new IllegalStateException("token expired");
}
confirmationTokenService.setConfirmedAt(token);
applicationUserService.enableApplicationUser(
confirmationToken.getApplicationUser().getEmail());
return "confirmed";
}
private String buildEmail(String name, String link) {
return " HTML CODE";
}
}
Confirmation Token Class
package com.andrekreou.iot.registration.token;
import com.andrekreou.iot.auth.ApplicationUser;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode
@Entity
public class ConfirmationToken {
@Id
@SequenceGenerator(
name = "confirmation_token_sequence",
sequenceName = "confirmation_token_sequence",
allocationSize = 1
)
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "confirmation_token_sequence"
)
private Long id;
@Column(nullable = false)
private String token;
@Column(nullable = false)
private LocalDateTime createdAt;
@Column(nullable = false)
private LocalDateTime expiresAt;
private LocalDateTime confirmedAt;
@ManyToOne
@JoinColumn(
nullable = false,
name = "app_user_id"
)
private ApplicationUser applicationUser;
public ConfirmationToken(String token,
LocalDateTime createdAt,
LocalDateTime expiresAt,
ApplicationUser applicationUser) {
this.token = token;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
this.applicationUser = applicationUser;
}
}
ApplicationUserService class
package com.andrekreou.iot.auth;
import com.andrekreou.iot.registration.RegistrationRequest;
import com.andrekreou.iot.registration.RegistrationService;
import com.andrekreou.iot.registration.token.ConfirmationToken;
import com.andrekreou.iot.registration.token.ConfirmationTokenRepository;
import com.andrekreou.iot.registration.token.ConfirmationTokenService;
import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
@AllArgsConstructor
public class ApplicationUserService implements UserDetailsService {
private final static String USER_NOT_FOUND_MSG =
"User with email %s not found";
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final ConfirmationTokenService confirmationTokenService;
private final ConfirmationTokenRepository confirmationTokenRepository;
private final ConfirmationToken confirmationToken;
private final RegistrationService registrationService;
private final RegistrationRequest request;
@Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() ->
new UsernameNotFoundException(String.format(
String.format(USER_NOT_FOUND_MSG, email))));
}
public String signUpUser(ApplicationUser applicationUser){
boolean mailExists = isMailExists(applicationUser);
boolean tokenExists = isTokenExists(confirmationToken);
if (mailExists && !tokenExists) {
registrationService.register(request);
} else {
throw new IllegalStateException("email already taken");
}
String encodedPassword = bCryptPasswordEncoder.encode(applicationUser.getPassword());
applicationUser.setPassword(encodedPassword);
userRepository.save(applicationUser);
String token = UUID.randomUUID().toString();
ConfirmationToken confirmationToken = new ConfirmationToken(
token,
LocalDateTime.now(),
LocalDateTime.now().plusMinutes(15),
applicationUser
);
confirmationTokenService.saveConfirmationToken(confirmationToken);
return token;
}
private boolean isTokenExists(ConfirmationToken confirmationToken){
return confirmationTokenRepository.findByToken(confirmationToken.getToken()).isPresent();
}
private boolean isMailExists(ApplicationUser applicationUser) {
return userRepository.findByEmail(applicationUser.getEmail()).isPresent();
}
public int enableApplicationUser(String email) {
return userRepository.enableAppUser(email);
}
}
I've created two boolean variables which check if the mail exists in my DB and if the e-mail token doesn't. When these two conditions are true, I want to send the e-mail confirmation again back to the user. But I am dealing with the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 4 of constructor in com.andrekreou.iot.auth.ApplicationUserService required a bean of type 'com.andrekreou.iot.registration.token.ConfirmationToken' that could not be found.
Action:
Consider defining a bean of type 'com.andrekreou.iot.registration.token.ConfirmationToken' in your configuration.
Process finished with exit code 1
I know that this is a common error, but I don't know how to address it. Any ideas?