feat(auth): add email verification feature and handler
Some checks failed
CI / Get Changed Files (pull_request) Successful in 7s
CI / Docker frontend validation (pull_request) Successful in 1m2s
CI / Checkstyle Main (pull_request) Successful in 1m8s
CI / eslint (pull_request) Failing after 1m29s
CI / Docker backend validation (pull_request) Successful in 1m32s
CI / prettier (pull_request) Failing after 31s
CI / oxlint (pull_request) Successful in 44s
CI / test-build (pull_request) Successful in 38s

This commit is contained in:
csimonis 2025-05-15 10:49:24 +02:00
commit d2225decc1
14 changed files with 124 additions and 27 deletions

View file

@ -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.BAD_REQUEST);
}
}

View file

@ -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");
}
}

View file

@ -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) {
if (authService.verifyEmail(token)) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
}

View file

@ -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(),
@ -56,4 +62,20 @@ public class AuthService {
user.getBalance()
);
}
public Boolean verifyEmail(String token) {
Optional<UserEntity> optionalUser = userService.getUserByVerificationToken(token);
if(!optionalUser.isPresent()) {
return false;
}
UserEntity user = optionalUser.get();
user.setEmailVerified(true);
user.setVerificationToken(null);
userService.saveUser(user);
return true;
}
}

View file

@ -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);
}

View file

@ -44,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();
}
}

View file

@ -123,7 +123,7 @@
<p>Klicken Sie auf den folgenden Button, um Ihre E-Mail-Adresse zu bestätigen:</p>
<div style="text-align: center;">
<a href="${feUrl}/backend/verify?token=${token}" class="button">E-Mail bestätigen</a>
<a href="${feUrl}/verify?token=${token}" class="button">E-Mail bestätigen</a>
</div>
<div class="info-box">