Skip to content

Commit

Permalink
Added idle detection and automatic logout.
Browse files Browse the repository at this point in the history
  • Loading branch information
SBriere committed Jan 7, 2022
1 parent 4b35755 commit 78845ce
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 13 deletions.
16 changes: 16 additions & 0 deletions Frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"@angular/platform-browser": "^12.2.11",
"@angular/platform-browser-dynamic": "^12.2.11",
"@angular/router": "^12.2.11",
"@ng-idle/core": "^11.1.0",
"@ng-idle/keepalive": "^11.0.3",
"@schematics/angular": "^9.1.15",
"angular-calendar": "^0.28.28",
"angular-material": "^1.2.3",
Expand Down
77 changes: 76 additions & 1 deletion Frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {WebsocketService} from '@services/websocket.service';
import {Pages} from '@core/utils/pages';
import {SelectedSourceService} from '@services/selected-source.service';
import {SessionManagerService} from '@services/session-manager.service';
import {DEFAULT_INTERRUPTSOURCES, Idle} from '@ng-idle/core';
import {Keepalive} from '@ng-idle/keepalive';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import {InformationComponent} from '@components/information/information/information.component';

@Component({
selector: 'app-root',
Expand All @@ -21,14 +25,18 @@ export class AppComponent implements OnInit, OnDestroy {
title = 'Portail Web';
loading: boolean;
private subscription: Subscription;
private inactivityDialog: MatDialogRef<InformationComponent, any> = null;

constructor(private cookieService: CookieService,
private authService: AuthenticationService,
private accountService: AccountService,
private webSocketService: WebsocketService,
private selectedSourceService: SelectedSourceService,
private router: Router,
private sessionManagerService: SessionManagerService) {
private sessionManagerService: SessionManagerService,
private idle: Idle,
private keepalive: Keepalive,
private dialog: MatDialog) {
}

ngOnInit(): void {
Expand All @@ -37,6 +45,73 @@ export class AppComponent implements OnInit, OnDestroy {
this.navigationInterceptor(e);
}
});
this.initIdleTimeout();
}

private initIdleTimeout(): void {
// sets an idle timeout of 2 hours
this.idle.setIdle(60 * 60 * 2);
// sets a timeout period of 60 seconds. After that delay, if no user input, it will be logged out.
this.idle.setTimeout(60);
// sets the default interrupts, in this case, things like clicks, scrolls, touches to the document
this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);

this.idle.onIdleEnd.subscribe(() => {
// console.log('No longer idle');
this.resetIdleTimer();
});

this.idle.onTimeout.subscribe(() => {
// console.log('Timed out!');
this.inactivityDialog.close();
this.authService.logout().subscribe();
});

this.idle.onIdleStart.subscribe(() => {
// console.log('You\'ve gone idle!');
this.idle.setInterrupts([]);
if (this.inactivityDialog == null){
this.inactivityDialog = this.dialog.open(InformationComponent, {
width: '350px',
data: {message: 'Idle', button_text: 'Ne pas me déconnecter'}
});
this.inactivityDialog.afterClosed().subscribe(() => {
// console.log('Dialog deleted');
this.resetIdleTimer();
this.inactivityDialog = null;
});
}
});

this.idle.onTimeoutWarning.subscribe((countdown) => {
const idleState = 'Vous serez automatiquement déconnectés dans ' + countdown + ' secondes...';
this.inactivityDialog.componentInstance.data.message = idleState;
// console.log(idleState);
});

// sets the ping interval to 15 seconds
this.keepalive.interval(15);

// this.keepalive.onPing.subscribe(() => this.lastPing = new Date());

// this.resetIdleTimer();
this.authService.loginStateChange().subscribe(userLoggedIn => {
if (userLoggedIn) {
this.resetIdleTimer();
} else {
this.stopIdleTimer();
}
});
}

private resetIdleTimer(): void{
this.idle.stop();
this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);
this.idle.watch();
}

private stopIdleTimer(): void{
this.idle.stop();
}

