diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 92730ab69f..e80715c742 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -28,6 +28,7 @@ import { LayoutModule } from "_layout/layout.module"; import { AppConfigService } from "app-config.service"; import { AppThemeService } from "app-theme.service"; import { SnackbarInterceptor } from "shared/interceptors/snackbar.interceptor"; +import { AuthInterceptor } from "shared/interceptors/auth.interceptor"; import { AuthService } from "shared/services/auth/auth.service"; import { InternalStorage, SDKStorage } from "shared/services/auth/base.storage"; import { CookieService } from "ngx-cookie-service"; @@ -112,6 +113,11 @@ const apiConfigurationFn = ( useClass: SnackbarInterceptor, multi: true, }, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptor, + multi: true, + }, { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { diff --git a/src/app/shared/interceptors/auth.interceptor.ts b/src/app/shared/interceptors/auth.interceptor.ts new file mode 100644 index 0000000000..bfacc1121d --- /dev/null +++ b/src/app/shared/interceptors/auth.interceptor.ts @@ -0,0 +1,45 @@ +import { Injectable } from "@angular/core"; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor, + HttpErrorResponse, +} from "@angular/common/http"; +import { Observable, throwError } from "rxjs"; +import { catchError } from "rxjs/operators"; +import { Router } from "@angular/router"; +import { NgZone } from "@angular/core"; +import { Subscription } from "rxjs"; +import { sessionTimeoutAction } from "state-management/actions/user.actions"; +import { Store } from "@ngrx/store"; +import { selectProfile } from "state-management/selectors/user.selectors"; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + constructor( + private router: Router, + private store: Store, + private zone: NgZone, + ) {} + + intercept( + request: HttpRequest, + next: HttpHandler, + ): Observable> { + return next.handle(request).pipe( + catchError((error: HttpErrorResponse) => { + const isSessionExpired = + error.status === 401 && error.error?.message === "SESSION_EXPIRED"; + + if (isSessionExpired) { + const currentUrl = this.router.url; + sessionStorage.setItem("postLoginRedirect", currentUrl); + this.store.dispatch(sessionTimeoutAction()); + } + + return throwError(() => error); + }), + ); + } +} diff --git a/src/app/state-management/actions/user.actions.ts b/src/app/state-management/actions/user.actions.ts index 50ac1b208c..28aed61fee 100644 --- a/src/app/state-management/actions/user.actions.ts +++ b/src/app/state-management/actions/user.actions.ts @@ -140,6 +140,8 @@ export const logoutCompleteAction = createAction( ); export const logoutFailedAction = createAction("[User] Logout Failed"); +export const sessionTimeoutAction = createAction("[User] Session Timeout"); + export const addCustomColumnsAction = createAction( "[User] Add Custom Columns", props<{ names: string[]; scope: "dataset" | "sample" }>(), diff --git a/src/app/state-management/effects/user.effects.ts b/src/app/state-management/effects/user.effects.ts index d377e04a03..7766fd7e14 100644 --- a/src/app/state-management/effects/user.effects.ts +++ b/src/app/state-management/effects/user.effects.ts @@ -246,6 +246,27 @@ export class UserEffects { ); }); + sessionTimeout$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.sessionTimeoutAction), + filter(() => this.authService.isAuthenticated()), + switchMap(() => { + this.authService.clear(); + return of( + clearDatasetsStateAction(), + clearInstrumentsStateAction(), + clearJobsStateAction(), + clearLogbooksStateAction(), + clearPoliciesStateAction(), + clearProposalsStateAction(), + clearPublishedDataStateAction(), + clearSamplesStateAction(), + fromActions.logoutCompleteAction({ logoutURL: "/login" }), + ); + }), + ); + }); + logoutNavigate$ = createEffect( () => { return this.actions$.pipe( diff --git a/src/app/state-management/reducers/user.reducer.ts b/src/app/state-management/reducers/user.reducer.ts index fc6cce1427..72c7859dd2 100644 --- a/src/app/state-management/reducers/user.reducer.ts +++ b/src/app/state-management/reducers/user.reducer.ts @@ -127,6 +127,13 @@ const reducer = createReducer( }), ), + on( + fromActions.sessionTimeoutAction, + (): UserState => ({ + ...initialUserState, + }), + ), + on( fromActions.addCustomColumnsAction, (state, { names, scope }): UserState => { diff --git a/src/app/users/login/login.component.ts b/src/app/users/login/login.component.ts index d4167ac7f8..77542e55a7 100644 --- a/src/app/users/login/login.component.ts +++ b/src/app/users/login/login.component.ts @@ -125,6 +125,12 @@ export class LoginComponent implements OnInit, OnDestroy { this.loginLocalEnabled = this.appConfig.loginLocalEnabled; this.oAuth2Endpoints = this.appConfig.oAuth2Endpoints; + const storedRedirect = sessionStorage.getItem("postLoginRedirect"); + if (storedRedirect) { + this.returnUrl = storedRedirect; + sessionStorage.removeItem("postLoginRedirect"); + } + this.proceedSubscription = this.vm$ .pipe(filter((vm) => vm.isLoggedIn)) .subscribe(() => {