From bb2e9e48340520b5ba22153c2a76b5900a4d4128 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 14 May 2025 12:12:33 +0200 Subject: [PATCH 001/193] feat(auth): emit closeDialog on successful registration --- frontend/src/app/feature/auth/register/register.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/feature/auth/register/register.component.ts b/frontend/src/app/feature/auth/register/register.component.ts index 7a3e381..60f289b 100644 --- a/frontend/src/app/feature/auth/register/register.component.ts +++ b/frontend/src/app/feature/auth/register/register.component.ts @@ -64,6 +64,7 @@ export class RegisterComponent { }) .subscribe({ next: () => { + this.closeDialog.emit(); this.router.navigate(['/home']); }, error: () => { From 59aa8319818efa633211f210fbf1a6cc66768631 Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 10:02:41 +0200 Subject: [PATCH 002/193] feat(auth): add email verification functionality --- .../casino/security/service/AuthService.java | 2 +- .../casino/security/service/EmailService.java | 18 +++ .../java/de/szut/casino/user/UserEntity.java | 7 +- .../java/de/szut/casino/user/UserService.java | 4 +- .../resources/templates/email/deposit.html | 2 +- .../resources/templates/email/verify.html | 149 ++++++++++++++++++ .../resources/templates/email/welcome.html | 2 +- 7 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/resources/templates/email/verify.html 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..c4d324d 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 @@ -47,7 +47,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(), 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..348fafd 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 @@ -35,6 +35,24 @@ public class EmailService { } } + 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 sendRegistrationEmail(UserEntity user) throws IOException, MessagingException { String template = loadTemplate("email/welcome.html"); String htmlContent = template 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/UserService.java b/backend/src/main/java/de/szut/casino/user/UserService.java index 25fabef..6855619 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); 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..008236d --- /dev/null +++ b/backend/src/main/resources/templates/email/verify.html @@ -0,0 +1,149 @@ + + + + + + E-Mail-Verifizierung - Trustworthy Casino© + + + +
+
+

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:

+ +
+ E-Mail 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

From 21209524be632ba86e8d2c19351dd80bbfc5f435 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Thu, 15 May 2025 10:27:43 +0200 Subject: [PATCH 003/193] feat: implement coinflip api --- .../de/szut/casino/coinflip/CoinSide.java | 6 +++ .../casino/coinflip/CoinflipController.java | 45 +++++++++++++++++++ .../de/szut/casino/coinflip/CoinflipDto.java | 20 +++++++++ .../szut/casino/coinflip/CoinflipResult.java | 15 +++++++ .../szut/casino/coinflip/CoinflipService.java | 34 ++++++++++++++ .../casino/shared/service/BalanceService.java | 2 +- 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/de/szut/casino/coinflip/CoinSide.java create mode 100644 backend/src/main/java/de/szut/casino/coinflip/CoinflipController.java create mode 100644 backend/src/main/java/de/szut/casino/coinflip/CoinflipDto.java create mode 100644 backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java create mode 100644 backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinSide.java b/backend/src/main/java/de/szut/casino/coinflip/CoinSide.java new file mode 100644 index 0000000..f369cb4 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinSide.java @@ -0,0 +1,6 @@ +package de.szut.casino.coinflip; + +public enum CoinSide { + HEAD, + TAILS; +} diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipController.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipController.java new file mode 100644 index 0000000..0a526f1 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipController.java @@ -0,0 +1,45 @@ +package de.szut.casino.coinflip; + +import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException; +import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException; +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import jakarta.validation.Valid; +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.RestController; + +import java.util.Optional; + +@RestController +public class CoinflipController { + private final UserService userService; + private final BalanceService balanceService; + private final CoinflipService coinflipService; + + public CoinflipController(UserService userService, BalanceService balanceService, CoinflipService coinflipService) { + this.userService = userService; + this.balanceService = balanceService; + this.coinflipService = coinflipService; + } + + + @PostMapping("/coinflip") + public ResponseEntity coinFlip(@RequestBody @Valid CoinflipDto coinflipDto) { + Optional optionalUser = userService.getCurrentUser(); + + if (optionalUser.isEmpty()) { + throw new UserNotFoundException(); + } + + UserEntity user = optionalUser.get(); + + if (!this.balanceService.hasFunds(user, coinflipDto)) { + throw new InsufficientFundsException(); + } + + return ResponseEntity.ok(coinflipService.play(user, coinflipDto)); + } +} diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipDto.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipDto.java new file mode 100644 index 0000000..8d5a47b --- /dev/null +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipDto.java @@ -0,0 +1,20 @@ +package de.szut.casino.coinflip; + +import de.szut.casino.shared.dto.BetDto; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +public class CoinflipDto extends BetDto { + @NotNull(message = "chosen side cannot be null") + private CoinSide coinSide; + + public CoinflipDto(BigDecimal betAmount, CoinSide coinSide) { + super(betAmount); + this.coinSide = coinSide; + } +} diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java new file mode 100644 index 0000000..71ead05 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java @@ -0,0 +1,15 @@ +package de.szut.casino.coinflip; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; + +@AllArgsConstructor +@Setter +@Getter +public class CoinflipResult { + private boolean isWin; + private BigDecimal payout; +} diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java new file mode 100644 index 0000000..4cdc3e1 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java @@ -0,0 +1,34 @@ +package de.szut.casino.coinflip; + +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Random; + +@Service +public class CoinflipService { + private final Random random = new Random(); + private final BalanceService balanceService; + + public CoinflipService(BalanceService balanceService) { + this.balanceService = balanceService; + } + + public CoinflipResult play(UserEntity user, CoinflipDto coinflipDto) { + this.balanceService.subtractFunds(user, coinflipDto.getBetAmount()); + + CoinflipResult coinflipResult = new CoinflipResult(false, BigDecimal.ZERO); + CoinSide coinSide = this.random.nextBoolean() ? CoinSide.HEAD : CoinSide.TAILS; + if (coinSide == coinflipDto.getCoinSide()) { + coinflipResult.setWin(true); + + BigDecimal payout = coinflipDto.getBetAmount().multiply(BigDecimal.TWO); + this.balanceService.addFunds(user, payout); + coinflipResult.setPayout(payout); + } + + return coinflipResult; + } +} diff --git a/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java b/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java index 40e6caa..048840b 100644 --- a/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java +++ b/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java @@ -9,7 +9,7 @@ import java.math.BigDecimal; @Service public class BalanceService { - private UserRepository userRepository; + private final UserRepository userRepository; public BalanceService(UserRepository userRepository) { this.userRepository = userRepository; From d2225decc19c942e6d03a9083dc9a15b33b60e3c Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 10:49:24 +0200 Subject: [PATCH 004/193] feat(auth): add email verification feature and handler --- .../GlobalExceptionHandler.java | 7 +++++ .../exceptions/EmailNotVerifiedException.java | 9 ++++++ .../szut/casino/security/AuthController.java | 19 +++++++++--- .../casino/security/service/AuthService.java | 24 +++++++++++++- .../de/szut/casino/user/UserRepository.java | 7 +++++ .../java/de/szut/casino/user/UserService.java | 18 +++++++++++ .../resources/templates/email/verify.html | 2 +- frontend/src/app/app.routes.ts | 4 +++ .../auth/register/register.component.ts | 19 ------------ .../verify-email/verify-email.component.css | 0 .../verify-email/verify-email.component.html | 1 + .../verify-email/verify-email.component.ts | 31 +++++++++++++++++++ .../app/feature/landing/landing.component.ts | 6 +++- frontend/src/app/service/auth.service.ts | 4 +++ 14 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 backend/src/main/java/de/szut/casino/exceptionHandling/exceptions/EmailNotVerifiedException.java create mode 100644 frontend/src/app/feature/auth/verify-email/verify-email.component.css create mode 100644 frontend/src/app/feature/auth/verify-email/verify-email.component.html create mode 100644 frontend/src/app/feature/auth/verify-email/verify-email.component.ts 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..65b3b4b 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.BAD_REQUEST); + } } 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..7862ae7 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) { + 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 c4d324d..714b7f3 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(), @@ -56,4 +62,20 @@ public class AuthService { user.getBalance() ); } + + public Boolean verifyEmail(String token) { + Optional optionalUser = userService.getUserByVerificationToken(token); + + if(!optionalUser.isPresent()) { + return false; + } + + UserEntity user = optionalUser.get(); + + user.setEmailVerified(true); + user.setVerificationToken(null); + userService.saveUser(user); + + return true; + } } 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 6855619..9113864 100644 --- a/backend/src/main/java/de/szut/casino/user/UserService.java +++ b/backend/src/main/java/de/szut/casino/user/UserService.java @@ -44,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/verify.html b/backend/src/main/resources/templates/email/verify.html index 008236d..b7dc2a6 100644 --- a/backend/src/main/resources/templates/email/verify.html +++ b/backend/src/main/resources/templates/email/verify.html @@ -123,7 +123,7 @@

