diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubController.java b/backend/src/main/java/de/szut/casino/security/GitHubController.java similarity index 94% rename from backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubController.java rename to backend/src/main/java/de/szut/casino/security/GitHubController.java index 9abf96c..b45fb62 100644 --- a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubController.java +++ b/backend/src/main/java/de/szut/casino/security/GitHubController.java @@ -1,6 +1,7 @@ -package de.szut.casino.security.oauth2.github; +package de.szut.casino.security; import de.szut.casino.security.dto.AuthResponseDto; +import de.szut.casino.security.dto.GithubCallbackDto; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubService.java b/backend/src/main/java/de/szut/casino/security/GitHubService.java similarity index 98% rename from backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubService.java rename to backend/src/main/java/de/szut/casino/security/GitHubService.java index 4a8df24..1051498 100644 --- a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubService.java +++ b/backend/src/main/java/de/szut/casino/security/GitHubService.java @@ -1,10 +1,12 @@ -package de.szut.casino.security.oauth2.github; +package de.szut.casino.security; import de.szut.casino.security.dto.AuthResponseDto; import de.szut.casino.security.jwt.JwtUtils; import de.szut.casino.user.AuthProvider; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/github/GithubCallbackDto.java b/backend/src/main/java/de/szut/casino/security/dto/GithubCallbackDto.java similarity index 65% rename from backend/src/main/java/de/szut/casino/security/oauth2/github/GithubCallbackDto.java rename to backend/src/main/java/de/szut/casino/security/dto/GithubCallbackDto.java index 620a708..07619aa 100644 --- a/backend/src/main/java/de/szut/casino/security/oauth2/github/GithubCallbackDto.java +++ b/backend/src/main/java/de/szut/casino/security/dto/GithubCallbackDto.java @@ -1,4 +1,4 @@ -package de.szut.casino.security.oauth2.github; +package de.szut.casino.security.dto; import lombok.Data; diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubOAuth2UserInfo.java b/backend/src/main/java/de/szut/casino/security/oauth2/GitHubOAuth2UserInfo.java similarity index 82% rename from backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubOAuth2UserInfo.java rename to backend/src/main/java/de/szut/casino/security/oauth2/GitHubOAuth2UserInfo.java index c764fc7..f98390e 100644 --- a/backend/src/main/java/de/szut/casino/security/oauth2/github/GitHubOAuth2UserInfo.java +++ b/backend/src/main/java/de/szut/casino/security/oauth2/GitHubOAuth2UserInfo.java @@ -1,6 +1,4 @@ -package de.szut.casino.security.oauth2.github; - -import de.szut.casino.security.oauth2.OAuth2UserInfo; +package de.szut.casino.security.oauth2; import java.util.Map; diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/OAuth2UserInfoFactory.java b/backend/src/main/java/de/szut/casino/security/oauth2/OAuth2UserInfoFactory.java index 66633e0..b7d4365 100644 --- a/backend/src/main/java/de/szut/casino/security/oauth2/OAuth2UserInfoFactory.java +++ b/backend/src/main/java/de/szut/casino/security/oauth2/OAuth2UserInfoFactory.java @@ -1,8 +1,6 @@ package de.szut.casino.security.oauth2; import de.szut.casino.exceptionHandling.exceptions.OAuth2AuthenticationProcessingException; -import de.szut.casino.security.oauth2.github.GitHubOAuth2UserInfo; -import de.szut.casino.security.oauth2.google.GoogleOAuth2UserInfo; import de.szut.casino.user.AuthProvider; import java.util.Map; @@ -12,8 +10,6 @@ public class OAuth2UserInfoFactory { public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { if (registrationId.equalsIgnoreCase(AuthProvider.GITHUB.toString())) { return new GitHubOAuth2UserInfo(attributes); - } else if (registrationId.equalsIgnoreCase(AuthProvider.GOOGLE.toString())) { - return new GoogleOAuth2UserInfo(attributes); } else { throw new OAuth2AuthenticationProcessingException("Sorry! Login with " + registrationId + " is not supported yet."); } diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleController.java b/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleController.java deleted file mode 100644 index 30907f8..0000000 --- a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleController.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.szut.casino.security.oauth2.google; - -import de.szut.casino.security.dto.AuthResponseDto; -import de.szut.casino.security.oauth2.github.GithubCallbackDto; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.view.RedirectView; - -@RestController -@RequestMapping("/oauth2/google") -public class GoogleController { - private static final Logger logger = LoggerFactory.getLogger(GoogleController.class); - - @Value("${spring.security.oauth2.client.registration.google.client-id}") - private String clientId; - - @Value("${spring.security.oauth2.client.provider.google.authorization-uri}") - private String authorizationUri; - - @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") - private String redirectUri; - - @Autowired - private GoogleService googleService; - - @GetMapping("/authorize") - public RedirectView authorizeGoogle() { - logger.info("Redirecting to Google for authorization"); - - String authUrl = authorizationUri + - "?client_id=" + clientId + - "&redirect_uri=" + redirectUri + - "&response_type=code" + - "&scope=email profile"; - - return new RedirectView(authUrl); - } - - @PostMapping("/callback") - public ResponseEntity googleCallback(@RequestBody GithubCallbackDto callbackDto) { - String code = callbackDto.getCode(); - AuthResponseDto response = googleService.processGoogleCode(code); - return ResponseEntity.ok(response); - } -} diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleOAuth2UserInfo.java b/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleOAuth2UserInfo.java deleted file mode 100644 index 819a9b3..0000000 --- a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleOAuth2UserInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.szut.casino.security.oauth2.google; - -import de.szut.casino.security.oauth2.OAuth2UserInfo; - -import java.util.Map; - -public class GoogleOAuth2UserInfo extends OAuth2UserInfo { - - public GoogleOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getId() { - return (String) attributes.get("sub"); - } - - @Override - public String getName() { - return (String) attributes.get("name"); - } - - @Override - public String getEmail() { - return (String) attributes.get("email"); - } -} diff --git a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleService.java b/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleService.java deleted file mode 100644 index fd30303..0000000 --- a/backend/src/main/java/de/szut/casino/security/oauth2/google/GoogleService.java +++ /dev/null @@ -1,164 +0,0 @@ -package de.szut.casino.security.oauth2.google; - -import de.szut.casino.security.dto.AuthResponseDto; -import de.szut.casino.security.jwt.JwtUtils; -import de.szut.casino.user.AuthProvider; -import de.szut.casino.user.UserEntity; -import de.szut.casino.user.UserRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.math.BigDecimal; -import java.util.*; - -@Service -public class GoogleService { - private static final Logger logger = LoggerFactory.getLogger(GoogleService.class); - - @Value("${spring.security.oauth2.client.registration.google.client-id}") - private String clientId; - - @Value("${spring.security.oauth2.client.registration.google.client-secret}") - private String clientSecret; - - @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") - private String redirectUri; - - @Value("${spring.security.oauth2.client.provider.google.token-uri}") - private String tokenUri; - - @Value("${spring.security.oauth2.client.provider.google.user-info-uri}") - private String userInfoUri; - - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private UserRepository userRepository; - - @Autowired - private JwtUtils jwtUtils; - - @Autowired - private PasswordEncoder oauth2PasswordEncoder; - - public AuthResponseDto processGoogleCode(String code) { - try { - RestTemplate restTemplate = new RestTemplate(); - - HttpHeaders tokenHeaders = new HttpHeaders(); - tokenHeaders.set("Content-Type", "application/x-www-form-urlencoded"); - - MultiValueMap tokenRequestBody = new LinkedMultiValueMap<>(); - tokenRequestBody.add("client_id", clientId); - tokenRequestBody.add("client_secret", clientSecret); - tokenRequestBody.add("code", code); - tokenRequestBody.add("redirect_uri", redirectUri); - tokenRequestBody.add("grant_type", "authorization_code"); - - HttpEntity> tokenRequestEntity = new HttpEntity<>(tokenRequestBody, tokenHeaders); - - ResponseEntity tokenResponse = restTemplate.exchange( - tokenUri, - HttpMethod.POST, - tokenRequestEntity, - Map.class - ); - - Map tokenResponseBody = tokenResponse.getBody(); - - if (tokenResponseBody == null || tokenResponseBody.containsKey("error")) { - String error = tokenResponseBody != null ? (String) tokenResponseBody.get("error") : "Unknown error"; - throw new RuntimeException("Google OAuth error: " + error); - } - - String accessToken = (String) tokenResponseBody.get("access_token"); - if (accessToken == null || accessToken.isEmpty()) { - throw new RuntimeException("Failed to receive access token from Google"); - } - - HttpHeaders userInfoHeaders = new HttpHeaders(); - userInfoHeaders.set("Authorization", "Bearer " + accessToken); - - HttpEntity userInfoRequestEntity = new HttpEntity<>(null, userInfoHeaders); - - ResponseEntity userResponse = restTemplate.exchange( - userInfoUri, - HttpMethod.GET, - userInfoRequestEntity, - Map.class - ); - - Map userAttributes = userResponse.getBody(); - if (userAttributes == null) { - throw new RuntimeException("Failed to fetch user data from Google"); - } - - String googleId = (String) userAttributes.get("sub"); - String email = (String) userAttributes.get("email"); - String name = (String) userAttributes.get("name"); - Boolean emailVerified = (Boolean) userAttributes.getOrDefault("email_verified", false); - - if (email == null) { - throw new RuntimeException("Google account does not have an email"); - } - - String username = name != null ? name.replaceAll("\\s+", "") : email.split("@")[0]; - - Optional userOptional = userRepository.findByProviderId(googleId); - UserEntity user; - - if (userOptional.isPresent()) { - user = userOptional.get(); - } else { - userOptional = userRepository.findByEmail(email); - - if (userOptional.isPresent()) { - user = userOptional.get(); - user.setProvider(AuthProvider.GOOGLE); - user.setProviderId(googleId); - } else { - user = new UserEntity(); - user.setEmail(email); - user.setUsername(username); - user.setProvider(AuthProvider.GOOGLE); - user.setProviderId(googleId); - user.setEmailVerified(emailVerified); - - user.setBalance(new BigDecimal("100.00")); - } - } - - String randomPassword = UUID.randomUUID().toString(); - user.setPassword(oauth2PasswordEncoder.encode(randomPassword)); - - userRepository.save(user); - - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(user.getEmail(), randomPassword) - ); - - String token = jwtUtils.generateToken(authentication); - - return new AuthResponseDto(token); - - } catch (Exception e) { - logger.error("Failed to process Google authentication", e); - throw new RuntimeException("Failed to process Google authentication", e); - } - } -} 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 83d65e0..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 @@ -28,7 +28,6 @@ public class EmailService { this.mailConfig = mailConfig; this.mailSender.setHost(mailConfig.host); this.mailSender.setPort(mailConfig.port); - this.mailSender.setProtocol(mailConfig.protocol); if (mailConfig.authenticationEnabled) { this.mailSender.setUsername(mailConfig.username); this.mailSender.setPassword(mailConfig.password); diff --git a/backend/src/main/java/de/szut/casino/security/service/MailConfig.java b/backend/src/main/java/de/szut/casino/security/service/MailConfig.java index 8a516fd..56c7250 100644 --- a/backend/src/main/java/de/szut/casino/security/service/MailConfig.java +++ b/backend/src/main/java/de/szut/casino/security/service/MailConfig.java @@ -22,7 +22,4 @@ public class MailConfig { @Value("${app.mail.from-address}") public String fromAddress; - - @Value("${app.mail.protocol}") - public String protocol; } diff --git a/backend/src/main/java/de/szut/casino/user/AuthProvider.java b/backend/src/main/java/de/szut/casino/user/AuthProvider.java index c26b45c..2216da7 100644 --- a/backend/src/main/java/de/szut/casino/user/AuthProvider.java +++ b/backend/src/main/java/de/szut/casino/user/AuthProvider.java @@ -2,6 +2,5 @@ package de.szut.casino.user; public enum AuthProvider { LOCAL, - GITHUB, - GOOGLE + GITHUB } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 7b761a7..e583c50 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -14,7 +14,6 @@ app.mail.port=${MAIL_PORT:1025} app.mail.username=${MAIL_USER:null} app.mail.password=${MAIL_PASS:null} app.mail.from-address=${MAIL_FROM:casino@localhost} -app.mail.protocol=${MAIL_PROTOCOL:smtp} spring.application.name=casino @@ -42,13 +41,3 @@ spring.security.oauth2.client.provider.github.user-name-attribute=login # OAuth Success and Failure URLs app.oauth2.authorizedRedirectUris=${app.frontend-host}/auth/oauth2/callback -# Google OAuth2 Configuration -spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:350791038883-c1r7v4o793itq8a0rh7dut7itm7uneam.apps.googleusercontent.com} -spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET: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} -spring.security.oauth2.client.registration.google.redirect-uri=${app.frontend-host}/oauth2/callback/google -spring.security.oauth2.client.registration.google.scope=email,profile -spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth -spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token -spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo -spring.security.oauth2.client.provider.google.user-name-attribute=sub - diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 7499296..a9ce6d0 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,12 +1,12 @@ import { Component, HostListener, inject, signal } from '@angular/core'; import { RouterOutlet } from '@angular/router'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; -import { FooterComponent } from '@shared/components/footer/footer.component'; +import { NavbarComponent } from './shared/components/navbar/navbar.component'; +import { FooterComponent } from './shared/components/footer/footer.component'; import { LoginComponent } from './feature/auth/login/login.component'; import { RegisterComponent } from './feature/auth/register/register.component'; -import RecoverPasswordComponent from './feature/auth/recover-password/recover-password.component'; -import { PlaySoundDirective } from '@shared/directives/play-sound.directive'; -import { SoundInitializerService } from '@shared/services/sound-initializer.service'; +import { RecoverPasswordComponent } from './feature/auth/recover-password/recover-password.component'; +import { PlaySoundDirective } from './shared/directives/play-sound.directive'; +import { SoundInitializerService } from './shared/services/sound-initializer.service'; @Component({ selector: 'app-root', diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 88f4fe1..6e9f9b7 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -14,68 +14,61 @@ export const routes: Routes = [ }, { path: 'verify', - loadComponent: () => import('./feature/auth/verify-email/verify-email.component'), + loadComponent: () => + import('./feature/auth/verify-email/verify-email.component').then( + (m) => m.VerifyEmailComponent + ), }, { path: 'recover-password', - loadComponent: () => import('./feature/auth/recover-password/recover-password.component'), + 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'), + loadComponent: () => + import('./feature/auth/recover-password/recover-password.component').then( + (m) => m.RecoverPasswordComponent + ), }, { - path: 'oauth2/callback', - children: [ - { - path: 'github', - loadComponent: () => import('./feature/auth/oauth2/oauth2-callback.component'), - data: { provider: 'github' }, - }, - { - path: 'google', - loadComponent: () => import('./feature/auth/oauth2/oauth2-callback.component'), - data: { provider: 'google' }, - }, - ], + path: 'oauth2/callback/github', + loadComponent: () => + import('./feature/auth/oauth2/oauth2-callback.component').then( + (m) => m.OAuth2CallbackComponent + ), }, { - path: 'game', - children: [ - { - path: 'blackjack', - loadComponent: () => import('./feature/game/blackjack/blackjack.component'), - canActivate: [authGuard], - }, - { - path: 'coinflip', - loadComponent: () => import('./feature/game/coinflip/coinflip.component'), - canActivate: [authGuard], - }, - { - path: 'slots', - loadComponent: () => import('./feature/game/slots/slots.component'), - canActivate: [authGuard], - }, - { - path: 'lootboxes', - loadComponent: () => - import('./feature/lootboxes/lootbox-selection/lootbox-selection.component'), - canActivate: [authGuard], - children: [ - { - path: 'open/:id', - loadComponent: () => - import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'), - canActivate: [authGuard], - }, - ], - }, - { - path: 'dice', - loadComponent: () => import('./feature/game/dice/dice.component'), - canActivate: [authGuard], - }, - ], + path: 'game/blackjack', + loadComponent: () => import('./feature/game/blackjack/blackjack.component'), + canActivate: [authGuard], + }, + { + path: 'game/coinflip', + loadComponent: () => import('./feature/game/coinflip/coinflip.component'), + canActivate: [authGuard], + }, + { + path: 'game/slots', + loadComponent: () => import('./feature/game/slots/slots.component'), + canActivate: [authGuard], + }, + { + path: 'game/lootboxes', + loadComponent: () => + import('./feature/lootboxes/lootbox-selection/lootbox-selection.component'), + canActivate: [authGuard], + }, + { + path: 'game/lootboxes/open/:id', + loadComponent: () => import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'), + canActivate: [authGuard], + }, + { + path: 'game/dice', + loadComponent: () => import('./feature/game/dice/dice.component').then((m) => m.DiceComponent), + canActivate: [authGuard], }, ]; diff --git a/frontend/src/app/feature/auth/login/login.component.html b/frontend/src/app/feature/auth/login/login.component.html index 044742c..14a7d1e 100644 --- a/frontend/src/app/feature/auth/login/login.component.html +++ b/frontend/src/app/feature/auth/login/login.component.html @@ -89,7 +89,7 @@
-
+
- -
diff --git a/frontend/src/app/feature/auth/login/login.component.ts b/frontend/src/app/feature/auth/login/login.component.ts index 09c1cdf..2b78f29 100644 --- a/frontend/src/app/feature/auth/login/login.component.ts +++ b/frontend/src/app/feature/auth/login/login.component.ts @@ -71,11 +71,6 @@ export class LoginComponent { window.location.href = `${environment.apiUrl}/oauth2/github/authorize`; } - loginWithGoogle(): void { - this.isLoading.set(true); - window.location.href = `${environment.apiUrl}/oauth2/google/authorize`; - } - switchToForgotPassword() { this.forgotPassword.emit(); } diff --git a/frontend/src/app/feature/auth/oauth2/oauth2-callback.component.ts b/frontend/src/app/feature/auth/oauth2/oauth2-callback.component.ts index 9c4bcf3..f905f75 100644 --- a/frontend/src/app/feature/auth/oauth2/oauth2-callback.component.ts +++ b/frontend/src/app/feature/auth/oauth2/oauth2-callback.component.ts @@ -1,7 +1,7 @@ -import { Component, computed, inject, OnInit, Signal } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; -import { Oauth2Service } from './oauth2.service'; +import { AuthService } from '@service/auth.service'; @Component({ selector: 'app-oauth2-callback', @@ -10,34 +10,51 @@ import { Oauth2Service } from './oauth2.service'; template: `
-

Authentifizierung...

+

Finishing authentication...

-

{{ error() }}

+

{{ error }}

`, }) -export default class OAuth2CallbackComponent implements OnInit { - error: Signal = computed(() => this.oauthService.error()); +export class OAuth2CallbackComponent implements OnInit { + error: string | null = null; - private route: ActivatedRoute = inject(ActivatedRoute); - private router: Router = inject(Router); - private oauthService: Oauth2Service = inject(Oauth2Service); + constructor( + private route: ActivatedRoute, + private router: Router, + private authService: AuthService + ) {} ngOnInit(): void { + // Check for code in URL params this.route.queryParams.subscribe((params) => { const code = params['code']; - const provider = this.route.snapshot.data['provider'] || 'github'; if (code) { - this.oauthService.oauth(provider, code); - } else { - this.oauthService.error.set( - 'Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.' - ); + // Exchange GitHub code for a JWT token + this.authService.githubAuth(code).subscribe({ + next: () => { + // Redirect to home after successful authentication + this.router.navigate(['/home']); + }, + error: (err) => { + console.error('GitHub authentication error:', err); + this.error = err.error?.message || 'Authentication failed. Please try again.'; + console.log('Error details:', err); + // Redirect back to landing page after showing error + setTimeout(() => { + this.router.navigate(['/']); + }, 3000); + }, + }); + } else { + this.error = 'Authentication failed. No authorization code received.'; + + // Redirect back to landing page after showing error setTimeout(() => { this.router.navigate(['/']); }, 3000); diff --git a/frontend/src/app/feature/auth/oauth2/oauth2.service.ts b/frontend/src/app/feature/auth/oauth2/oauth2.service.ts deleted file mode 100644 index 79ad6d9..0000000 --- a/frontend/src/app/feature/auth/oauth2/oauth2.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { inject, Injectable, signal } from '@angular/core'; -import { Router } from '@angular/router'; -import { AuthService } from '@service/auth.service'; - -@Injectable({ - providedIn: 'root', -}) -export class Oauth2Service { - private router: Router = inject(Router); - private authService: AuthService = inject(AuthService); - private _error = signal(''); - - oauth(provider: string, code: string) { - const oauth$ = - provider === 'github' ? this.authService.githubAuth(code) : this.authService.googleAuth(code); - - oauth$.subscribe({ - next: () => { - this.router.navigate(['/home']); - }, - error: (err) => { - this._error.set( - err.error?.message || 'Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.' - ); - - setTimeout(() => { - this.router.navigate(['/']); - }, 3000); - }, - }); - } - - public get error() { - return this._error; - } -} 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 index 89f5d96..905f200 100644 --- a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts @@ -10,7 +10,7 @@ import { AuthService } from '@service/auth.service'; imports: [CommonModule, ReactiveFormsModule, RouterModule], templateUrl: './recover-password.component.html', }) -export default class RecoverPasswordComponent implements OnInit { +export class RecoverPasswordComponent implements OnInit { emailForm: FormGroup; resetPasswordForm: FormGroup; errorMessage = signal(''); 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 54f4c2f..6e04877 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 default class VerifyEmailComponent implements OnInit { +export class VerifyEmailComponent implements OnInit { route: ActivatedRoute = inject(ActivatedRoute); router: Router = inject(Router); authService: AuthService = inject(AuthService); diff --git a/frontend/src/app/feature/game/coinflip/coinflip.component.html b/frontend/src/app/feature/game/coinflip/coinflip.component.html index 8dd6be8..671bc31 100644 --- a/frontend/src/app/feature/game/coinflip/coinflip.component.html +++ b/frontend/src/app/feature/game/coinflip/coinflip.component.html @@ -5,11 +5,13 @@ @if (gameResult()) {

- {{ gameResult()?.isWin ? 'Du hast gewonnen!' : 'Du hast verloren' }} + {{ gameResult()?.isWin ? 'You Won!' : 'You Lost' }}

- Münze zeigt: - {{ gameResult()?.coinSide === 'HEAD' ? 'KOPF' : 'ZAHL' }} + Coin landed on: + {{ + gameResult()?.coinSide === 'HEAD' ? 'HEAD' : 'TAILS' + }}

@if (gameResult()?.isWin) {

@@ -33,7 +35,7 @@

-
KOPF
+
HEAD
@@ -41,7 +43,7 @@ class="back coin-side bg-gray-700 flex items-center justify-center text-2xl font-bold text-white" > - ZAHL + TAILS
@@ -54,7 +56,7 @@ class="button-primary py-3 px-6 relative text-lg" [class.opacity-50]="gameInProgress()" > - Auf ZAHL setzen + Bet TAILS @@ -70,11 +72,11 @@
-

Spielinformationen

+

Game Information

- Aktueller Einsatz: + Current Bet: @@ -82,7 +84,7 @@
- Dein Guthaben: + Your Balance: {{ balance() | currency: 'EUR' }} @@ -101,9 +103,9 @@
- + Darf Guthaben nicht überschreitenCannot exceed balance
-

Spielregeln

+

How to Play

    -
  • • Wähle deinen Einsatzbetrag
  • -
  • • Wähle Kopf oder Zahl
  • -
  • • Gewinne das Doppelte deines Einsatzes bei richtiger Wahl
  • +
  • • Choose your bet amount
  • +
  • • Select Heads or Tails
  • +
  • • Win double your bet if correct
diff --git a/frontend/src/app/feature/game/coinflip/coinflip.component.ts b/frontend/src/app/feature/game/coinflip/coinflip.component.ts index 112a300..1766e4d 100644 --- a/frontend/src/app/feature/game/coinflip/coinflip.component.ts +++ b/frontend/src/app/feature/game/coinflip/coinflip.component.ts @@ -44,14 +44,14 @@ export default class CoinflipComponent implements OnInit { private coinflipSound?: HTMLAudioElement; ngOnInit(): void { - // Abonniere Benutzerupdates für Echtzeitaktualisierungen des Guthabens + // Subscribe to user updates for real-time balance changes this.authService.userSubject.subscribe((user) => { if (user) { this.balance.set(user.balance); } }); - // Initialisiere Münzwurf-Sound + // Initialize coinflip sound this.coinflipSound = new Audio('/sounds/coinflip.mp3'); } @@ -65,26 +65,26 @@ export default class CoinflipComponent implements OnInit { const inputElement = event.target as HTMLInputElement; let value = Number(inputElement.value); - // Setze ungültigen Einsatz-Status zurück + // Reset invalid bet state this.isInvalidBet.set(false); - // Erzwinge Mindesteinsatz von 1 + // Enforce minimum bet of 1 if (value <= 0) { value = 1; } - // Begrenze Einsatz auf verfügbares Guthaben und zeige Feedback + // Cap bet at available balance and show feedback if (value > this.balance()) { value = this.balance(); - // Visuelles Feedback anzeigen + // Show visual feedback this.isInvalidBet.set(true); - // Zeige den Fehler kurz an + // Indicate the error briefly setTimeout(() => this.isInvalidBet.set(false), 800); - // Aktualisiere das Eingabefeld direkt, um dem Benutzer den maximalen Wert anzuzeigen + // Update the input field directly to show the user the max value inputElement.value = String(value); } - // Aktualisiere Signale + // Update signals this.betInputValue.set(value); this.currentBet.set(value); } @@ -100,34 +100,34 @@ export default class CoinflipComponent implements OnInit { private placeBet(side: 'HEAD' | 'TAILS') { if (this.gameInProgress() || this.isActionInProgress()) return; - // Setze vorheriges Ergebnis zurück + // Reset previous result this.gameResult.set(null); this.errorMessage.set(''); - // Setze Spielstatus + // Set game state this.gameInProgress.set(true); this.isActionInProgress.set(true); - // Spiele Einsatz-Sound + // Play bet sound this.audioService.playBetSound(); - // Erstelle Einsatz-Anfrage + // Create bet request const request: CoinflipRequest = { betAmount: this.currentBet(), coinSide: side, }; - // API aufrufen + // Call API this.http .post('/backend/coinflip', request) .pipe( catchError((error) => { - console.error('Fehler beim Spielen von Coinflip:', error); + console.error('Error playing coinflip:', error); if (error.status === 400 && error.error.message.includes('insufficient')) { - this.errorMessage.set('Unzureichendes Guthaben'); + this.errorMessage.set('Insufficient funds'); } else { - this.errorMessage.set('Ein Fehler ist aufgetreten. Bitte versuche es erneut.'); + this.errorMessage.set('An error occurred. Please try again.'); } this.gameInProgress.set(false); @@ -140,37 +140,37 @@ export default class CoinflipComponent implements OnInit { .subscribe((result) => { if (!result) return; - console.log('API-Antwort:', result); + console.log('API response:', result); - // Behebe mögliche Inkonsistenzen bei der Eigenschaftenbenennung vom Backend + // Fix potential property naming inconsistency from the backend const fixedResult: CoinflipGame = { isWin: result.isWin ?? result.win, payout: result.payout, coinSide: result.coinSide, }; - console.log('Korrigiertes Ergebnis:', fixedResult); + console.log('Fixed result:', fixedResult); - // Spiele Münzwurf-Animation und -Sound + // Play coin flip animation and sound this.playCoinFlipAnimation(fixedResult.coinSide); - // Setze Ergebnis nach Abschluss der Animation + // Set result after animation completes setTimeout(() => { this.gameResult.set(fixedResult); - // Aktualisiere Guthaben mit neuem Wert vom Auth-Service + // Update balance with new value from auth service this.authService.loadCurrentUser(); - // Spiele Gewinn-Sound, wenn der Spieler gewonnen hat + // Play win sound if player won if (fixedResult.isWin) { this.audioService.playWinSound(); } - // Setze Spielstatus nach Anzeigen des Ergebnisses zurück + // Reset game state after showing result setTimeout(() => { this.gameInProgress.set(false); }, 1500); - }, 1100); // Kurz nach Ende der Animation + }, 1100); // Just after animation ends }); } @@ -179,50 +179,48 @@ export default class CoinflipComponent implements OnInit { const coinEl = this.coinElement.nativeElement; - // Setze bestehende Animationen zurück + // Reset any existing animations coinEl.classList.remove('animate-to-heads', 'animate-to-tails'); - // Setze alle Inline-Styles von vorherigen Animationen zurück + // Reset any inline styles from previous animations coinEl.style.transform = ''; - // Erzwinge Reflow, um Animation neu zu starten + // Force a reflow to restart animation void coinEl.offsetWidth; - // Spiele Münzwurf-Sound + // Play flip sound if (this.coinflipSound) { this.coinflipSound.currentTime = 0; - this.coinflipSound - .play() - .catch((err) => console.error('Fehler beim Abspielen des Sounds:', err)); + this.coinflipSound.play().catch((err) => console.error('Error playing sound:', err)); } - // Füge passende Animationsklasse basierend auf dem Ergebnis hinzu + // Add appropriate animation class based on result if (result === 'HEAD') { coinEl.classList.add('animate-to-heads'); } else { coinEl.classList.add('animate-to-tails'); } - console.log(`Animation angewendet für Ergebnis: ${result}`); + console.log(`Animation applied for result: ${result}`); } /** - * Validiert Eingabe während der Benutzer tippt, um ungültige Werte zu verhindern + * Validates input as the user types to prevent invalid values */ validateBetInput(event: KeyboardEvent) { - // Erlaube Navigationstasten (Pfeile, Entf, Rücktaste, Tab) + // Allow navigation keys (arrows, delete, backspace, tab) const navigationKeys = ['ArrowLeft', 'ArrowRight', 'Delete', 'Backspace', 'Tab']; if (navigationKeys.includes(event.key)) { return; } - // Erlaube nur Zahlen + // Only allow numbers if (!/^\d$/.test(event.key)) { event.preventDefault(); return; } - // Ermittle den Wert, der nach dem Tastendruck entstehen würde + // Get the value that would result after the keypress const input = event.target as HTMLInputElement; const currentValue = input.value; const cursorPosition = input.selectionStart || 0; @@ -232,14 +230,14 @@ export default class CoinflipComponent implements OnInit { currentValue.substring(input.selectionEnd || cursorPosition); const numValue = Number(newValue); - // Verhindere Werte, die größer als das Guthaben sind + // Prevent values greater than balance if (numValue > this.balance()) { event.preventDefault(); } } - // Der Paste-Handler wurde der Einfachheit halber entfernt, da die updateBet-Methode - // jeden Wert behandelt, der in das Eingabefeld gelangt + // We removed the paste handler for simplicity since the updateBet method + // will handle any value that gets into the input field getResultClass() { if (!this.gameResult()) return ''; diff --git a/frontend/src/app/feature/game/dice/dice.component.ts b/frontend/src/app/feature/game/dice/dice.component.ts index aaca5c5..4967b78 100644 --- a/frontend/src/app/feature/game/dice/dice.component.ts +++ b/frontend/src/app/feature/game/dice/dice.component.ts @@ -27,7 +27,7 @@ type DiceFormGroup = FormGroup<{ imports: [CommonModule, ReactiveFormsModule, PlaySoundDirective, DragSoundDirective], templateUrl: './dice.component.html', }) -export default class DiceComponent implements OnInit { +export class DiceComponent implements OnInit { private readonly formBuilder = inject(FormBuilder); private readonly diceService = inject(DiceService); private readonly userService = inject(UserService); diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts index d354fa4..aa1867e 100644 --- a/frontend/src/app/feature/landing/landing.component.ts +++ b/frontend/src/app/feature/landing/landing.component.ts @@ -11,8 +11,7 @@ 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'; -import '../auth/recover-password/recover-password.component'; -import RecoverPasswordComponent from '../auth/recover-password/recover-password.component'; +import { RecoverPasswordComponent } from '../auth/recover-password/recover-password.component'; @Component({ selector: 'app-landing-page', diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 61b3e7d..081ad72 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -79,15 +79,6 @@ export class AuthService { ); } - googleAuth(code: string): Observable { - return this.http.post(`${this.oauthUrl}/google/callback`, { code }).pipe( - tap((response) => { - this.setToken(response.token); - this.loadCurrentUser(); - }) - ); - } - logout(): void { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(USER_KEY);