package com.porterhead.user; import com.porterhead.mail.EmailServiceTokenModel; import com.porterhead.mail.MailSenderService; import com.porterhead.service.BaseService; import com.porterhead.user.api.LostPasswordRequest; import com.porterhead.user.api.PasswordRequest; import com.porterhead.user.exception.*; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.validation.Validator; import java.util.List; import java.util.regex.Pattern; /** * @version 1.0 * @author: Iain Porter * @since 13/05/2013 */ @Service("verificationTokenService") public class VerificationTokenServiceImpl extends BaseService implements VerificationTokenService { private static final Pattern UUID_PATTERN = Pattern.compile("^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$"); private VerificationTokenRepository tokenRepository; private MailSenderService mailSenderService; private UserRepository userRepository; private PasswordEncoder passwordEncoder; @Value("${token.emailVerification.timeToLive.inMinutes}") private int emailVerificationTokenExpiryTimeInMinutes; @Value("${token.emailRegistration.timeToLive.inMinutes}") private int emailRegistrationTokenExpiryTimeInMinutes; @Value("${token.lostPassword.timeToLive.inMinutes}") private int lostPasswordTokenExpiryTimeInMinutes; @Value("${hostName.url}") private String hostNameUrl; public VerificationTokenServiceImpl(Validator validator) { super(validator); } @Autowired public VerificationTokenServiceImpl(UserRepository userRepository, VerificationTokenRepository tokenRepository, MailSenderService mailSenderService, Validator validator, PasswordEncoder passwordEncoder) { this(validator); this.userRepository = userRepository; this.tokenRepository = tokenRepository; this.mailSenderService = mailSenderService; this.passwordEncoder = passwordEncoder; } @Transactional @Async public VerificationToken sendEmailVerificationToken(String userId) { User user = ensureUserIsLoaded(userId); return sendEmailVerificationToken(user); } private VerificationToken sendEmailVerificationToken(User user) { VerificationToken token = new VerificationToken(user, VerificationTokenType.emailVerification, emailVerificationTokenExpiryTimeInMinutes); tokenRepository.save(token); mailSenderService.sendVerificationEmail(new EmailServiceTokenModel(user, token, hostNameUrl)); return token; } @Transactional @Async public VerificationToken sendEmailRegistrationToken(String userId) { User user = ensureUserIsLoaded(userId); VerificationToken token = new VerificationToken(user, VerificationTokenType.emailRegistration, emailRegistrationTokenExpiryTimeInMinutes); tokenRepository.save(token); mailSenderService.sendRegistrationEmail(new EmailServiceTokenModel(user, token, hostNameUrl)); return token; } /** * generate token if user found otherwise do nothing * * @param lostPasswordRequest * @return a token or null if user not found */ @Transactional @Async public VerificationToken sendLostPasswordToken(LostPasswordRequest lostPasswordRequest) { validate(lostPasswordRequest); VerificationToken token = null; User user = userRepository.findByEmailAddress(lostPasswordRequest.getEmailAddress()); if (user != null) { List<VerificationToken> tokens = tokenRepository.findByUserIdAndTokenType(user.getId(), VerificationTokenType.lostPassword); token = getActiveToken(tokens); if (token == null) { token = new VerificationToken(user, VerificationTokenType.lostPassword, lostPasswordTokenExpiryTimeInMinutes); tokenRepository.save(token); } mailSenderService.sendLostPasswordEmail(new EmailServiceTokenModel(user, token, hostNameUrl)); } return token; } @Transactional public VerificationToken verify(String base64EncodedToken) { VerificationToken token = loadToken(base64EncodedToken); User user = userRepository.findOne(token.getUserId()); if (token.isVerified() || user.isVerified()) { throw new AlreadyVerifiedException(); } token.setVerified(true); user.setVerified(true); userRepository.save(user); tokenRepository.save(token); return token; } @Transactional public VerificationToken generateEmailVerificationToken(String emailAddress) { Assert.notNull(emailAddress); User user = userRepository.findByEmailAddress(emailAddress); if (user == null) { throw new UserNotFoundException(); } if (user.isVerified()) { throw new AlreadyVerifiedException(); } //if token still active resend that VerificationToken token = getActiveToken(tokenRepository.findByUserIdAndTokenType(user.getId(), VerificationTokenType.emailVerification)); if (token == null) { token = sendEmailVerificationToken(user); } else { mailSenderService.sendVerificationEmail(new EmailServiceTokenModel(user, token, hostNameUrl)); } return token; } @Transactional public VerificationToken resetPassword(String base64EncodedToken, PasswordRequest passwordRequest) { Assert.notNull(base64EncodedToken); validate(passwordRequest); VerificationToken token = loadToken(base64EncodedToken); if (token.isVerified()) { throw new AlreadyVerifiedException(); } token.setVerified(true); User user = userRepository.findOne(token.getUserId()); try { user.setHashedPassword(passwordEncoder.encode(passwordRequest.getPassword())); } catch (Exception e) { throw new AuthenticationException(); } //set user to verified if not already and authenticated role user.setVerified(true); userRepository.save(user); tokenRepository.save(token); return token; } private VerificationToken loadToken(String base64EncodedToken) { Assert.notNull(base64EncodedToken); String rawToken = new String(Base64.decodeBase64(base64EncodedToken.getBytes())); VerificationToken token = tokenRepository.findByToken(rawToken); if (token == null) { throw new TokenNotFoundException(); } if (token.hasExpired()) { throw new TokenHasExpiredException(); } return token; } @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } private User ensureUserIsLoaded(String userIdentifier) { User user = null; if (isValidUuid(userIdentifier)) { user = userRepository.findOne(userIdentifier); } else { user = userRepository.findByEmailAddress(userIdentifier); } if (user == null) { throw new UserNotFoundException(); } return user; } private VerificationToken getActiveToken(List<VerificationToken> tokens) { VerificationToken activeToken = null; for (VerificationToken token : tokens) { if (!token.hasExpired() && !token.isVerified()) { activeToken = token; break; } } return activeToken; } private boolean isValidUuid(String uuid) { return UUID_PATTERN.matcher(uuid).matches(); } public void setEmailVerificationTokenExpiryTimeInMinutes(int emailVerificationTokenExpiryTimeInMinutes) { this.emailVerificationTokenExpiryTimeInMinutes = emailVerificationTokenExpiryTimeInMinutes; } public void setEmailRegistrationTokenExpiryTimeInMinutes(int emailRegistrationTokenExpiryTimeInMinutes) { this.emailRegistrationTokenExpiryTimeInMinutes = emailRegistrationTokenExpiryTimeInMinutes; } public void setLostPasswordTokenExpiryTimeInMinutes(int lostPasswordTokenExpiryTimeInMinutes) { this.lostPasswordTokenExpiryTimeInMinutes = lostPasswordTokenExpiryTimeInMinutes; } public void setHostNameUrl(String hostNameUrl) { this.hostNameUrl = hostNameUrl; } }