Klicken Sie auf den folgenden Button, um Ihre E-Mail-Adresse zu bestätigen:

diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 3792038..48c1e8e 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -12,6 +12,10 @@ 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.css b/frontend/src/app/feature/auth/verify-email/verify-email.component.css new file mode 100644 index 0000000..e69de29 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..c39c8c0 --- /dev/null +++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.html @@ -0,0 +1 @@ +

verify-email works!

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..c67e1ff --- /dev/null +++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.ts @@ -0,0 +1,31 @@ +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', + styleUrl: './verify-email.component.css' +}) +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..1133958 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); } From db9fe842599b9a46d0770c346b4f7c23021033cc Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 10:51:29 +0200 Subject: [PATCH 005/193] fix: change status code for EmailNotVerifiedException --- .../szut/casino/exceptionHandling/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 65b3b4b..9c185a8 100644 --- a/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java +++ b/backend/src/main/java/de/szut/casino/exceptionHandling/GlobalExceptionHandler.java @@ -36,6 +36,6 @@ public class GlobalExceptionHandler { @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); + return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED); } } From 0963dbae06cdc0f534fe07fad33c046b54bf596b Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 10:57:14 +0200 Subject: [PATCH 006/193] refactor(verify-email): remove unused CSS file and update template --- .../app/feature/auth/verify-email/verify-email.component.css | 0 .../app/feature/auth/verify-email/verify-email.component.html | 2 +- .../src/app/feature/auth/verify-email/verify-email.component.ts | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 frontend/src/app/feature/auth/verify-email/verify-email.component.css diff --git a/frontend/src/app/feature/auth/verify-email/verify-email.component.css b/frontend/src/app/feature/auth/verify-email/verify-email.component.css deleted file mode 100644 index e69de29..0000000 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 index c39c8c0..d7bc11c 100644 --- a/frontend/src/app/feature/auth/verify-email/verify-email.component.html +++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.html @@ -1 +1 @@ -