private refreshToken(): void {
Expand Down
4 changes: 4 additions & 0 deletions Frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {ParticipantModule} from '@src/app/modules/participant.module';
import {HeaderModule} from '@src/app/modules/header.module';
import {EventDialogComponent} from '@components/event-dialog/event-dialog.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NgIdleKeepaliveModule} from '@ng-idle/keepalive';
import { InformationComponent } from './components/information/information/information.component';

registerLocaleData(localeFr, 'fr');

Expand All @@ -27,6 +29,7 @@ registerLocaleData(localeFr, 'fr');
ParticipantLayoutComponent,
NotFoundComponent,
EventDialogComponent,
InformationComponent,
],
imports: [
SharedModule,
Expand All @@ -36,6 +39,7 @@ registerLocaleData(localeFr, 'fr');
UserModule,
ParticipantModule,
AppRoutingModule,
NgIdleKeepaliveModule.forRoot(),
],
providers: [],
exports: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="message-box">
<div mat-dialog-content>
{{data.message}}
</div>
<div mat-dialog-actions class="btn-group">
<button mat-button [mat-dialog-close]="true">{{data.button_text}}</button>
</div>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';

@Component({
selector: 'app-information',
templateUrl: './information.component.html',
styleUrls: ['./information.component.scss']
})
export class InformationComponent implements OnInit {

constructor(public dialogRef: MatDialogRef<InformationComponent>,
@Inject(MAT_DIALOG_DATA) public data: {message: string, button_text: string}) {
}

ngOnInit(): void {
}

}
28 changes: 16 additions & 12 deletions Frontend/src/app/services/authentication.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {BehaviorSubject, Observable} from 'rxjs';
import {map, switchMap, tap} from 'rxjs/operators';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {GlobalConstants} from '@core/utils/global-constants';
Expand All @@ -23,11 +23,11 @@ export class AuthenticationService {
private cookieValue = GlobalConstants.cookieValue;
private API_URL = makeApiURL();
private refreshTokenTimeout: any;
private isLoggedIn = false;

private _lastAuthenticatedPath: string = this.defaultPath;
// private isLoggedIn = false;
private isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private m_lastAuthenticatedPath: string = this.defaultPath;
set lastAuthenticatedPath(value: string) {
this._lastAuthenticatedPath = value;
this.m_lastAuthenticatedPath = value;
}

constructor(private http: HttpClient,
Expand All @@ -41,8 +41,12 @@ export class AuthenticationService {
}

isAuthenticated(): boolean {
this.isLoggedIn = !!this.cookieService.get(this.cookieValue);
return this.isLoggedIn;
this.isLoggedIn.next(!!this.cookieService.get(this.cookieValue));
return this.isLoggedIn.getValue();
}

loginStateChange(): Observable<boolean> {
return this.isLoggedIn.asObservable();
}

login(username: string, password: string, isManager: boolean = false): Observable<any> {
Expand All @@ -51,9 +55,9 @@ export class AuthenticationService {
return this.http.get(apiUrl, {headers}).pipe(
tap((response: any) => {
const token = isManager ? response.user_token : response.participant_token;
this.isLoggedIn = true;
this.isLoggedIn.next(true);
this.cookieService.set(this.cookieValue, token, 0.5, '/');
this.router.navigate([this._lastAuthenticatedPath]);
this.router.navigate([this.m_lastAuthenticatedPath]);
// Connect websocket
this.websocketService.connect(response.websocket_url);
this.startRefreshTokenTimer();
Expand All @@ -62,9 +66,9 @@ export class AuthenticationService {
}

loginWithToken(token: string): void {
this.isLoggedIn = true;
this.isLoggedIn.next(true);
this.cookieService.set(this.cookieValue, token, null, '/');
this.router.navigate([this._lastAuthenticatedPath]);
this.router.navigate([this.m_lastAuthenticatedPath]);
}

logout(isManager: boolean = false): Observable<any> {
Expand All @@ -77,7 +81,7 @@ export class AuthenticationService {
}

reset(): void {
this.isLoggedIn = false;
this.isLoggedIn.next(false);
this.router.navigate([Pages.loginPage]);
this.cookieService.delete(this.cookieValue, '/');
this.selectedProjectService.setSelectedProject(new Project());
Expand Down

0 comments on commit 78845ce

Please sign in to comment.