feat: create AuthInterceptor and integrate it; adjust AuthGuards accordingly

This commit is contained in:
Chneemann 2025-04-06 11:01:59 +02:00
parent c58d5c026b
commit cf4a994ad0
7 changed files with 98 additions and 48 deletions

View file

@ -19,6 +19,7 @@ import { ToastrModule } from 'ngx-toastr';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpErrorInterceptor } from './interceptors/http-interceptor'; import { HttpErrorInterceptor } from './interceptors/http-interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth-interceptor';
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json'); return new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -68,5 +69,10 @@ export const appConfig: ApplicationConfig = {
useClass: HttpErrorInterceptor, useClass: HttpErrorInterceptor,
multi: true, multi: true,
}, },
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
], ],
}; };

View file

@ -1,7 +1,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { LanguageService } from '../../services/language.service'; import { LanguageService } from '../../services/language.service';
import { SidebarMobileComponent } from '../../shared/components/sidebar/sidebar-mobile/sidebar-mobile.component'; import { SidebarMobileComponent } from '../../shared/components/sidebar/sidebar-mobile/sidebar-mobile.component';
import { SidebarComponent } from '../../shared/components/sidebar/sidebar.component'; import { SidebarComponent } from '../../shared/components/sidebar/sidebar.component';

View file

@ -1,46 +1,33 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { catchError, map, Observable, of } from 'rxjs'; import { map, Observable } from 'rxjs';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { TokenService } from '../services/token.service'; import { TokenService } from '../services/token.service';
import { ToastrService } from 'ngx-toastr';
import { ToastNotificationService } from '../services/toast-notification.service'; import { ToastNotificationService } from '../services/toast-notification.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthenticatedGuard { export class AuthenticatedGuard implements CanActivate {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private tokenService: TokenService, private tokenService: TokenService,
private router: Router, private toastNotificationService: ToastNotificationService,
private toastrService: ToastrService, private router: Router
private toastNotificationService: ToastNotificationService
) {} ) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean { canActivate(): Observable<boolean> | Promise<boolean> | boolean {
const authToken = this.tokenService.getAuthToken(); if (
!this.tokenService.isTokenAvailable() ||
if (!authToken || !this.tokenService.isTokenExpired(authToken)) { !this.tokenService.isUserIdAvailable()
) {
this.router.navigate(['/login']); this.router.navigate(['/login']);
this.toastNotificationService.showSessionExpiredToast();
return false; return false;
} }
return this.authService.checkAuthUser().pipe( return this.authService
map((isAuthenticated) => { .checkAuthUser()
if (isAuthenticated) { .pipe(map((isAuthenticated) => isAuthenticated));
return true;
} else {
this.toastNotificationService.showSessionExpiredMessage();
this.router.navigate(['/login']);
return false;
}
}),
catchError(() => {
this.toastNotificationService.showSessionExpiredMessage();
this.router.navigate(['/login']);
return of(false);
})
);
} }
} }

View file

@ -1,14 +1,13 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { Observable, of } from 'rxjs'; import { Observable, map } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { TokenService } from '../services/token.service'; import { TokenService } from '../services/token.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class RedirectIfAuthenticatedGuard { export class RedirectIfAuthenticatedGuard implements CanActivate {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private tokenService: TokenService, private tokenService: TokenService,
@ -16,22 +15,20 @@ export class RedirectIfAuthenticatedGuard {
) {} ) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean { canActivate(): Observable<boolean> | Promise<boolean> | boolean {
const authToken = this.tokenService.getAuthToken(); if (
!this.tokenService.isTokenAvailable() ||
if (!authToken || !this.tokenService.isTokenExpired(authToken)) { !this.tokenService.isUserIdAvailable()
) {
return true; return true;
} }
return this.authService.checkAuthUser().pipe( return this.authService.checkAuthUser().pipe(
map((isAuthenticated) => { map((isAuthenticated) => {
if (isAuthenticated) { if (isAuthenticated) {
this.router.navigate(['/summary']);
return false; return false;
} }
return true; return true;
}),
catchError(() => {
this.router.navigate(['/login']);
return of(false);
}) })
); );
} }

View file

@ -0,0 +1,64 @@
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, filter } from 'rxjs/operators';
import { Router, NavigationStart } from '@angular/router';
import { TokenService } from '../services/token.service';
import { ToastNotificationService } from '../services/toast-notification.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isNavigatingToLogin = false;
constructor(
private tokenService: TokenService,
private router: Router,
private toast: ToastNotificationService
) {
this.router.events
.pipe(
filter(
(event): event is NavigationStart => event instanceof NavigationStart
)
)
.subscribe((event) => {
if (event.url === '/login') {
this.isNavigatingToLogin = true;
} else {
this.isNavigatingToLogin = false;
}
});
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const token = this.tokenService.getAuthToken();
let clonedReq = req;
if (token) {
clonedReq = req.clone({
setHeaders: {
Authorization: `Token ${token}`,
},
});
}
return next.handle(clonedReq).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401 && !this.isNavigatingToLogin) {
this.toast.showSessionExpiredToast();
this.router.navigate(['/login']);
}
return throwError(() => error);
})
);
}
}

View file

@ -43,7 +43,7 @@ export class ToastNotificationService {
); );
} }
showSessionExpiredMessage(): void { showSessionExpiredToast(): void {
this.createInfoToast( this.createInfoToast(
'Your session has expired, please log in again.', 'Your session has expired, please log in again.',
'Session Expired' 'Session Expired'

View file

@ -43,14 +43,11 @@ export class TokenService {
sessionStorage.removeItem(this.USER_ID_KEY); sessionStorage.removeItem(this.USER_ID_KEY);
} }
isTokenExpired(token: string): boolean { isTokenAvailable(): boolean {
try { return !!this.getAuthToken();
const payload = JSON.parse(atob(token.split('.')[1]));
const expiry = payload.exp;
if (!expiry) return true;
return expiry * 1000 < Date.now();
} catch (e) {
return true;
} }
isUserIdAvailable(): boolean {
return !!this.getUserId();
} }
} }