feat: create AuthInterceptor and integrate it; adjust AuthGuards accordingly
This commit is contained in:
parent
c58d5c026b
commit
cf4a994ad0
7 changed files with 98 additions and 48 deletions
|
|
@ -19,6 +19,7 @@ import { ToastrModule } from 'ngx-toastr';
|
|||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HttpErrorInterceptor } from './interceptors/http-interceptor';
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { AuthInterceptor } from './interceptors/auth-interceptor';
|
||||
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
|
|
@ -68,5 +69,10 @@ export const appConfig: ApplicationConfig = {
|
|||
useClass: HttpErrorInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
import { SidebarMobileComponent } from '../../shared/components/sidebar/sidebar-mobile/sidebar-mobile.component';
|
||||
import { SidebarComponent } from '../../shared/components/sidebar/sidebar.component';
|
||||
|
|
|
|||
|
|
@ -1,46 +1,33 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { catchError, map, Observable, of } from 'rxjs';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { TokenService } from '../services/token.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ToastNotificationService } from '../services/toast-notification.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthenticatedGuard {
|
||||
export class AuthenticatedGuard implements CanActivate {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private tokenService: TokenService,
|
||||
private router: Router,
|
||||
private toastrService: ToastrService,
|
||||
private toastNotificationService: ToastNotificationService
|
||||
private toastNotificationService: ToastNotificationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
canActivate(): Observable<boolean> | Promise<boolean> | boolean {
|
||||
const authToken = this.tokenService.getAuthToken();
|
||||
|
||||
if (!authToken || !this.tokenService.isTokenExpired(authToken)) {
|
||||
if (
|
||||
!this.tokenService.isTokenAvailable() ||
|
||||
!this.tokenService.isUserIdAvailable()
|
||||
) {
|
||||
this.router.navigate(['/login']);
|
||||
this.toastNotificationService.showSessionExpiredToast();
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.authService.checkAuthUser().pipe(
|
||||
map((isAuthenticated) => {
|
||||
if (isAuthenticated) {
|
||||
return true;
|
||||
} else {
|
||||
this.toastNotificationService.showSessionExpiredMessage();
|
||||
this.router.navigate(['/login']);
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
catchError(() => {
|
||||
this.toastNotificationService.showSessionExpiredMessage();
|
||||
this.router.navigate(['/login']);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
return this.authService
|
||||
.checkAuthUser()
|
||||
.pipe(map((isAuthenticated) => isAuthenticated));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { Observable, map } from 'rxjs';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { TokenService } from '../services/token.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RedirectIfAuthenticatedGuard {
|
||||
export class RedirectIfAuthenticatedGuard implements CanActivate {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private tokenService: TokenService,
|
||||
|
|
@ -16,22 +15,20 @@ export class RedirectIfAuthenticatedGuard {
|
|||
) {}
|
||||
|
||||
canActivate(): Observable<boolean> | Promise<boolean> | boolean {
|
||||
const authToken = this.tokenService.getAuthToken();
|
||||
|
||||
if (!authToken || !this.tokenService.isTokenExpired(authToken)) {
|
||||
if (
|
||||
!this.tokenService.isTokenAvailable() ||
|
||||
!this.tokenService.isUserIdAvailable()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.authService.checkAuthUser().pipe(
|
||||
map((isAuthenticated) => {
|
||||
if (isAuthenticated) {
|
||||
this.router.navigate(['/summary']);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
catchError(() => {
|
||||
this.router.navigate(['/login']);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
64
src/app/interceptors/auth-interceptor.ts
Normal file
64
src/app/interceptors/auth-interceptor.ts
Normal 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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ export class ToastNotificationService {
|
|||
);
|
||||
}
|
||||
|
||||
showSessionExpiredMessage(): void {
|
||||
showSessionExpiredToast(): void {
|
||||
this.createInfoToast(
|
||||
'Your session has expired, please log in again.',
|
||||
'Session Expired'
|
||||
|
|
|
|||
|
|
@ -43,14 +43,11 @@ export class TokenService {
|
|||
sessionStorage.removeItem(this.USER_ID_KEY);
|
||||
}
|
||||
|
||||
isTokenExpired(token: string): boolean {
|
||||
try {
|
||||
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;
|
||||
isTokenAvailable(): boolean {
|
||||
return !!this.getAuthToken();
|
||||
}
|
||||
|
||||
isUserIdAvailable(): boolean {
|
||||
return !!this.getUserId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue