feat(auth): rewrite authentication to not use oauth manage users in app instead #163

Merged
jleibl merged 13 commits from feature/authentication into main 2025-05-07 14:28:51 +00:00
12 changed files with 24 additions and 115 deletions
Showing only changes of commit 3b95725d88 - Show all commits

View file

@ -8,6 +8,7 @@ import de.szut.casino.deposit.dto.AmountDto;
import de.szut.casino.deposit.dto.SessionIdDto; import de.szut.casino.deposit.dto.SessionIdDto;
import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserRepository; import de.szut.casino.user.UserRepository;
import de.szut.casino.user.UserService;
import de.szut.casino.user.dto.KeycloakUserDto; import de.szut.casino.user.dto.KeycloakUserDto;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -34,23 +35,18 @@ public class DepositController {
private final TransactionService transactionService; private final TransactionService transactionService;
private final RestTemplate restTemplate; private UserService userService;
private final UserRepository userRepository; public DepositController(TransactionService transactionService, UserService userService) {
public DepositController(TransactionService transactionService, RestTemplate restTemplate, UserRepository userRepository) {
this.transactionService = transactionService; this.transactionService = transactionService;
this.restTemplate = restTemplate; this.userService = userService;
this.userRepository = userRepository;
} }
@PostMapping("/deposit/checkout") @PostMapping("/deposit/checkout")
public ResponseEntity<SessionIdDto> checkout(@RequestBody @Valid AmountDto amountDto, @RequestHeader("Authorization") String token) throws StripeException { public ResponseEntity<SessionIdDto> checkout(@RequestBody @Valid AmountDto amountDto, @RequestHeader("Authorization") String token) throws StripeException {
Stripe.apiKey = stripeKey; Stripe.apiKey = stripeKey;
KeycloakUserDto userData = getAuthentikUserInfo(token); Optional<UserEntity> optionalUserEntity = this.userService.getCurrentUser();
Optional<UserEntity> optionalUserEntity = this.userRepository.findByEmail(userData.getSub());
SessionCreateParams params = SessionCreateParams.builder() SessionCreateParams params = SessionCreateParams.builder()
.addLineItem(SessionCreateParams.LineItem.builder() .addLineItem(SessionCreateParams.LineItem.builder()
@ -78,13 +74,5 @@ public class DepositController {
return ResponseEntity.ok(new SessionIdDto(session.getId())); return ResponseEntity.ok(new SessionIdDto(session.getId()));
} }
private KeycloakUserDto getAuthentikUserInfo(String token) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
ResponseEntity<KeycloakUserDto> response = this.restTemplate.exchange("https://oauth.simonis.lol/application/o/userinfo/", HttpMethod.GET, new HttpEntity<>(headers), KeycloakUserDto.class);
return response.getBody();
}
} }

View file

@ -11,7 +11,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/auth")
public class AuthController { public class AuthController {
@Autowired @Autowired

View file

@ -65,7 +65,7 @@ public class SecurityConfig {
.csrf(csrf -> csrf.disable()) .csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> { .authorizeHttpRequests(auth -> {
auth.requestMatchers("/api/auth/**", "/webhook", "/swagger/**", "/swagger-ui/**", "/health", "/error").permitAll() auth.requestMatchers("/auth/**", "/webhook", "/swagger/**", "/swagger-ui/**", "/health", "/error").permitAll()
.requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated(); .anyRequest().authenticated();
}) })

View file

@ -1,6 +1,5 @@
package de.szut.casino.user; package de.szut.casino.user;
import de.szut.casino.exceptionHandling.exceptions.UserNotFoundException;
import de.szut.casino.user.dto.GetUserDto; import de.szut.casino.user.dto.GetUserDto;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -10,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
@Slf4j @Slf4j
@RestController @RestController
@CrossOrigin @CrossOrigin
@RequestMapping("/api/users") @RequestMapping("/users")
public class UserController { public class UserController {
@Autowired @Autowired
@ -23,19 +22,4 @@ public class UserController {
public ResponseEntity<GetUserDto> getCurrentUser() { public ResponseEntity<GetUserDto> getCurrentUser() {
return ResponseEntity.ok(userMappingService.mapToGetUserDto(userService.getCurrentUser().orElseThrow())); return ResponseEntity.ok(userMappingService.mapToGetUserDto(userService.getCurrentUser().orElseThrow()));
} }
@GetMapping("/current")
public ResponseEntity<GetUserDto> getCurrentUserAlternative() {
return ResponseEntity.ok(userMappingService.mapToGetUserDto(userService.getCurrentUser().orElseThrow()));
}
@GetMapping("/{id}")
public ResponseEntity<GetUserDto> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@GetMapping("/username/{username}")
public ResponseEntity<GetUserDto> getUserByUsername(@PathVariable String username) {
return ResponseEntity.ok(userService.getUserByUsername(username));
}
} }

View file

@ -16,9 +16,6 @@ public class UserService {
@Autowired @Autowired
private UserRepository userRepository; private UserRepository userRepository;
@Autowired
private UserMappingService mappingService;
@Autowired @Autowired
private PasswordEncoder passwordEncoder; private PasswordEncoder passwordEncoder;
@ -35,50 +32,15 @@ public class UserService {
createUserDto.getEmail(), createUserDto.getEmail(),
createUserDto.getUsername(), createUserDto.getUsername(),
passwordEncoder.encode(createUserDto.getPassword()), passwordEncoder.encode(createUserDto.getPassword()),
BigDecimal.valueOf(1000) // Starting balance BigDecimal.valueOf(10) // Starting balance
); );
return userRepository.save(user); return userRepository.save(user);
} }
public GetUserDto getUserById(Long id) {
UserEntity user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
return mappingService.mapToGetUserDto(user);
}
public GetUserDto getUserByUsername(String username) {
UserEntity user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found with username: " + username));
return mappingService.mapToGetUserDto(user);
}
public GetUserDto getUserByEmail(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UserNotFoundException("User not found with email: " + email));
return mappingService.mapToGetUserDto(user);
}
public Optional<UserEntity> getCurrentUser() { public Optional<UserEntity> getCurrentUser() {
String username = SecurityContextHolder.getContext().getAuthentication().getName(); String username = SecurityContextHolder.getContext().getAuthentication().getName();
return userRepository.findByUsername(username); return userRepository.findByUsername(username);
} }
public UserEntity getCurrentUserEntity() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
return userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found with username: " + username));
}
public boolean existsByUsername(String username) {
return userRepository.existsByUsername(username);
}
public boolean existsByEmail(String email) {
return userRepository.existsByEmail(email);
}
} }

