diff --git a/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java b/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java
index 573abb8..9c185a8 100644
--- a/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java
+++ b/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java
@@ -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);
+ }
}
diff --git a/backend/src/main/java/de/szut/casino/exceptionHandling/exceptions/EmailNotVerifiedException.java b/backend/src/main/java/de/szut/casino/exceptionHandling/exceptions/EmailNotVerifiedException.java
new file mode 100644
index 0000000..ea08367
--- /dev/null
+++ b/backend/src/main/java/de/szut/casino/exceptionHandling/exceptions/EmailNotVerifiedException.java
@@ -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");
+ }
+}
diff --git a/backend/src/main/java/de/szut/casino/security/AuthController.java b/backend/src/main/java/de/szut/casino/security/AuthController.java
index 6d99625..f833d78 100644
--- a/backend/src/main/java/de/szut/casino/security/AuthController.java
+++ b/backend/src/main/java/de/szut/casino/security/AuthController.java
@@ -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 authenticateUser(@Valid @RequestBody LoginRequestDto loginRequest) {
+ public ResponseEntity 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 verifyEmail(@RequestParam("token") String token) throws MessagingException, IOException {
+ if (authService.verifyEmail(token)) {
+ return ResponseEntity.badRequest().build();
+ }
+
+ return ResponseEntity.ok().build();
+ }
}
diff --git a/backend/src/main/java/de/szut/casino/security/service/AuthService.java b/backend/src/main/java/de/szut/casino/security/service/AuthService.java
index ed2b70c..f51ff83 100644
--- a/backend/src/main/java/de/szut/casino/security/service/AuthService.java
+++ b/backend/src/main/java/de/szut/casino/security/service/AuthService.java
@@ -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 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;
+ }
}
diff --git a/backend/src/main/java/de/szut/casino/security/service/EmailService.java b/backend/src/main/java/de/szut/casino/security/service/EmailService.java
index 861a0c2..4d83262 100644
--- a/backend/src/main/java/de/szut/casino/security/service/EmailService.java
+++ b/backend/src/main/java/de/szut/casino/security/service/EmailService.java
@@ -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())
diff --git a/backend/src/main/java/de/szut/casino/user/UserEntity.java b/backend/src/main/java/de/szut/casino/user/UserEntity.java
index 270d178..161ce52 100644
--- a/backend/src/main/java/de/szut/casino/user/UserEntity.java
+++ b/backend/src/main/java/de/szut/casino/user/UserEntity.java
@@ -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) {
diff --git a/backend/src/main/java/de/szut/casino/user/UserRepository.java b/backend/src/main/java/de/szut/casino/user/UserRepository.java
index 863e744..9eafd54 100644
--- a/backend/src/main/java/de/szut/casino/user/UserRepository.java
+++ b/backend/src/main/java/de/szut/casino/user/UserRepository.java
@@ -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 {
boolean existsByUsername(String username);
boolean existsByEmail(String email);
+
+ @Query("SELECT u FROM UserEntity u WHERE u.verificationToken = ?1")
+ Optional findOneByVerificationToken(String token);
+
+ @Query("SELECT u FROM UserEntity u WHERE u.username = ?1 OR u.email = ?1")
+ Optional findOneByUsernameOrEmail(String usernameOrEmail);
}
diff --git a/backend/src/main/java/de/szut/casino/user/UserService.java b/backend/src/main/java/de/szut/casino/user/UserService.java
index 25fabef..9113864 100644
--- a/backend/src/main/java/de/szut/casino/user/UserService.java
+++ b/backend/src/main/java/de/szut/casino/user/UserService.java
@@ -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 getUserByVerificationToken(String token) {
+ return this.userRepository.findOneByVerificationToken(token);
+ }
+
+ public void saveUser(UserEntity user) {
+ userRepository.save(user);
+ }
+
+ public boolean isVerified(String usernameOrEmail) {
+ Optional optionalUser = userRepository.findOneByUsernameOrEmail(usernameOrEmail);
+
+ if (!optionalUser.isPresent()) {
+ return false;
+ }
+
+ return optionalUser.get().getEmailVerified();
+ }
}
diff --git a/backend/src/main/resources/templates/email/deposit.html b/backend/src/main/resources/templates/email/deposit.html
index 632d7ab..be54677 100644
--- a/backend/src/main/resources/templates/email/deposit.html
+++ b/backend/src/main/resources/templates/email/deposit.html
@@ -128,7 +128,7 @@
Ihr Trustworthy Casino Team
diff --git a/backend/src/main/resources/templates/email/verify.html b/backend/src/main/resources/templates/email/verify.html
new file mode 100644
index 0000000..b7dc2a6
--- /dev/null
+++ b/backend/src/main/resources/templates/email/verify.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+ E-Mail-Verifizierung - Trustworthy Casino©
+
+
+
+
+
+
+
Hallo ${username},
+
+
vielen Dank für Ihre Registrierung bei Trustworthy Casino. Um Ihr Konto zu aktivieren und Zugang zu allen Funktionen zu erhalten, bestätigen Sie bitte Ihre E-Mail-Adresse.
+
+
+
+
Klicken Sie auf den folgenden Button, um Ihre E-Mail-Adresse zu bestätigen:
+
+
+
+
+
Hinweis: Der Bestätigungscode könnte nur 24 Stunden gültig sein und kann vielleicht auch nur einmal verwendet werden.
+
+
+
+
+
Nach der Bestätigung Ihrer E-Mail-Adresse können Sie sofort mit dem Spielen beginnen und alle Vorteile Ihres Kontos nutzen.
+
+
Bei Fragen stehen wir Ihnen jederzeit zur Verfügung.
+
+
Mit freundlichen Grüßen,
+ Ihr Trustworthy Casino Team
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/main/resources/templates/email/welcome.html b/backend/src/main/resources/templates/email/welcome.html
index 2a10134..ed43938 100644
--- a/backend/src/main/resources/templates/email/welcome.html
+++ b/backend/src/main/resources/templates/email/welcome.html
@@ -120,7 +120,7 @@
Ihr Trustworthy Casino Team
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 3792038..5c57416 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -12,6 +12,13 @@ export const routes: Routes = [
loadComponent: () => import('./feature/home/home.component'),
canActivate: [authGuard],
},
+ {
+ path: 'verify',
+ loadComponent: () =>
+ import('./feature/auth/verify-email/verify-email.component').then(
+ (m) => m.VerifyEmailComponent
+ ),
+ },
{
path: 'game/blackjack',
loadComponent: () => import('./feature/game/blackjack/blackjack.component'),
diff --git a/frontend/src/app/feature/auth/register/register.component.ts b/frontend/src/app/feature/auth/register/register.component.ts
index 60f289b..a421184 100644
--- a/frontend/src/app/feature/auth/register/register.component.ts
+++ b/frontend/src/app/feature/auth/register/register.component.ts
@@ -56,25 +56,6 @@ export class RegisterComponent {
};
this.authService.register(registerRequest).subscribe({
- next: () => {
- this.authService
- .login({
- usernameOrEmail: registerRequest.email,
- password: registerRequest.password,
- })
- .subscribe({
- next: () => {
- this.closeDialog.emit();
- this.router.navigate(['/home']);
- },
- error: () => {
- this.isLoading.set(false);
- this.errorMessage.set(
- 'Registration successful but failed to login automatically. Please log in manually.'
- );
- },
- });
- },
error: (err: HttpErrorResponse) => {
this.isLoading.set(false);
diff --git a/frontend/src/app/feature/auth/verify-email/verify-email.component.html b/frontend/src/app/feature/auth/verify-email/verify-email.component.html
new file mode 100644
index 0000000..d7bc11c
--- /dev/null
+++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.html
@@ -0,0 +1 @@
+Verifying...
diff --git a/frontend/src/app/feature/auth/verify-email/verify-email.component.ts b/frontend/src/app/feature/auth/verify-email/verify-email.component.ts
new file mode 100644
index 0000000..5f2814a
--- /dev/null
+++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.ts
@@ -0,0 +1,30 @@
+import { Component, inject, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { AuthService } from '@service/auth.service';
+
+@Component({
+ selector: 'app-verify-email',
+ imports: [],
+ templateUrl: './verify-email.component.html',
+})
+export class VerifyEmailComponent implements OnInit {
+ route: ActivatedRoute = inject(ActivatedRoute);
+ router: Router = inject(Router);
+ authService: AuthService = inject(AuthService);
+
+ ngOnInit(): void {
+ const token = this.route.snapshot.queryParamMap.get('token');
+
+ if (!token) {
+ this.router.navigate(['']);
+ console.log('no token');
+ return;
+ }
+
+ this.authService.verifyEmail(token).subscribe(() => {
+ this.router.navigate([''], {
+ queryParams: { login: true },
+ });
+ });
+ }
+}
diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts
index d4d6078..5cfe0c5 100644
--- a/frontend/src/app/feature/landing/landing.component.ts
+++ b/frontend/src/app/feature/landing/landing.component.ts
@@ -7,7 +7,7 @@ import {
signal,
} from '@angular/core';
import { NgFor } from '@angular/common';
-import { RouterLink } from '@angular/router';
+import { ActivatedRoute, RouterLink } from '@angular/router';
import { AuthService } from '@service/auth.service';
import { LoginComponent } from '../auth/login/login.component';
import { RegisterComponent } from '../auth/register/register.component';
@@ -23,12 +23,16 @@ export class LandingComponent implements OnInit, OnDestroy {
currentSlide = 0;
private autoplayInterval: ReturnType | undefined;
authService: AuthService = inject(AuthService);
+ route: ActivatedRoute = inject(ActivatedRoute);
showLogin = signal(false);
showRegister = signal(false);
ngOnInit() {
this.startAutoplay();
document.body.style.overflow = 'auto';
+ if (this.route.snapshot.queryParamMap.get('login') === 'true') {
+ this.showLoginForm();
+ }
}
ngOnDestroy() {
diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts
index 1066008..657067f 100644
--- a/frontend/src/app/service/auth.service.ts
+++ b/frontend/src/app/service/auth.service.ts
@@ -74,6 +74,10 @@ export class AuthService {
});
}
+ public verifyEmail(token: string): Observable {
+ return this.http.post(`${this.authUrl}/verify?token=${token}`, null);
+ }
+
private setToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token);
}