error logs

This commit is contained in:
Tizian.Breuch
2025-09-29 14:37:54 +02:00
parent f7147f281a
commit 991d57d183
5 changed files with 541 additions and 63 deletions

View File

@@ -1,11 +1,20 @@
// /src/app/app.config.ts
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
import {
ApplicationConfig,
importProvidersFrom,
provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import {
provideHttpClient,
withFetch,
withInterceptors,
} from '@angular/common/http';
import { provideClientHydration } from '@angular/platform-browser';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { errorInterceptor } from './core/interceptors/error.interceptor';
import { routes } from './app.routes';
@@ -25,7 +34,7 @@ export const appConfig: ApplicationConfig = {
// withFetch() entfernen, da es mit Interceptors noch Probleme geben kann
// Stattdessen withInterceptors() verwenden
provideHttpClient(
withInterceptors([authInterceptor]) // Registriert unseren neuen Interceptor
withInterceptors([authInterceptor, errorInterceptor]) // Registriert unseren neuen Interceptor
),
// --- Forms Provider (sauberer Import) ---
@@ -37,6 +46,6 @@ export const appConfig: ApplicationConfig = {
// --- API_URL Provider hinzufügen ---
// Stellt den API_URL-Token mit dem Wert aus der passenden environment-Datei bereit
{ provide: API_URL, useValue: environment.apiUrl }
]
};
{ provide: API_URL, useValue: environment.apiUrl },
],
};

View File

@@ -0,0 +1,34 @@
// /src/app/core/interceptors/error.interceptor.ts
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
import { LoggingService } from '../services/logging.service';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const logger = inject(LoggingService);
return next(req).pipe(
catchError((error: unknown) => {
if (error instanceof HttpErrorResponse) {
// Speziell den 401-Fehler (Unauthorized) abfangen
if (error.status === 401) {
logger.warn('Unauthorized request (401). Token might be expired or invalid. Logging out.');
// Den Benutzer ausloggen und zur Login-Seite weiterleiten
authService.logout();
} else {
// Andere Server-Fehler loggen
logger.error(`HTTP Error: ${error.status} ${error.statusText}`, error);
}
} else {
// Client-seitige Fehler loggen
logger.error('An unknown client-side error occurred', error);
}
// Den Fehler an den aufrufenden Service weitergeben
return throwError(() => error);
})
);
};

View File

@@ -5,11 +5,18 @@ import { HttpClient } from '@angular/common/http';
import { isPlatformBrowser } from '@angular/common';
import { Router } from '@angular/router';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { tap, catchError, map } from 'rxjs/operators';
import { tap, catchError } from 'rxjs/operators';
import { jwtDecode } from 'jwt-decode';
import { LoginRequest, AuthResponse, RegisterRequest } from '../models/auth.models'
import { LoginRequest, AuthResponse, RegisterRequest } from '../models/auth.models';
import { API_URL } from '../tokens/api-url.token';
// Ein Hilfs-Interface, um die Struktur des dekodierten Tokens typsicher zu machen.
interface DecodedToken {
exp: number; // Expiration time als UNIX-Timestamp in Sekunden
// Hier könnten weitere Claims wie 'email', 'sub', 'role' etc. stehen
}
@Injectable({
providedIn: 'root'
})
@@ -18,30 +25,46 @@ export class AuthService {
private http = inject(HttpClient);
private router = inject(Router);
private platformId = inject(PLATFORM_ID);
private apiUrl = inject(API_URL); // <-- SAUBERE INJEKTION!
private apiUrl = inject(API_URL);
private readonly endpoint = '/Auth';
// Keys für die Speicherung im localStorage
private readonly TOKEN_KEY = 'auth-token';
private readonly ROLES_KEY = 'auth-user-roles';
private loggedInStatus = new BehaviorSubject<boolean>(this.isBrowser() && !!this.getToken());
public isLoggedIn$ = this.loggedInStatus.asObservable();
private tokenExpirationTimer: any;
constructor() {
// Beim Initialisieren des Services prüfen, ob bereits ein Token vorhanden ist
// und ggf. den Logout-Timer dafür starten (z.B. nach einem Neuladen der Seite).
this.initTokenCheck();
}
loginAdmin(credentials: LoginRequest): Observable<AuthResponse | null> {
return this.http.post<AuthResponse>(`${this.apiUrl}${this.endpoint}/login/admin`, credentials).pipe(
tap(response => this.setSession(response)),
tap(response => {
if (response?.isAuthSuccessful) {
this.setSession(response);
this.startTokenExpirationTimer(); // Timer nach erfolgreichem Login starten
}
}),
catchError(() => {
this.clearSession(); // Bei fehlgeschlagenem Login immer die Session aufräumen
return of(null); // Gib ein "leeres" Observable zurück statt einen Fehler zu werfen
this.clearSession();
return of(null);
})
);
}
loginCustomer(credentials: LoginRequest): Observable<AuthResponse | null> {
return this.http.post<AuthResponse>(`${this.apiUrl}${this.endpoint}/login/customer`, credentials).pipe(
tap(response => this.setSession(response)),
tap(response => {
if (response?.isAuthSuccessful) {
this.setSession(response);
this.startTokenExpirationTimer(); // Timer nach erfolgreichem Login starten
}
}),
catchError(() => {
this.clearSession();
return of(null);
@@ -57,7 +80,11 @@ export class AuthService {
logout(): void {
this.clearSession();
this.router.navigate(['/auth/login']); // Empfehlung: Routen gruppieren
// Den proaktiven Timer stoppen, da der Logout manuell erfolgt.
if (this.tokenExpirationTimer) {
clearTimeout(this.tokenExpirationTimer);
}
this.router.navigate(['/auth/login']);
}
getToken(): string | null {
@@ -93,4 +120,41 @@ export class AuthService {
private isBrowser(): boolean {
return isPlatformBrowser(this.platformId);
}
private startTokenExpirationTimer(): void {
if (this.tokenExpirationTimer) {
clearTimeout(this.tokenExpirationTimer);
}
const token = this.getToken();
if (!token) {
return;
}
try {
const decodedToken = jwtDecode<DecodedToken>(token);
const expirationDate = new Date(decodedToken.exp * 1000); // JWT 'exp' ist in Sekunden, Date() braucht Millisekunden
const timeoutDuration = expirationDate.getTime() - new Date().getTime();
if (timeoutDuration > 0) {
this.tokenExpirationTimer = setTimeout(() => {
console.warn('Sitzung proaktiv beendet, da das Token abgelaufen ist.');
this.logout();
// Hier könnte man eine Snackbar-Nachricht anzeigen
}, timeoutDuration);
} else {
// Das gespeicherte Token ist bereits abgelaufen
this.clearSession();
}
} catch (error) {
console.error('Fehler beim Dekodieren des Tokens. Session wird bereinigt.', error);
this.clearSession();
}
}
private initTokenCheck(): void {
if (this.isBrowser()) {
this.startTokenExpirationTimer();
}
}
}