View file

@ -21,11 +21,8 @@
"@tailwindcss/postcss": "^4.0.3", "@tailwindcss/postcss": "^4.0.3",
"ajv": "8.17.1", "ajv": "8.17.1",
"ajv-formats": "3.0.1", "ajv-formats": "3.0.1",
"angular-oauth2-oidc": "^19.0.0",
"countup.js": "^2.8.0", "countup.js": "^2.8.0",
"gsap": "^3.12.7", "gsap": "^3.12.7",
"keycloak-angular": "^19.0.0",
"keycloak-js": "^26.0.0",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"rxjs": "~7.8.2", "rxjs": "~7.8.2",
"tailwindcss": "^4.0.3", "tailwindcss": "^4.0.3",
@ -757,8 +754,6 @@
"angular-eslint": ["angular-eslint@19.3.0", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/builder": "19.3.0", "@angular-eslint/eslint-plugin": "19.3.0", "@angular-eslint/eslint-plugin-template": "19.3.0", "@angular-eslint/schematics": "19.3.0", "@angular-eslint/template-parser": "19.3.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" } }, "sha512-19hkkH3z/2wGhKk3LfttEBkl6CtQP/tFK6/mJoO/MbIkXV0SSJWtbPbOpEaxICLlfCw0oR6W9OoQqByWkwXjkQ=="], "angular-eslint": ["angular-eslint@19.3.0", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/builder": "19.3.0", "@angular-eslint/eslint-plugin": "19.3.0", "@angular-eslint/eslint-plugin-template": "19.3.0", "@angular-eslint/schematics": "19.3.0", "@angular-eslint/template-parser": "19.3.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" } }, "sha512-19hkkH3z/2wGhKk3LfttEBkl6CtQP/tFK6/mJoO/MbIkXV0SSJWtbPbOpEaxICLlfCw0oR6W9OoQqByWkwXjkQ=="],
"angular-oauth2-oidc": ["angular-oauth2-oidc@19.0.0", "", { "dependencies": { "tslib": "^2.5.2" }, "peerDependencies": { "@angular/common": ">=19.0.0", "@angular/core": ">=19.0.0" } }, "sha512-EogHyF7MpCJSjSKIyVmdB8pJu7dU5Ilj9VNVSnFbLng4F77PIlaE4egwKUlUvk0i4ZvmO9rLXNQCm05R7Tyhcw=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
@ -1275,10 +1270,6 @@
"karma-source-map-support": ["karma-source-map-support@1.4.0", "", { "dependencies": { "source-map-support": "^0.5.5" } }, "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A=="], "karma-source-map-support": ["karma-source-map-support@1.4.0", "", { "dependencies": { "source-map-support": "^0.5.5" } }, "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A=="],
"keycloak-angular": ["keycloak-angular@19.0.2", "", { "dependencies": { "tslib": "^2.3.1" }, "peerDependencies": { "@angular/common": "^19", "@angular/core": "^19", "@angular/router": "^19", "keycloak-js": "^18 || ^19 || ^20 || ^21 || ^22 || ^23 || ^24 || ^25 || ^26" } }, "sha512-GzQKC/jFJLZRmUxWOEXkla+6shDAZFAOe6Z3qsw916Ckb/UhZnO704HMZrd8xyVB3RH6xOcNCp45oHmIiqJ7dA=="],
"keycloak-js": ["keycloak-js@26.1.4", "", {}, "sha512-4h2RicCzIAtsjKIG8DIO+8NKlpWX2fiNkbS0jlbtjZFbIGGjbQBzjS/5NkyWlzxamXVow9prHTIgIiwfo3GAmQ=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],

View file

@ -33,9 +33,6 @@
"ajv-formats": "3.0.1", "ajv-formats": "3.0.1",
"countup.js": "^2.8.0", "countup.js": "^2.8.0",
"gsap": "^3.12.7", "gsap": "^3.12.7",
"angular-oauth2-oidc": "^19.0.0",
"keycloak-angular": "^19.0.0",
"keycloak-js": "^26.0.0",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"rxjs": "~7.8.2", "rxjs": "~7.8.2",
"tailwindcss": "^4.0.3", "tailwindcss": "^4.0.3",

View file

@ -6,7 +6,7 @@ import { LoginRequest } from '../model/auth/LoginRequest';
import { RegisterRequest } from '../model/auth/RegisterRequest'; import { RegisterRequest } from '../model/auth/RegisterRequest';
import { AuthResponse } from '../model/auth/AuthResponse'; import { AuthResponse } from '../model/auth/AuthResponse';
import { User } from '../model/User'; import { User } from '../model/User';
import { environment } from '../../environments/environment'; import { environment } from '@environments/environment';
const TOKEN_KEY = 'auth-token'; const TOKEN_KEY = 'auth-token';
const USER_KEY = 'auth-user'; const USER_KEY = 'auth-user';
@ -15,8 +15,8 @@ const USER_KEY = 'auth-user';
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
private authUrl = `${environment.apiUrl}/api/auth`; private authUrl = `${environment.apiUrl}/auth`;
private userUrl = `${environment.apiUrl}/api/users`; private userUrl = `${environment.apiUrl}/users`;
private currentUserSubject: BehaviorSubject<User | null>; private currentUserSubject: BehaviorSubject<User | null>;
public currentUser: Observable<User | null>; public currentUser: Observable<User | null>;
@ -30,7 +30,10 @@ export class AuthService {
// Check if token exists and load user data // Check if token exists and load user data
if (this.getToken()) { if (this.getToken()) {
console.log('Token found, loading user data...');
this.loadCurrentUser(); this.loadCurrentUser();
} else {
console.log('No token found, user not logged in.');
} }
} }

View file

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, catchError, EMPTY, Observable, tap } from 'rxjs'; import { BehaviorSubject, catchError, EMPTY, Observable, tap } from 'rxjs';
import { User } from '../model/User'; import { User } from '../model/User';
@ -8,18 +8,11 @@ import { environment } from '@environments/environment';
providedIn: 'root', providedIn: 'root',
}) })
export class UserService { export class UserService {
private apiUrl = `${environment.apiUrl}/api/users`; private apiUrl = `${environment.apiUrl}/users`;
private currentUserSubject = new BehaviorSubject<User | null>(null); private currentUserSubject = new BehaviorSubject<User | null>(null);
private http: HttpClient = inject(HttpClient);
constructor(private http: HttpClient) {}
public getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
public getUserByUsername(username: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/username/${username}`);
}
public getCurrentUser(): Observable<User | null> { public getCurrentUser(): Observable<User | null> {
return this.http.get<User | null>('/backend/user').pipe( return this.http.get<User | null>('/backend/user').pipe(

View file

@ -1,14 +1,6 @@
import { import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
ChangeDetectionStrategy,
Component,
inject,
OnDestroy,
OnInit,
signal,
} from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AuthService } from '../../../service/auth.service'; import { AuthService } from '@service/auth.service';
import { UserService } from '@service/user.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component';
@ -24,7 +16,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
private authService: AuthService = inject(AuthService); private authService: AuthService = inject(AuthService);
isLoggedIn = this.authService.isLoggedIn(); isLoggedIn = this.authService.isLoggedIn();
private userService = inject(UserService);
private userSubscription: Subscription | undefined; private userSubscription: Subscription | undefined;
private authSubscription: Subscription | undefined; private authSubscription: Subscription | undefined;
public balance = signal(0); public balance = signal(0);

View file

@ -1,5 +1,5 @@
export const environment = { export const environment = {
STRIPE_KEY: STRIPE_KEY:
'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG', 'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG',
apiUrl: 'http://localhost:8080', apiUrl: 'http://localhost:4200/backend',
}; };

View file

@ -1,10 +1,10 @@
{ {
"/api": { "/backend": {
"target": "http://localhost:8080/", "target": "http://localhost:8080/",
"secure": false, "secure": false,
"logLevel": "debug", "logLevel": "debug",
"pathRewrite": { "pathRewrite": {
"^/api": "" "^/backend": ""
}, },
"changeOrigin": true "changeOrigin": true
} }