verify-email works!

+

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 index c67e1ff..26f0317 100644 --- a/frontend/src/app/feature/auth/verify-email/verify-email.component.ts +++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.ts @@ -6,7 +6,6 @@ import { AuthService } from '@service/auth.service'; selector: 'app-verify-email', imports: [], templateUrl: './verify-email.component.html', - styleUrl: './verify-email.component.css' }) export class VerifyEmailComponent implements OnInit{ route: ActivatedRoute = inject(ActivatedRoute); From decf2e21a393bca68f9aeffc16c79a77e2ee0a15 Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 10:58:53 +0200 Subject: [PATCH 007/193] feat(email): add welcome email sending on verification success --- .../src/main/java/de/szut/casino/security/AuthController.java | 2 +- .../main/java/de/szut/casino/security/service/AuthService.java | 3 ++- .../java/de/szut/casino/security/service/EmailService.java | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) 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 7862ae7..f833d78 100644 --- a/backend/src/main/java/de/szut/casino/security/AuthController.java +++ b/backend/src/main/java/de/szut/casino/security/AuthController.java @@ -36,7 +36,7 @@ public class AuthController { } @PostMapping("/verify") - public ResponseEntity verifyEmail(@RequestParam("token") String token) { + public ResponseEntity verifyEmail(@RequestParam("token") String token) throws MessagingException, IOException { if (authService.verifyEmail(token)) { return ResponseEntity.badRequest().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 714b7f3..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 @@ -63,7 +63,7 @@ public class AuthService { ); } - public Boolean verifyEmail(String token) { + public Boolean verifyEmail(String token) throws MessagingException, IOException { Optional optionalUser = userService.getUserByVerificationToken(token); if(!optionalUser.isPresent()) { @@ -75,6 +75,7 @@ public class AuthService { 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 348fafd..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 { @@ -53,7 +52,7 @@ public class EmailService { mailSender.send(message); } - public void sendRegistrationEmail(UserEntity user) throws IOException, MessagingException { + public void sendWelcomeEmail(UserEntity user) throws IOException, MessagingException { String template = loadTemplate("email/welcome.html"); String htmlContent = template .replace("${username}", user.getUsername()) From 51984318e6df018e041e9b08fb20c32e2a994b7c Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 11:01:47 +0200 Subject: [PATCH 008/193] refactor(auth.service): change observable type to unknown --- frontend/src/app/service/auth.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 1133958..657067f 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -74,8 +74,8 @@ export class AuthService { }); } - public verifyEmail(token: string): Observable { - return this.http.post(`${this.authUrl}/verify?token=${token}`, null); + public verifyEmail(token: string): Observable { + return this.http.post(`${this.authUrl}/verify?token=${token}`, null); } private setToken(token: string): void { From 2f21408e3da6db9a692286ec7e9ca3e5a16b5f95 Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 11:03:50 +0200 Subject: [PATCH 009/193] style: format code for better readability --- frontend/src/app/app.routes.ts | 5 ++++- .../app/feature/auth/verify-email/verify-email.component.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 48c1e8e..5c57416 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -14,7 +14,10 @@ export const routes: Routes = [ }, { path: 'verify', - loadComponent: () => import('./feature/auth/verify-email/verify-email.component').then(m => m.VerifyEmailComponent), + loadComponent: () => + import('./feature/auth/verify-email/verify-email.component').then( + (m) => m.VerifyEmailComponent + ), }, { path: 'game/blackjack', 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 index 26f0317..5f2814a 100644 --- a/frontend/src/app/feature/auth/verify-email/verify-email.component.ts +++ b/frontend/src/app/feature/auth/verify-email/verify-email.component.ts @@ -7,7 +7,7 @@ import { AuthService } from '@service/auth.service'; imports: [], templateUrl: './verify-email.component.html', }) -export class VerifyEmailComponent implements OnInit{ +export class VerifyEmailComponent implements OnInit { route: ActivatedRoute = inject(ActivatedRoute); router: Router = inject(Router); authService: AuthService = inject(AuthService); @@ -25,6 +25,6 @@ export class VerifyEmailComponent implements OnInit{ this.router.navigate([''], { queryParams: { login: true }, }); - }) + }); } } From 47e04567a92495f036db46725837fc20806af42b Mon Sep 17 00:00:00 2001 From: jank Date: Thu, 15 May 2025 11:07:00 +0200 Subject: [PATCH 010/193] perf: Update bun jobs --- .gitea/workflows/ci.yml | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7742416..e277f53 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -113,17 +113,15 @@ jobs: push: false eslint: - runs-on: ubuntu-latest + runs-on: docker + container: + image: git.kjan.de/actions/runner-bun:latest name: eslint needs: changed_files if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} - container: - image: catthehacker/ubuntu:act-latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Install bun - uses: oven-sh/setup-bun@v2 - uses: actions/cache@v4 working-directory: ./frontend with: @@ -142,17 +140,15 @@ jobs: bun run lint oxlint: - runs-on: ubuntu-latest + runs-on: docker name: oxlint needs: changed_files if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} container: - image: catthehacker/ubuntu:act-latest + image: git.kjan.de/actions/runner-bun:latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Install bun - uses: oven-sh/setup-bun@v2 - uses: actions/cache@v4 working-directory: ./frontend with: @@ -171,17 +167,15 @@ jobs: bun run oxlint prettier: - runs-on: ubuntu-latest + runs-on: docker name: prettier needs: changed_files if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} container: - image: catthehacker/ubuntu:act-latest + image: git.kjan.de/actions/runner-bun:latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Install bun - uses: oven-sh/setup-bun@v2 - uses: actions/cache@v4 working-directory: ./frontend with: @@ -200,17 +194,15 @@ jobs: bun run format:check test-build: - runs-on: ubuntu-latest + runs-on: docker name: test-build needs: changed_files if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} container: - image: catthehacker/ubuntu:act-latest + image: git.kjan.de/actions/runner-bun:latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Install bun - uses: oven-sh/setup-bun@v2 - uses: actions/cache@v4 working-directory: ./frontend with: From d7f2e72a15e4c85d420c442a0791ba7d11d6d36c Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Thu, 15 May 2025 11:44:56 +0200 Subject: [PATCH 011/193] feat: add coinside result --- .../src/main/java/de/szut/casino/coinflip/CoinflipResult.java | 1 + .../src/main/java/de/szut/casino/coinflip/CoinflipService.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java index 71ead05..4c8fbdf 100644 --- a/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipResult.java @@ -12,4 +12,5 @@ import java.math.BigDecimal; public class CoinflipResult { private boolean isWin; private BigDecimal payout; + private CoinSide coinSide; } diff --git a/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java b/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java index 4cdc3e1..c706ffa 100644 --- a/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java +++ b/backend/src/main/java/de/szut/casino/coinflip/CoinflipService.java @@ -19,8 +19,8 @@ public class CoinflipService { public CoinflipResult play(UserEntity user, CoinflipDto coinflipDto) { this.balanceService.subtractFunds(user, coinflipDto.getBetAmount()); - CoinflipResult coinflipResult = new CoinflipResult(false, BigDecimal.ZERO); CoinSide coinSide = this.random.nextBoolean() ? CoinSide.HEAD : CoinSide.TAILS; + CoinflipResult coinflipResult = new CoinflipResult(false, BigDecimal.ZERO, coinSide); if (coinSide == coinflipDto.getCoinSide()) { coinflipResult.setWin(true); From 9827f81230b17c5cee598eb51f7624536edfd19e Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 11:19:46 +0200 Subject: [PATCH 012/193] wip: stuff --- .../szut/casino/security/AuthController.java | 6 + .../casino/security/service/AuthService.java | 17 +- .../casino/security/service/EmailService.java | 17 ++ .../java/de/szut/casino/user/UserService.java | 4 + .../templates/email/recover-password.html | 160 ++++++++++++++++++ 5 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/resources/templates/email/recover-password.html 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 f833d78..5d8717e 100644 --- a/backend/src/main/java/de/szut/casino/security/AuthController.java +++ b/backend/src/main/java/de/szut/casino/security/AuthController.java @@ -43,4 +43,10 @@ public class AuthController { return ResponseEntity.ok().build(); } + + @PostMapping("/recover-password") + public ResponseEntity recoverPassword(@RequestParam("email") String email) throws MessagingException, IOException { + authService.recoverPassword(email); + 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 f51ff83..380b5e4 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 @@ -16,6 +16,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import javax.swing.text.html.Option; import java.io.IOException; import java.util.Optional; @@ -46,7 +47,7 @@ public class AuthService { SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtUtils.generateToken(authentication); - + return new AuthResponseDto(jwt); } @@ -66,7 +67,7 @@ public class AuthService { public Boolean verifyEmail(String token) throws MessagingException, IOException { Optional optionalUser = userService.getUserByVerificationToken(token); - if(!optionalUser.isPresent()) { + if (!optionalUser.isPresent()) { return false; } @@ -79,4 +80,16 @@ public class AuthService { return true; } + + public void recoverPassword(String email) { + Optional optionalUser = userService.getUserByEmail(email); + + if (optionalUser.isPresent()) { + UserEntity user = optionalUser.get(); + String token = jwtUtils.generateToken(user.getUsername()); + user.setVerificationToken(token); + userService.saveUser(user); + this.emailService.sendPasswordRecoveryEmail(user); + } + } } 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 4d83262..b7a05b9 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 @@ -87,6 +87,23 @@ public class EmailService { mailSender.send(message); } + public void sendPasswordRecoveryEmail(UserEntity user) throws IOException, MessagingException { + String template = loadTemplate("email/recover-password.html"); + String htmlContent = template + .replace("${username}", user.getUsername()) + .replace("${feUrl}", feUrl); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setFrom(mailConfig.fromAddress); + helper.setTo(user.getEmailAddress()); + helper.setSubject("Zurücksetzen ihres Passworts"); + helper.setText(htmlContent, true); + + mailSender.send(message); + } + private String loadTemplate(String templatePath) throws IOException { ClassPathResource resource = new ClassPathResource("templates/" + templatePath); try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) { 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 9113864..a04b02e 100644 --- a/backend/src/main/java/de/szut/casino/user/UserService.java +++ b/backend/src/main/java/de/szut/casino/user/UserService.java @@ -62,4 +62,8 @@ public class UserService { return optionalUser.get().getEmailVerified(); } + + public Optional getUserByEmail(String email) { + return userRepository.findByEmail(email); + } } diff --git a/backend/src/main/resources/templates/email/recover-password.html b/backend/src/main/resources/templates/email/recover-password.html new file mode 100644 index 0000000..6d4e1a1 --- /dev/null +++ b/backend/src/main/resources/templates/email/recover-password.html @@ -0,0 +1,160 @@ + + + + + + Passwort zurücksetzen - Trustworthy Casino© + + + +
+
+

Trustworthy Casino

+
+
+

Hallo ${username},

+ +

wir haben eine Anfrage zum Zurücksetzen Ihres Passworts für Ihr Trustworthy Casino Konto erhalten. Um Ihr Passwort zurückzusetzen, klicken Sie bitte auf den folgenden Button:

+ + + +

Alternativ können Sie auch diesen Code verwenden:

+ +
${resetCode}
+ +
+

Hinweis: Dieser Link und Code sind aus Sicherheitsgründen nur 60 Minuten gültig.

+
+ +
+ +
+

Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail bitte. In diesem Fall empfehlen wir Ihnen, Ihr Passwort zu ändern und unseren Kundenservice zu kontaktieren, um die Sicherheit Ihres Kontos zu gewährleisten.

+
+ +
+ +

Bei Fragen steht Ihnen unser Support-Team jederzeit zur Verfügung.

+ +

Mit freundlichen Grüßen,
+ Ihr Trustworthy Casino Team

+
+ +
+ + \ No newline at end of file From c8f2d16f076d04d64fbb8cbc2761a10bb1153d5e Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 12:15:33 +0200 Subject: [PATCH 013/193] feat(auth): add password reset functionality and DTO --- .../szut/casino/security/AuthController.java | 7 ++++++ .../casino/security/dto/ResetPasswordDto.java | 15 +++++++++++++ .../casino/security/service/AuthService.java | 22 ++++++++++++++++--- .../casino/security/service/EmailService.java | 1 + .../java/de/szut/casino/user/UserEntity.java | 2 ++ .../de/szut/casino/user/UserRepository.java | 3 +++ .../java/de/szut/casino/user/UserService.java | 4 ++++ .../templates/email/recover-password.html | 12 ++++------ 8 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 backend/src/main/java/de/szut/casino/security/dto/ResetPasswordDto.java 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 5d8717e..d22de30 100644 --- a/backend/src/main/java/de/szut/casino/security/AuthController.java +++ b/backend/src/main/java/de/szut/casino/security/AuthController.java @@ -4,6 +4,7 @@ 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.dto.ResetPasswordDto; import de.szut.casino.security.service.AuthService; import de.szut.casino.user.dto.CreateUserDto; import de.szut.casino.user.dto.GetUserDto; @@ -49,4 +50,10 @@ public class AuthController { authService.recoverPassword(email); return ResponseEntity.ok().build(); } + + @PostMapping("/reset-password") + public ResponseEntity resetPassword(@Valid @RequestBody ResetPasswordDto passwordDto) throws MessagingException, IOException { + authService.resetPassword(passwordDto); + return ResponseEntity.ok().build(); + } } diff --git a/backend/src/main/java/de/szut/casino/security/dto/ResetPasswordDto.java b/backend/src/main/java/de/szut/casino/security/dto/ResetPasswordDto.java new file mode 100644 index 0000000..192d928 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/security/dto/ResetPasswordDto.java @@ -0,0 +1,15 @@ +package de.szut.casino.security.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class ResetPasswordDto { + private String token; + private String password; +} 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 380b5e4..959a55a 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 @@ -3,17 +3,20 @@ 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.dto.ResetPasswordDto; import de.szut.casino.security.jwt.JwtUtils; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserService; import de.szut.casino.user.dto.CreateUserDto; import de.szut.casino.user.dto.GetUserDto; import jakarta.mail.MessagingException; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import javax.swing.text.html.Option; @@ -35,6 +38,9 @@ public class AuthService { @Autowired private EmailService emailService; + @Autowired + private PasswordEncoder passwordEncoder; + public AuthResponseDto login(LoginRequestDto loginRequest) throws EmailNotVerifiedException { if (!userService.isVerified(loginRequest.getUsernameOrEmail())) { throw new EmailNotVerifiedException(); @@ -81,15 +87,25 @@ public class AuthService { return true; } - public void recoverPassword(String email) { + public void recoverPassword(String email) throws MessagingException, IOException { Optional optionalUser = userService.getUserByEmail(email); if (optionalUser.isPresent()) { UserEntity user = optionalUser.get(); - String token = jwtUtils.generateToken(user.getUsername()); - user.setVerificationToken(token); + user.setPasswordResetToken(RandomStringUtils.randomAlphanumeric(64)); userService.saveUser(user); this.emailService.sendPasswordRecoveryEmail(user); } } + + public void resetPassword(ResetPasswordDto passwordDto) { + Optional optionalUser = userService.getUserByPasswordResetToken(passwordDto.getToken()); + + if (optionalUser.isPresent()) { + UserEntity user = optionalUser.get(); + user.setPassword(passwordEncoder.encode(passwordDto.getPassword())); + user.setPasswordResetToken(null); + userService.saveUser(user); + } + } } 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 b7a05b9..f276a3c 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 @@ -91,6 +91,7 @@ public class EmailService { String template = loadTemplate("email/recover-password.html"); String htmlContent = template .replace("${username}", user.getUsername()) + .replace("${resetToken}", user.getPasswordResetToken()) .replace("${feUrl}", feUrl); MimeMessage message = mailSender.createMimeMessage(); 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 161ce52..2190867 100644 --- a/backend/src/main/java/de/szut/casino/user/UserEntity.java +++ b/backend/src/main/java/de/szut/casino/user/UserEntity.java @@ -34,6 +34,8 @@ public class UserEntity { private String verificationToken; + private String passwordResetToken; + public UserEntity(String email, String username, String password, BigDecimal balance, String verificationToken) { this.email = email; this.username = username; 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 9eafd54..29790f5 100644 --- a/backend/src/main/java/de/szut/casino/user/UserRepository.java +++ b/backend/src/main/java/de/szut/casino/user/UserRepository.java @@ -21,4 +21,7 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM UserEntity u WHERE u.username = ?1 OR u.email = ?1") Optional findOneByUsernameOrEmail(String usernameOrEmail); + + @Query("SELECT u FROM UserEntity u WHERE u.passwordResetToken = ?1") + Optional findOneByPasswordResetToken(String token); } 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 a04b02e..baa2eab 100644 --- a/backend/src/main/java/de/szut/casino/user/UserService.java +++ b/backend/src/main/java/de/szut/casino/user/UserService.java @@ -66,4 +66,8 @@ public class UserService { public Optional getUserByEmail(String email) { return userRepository.findByEmail(email); } + + public Optional getUserByPasswordResetToken(String token) { + return this.userRepository.findOneByPasswordResetToken(token); + } } diff --git a/backend/src/main/resources/templates/email/recover-password.html b/backend/src/main/resources/templates/email/recover-password.html index 6d4e1a1..cf666d1 100644 --- a/backend/src/main/resources/templates/email/recover-password.html +++ b/backend/src/main/resources/templates/email/recover-password.html @@ -129,12 +129,8 @@ Passwort zurücksetzen
-

Alternativ können Sie auch diesen Code verwenden:

- -
${resetCode}
-
-

Hinweis: Dieser Link und Code sind aus Sicherheitsgründen nur 60 Minuten gültig.

+

Hinweis: Dieser Link und Code sind aus Sicherheitsgründen vielleicht nur 60 Minuten gültig.

@@ -145,15 +141,15 @@
-

Bei Fragen steht Ihnen unser Support-Team jederzeit zur Verfügung.

+

Bei Fragen steht Ihnen unser Support-Team nicht zur Verfügung.

Mit freundlichen Grüßen,
Ihr Trustworthy Casino Team

From 2305e836478571679546cf4087b649c3cd63663f Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 12:31:08 +0200 Subject: [PATCH 014/193] feat(auth): add recover and reset password functionality --- frontend/src/app/app.routes.ts | 14 ++ .../feature/auth/login/login.component.html | 12 ++ .../app/feature/auth/login/login.component.ts | 5 + .../recover-password.component.html | 162 ++++++++++++++++++ .../recover-password.component.ts | 121 +++++++++++++ frontend/src/app/service/auth.service.ts | 8 + 6 files changed, 322 insertions(+) create mode 100644 frontend/src/app/feature/auth/recover-password/recover-password.component.html create mode 100644 frontend/src/app/feature/auth/recover-password/recover-password.component.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 5c57416..b4bf818 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -19,6 +19,20 @@ export const routes: Routes = [ (m) => m.VerifyEmailComponent ), }, + { + path: 'recover-password', + loadComponent: () => + import('./feature/auth/recover-password/recover-password.component').then( + (m) => m.RecoverPasswordComponent + ), + }, + { + path: 'reset-password', + loadComponent: () => + import('./feature/auth/recover-password/recover-password.component').then( + (m) => m.RecoverPasswordComponent + ), + }, { path: 'game/blackjack', loadComponent: () => import('./feature/game/blackjack/blackjack.component'), diff --git a/frontend/src/app/feature/auth/login/login.component.html b/frontend/src/app/feature/auth/login/login.component.html index 5f4c535..dba76b2 100644 --- a/frontend/src/app/feature/auth/login/login.component.html +++ b/frontend/src/app/feature/auth/login/login.component.html @@ -83,6 +83,18 @@ +
+

+ Passwort vergessen? + +

+
+

Noch kein Konto? diff --git a/frontend/src/app/feature/auth/login/login.component.ts b/frontend/src/app/feature/auth/login/login.component.ts index b5a67a0..946f412 100644 --- a/frontend/src/app/feature/auth/login/login.component.ts +++ b/frontend/src/app/feature/auth/login/login.component.ts @@ -63,4 +63,9 @@ export class LoginComponent { }, }); } + + switchToForgotPassword() { + this.closeDialog.emit(); + this.router.navigate(['/recover-password']); + } } diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.html b/frontend/src/app/feature/auth/recover-password/recover-password.component.html new file mode 100644 index 0000000..5879f3d --- /dev/null +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.html @@ -0,0 +1,162 @@ +

+ +
diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts new file mode 100644 index 0000000..fc3a4d9 --- /dev/null +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts @@ -0,0 +1,121 @@ +import { Component, EventEmitter, Output, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AuthService } from '@service/auth.service'; + +@Component({ + selector: 'app-recover-password', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + ], + templateUrl: './recover-password.component.html', +}) +export class RecoverPasswordComponent { + emailForm: FormGroup; + resetPasswordForm: FormGroup; + errorMessage = signal(''); + successMessage = signal(''); + isLoading = signal(false); + token = ''; + isResetMode = signal(false); + + @Output() closeDialog = new EventEmitter(); + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute + ) { + this.emailForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]] + }); + + this.resetPasswordForm = this.fb.group({ + password: ['', [Validators.required, Validators.minLength(8)]], + confirmPassword: ['', [Validators.required]] + }, { + validators: this.passwordMatchValidator + }); + + // Check if we're in reset mode + this.route.queryParamMap.subscribe(params => { + const token = params.get('token'); + if (token) { + this.token = token; + this.isResetMode.set(true); + } + }); + } + + passwordMatchValidator(form: FormGroup) { + const password = form.get('password')?.value; + const confirmPassword = form.get('confirmPassword')?.value; + return password === confirmPassword ? null : { passwordMismatch: true }; + } + + get emailFormControls() { + return this.emailForm.controls; + } + + get resetFormControls() { + return this.resetPasswordForm.controls; + } + + onSubmitEmail(): void { + if (this.emailForm.invalid) { + return; + } + + this.isLoading.set(true); + this.errorMessage.set(''); + this.successMessage.set(''); + + const email = this.emailFormControls['email'].value; + + this.authService.recoverPassword(email).subscribe({ + next: () => { + this.isLoading.set(false); + this.successMessage.set('Wenn ein Konto mit dieser E-Mail existiert, wird eine E-Mail mit weiteren Anweisungen gesendet.'); + this.emailForm.reset(); + }, + error: (err) => { + this.isLoading.set(false); + this.errorMessage.set( + err.error?.message || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.' + ); + } + }); + } + + onSubmitReset(): void { + if (this.resetPasswordForm.invalid) { + return; + } + + this.isLoading.set(true); + this.errorMessage.set(''); + this.successMessage.set(''); + + const password = this.resetFormControls['password'].value; + + this.authService.resetPassword(this.token, password).subscribe({ + next: () => { + this.isLoading.set(false); + this.successMessage.set('Dein Passwort wurde erfolgreich zurückgesetzt. Du kannst dich jetzt anmelden.'); + setTimeout(() => { + this.router.navigate([''], { queryParams: { login: true } }); + }, 3000); + }, + error: (err) => { + this.isLoading.set(false); + this.errorMessage.set( + err.error?.message || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.' + ); + } + }); + } +} diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 657067f..b51a4a1 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -77,6 +77,14 @@ export class AuthService { public verifyEmail(token: string): Observable { return this.http.post(`${this.authUrl}/verify?token=${token}`, null); } + + public recoverPassword(email: string): Observable { + return this.http.post(`${this.authUrl}/recover-password?email=${email}`, null); + } + + public resetPassword(token: string, password: string): Observable { + return this.http.post(`${this.authUrl}/reset-password`, { token, password }); + } private setToken(token: string): void { localStorage.setItem(TOKEN_KEY, token); From d049048206dfbf5b9d95ab954b0957e3f43e491c Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 12:34:28 +0200 Subject: [PATCH 015/193] style: format HTML and TypeScript files for consistency --- .../feature/auth/login/login.component.html | 20 +++++------ .../recover-password.component.html | 28 ++++++++++----- .../recover-password.component.ts | 36 ++++++++++--------- frontend/src/app/service/auth.service.ts | 4 +-- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/frontend/src/app/feature/auth/login/login.component.html b/frontend/src/app/feature/auth/login/login.component.html index dba76b2..04afd42 100644 --- a/frontend/src/app/feature/auth/login/login.component.html +++ b/frontend/src/app/feature/auth/login/login.component.html @@ -84,16 +84,16 @@
-

- Passwort vergessen? - -

-
+

+ Passwort vergessen? + +

+

diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.html b/frontend/src/app/feature/auth/recover-password/recover-password.component.html index 5879f3d..8dccc09 100644 --- a/frontend/src/app/feature/auth/recover-password/recover-password.component.html +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.html @@ -22,7 +22,11 @@

@if (errorMessage()) { @@ -42,10 +46,11 @@

- Gib deine E-Mail-Adresse ein, und wir senden dir einen Link zum Zurücksetzen deines Passworts. + Gib deine E-Mail-Adresse ein, und wir senden dir einen Link zum Zurücksetzen deines + Passworts.

- +