Merge branch 'main' into feat-coinside
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Successful in 47s
CI / Docker backend validation (pull_request) Successful in 1m8s
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / eslint (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Checkstyle Main (pull_request) Successful in 47s
CI / Docker backend validation (pull_request) Successful in 1m8s
This commit is contained in:
commit
963516a5bd
18 changed files with 315 additions and 50 deletions
|
@ -113,17 +113,15 @@ jobs:
|
||||||
push: false
|
push: false
|
||||||
|
|
||||||
eslint:
|
eslint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: git.kjan.de/actions/runner-bun:latest
|
||||||
name: eslint
|
name: eslint
|
||||||
needs: changed_files
|
needs: changed_files
|
||||||
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
with:
|
with:
|
||||||
|
@ -142,17 +140,15 @@ jobs:
|
||||||
bun run lint
|
bun run lint
|
||||||
|
|
||||||
oxlint:
|
oxlint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
name: oxlint
|
name: oxlint
|
||||||
needs: changed_files
|
needs: changed_files
|
||||||
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: git.kjan.de/actions/runner-bun:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
with:
|
with:
|
||||||
|
@ -171,17 +167,15 @@ jobs:
|
||||||
bun run oxlint
|
bun run oxlint
|
||||||
|
|
||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
name: prettier
|
name: prettier
|
||||||
needs: changed_files
|
needs: changed_files
|
||||||
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: git.kjan.de/actions/runner-bun:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
with:
|
with:
|
||||||
|
@ -200,17 +194,15 @@ jobs:
|
||||||
bun run format:check
|
bun run format:check
|
||||||
|
|
||||||
test-build:
|
test-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
name: test-build
|
name: test-build
|
||||||
needs: changed_files
|
needs: changed_files
|
||||||
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: git.kjan.de/actions/runner-bun:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.szut.casino.exceptionHandling;
|
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.InsufficientFundsException;
|
||||||
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
|
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
|
||||||
import jakarta.persistence.EntityExistsException;
|
import jakarta.persistence.EntityExistsException;
|
||||||
|
@ -31,4 +32,10 @@ public class GlobalExceptionHandler {
|
||||||
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
|
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
|
||||||
return new ResponseEntity<>(errorDetails, HttpStatus.CONFLICT);
|
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;
|
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.AuthResponseDto;
|
||||||
import de.szut.casino.security.dto.LoginRequestDto;
|
import de.szut.casino.security.dto.LoginRequestDto;
|
||||||
import de.szut.casino.security.service.AuthService;
|
import de.szut.casino.security.service.AuthService;
|
||||||
|
@ -9,12 +11,10 @@ import jakarta.mail.MessagingException;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
|
@ -24,7 +24,7 @@ public class AuthController {
|
||||||
private AuthService authService;
|
private AuthService authService;
|
||||||
|
|
||||||
@PostMapping("/login")
|
@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);
|
AuthResponseDto response = authService.login(loginRequest);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,13 @@ public class AuthController {
|
||||||
GetUserDto response = authService.register(signUpRequest);
|
GetUserDto response = authService.register(signUpRequest);
|
||||||
return ResponseEntity.ok(response);
|
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;
|
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.AuthResponseDto;
|
||||||
import de.szut.casino.security.dto.LoginRequestDto;
|
import de.szut.casino.security.dto.LoginRequestDto;
|
||||||
import de.szut.casino.security.jwt.JwtUtils;
|
import de.szut.casino.security.jwt.JwtUtils;
|
||||||
|
@ -16,6 +17,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
@ -32,7 +34,11 @@ public class AuthService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private EmailService emailService;
|
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(
|
Authentication authentication = authenticationManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(
|
new UsernamePasswordAuthenticationToken(
|
||||||
loginRequest.getUsernameOrEmail(),
|
loginRequest.getUsernameOrEmail(),
|
||||||
|
@ -47,7 +53,7 @@ public class AuthService {
|
||||||
public GetUserDto register(CreateUserDto signUpRequest) throws MessagingException, IOException {
|
public GetUserDto register(CreateUserDto signUpRequest) throws MessagingException, IOException {
|
||||||
UserEntity user = userService.createUser(signUpRequest);
|
UserEntity user = userService.createUser(signUpRequest);
|
||||||
|
|
||||||
this.emailService.sendRegistrationEmail(user);
|
this.emailService.sendEmailVerificationEmail(user);
|
||||||
|
|
||||||
return new GetUserDto(
|
return new GetUserDto(
|
||||||
user.getId(),
|
user.getId(),
|
||||||
|
@ -56,4 +62,21 @@ public class AuthService {
|
||||||
user.getBalance()
|
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.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EmailService {
|
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 template = loadTemplate("email/welcome.html");
|
||||||
String htmlContent = template
|
String htmlContent = template
|
||||||
.replace("${username}", user.getUsername())
|
.replace("${username}", user.getUsername())
|
||||||
|
|
|
@ -30,11 +30,16 @@ public class UserEntity {
|
||||||
@Column(precision = 19, scale = 2)
|
@Column(precision = 19, scale = 2)
|
||||||
private BigDecimal balance;
|
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.email = email;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.balance = balance;
|
this.balance = balance;
|
||||||
|
this.verificationToken = verificationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBalance(BigDecimal amountToAdd) {
|
public void addBalance(BigDecimal amountToAdd) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.szut.casino.user;
|
package de.szut.casino.user;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -14,4 +15,10 @@ public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
||||||
boolean existsByUsername(String username);
|
boolean existsByUsername(String username);
|
||||||
|
|
||||||
boolean existsByEmail(String email);
|
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 de.szut.casino.user.dto.CreateUserDto;
|
||||||
import jakarta.persistence.EntityExistsException;
|
import jakarta.persistence.EntityExistsException;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
@ -31,7 +32,8 @@ public class UserService {
|
||||||
createUserDto.getEmail(),
|
createUserDto.getEmail(),
|
||||||
createUserDto.getUsername(),
|
createUserDto.getUsername(),
|
||||||
passwordEncoder.encode(createUserDto.getPassword()),
|
passwordEncoder.encode(createUserDto.getPassword()),
|
||||||
BigDecimal.valueOf(100) // Starting balance
|
BigDecimal.valueOf(100),
|
||||||
|
RandomStringUtils.randomAlphanumeric(64)
|
||||||
);
|
);
|
||||||
|
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
|
@ -42,4 +44,22 @@ public class UserService {
|
||||||
|
|
||||||
return userRepository.findByUsername(username);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@
|
||||||
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
|
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>2025 Trustworthy Casino - Alle Rechte vorbehalten</p>
|
<p>2025 Trustworthy Casino - Keine Rechte vorbehalten</p>
|
||||||
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
149
backend/src/main/resources/templates/email/verify.html
Normal file
149
backend/src/main/resources/templates/email/verify.html
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>E-Mail-Verifizierung - Trustworthy Casino©</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #0a1219;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #1a2835;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
color: #ffffff;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background-color: #121e27;
|
||||||
|
padding: 30px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background-color: #1a2835;
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #10b981;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 12px 24px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: #059669;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #ffffff;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
li::marker {
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
color: #10b981;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #1a2835;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.verification-code {
|
||||||
|
background-color: #1a2835;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
letter-spacing: 5px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
background-color: #1a2835;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #f59e0b;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Trustworthy Casino</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Hallo <span class="highlight">${username}</span>,</h2>
|
||||||
|
|
||||||
|
<p>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.</p>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<p>Klicken Sie auf den folgenden Button, um Ihre E-Mail-Adresse zu bestätigen:</p>
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="${feUrl}/verify?token=${token}" class="button">E-Mail bestätigen</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<p><span class="warning">Hinweis:</span> Der Bestätigungscode könnte nur 24 Stunden gültig sein und kann vielleicht auch nur einmal verwendet werden.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<p>Nach der Bestätigung Ihrer E-Mail-Adresse können Sie sofort mit dem Spielen beginnen und alle Vorteile Ihres Kontos nutzen.</p>
|
||||||
|
|
||||||
|
<p>Bei Fragen stehen wir Ihnen jederzeit zur Verfügung.</p>
|
||||||
|
|
||||||
|
<p>Mit freundlichen Grüßen,<br>
|
||||||
|
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>2025 Trustworthy Casino - Keine Rechte vorbehalten</p>
|
||||||
|
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
||||||
|
<p>Falls Sie diese E-Mail nicht angefordert haben, ignorieren Sie diese bitte.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -120,7 +120,7 @@
|
||||||
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
|
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>2025 Trustworthy Casino - Alle Rechte vorbehalten</p>
|
<p>2025 Trustworthy Casino - Keine Rechte vorbehalten</p>
|
||||||
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,13 @@ export const routes: Routes = [
|
||||||
loadComponent: () => import('./feature/home/home.component'),
|
loadComponent: () => import('./feature/home/home.component'),
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'verify',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./feature/auth/verify-email/verify-email.component').then(
|
||||||
|
(m) => m.VerifyEmailComponent
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'game/blackjack',
|
path: 'game/blackjack',
|
||||||
loadComponent: () => import('./feature/game/blackjack/blackjack.component'),
|
loadComponent: () => import('./feature/game/blackjack/blackjack.component'),
|
||||||
|
|
|
@ -56,25 +56,6 @@ export class RegisterComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.authService.register(registerRequest).subscribe({
|
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) => {
|
error: (err: HttpErrorResponse) => {
|
||||||
this.isLoading.set(false);
|
this.isLoading.set(false);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<p>Verifying...</p>
|
|
@ -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 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import {
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NgFor } from '@angular/common';
|
import { NgFor } from '@angular/common';
|
||||||
import { RouterLink } from '@angular/router';
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
import { AuthService } from '@service/auth.service';
|
import { AuthService } from '@service/auth.service';
|
||||||
import { LoginComponent } from '../auth/login/login.component';
|
import { LoginComponent } from '../auth/login/login.component';
|
||||||
import { RegisterComponent } from '../auth/register/register.component';
|
import { RegisterComponent } from '../auth/register/register.component';
|
||||||
|
@ -23,12 +23,16 @@ export class LandingComponent implements OnInit, OnDestroy {
|
||||||
currentSlide = 0;
|
currentSlide = 0;
|
||||||
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
|
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
|
||||||
authService: AuthService = inject(AuthService);
|
authService: AuthService = inject(AuthService);
|
||||||
|
route: ActivatedRoute = inject(ActivatedRoute);
|
||||||
showLogin = signal(false);
|
showLogin = signal(false);
|
||||||
showRegister = signal(false);
|
showRegister = signal(false);
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.startAutoplay();
|
this.startAutoplay();
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
|
if (this.route.snapshot.queryParamMap.get('login') === 'true') {
|
||||||
|
this.showLoginForm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|
|
@ -74,6 +74,10 @@ export class AuthService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public verifyEmail(token: string): Observable<unknown> {
|
||||||
|
return this.http.post<unknown>(`${this.authUrl}/verify?token=${token}`, null);
|
||||||
|
}
|
||||||
|
|
||||||
private setToken(token: string): void {
|
private setToken(token: string): void {
|
||||||
localStorage.setItem(TOKEN_KEY, token);
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue