Merge pull request 'feat: verify email (CAS-66)' (!192) from feat/verify-email into main
Reviewed-on: #192 Reviewed-by: Jan K9f <jan@kjan.email>
This commit is contained in:
commit
97a25af1c6
17 changed files with 306 additions and 33 deletions
|
@ -1,5 +1,6 @@
|
|||
package de.szut.casino.exceptionHandling;
|
||||
|
||||
import de.szut.casino.exceptionHandling.exceptions.EmailNotVerifiedException;
|
||||
import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException;
|
||||
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
|
||||
import jakarta.persistence.EntityExistsException;
|
||||
|
@ -31,4 +32,10 @@ public class GlobalExceptionHandler {
|
|||
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
|
||||
return new ResponseEntity<>(errorDetails, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@ExceptionHandler(EmailNotVerifiedException.class)
|
||||
public ResponseEntity<?> handleEmailNotVerifiedException(EmailNotVerifiedException ex, WebRequest request) {
|
||||
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
|
||||
return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package de.szut.casino.exceptionHandling.exceptions;
|
||||
|
||||
import de.szut.casino.security.service.EmailService;
|
||||
|
||||
public class EmailNotVerifiedException extends Exception {
|
||||
public EmailNotVerifiedException() {
|
||||
super("Email not verified");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package de.szut.casino.security;
|
||||
|
||||
import de.szut.casino.exceptionHandling.ErrorDetails;
|
||||
import de.szut.casino.exceptionHandling.exceptions.EmailNotVerifiedException;
|
||||
import de.szut.casino.security.dto.AuthResponseDto;
|
||||
import de.szut.casino.security.dto.LoginRequestDto;
|
||||
import de.szut.casino.security.service.AuthService;
|
||||
|
@ -9,12 +11,10 @@ import jakarta.mail.MessagingException;
|
|||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
|
@ -24,7 +24,7 @@ public class AuthController {
|
|||
private AuthService authService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthResponseDto> authenticateUser(@Valid @RequestBody LoginRequestDto loginRequest) {
|
||||
public ResponseEntity<AuthResponseDto> authenticateUser(@Valid @RequestBody LoginRequestDto loginRequest) throws EmailNotVerifiedException {
|
||||
AuthResponseDto response = authService.login(loginRequest);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
@ -34,4 +34,13 @@ public class AuthController {
|
|||
GetUserDto response = authService.register(signUpRequest);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/verify")
|
||||
public ResponseEntity<Void> verifyEmail(@RequestParam("token") String token) throws MessagingException, IOException {
|
||||
if (authService.verifyEmail(token)) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.szut.casino.security.service;
|
||||
|
||||
import de.szut.casino.exceptionHandling.exceptions.EmailNotVerifiedException;
|
||||
import de.szut.casino.security.dto.AuthResponseDto;
|
||||
import de.szut.casino.security.dto.LoginRequestDto;
|
||||
import de.szut.casino.security.jwt.JwtUtils;
|
||||
|
@ -16,6 +17,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
@ -32,7 +34,11 @@ public class AuthService {
|
|||
@Autowired
|
||||
private EmailService emailService;
|
||||
|
||||
public AuthResponseDto login(LoginRequestDto loginRequest) {
|
||||
public AuthResponseDto login(LoginRequestDto loginRequest) throws EmailNotVerifiedException {
|
||||
if (!userService.isVerified(loginRequest.getUsernameOrEmail())) {
|
||||
throw new EmailNotVerifiedException();
|
||||
}
|
||||
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
loginRequest.getUsernameOrEmail(),
|
||||
|
@ -47,7 +53,7 @@ public class AuthService {
|
|||
public GetUserDto register(CreateUserDto signUpRequest) throws MessagingException, IOException {
|
||||
UserEntity user = userService.createUser(signUpRequest);
|
||||
|
||||
this.emailService.sendRegistrationEmail(user);
|
||||
this.emailService.sendEmailVerificationEmail(user);
|
||||
|
||||
return new GetUserDto(
|
||||
user.getId(),
|
||||
|
@ -56,4 +62,21 @@ public class AuthService {
|
|||
user.getBalance()
|
||||
);
|
||||
}
|
||||
|
||||
public Boolean verifyEmail(String token) throws MessagingException, IOException {
|
||||
Optional<UserEntity> optionalUser = userService.getUserByVerificationToken(token);
|
||||
|
||||
if(!optionalUser.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserEntity user = optionalUser.get();
|
||||
|
||||
user.setEmailVerified(true);
|
||||
user.setVerificationToken(null);
|
||||
userService.saveUser(user);
|
||||
this.emailService.sendWelcomeEmail(user);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import java.io.IOException;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class EmailService {
|
||||
|
@ -35,7 +34,25 @@ public class EmailService {
|
|||
}
|
||||
}
|
||||
|
||||
public void sendRegistrationEmail(UserEntity user) throws IOException, MessagingException {
|
||||
public void sendEmailVerificationEmail(UserEntity user) throws IOException, MessagingException {
|
||||
String template = loadTemplate("email/verify.html");
|
||||
String htmlContent = template
|
||||
.replace("${username}", user.getUsername())
|
||||
.replace("${feUrl}", feUrl)
|
||||
.replace("${token}", user.getVerificationToken());
|
||||
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
||||
|
||||
helper.setFrom(mailConfig.fromAddress);
|
||||
helper.setTo(user.getEmailAddress());
|
||||
helper.setSubject("E-Mail Bestätigung");
|
||||
helper.setText(htmlContent, true);
|
||||
|
||||
mailSender.send(message);
|
||||
}
|
||||
|
||||
public void sendWelcomeEmail(UserEntity user) throws IOException, MessagingException {
|
||||
String template = loadTemplate("email/welcome.html");
|
||||
String htmlContent = template
|
||||
.replace("${username}", user.getUsername())
|
||||
|
|
|
@ -30,11 +30,16 @@ public class UserEntity {
|
|||
@Column(precision = 19, scale = 2)
|
||||
private BigDecimal balance;
|
||||
|
||||
public UserEntity(String email, String username, String password, BigDecimal balance) {
|
||||
private Boolean emailVerified = false;
|
||||
|
||||
private String verificationToken;
|
||||
|
||||
public UserEntity(String email, String username, String password, BigDecimal balance, String verificationToken) {
|
||||
this.email = email;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.balance = balance;
|
||||
this.verificationToken = verificationToken;
|
||||
}
|
||||
|
||||
public void addBalance(BigDecimal amountToAdd) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package de.szut.casino.user;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
@ -14,4 +15,10 @@ public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
|||
boolean existsByUsername(String username);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.verificationToken = ?1")
|
||||
Optional<UserEntity> findOneByVerificationToken(String token);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.username = ?1 OR u.email = ?1")
|
||||
Optional<UserEntity> findOneByUsernameOrEmail(String usernameOrEmail);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.szut.casino.user;
|
|||
|
||||
import de.szut.casino.user.dto.CreateUserDto;
|
||||
import jakarta.persistence.EntityExistsException;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
@ -31,7 +32,8 @@ public class UserService {
|
|||
createUserDto.getEmail(),
|
||||
createUserDto.getUsername(),
|
||||
passwordEncoder.encode(createUserDto.getPassword()),
|
||||
BigDecimal.valueOf(100) // Starting balance
|
||||
BigDecimal.valueOf(100),
|
||||
RandomStringUtils.randomAlphanumeric(64)
|
||||
);
|
||||
|
||||
return userRepository.save(user);
|
||||
|
@ -42,4 +44,22 @@ public class UserService {
|
|||
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
public Optional<UserEntity> getUserByVerificationToken(String token) {
|
||||
return this.userRepository.findOneByVerificationToken(token);
|
||||
}
|
||||
|
||||
public void saveUser(UserEntity user) {
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public boolean isVerified(String usernameOrEmail) {
|
||||
Optional<UserEntity> optionalUser = userRepository.findOneByUsernameOrEmail(usernameOrEmail);
|
||||
|
||||
if (!optionalUser.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return optionalUser.get().getEmailVerified();
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue