added header, started implementing login
This commit is contained in:
parent
4ef6d32093
commit
e25bae9700
@ -0,0 +1,3 @@
|
|||||||
|
package at.mueller.eeg.backend.common.config.exception;
|
||||||
|
|
||||||
|
public record ErrorResponse(String message, int status) {}
|
||||||
@ -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<ErrorResponse> handleBadCredentials(BadCredentialsException ex) {
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.UNAUTHORIZED)
|
||||||
|
.body(new ErrorResponse("Benutzername oder Passwort ist falsch.", HttpStatus.UNAUTHORIZED.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(DisabledException.class)
|
||||||
|
public ResponseEntity<ErrorResponse> 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<ErrorResponse> 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,11 +38,7 @@ public class AuthController {
|
|||||||
@Operation(summary = "User Login",
|
@Operation(summary = "User Login",
|
||||||
description = "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING ist.")
|
description = "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING ist.")
|
||||||
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
|
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
// Logik: Spring Security Authentication Manager aufrufen
|
return ResponseEntity.ok(authService.login(request));
|
||||||
// Wenn erfolgreich: JWT generieren
|
|
||||||
// Wenn PENDING: Spezifische Exception werfen, die als 403 Forbidden gemappt wird
|
|
||||||
|
|
||||||
AuthResponse response = new AuthResponse("ey...", "APPROVED", "USER");
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,4 +46,6 @@ public interface IamDtos {
|
|||||||
String primaryAtNumber // Zur schnellen Anzeige in der Liste
|
String primaryAtNumber // Zur schnellen Anzeige in der Liste
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
record ErrorResponse(String message, int status) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,22 @@ package at.mueller.eeg.backend.iam.domain;
|
|||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
import lombok.Setter;
|
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;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class User {
|
public class User implements UserDetails {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.UUID)
|
@GeneratedValue(strategy = GenerationType.UUID)
|
||||||
@ -29,7 +36,7 @@ public class User {
|
|||||||
private String iban;
|
private String iban;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private UserRole role;
|
private UserRole role = UserRole.MEMBER;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@ -37,4 +44,21 @@ public class User {
|
|||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean enabled = false;
|
private boolean enabled = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return List.of(new SimpleGrantedAuthority(role.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getPassword() {
|
||||||
|
return this.passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String getUsername() {
|
||||||
|
return this.email;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||||
import org.springframework.security.oauth2.jwt.JwsHeader;
|
import org.springframework.security.oauth2.jwt.JwsHeader;
|
||||||
@ -18,8 +19,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
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.*;
|
||||||
import static at.mueller.eeg.backend.iam.api.dto.IamDtos.RegistrationRequest;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -33,14 +33,19 @@ public class AuthService {
|
|||||||
/**
|
/**
|
||||||
* Verarbeitet den Login und gibt den JWT-Token als String zurück.
|
* Verarbeitet den Login und gibt den JWT-Token als String zurück.
|
||||||
*/
|
*/
|
||||||
public String login(LoginRequest request) {
|
public AuthResponse login(LoginRequest request) {
|
||||||
// 1. Prüft, ob E-Mail und Passwort in der Datenbank übereinstimmen
|
|
||||||
Authentication authentication = authenticationManager.authenticate(
|
Authentication authentication = authenticationManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(request.email(), request.password())
|
new UsernamePasswordAuthenticationToken(request.email(), request.password())
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Wenn erfolgreich, Token generieren
|
String role = authentication.getAuthorities().stream()
|
||||||
return generateToken(authentication.getName());
|
.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.
|
* Hilfsmethode zum Bauen des JWT.
|
||||||
*/
|
*/
|
||||||
private String generateToken(String email) {
|
private String generateToken(String email, String role) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
JwtClaimsSet claims = JwtClaimsSet.builder()
|
JwtClaimsSet claims = JwtClaimsSet.builder()
|
||||||
.issuer("eeg-portal")
|
.issuer("eeg-portal")
|
||||||
.issuedAt(now)
|
.issuedAt(now)
|
||||||
.expiresAt(now.plus(2, ChronoUnit.HOURS)) // Token ist 2 Stunden gültig
|
.expiresAt(now.plus(2, ChronoUnit.HOURS))
|
||||||
.subject(email) // Der "Besitzer" des Tokens
|
.subject(email)
|
||||||
.claim("role", "MEMBER") // Hier könntest du später Admin/Mitglied Rollen einbauen
|
.claim("role", role.replace("ROLE_", "")) // Dynamische Rolle einfügen
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build();
|
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build();
|
||||||
|
|||||||
@ -1 +1,7 @@
|
|||||||
<router-outlet />
|
<!-- Der Header bleibt immer fix oben stehen -->
|
||||||
|
<app-header></app-header>
|
||||||
|
|
||||||
|
<!-- Hier drin werden je nach Route die Login-, Register- oder Dashboard-Seiten geladen -->
|
||||||
|
<main class="min-h-[calc(100vh-4rem)] bg-gray-50">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</main>
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import {Routes} from '@angular/router';
|
import {Routes} from '@angular/router';
|
||||||
import {LandingPage} from './pages/landing-page/landing-page';
|
import {LandingPage} from './pages/landing-page/landing-page';
|
||||||
import {Register} from './pages/register/register';
|
import {Register} from './pages/register/register';
|
||||||
|
import {LoginComponent} from './pages/login/login';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: LandingPage },
|
{ path: '', component: LandingPage },
|
||||||
{ path: 'register', component: Register }
|
{ path: 'register', component: Register },
|
||||||
|
{ path: 'login', component: LoginComponent}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import {Component, signal} from '@angular/core';
|
import {Component, signal} from '@angular/core';
|
||||||
import {RouterOutlet} from '@angular/router';
|
import {RouterOutlet} from '@angular/router';
|
||||||
|
import {HeaderComponent} from './layout/header/header';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet, HeaderComponent],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss',
|
styleUrl: './app.scss',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,29 +1,5 @@
|
|||||||
<div class="min-h-screen flex flex-col bg-slate-50">
|
<div class="min-h-screen flex flex-col bg-slate-50">
|
||||||
|
|
||||||
<header class="bg-white shadow-sm sticky top-0 z-50">
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<mat-icon color="primary">bolt</mat-icon>
|
|
||||||
<span class="font-bold text-xl tracking-tight text-slate-900">
|
|
||||||
Energie Portal
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="hidden md:flex gap-6">
|
|
||||||
<a href="#vorteile" class="text-slate-600 hover:text-slate-900 font-medium">Vorteile</a>
|
|
||||||
<a href="#so-gehts" class="text-slate-600 hover:text-slate-900 font-medium">So funktioniert's</a>
|
|
||||||
<a href="#faq" class="text-slate-600 hover:text-slate-900 font-medium">FAQ</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<button mat-button class="hidden sm:inline-flex">Login</button>
|
|
||||||
<button mat-flat-button color="primary" routerLink="/register">Mitglied werden</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="flex-grow">
|
<main class="flex-grow">
|
||||||
|
|
||||||
<section class="relative bg-white pt-20 pb-32 overflow-hidden">
|
<section class="relative bg-white pt-20 pb-32 overflow-hidden">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {inject, Injectable} from '@angular/core';
|
import {inject, Injectable} from '@angular/core';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Observable} from 'rxjs';
|
import {BehaviorSubject, Observable, tap} from 'rxjs';
|
||||||
|
import {AuthResponse} from '../api';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -11,8 +12,35 @@ export class AuthService {
|
|||||||
// Die URL zu deinem lokalen Spring Boot Backend
|
// Die URL zu deinem lokalen Spring Boot Backend
|
||||||
private apiUrl = 'http://localhost:8080/api/auth';
|
private apiUrl = 'http://localhost:8080/api/auth';
|
||||||
|
|
||||||
|
private isLoggedInSubject = new BehaviorSubject<boolean>(!!localStorage.getItem('token'));
|
||||||
|
|
||||||
|
isLoggedIn$ = this.isLoggedInSubject.asObservable();
|
||||||
|
|
||||||
register(userData: any): Observable<any> {
|
register(userData: any): Observable<any> {
|
||||||
// Sendet die Daten als JSON-Body an den Endpunkt /register
|
// Sendet die Daten als JSON-Body an den Endpunkt /register
|
||||||
return this.http.post(`${this.apiUrl}/register`, userData);
|
return this.http.post(`${this.apiUrl}/register`, userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
login(credentials: any): Observable<AuthResponse> {
|
||||||
|
return this.http.post<AuthResponse>(`${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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user