diff --git a/smp-angular/src/app/app.component.ts b/smp-angular/src/app/app.component.ts index dd6f64b5d557c7ad908de5dd8308b486728b7af5..fd3eb7802e2c9d5da2501a3be2d32e7bdd6739ed 100644 --- a/smp-angular/src/app/app.component.ts +++ b/smp-angular/src/app/app.component.ts @@ -1,8 +1,10 @@ -import {Component, ViewChild} from '@angular/core'; +import {Component, HostListener, ViewChild} from '@angular/core'; import {SecurityService} from './security/security.service'; import {Router} from '@angular/router'; import {Authority} from "./security/authority.model"; -import {AlertMessageService} from "./common/alert-message/alert-message.service"; +import { + AlertMessageService +} from "./common/alert-message/alert-message.service"; import {MatDialog} from "@angular/material/dialog"; import {GlobalLookups} from "./common/global-lookups"; import {HttpClient} from "@angular/common/http"; @@ -92,7 +94,7 @@ export class AppComponent { this.alertService.clearAlert(); } - onDrawerContentScroll(scrollEvent: any){ + onDrawerContentScroll(scrollEvent: any) { let scrollTop = scrollEvent.srcElement.scrollTop; this.alertService.setKeepAfterNavigationChange(scrollTop > 0) } @@ -100,4 +102,34 @@ export class AppComponent { get showSpinner(): boolean { return this.windowSpinnerService.showSpinner } + +// Listeners for activity monitoring +// Every time one of these events are triggered, all the "watches" are reseted + @HostListener('window:mousemove') + mouseMove() { + this.refreshUserState(); + } + + @HostListener('keydown') + keyboardClick() { + this.refreshUserState(); + } + + @HostListener('touchstart') + screenTouched() { + this.refreshUserState(); + } + + @HostListener('touchmove') + screenDragged() { + this.refreshUserState(); + } + + refreshUserState() { + // if user is not logged in, do nothing + if (!this.securityService.isAuthenticated(false)) { + return; + } + this.securityService.uiUserActivityDetected() + } } diff --git a/smp-angular/src/app/common/global-lookups.ts b/smp-angular/src/app/common/global-lookups.ts index d5a29a494fec7cd39dab49a97e28110afebfd027..05e02efc2fbc89b4b94a7d2543a4e62d9573fd5a 100644 --- a/smp-angular/src/app/common/global-lookups.ts +++ b/smp-angular/src/app/common/global-lookups.ts @@ -115,7 +115,6 @@ export class GlobalLookups { console.log("getSmpInfo:" + err); } }); - } public refreshApplicationConfiguration() { diff --git a/smp-angular/src/app/http/http-session-interceptor.ts b/smp-angular/src/app/http/http-session-interceptor.ts index 2e6a7f1cde465472910665a655097406c53b34fa..9494440b0b4118f96a06a5fd855dec810ccec651 100644 --- a/smp-angular/src/app/http/http-session-interceptor.ts +++ b/smp-angular/src/app/http/http-session-interceptor.ts @@ -20,9 +20,6 @@ import {TranslateService} from "@ngx-translate/core"; }) export class HttpSessionInterceptor implements HttpInterceptor { - private readonly TIME_BEFORE_EXPIRATION_IN_SECONDS = 60; - private readonly MAXIMUM_TIMEOUT_VALUE = 2147483647; - private timerId: number; private timerToLogoutId: number; @@ -36,23 +33,28 @@ export class HttpSessionInterceptor implements HttpInterceptor { clearTimeout(this.timerId); clearTimeout(this.timerToLogoutId); let user = this.securityService.getCurrentUser(); - if (user?.sessionMaxIntervalTimeoutInSeconds && user.sessionMaxIntervalTimeoutInSeconds > this.TIME_BEFORE_EXPIRATION_IN_SECONDS) { - let timeout = Math.min((user.sessionMaxIntervalTimeoutInSeconds - this.TIME_BEFORE_EXPIRATION_IN_SECONDS) * 1000, this.MAXIMUM_TIMEOUT_VALUE); + // set the last UI session call + this.securityService.uiUserSessionCallDetected() + if (user?.sessionMaxIntervalTimeoutInSeconds && user.sessionMaxIntervalTimeoutInSeconds > SecurityService.TIME_BEFORE_EXPIRATION_IN_SECONDS) { + let timeout = Math.min((user.sessionMaxIntervalTimeoutInSeconds - SecurityService.TIME_BEFORE_EXPIRATION_IN_SECONDS) * 1000, SecurityService.MAXIMUM_TIMEOUT_VALUE); this.timerId = setTimeout(() => this.sessionExpiringSoon(user.sessionMaxIntervalTimeoutInSeconds), timeout); } return next.handle(req); } private sessionExpiringSoon(timeout) { + // Logout the user after the session expires this.timerToLogoutId = setTimeout(() => { this.securityService.logout(); this.alertService.errorForTranslation("session.alert.message.logout.expired", true); - }, this.TIME_BEFORE_EXPIRATION_IN_SECONDS * 1000); - + }, SecurityService.TIME_BEFORE_EXPIRATION_IN_SECONDS * 1000); + // disable the automatic UI session extension, + // because the user has dialog to extend the session + this.securityService.uiUserSessionExtensionDisable(); this.dialog.open(SessionExpirationDialogComponent, { data: { - timeLeft: this.TIME_BEFORE_EXPIRATION_IN_SECONDS, + timeLeft: SecurityService.TIME_BEFORE_EXPIRATION_IN_SECONDS, timeout } }); diff --git a/smp-angular/src/app/security/security.service.ts b/smp-angular/src/app/security/security.service.ts index c77e2e7559af3cbfdb7ff934c19f70cb2db33206..81fc3920531b90836ab3d603a0f13e1aebcaa63f 100644 --- a/smp-angular/src/app/security/security.service.ts +++ b/smp-angular/src/app/security/security.service.ts @@ -16,12 +16,19 @@ import {Router} from "@angular/router"; import {TranslateService} from "@ngx-translate/core"; import {WindowSpinnerService} from "../common/services/window-spinner.service"; import {SmpErrorCode} from "../common/enums/smp-error-code.enum"; +import {SmpInfo} from "../app-info/smp-info.model"; @Injectable() export class SecurityService { + public static readonly TIME_BEFORE_EXPIRATION_IN_SECONDS: number = 60; + public static readonly DELAY_BEFORE_UI_SESSION_EXTENSION_IN_MS: number = 3000; + public static readonly MAXIMUM_TIMEOUT_VALUE: number = 2147483647; readonly LOCAL_STORAGE_KEY_CURRENT_USER = 'currentUser'; + lastUIActivity: Date = new Date(); + lastUISessionCall: Date = new Date(); + constructor( private http: HttpClient, private alertService: AlertMessageService, @@ -277,4 +284,51 @@ export class SecurityService { public clearLocalStorage() { localStorage.removeItem(this.LOCAL_STORAGE_KEY_CURRENT_USER); } + + /** + * + */ + uiUserActivityDetected() { + console.log("User activity detected"); + let user = this.getCurrentUser(); + if (!this.isAuthenticated(false) + || !user + || !this.lastUISessionCall) { + return; + } + this.lastUIActivity = new Date(); + // to prevent multiple calls to the backend, we check if the last call + // was more than DELAY_BEFORE_UI_SESSION_EXTENSION_IN_MS + if (this.lastUIActivity.getTime() - this.lastUISessionCall.getTime() > SecurityService.DELAY_BEFORE_UI_SESSION_EXTENSION_IN_MS) { + // make a call to the backend to extend the session + this.refreshApplicationInfo(); + } + } + + /** + * This method is called when a UI session call to server is detected. + */ + uiUserSessionCallDetected() { + if (!this.isAuthenticated(false)) { + return; + } + this.lastUISessionCall = new Date(); + } + + uiUserSessionExtensionDisable() { + this.lastUISessionCall = null; + } + + public refreshApplicationInfo() { + + this.http.get<SmpInfo>(SmpConstants.REST_PUBLIC_APPLICATION_INFO) + .subscribe({ + next: (res: SmpInfo): void => { + + }, + error: (err: any): void => { + console.log("getSmpInfo:" + err); + } + }); + } }