From dcad05294c056e79bd1452eefa6bb491448cfdd7 Mon Sep 17 00:00:00 2001 From: Sebastian-Ion TINCU <Sebastian-Ion.TINCU@ext.ec.europa.eu> Date: Thu, 6 Jun 2024 13:48:11 +0200 Subject: [PATCH] EDELIVERY-12752 UI enhancement Users are warned before session expire [PR] Prompt users when session is about to expire. --- smp-angular/src/app/app.module.ts | 4 ++ .../session-expiration-dialog.component.html | 6 ++ .../session-expiration-dialog.component.ts | 26 ++++++++ .../src/app/http/http-session-interceptor.ts | 60 +++++++++---------- 4 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.html create mode 100644 smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.ts diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index a2a267343..84483a36c 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -149,6 +149,9 @@ import { } from "./tools/dns-tools/dns-query-panel/dns-query-panel.component"; import {ResourceFilterOptionsService} from "./common/services/resource-filter-options.service"; import {HttpSessionInterceptor} from "./http/http-session-interceptor"; +import { + SessionExpirationDialogComponent +} from "./common/dialogs/session-expiration-dialog/session-expiration-dialog.component"; @NgModule({ @@ -235,6 +238,7 @@ import {HttpSessionInterceptor} from "./http/http-session-interceptor"; UserCertificatesComponent, UserProfileComponent, UserProfilePanelComponent, + SessionExpirationDialogComponent, ], imports: [ BrowserAnimationsModule, diff --git a/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.html b/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.html new file mode 100644 index 000000000..b4b363b05 --- /dev/null +++ b/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.html @@ -0,0 +1,6 @@ +<h2 mat-dialog-title>Extend session</h2> +<mat-dialog-content>Your session is about to expire in <b>{{data.timeLeft}}</b> seconds!<br />Would you like to logout now or extend it for another <b>{{data.timeout}}</b> seconds?</mat-dialog-content> +<mat-dialog-actions> + <button mat-button mat-dialog-close (click)="onLogoutClicked()" tabindex="-1">Logout</button> + <button mat-button mat-dialog-close (click)="onExtendSessionClicked()">Extend</button> +</mat-dialog-actions> diff --git a/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.ts b/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.ts new file mode 100644 index 000000000..8d459600e --- /dev/null +++ b/smp-angular/src/app/common/dialogs/session-expiration-dialog/session-expiration-dialog.component.ts @@ -0,0 +1,26 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {SecurityService} from "../../../security/security.service"; + +@Component({ + templateUrl: './session-expiration-dialog.component.html', +}) +export class SessionExpirationDialogComponent { + + constructor(@Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef<SessionExpirationDialogComponent>, + public securityService: SecurityService) { + } + + public onExtendSessionClicked() { + // just make another simple call to the backend which cancels out the current inactivity + this.securityService.isAuthenticated(true); + this.dialogRef.close(); + } + + onLogoutClicked() { + this.securityService.logout(); + this.dialogRef.close(); + } +} + diff --git a/smp-angular/src/app/http/http-session-interceptor.ts b/smp-angular/src/app/http/http-session-interceptor.ts index 80e2d3339..f8bfcf469 100644 --- a/smp-angular/src/app/http/http-session-interceptor.ts +++ b/smp-angular/src/app/http/http-session-interceptor.ts @@ -1,51 +1,49 @@ import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; -import {Observable, Subscription} from "rxjs"; -import {Injectable, OnDestroy, OnInit} from "@angular/core"; -import {SecurityEventService} from "../security/security-event.service"; +import {Observable} from "rxjs"; +import {Injectable} from "@angular/core"; import {SecurityService} from "../security/security.service"; import {AlertMessageService} from "../common/alert-message/alert-message.service"; - +import {MatDialog} from "@angular/material/dialog"; +import { + SessionExpirationDialogComponent +} from "../common/dialogs/session-expiration-dialog/session-expiration-dialog.component"; + +/* + * An custom interceptor that handles session expiration before it happens. + * + * Users are prompted 60 seconds before their HTTP sessions are about to expire + * and asked whether they would like to logout or extend the session time again. + */ @Injectable({ providedIn: 'root' }) -export class HttpSessionInterceptor implements HttpInterceptor, OnInit, OnDestroy { - - private securityEventService: SecurityEventService; - - private securityService: SecurityService; +export class HttpSessionInterceptor implements HttpInterceptor { - private alertMessageService: AlertMessageService; - - private loginSubscription: Subscription; + private readonly TIME_BEFORE_EXPIRATION_IN_SECONDS = 60; private timerId: number; - private sessionExpiringSoon = false; - - constructor(securityService: SecurityService, - securityEventService: SecurityEventService, - alertMessageService: AlertMessageService) { - this.securityService = securityService; - this.securityEventService = securityEventService; - this.alertMessageService = alertMessageService; - } - - ngOnInit() { - this.loginSubscription = this.securityEventService.onLoginSuccessEvent().subscribe(() => this.sessionExpiringSoon = false); - } - - ngOnDestroy() { - this.loginSubscription.unsubscribe(); + constructor(public securityService: SecurityService, + public alertMessageService: AlertMessageService, + private dialog: MatDialog) { } public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { clearTimeout(this.timerId); let user = this.securityService.getCurrentUser(); - if (user && user.sessionMaxIntervalTimeoutInSeconds && user.sessionMaxIntervalTimeoutInSeconds > 60) { - let timeout = (user.sessionMaxIntervalTimeoutInSeconds - 60) * 1000; - this.timerId = setTimeout(() => this.alertMessageService.warning("Your current session is about to expire!"), timeout); + if (user && user.sessionMaxIntervalTimeoutInSeconds && user.sessionMaxIntervalTimeoutInSeconds > this.TIME_BEFORE_EXPIRATION_IN_SECONDS) { + let timeout = (user.sessionMaxIntervalTimeoutInSeconds - this.TIME_BEFORE_EXPIRATION_IN_SECONDS) * 1000; + this.timerId = setTimeout(() => this.sessionExpiringSoon(user.sessionMaxIntervalTimeoutInSeconds), timeout); } return next.handle(req); } + private sessionExpiringSoon(timeout) { + this.dialog.open(SessionExpirationDialogComponent, { + data: { + timeLeft: this.TIME_BEFORE_EXPIRATION_IN_SECONDS, + timeout + } + }); + } } -- GitLab