diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/ErrorResponse.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/ErrorResponse.java new file mode 100644 index 0000000..be88620 --- /dev/null +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/ErrorResponse.java @@ -0,0 +1,3 @@ +package at.mueller.eeg.backend.common.config.exception; + +public record ErrorResponse(String message, int status) {} diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/GlobalExceptionHandler.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..7311a12 --- /dev/null +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/common/config/exception/GlobalExceptionHandler.java @@ -0,0 +1,33 @@ +package at.mueller.eeg.backend.common.config.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity handleBadCredentials(BadCredentialsException ex) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(new ErrorResponse("Benutzername oder Passwort ist falsch.", HttpStatus.UNAUTHORIZED.value())); + } + + @ExceptionHandler(DisabledException.class) + public ResponseEntity handleDisabled(DisabledException ex) { + return ResponseEntity + .status(HttpStatus.FORBIDDEN) + .body(new ErrorResponse("Dein Account wurde noch nicht vom Admin freigeschaltet. Bitte habe etwas Geduld.", HttpStatus.FORBIDDEN.value())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneric(Exception ex) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Ein unerwarteter Fehler ist aufgetreten: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value())); + } +} \ No newline at end of file diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/AuthController.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/AuthController.java index 1417104..851b8e1 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/AuthController.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/AuthController.java @@ -38,11 +38,7 @@ public class AuthController { @Operation(summary = "User Login", description = "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING ist.") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { - // Logik: Spring Security Authentication Manager aufrufen - // Wenn erfolgreich: JWT generieren - // Wenn PENDING: Spezifische Exception werfen, die als 403 Forbidden gemappt wird + return ResponseEntity.ok(authService.login(request)); - AuthResponse response = new AuthResponse("ey...", "APPROVED", "USER"); - return ResponseEntity.ok(response); } } diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/dto/IamDtos.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/dto/IamDtos.java index efc1abb..cbb8e28 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/dto/IamDtos.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/dto/IamDtos.java @@ -46,4 +46,6 @@ public interface IamDtos { String primaryAtNumber // Zur schnellen Anzeige in der Liste ) {} + record ErrorResponse(String message, int status) {} + } diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/domain/User.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/domain/User.java index d7f8554..da6e2c0 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/domain/User.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/domain/User.java @@ -2,15 +2,22 @@ package at.mueller.eeg.backend.iam.domain; import jakarta.persistence.*; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; +import org.jspecify.annotations.Nullable; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.List; import java.util.UUID; @Entity @Table(name = "users") @Getter @Setter -public class User { +public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.UUID) @@ -29,7 +36,7 @@ public class User { private String iban; @Enumerated(EnumType.STRING) - private UserRole role; + private UserRole role = UserRole.MEMBER; @Enumerated(EnumType.STRING) @Column(nullable = false) @@ -37,4 +44,21 @@ public class User { @Column(nullable = false) private boolean enabled = false; + + @Override + @NonNull + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority(role.name())); + } + + @Override + public @Nullable String getPassword() { + return this.passwordHash; + } + + @Override + @NonNull + public String getUsername() { + return this.email; + } } diff --git a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AuthService.java b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AuthService.java index 959e6d0..bb0f7d2 100644 --- a/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AuthService.java +++ b/eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AuthService.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; @@ -18,8 +19,7 @@ import org.springframework.stereotype.Service; import java.time.Instant; import java.time.temporal.ChronoUnit; -import static at.mueller.eeg.backend.iam.api.dto.IamDtos.LoginRequest; -import static at.mueller.eeg.backend.iam.api.dto.IamDtos.RegistrationRequest; +import static at.mueller.eeg.backend.iam.api.dto.IamDtos.*; @Service @RequiredArgsConstructor @@ -33,14 +33,19 @@ public class AuthService { /** * Verarbeitet den Login und gibt den JWT-Token als String zurück. */ - public String login(LoginRequest request) { - // 1. Prüft, ob E-Mail und Passwort in der Datenbank übereinstimmen + public AuthResponse login(LoginRequest request) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.email(), request.password()) ); - // 2. Wenn erfolgreich, Token generieren - return generateToken(authentication.getName()); + String role = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .findFirst() + .orElse("ROLE_MEMBER"); + + String token = generateToken(authentication.getName(), role); + + return new AuthResponse(token, authentication.getName(), role); } /** @@ -68,14 +73,14 @@ public class AuthService { /** * Hilfsmethode zum Bauen des JWT. */ - private String generateToken(String email) { + private String generateToken(String email, String role) { Instant now = Instant.now(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("eeg-portal") .issuedAt(now) - .expiresAt(now.plus(2, ChronoUnit.HOURS)) // Token ist 2 Stunden gültig - .subject(email) // Der "Besitzer" des Tokens - .claim("role", "MEMBER") // Hier könntest du später Admin/Mitglied Rollen einbauen + .expiresAt(now.plus(2, ChronoUnit.HOURS)) + .subject(email) + .claim("role", role.replace("ROLE_", "")) // Dynamische Rolle einfügen .build(); JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); diff --git a/eeg_frontend/src/app/app.html b/eeg_frontend/src/app/app.html index 67e7bd4..26be869 100644 --- a/eeg_frontend/src/app/app.html +++ b/eeg_frontend/src/app/app.html @@ -1 +1,7 @@ - + + + + +
+ +
diff --git a/eeg_frontend/src/app/app.routes.ts b/eeg_frontend/src/app/app.routes.ts index 0b78c17..e38ff97 100644 --- a/eeg_frontend/src/app/app.routes.ts +++ b/eeg_frontend/src/app/app.routes.ts @@ -1,8 +1,10 @@ import {Routes} from '@angular/router'; import {LandingPage} from './pages/landing-page/landing-page'; import {Register} from './pages/register/register'; +import {LoginComponent} from './pages/login/login'; export const routes: Routes = [ { path: '', component: LandingPage }, - { path: 'register', component: Register } + { path: 'register', component: Register }, + { path: 'login', component: LoginComponent} ]; diff --git a/eeg_frontend/src/app/app.ts b/eeg_frontend/src/app/app.ts index ce90935..3991aba 100644 --- a/eeg_frontend/src/app/app.ts +++ b/eeg_frontend/src/app/app.ts @@ -1,9 +1,10 @@ -import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import {Component, signal} from '@angular/core'; +import {RouterOutlet} from '@angular/router'; +import {HeaderComponent} from './layout/header/header'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, HeaderComponent], templateUrl: './app.html', styleUrl: './app.scss', }) diff --git a/eeg_frontend/src/app/pages/landing-page/landing-page.html b/eeg_frontend/src/app/pages/landing-page/landing-page.html index 7d82481..21a04a0 100644 --- a/eeg_frontend/src/app/pages/landing-page/landing-page.html +++ b/eeg_frontend/src/app/pages/landing-page/landing-page.html @@ -1,29 +1,5 @@
-
-
- -
- bolt - - Energie Portal - -
- - - -
- - -
- -
-
-
diff --git a/eeg_frontend/src/app/services/auth.ts b/eeg_frontend/src/app/services/auth.ts index ca38c97..2b62524 100644 --- a/eeg_frontend/src/app/services/auth.ts +++ b/eeg_frontend/src/app/services/auth.ts @@ -1,6 +1,7 @@ import {inject, Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; -import {Observable} from 'rxjs'; +import {BehaviorSubject, Observable, tap} from 'rxjs'; +import {AuthResponse} from '../api'; @Injectable({ providedIn: 'root' @@ -11,8 +12,35 @@ export class AuthService { // Die URL zu deinem lokalen Spring Boot Backend private apiUrl = 'http://localhost:8080/api/auth'; + private isLoggedInSubject = new BehaviorSubject(!!localStorage.getItem('token')); + + isLoggedIn$ = this.isLoggedInSubject.asObservable(); + register(userData: any): Observable { // Sendet die Daten als JSON-Body an den Endpunkt /register return this.http.post(`${this.apiUrl}/register`, userData); } + + login(credentials: any): Observable { + return this.http.post(`${this.apiUrl}/login`, credentials).pipe( + tap(response => { + // Token und Rolle im LocalStorage (oder SessionStorage) ablegen + localStorage.setItem('token', response.token!); + localStorage.setItem('user_role', response.role!); + }) + ); + } + + getUserRole(): string | null { + return localStorage.getItem('user_role'); + } + + isLoggedIn(): boolean { + return !!localStorage.getItem('token'); + } + + logout() { + localStorage.clear(); + this.isLoggedInSubject.next(false); + } }