From 78399a6b1efd761cf39c0f9451be070210cec8f1 Mon Sep 17 00:00:00 2001 From: limcaaarl Date: Tue, 12 Nov 2024 23:14:07 +0800 Subject: [PATCH 1/6] Bug fix for #77 - Users will now be warned if their tokens are about to expire - Users will be logged out upon token expiration --- .../src/_services/authentication.service.ts | 35 +++++++++++++++++++ frontend/src/app/app.component.html | 1 + frontend/src/app/app.component.ts | 16 ++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index 303303b39e..a3e4894f38 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -7,6 +7,7 @@ import { map, switchMap } from 'rxjs/operators'; import { UServRes } from '../_models/user.service.model'; import { User } from '../_models/user.model'; import { ApiService } from './api.service'; +import { ToastService } from './toast.service'; @Injectable({ providedIn: 'root' }) export class AuthenticationService extends ApiService { @@ -18,6 +19,7 @@ export class AuthenticationService extends ApiService { constructor( private router: Router, private http: HttpClient, + private toastService: ToastService, ) { super(); const userData = localStorage.getItem('user'); @@ -53,6 +55,8 @@ export class AuthenticationService extends ApiService { } localStorage.setItem('user', JSON.stringify(user)); this.userSubject.next(user); + this.startTokenExpiryCheck(); + return user; }), ); @@ -99,4 +103,35 @@ export class AuthenticationService extends ApiService { }), ); } + public startTokenExpiryCheck(): void { + const tokenExpirationTime = this.getTokenExpiration(); + + if (!tokenExpirationTime) { + this.logout(); + return; + } + + const oneMinute = 60 * 1000; + const checkInterval = oneMinute; + + setInterval(() => { + const now = Date.now(); + const timeLeft = tokenExpirationTime - now; + + if (timeLeft <= 0) { + this.toastService.showToast('Your session has expired. Please log in again.'); + this.logout(); + } else if (timeLeft < 5 * oneMinute) { + this.toastService.showToast('Your session will expire in less than 5 minutes. Please log in again'); + } + }, checkInterval); + } + + private getTokenExpiration() { + const user = this.userValue; + if (!user || !user.accessToken) return null; + + const tokenPayload = JSON.parse(atob(user.accessToken.split('.')[1])); + return tokenPayload.exp ? tokenPayload.exp * 1000 : null; + } } diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index a20a5cb8af..d702e4580e 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,4 +1,5 @@
+
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index d0722c2b03..0e29dabf67 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,16 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { ButtonModule } from 'primeng/button'; import { PasswordModule } from 'primeng/password'; +import { ToastModule } from 'primeng/toast'; import { NavigationBarComponent } from './navigation-bar/navigation-bar.component'; - +import { AuthenticationService } from '../_services/authentication.service'; +import { MessageService } from 'primeng/api'; @Component({ selector: 'app-root', standalone: true, - imports: [NavigationBarComponent, RouterOutlet, ButtonModule, PasswordModule], + imports: [NavigationBarComponent, RouterOutlet, ButtonModule, PasswordModule, ToastModule], + providers: [MessageService], templateUrl: './app.component.html', styleUrl: './app.component.css', }) -export class AppComponent { +export class AppComponent implements OnInit { title = 'frontend'; + + constructor(private authService: AuthenticationService) {} + ngOnInit() { + this.authService.startTokenExpiryCheck(); + } } From d841133f4f50fecd89fccd2f2c79740352d49c31 Mon Sep 17 00:00:00 2001 From: limcaaarl Date: Tue, 12 Nov 2024 23:19:56 +0800 Subject: [PATCH 2/6] Update from toast to alert for expired token --- frontend/src/_services/authentication.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index a3e4894f38..0c1f443dd1 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -119,7 +119,7 @@ export class AuthenticationService extends ApiService { const timeLeft = tokenExpirationTime - now; if (timeLeft <= 0) { - this.toastService.showToast('Your session has expired. Please log in again.'); + alert('Your session has expired. Please log in again.'); this.logout(); } else if (timeLeft < 5 * oneMinute) { this.toastService.showToast('Your session will expire in less than 5 minutes. Please log in again'); From 86f6f40b669df6c6669a2d7448015ff736a77fea Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Wed, 13 Nov 2024 20:50:33 +0800 Subject: [PATCH 3/6] Replace interval with timer --- .../src/_services/authentication.service.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index 0c1f443dd1..07b895b3e9 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -2,7 +2,8 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, timer } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { map, switchMap } from 'rxjs/operators'; import { UServRes } from '../_models/user.service.model'; import { User } from '../_models/user.model'; @@ -112,19 +113,26 @@ export class AuthenticationService extends ApiService { } const oneMinute = 60 * 1000; - const checkInterval = oneMinute; - setInterval(() => { - const now = Date.now(); - const timeLeft = tokenExpirationTime - now; + const timeLeft = tokenExpirationTime - Date.now(); + console.log(timeLeft); + + if (timeLeft > 5 * oneMinute) { + timer(timeLeft - 5 * oneMinute) + .pipe(takeUntilDestroyed()) + .subscribe(() => { + this.toastService.showToast( + 'Your session will expire in less than 5 minutes. Please log in again.', + ); + }); + } - if (timeLeft <= 0) { + timer(timeLeft) + .pipe(takeUntilDestroyed()) + .subscribe(() => { alert('Your session has expired. Please log in again.'); this.logout(); - } else if (timeLeft < 5 * oneMinute) { - this.toastService.showToast('Your session will expire in less than 5 minutes. Please log in again'); - } - }, checkInterval); + }); } private getTokenExpiration() { From eda4223aa27272730880ba13a0cd01a0065d7455 Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Wed, 13 Nov 2024 21:12:39 +0800 Subject: [PATCH 4/6] Enhance warning --- .../src/_services/authentication.service.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index 07b895b3e9..d46c4eee7e 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -104,27 +104,28 @@ export class AuthenticationService extends ApiService { }), ); } + + displaySessionExpiryWarning(): void { + this.toastService.showToast( + 'Your session will expire in less than 5 minutes. Please log in again.', + ); + } + public startTokenExpiryCheck(): void { const tokenExpirationTime = this.getTokenExpiration(); - if (!tokenExpirationTime) { this.logout(); return; } const oneMinute = 60 * 1000; - const timeLeft = tokenExpirationTime - Date.now(); - console.log(timeLeft); - if (timeLeft > 5 * oneMinute) { timer(timeLeft - 5 * oneMinute) .pipe(takeUntilDestroyed()) - .subscribe(() => { - this.toastService.showToast( - 'Your session will expire in less than 5 minutes. Please log in again.', - ); - }); + .subscribe(() => this.displaySessionExpiryWarning()); + } else { + this.displaySessionExpiryWarning(); } timer(timeLeft) From 5506901426a047dd8e55015ea50d512ac0e021f1 Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Wed, 13 Nov 2024 21:13:36 +0800 Subject: [PATCH 5/6] Fix linting --- frontend/src/_services/authentication.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index d46c4eee7e..10e95131fc 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -106,9 +106,7 @@ export class AuthenticationService extends ApiService { } displaySessionExpiryWarning(): void { - this.toastService.showToast( - 'Your session will expire in less than 5 minutes. Please log in again.', - ); + this.toastService.showToast('Your session will expire in less than 5 minutes. Please log in again.'); } public startTokenExpiryCheck(): void { From 9cdf9babfe3375e7899ed4717f703ceca65d87c8 Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Wed, 13 Nov 2024 22:03:36 +0800 Subject: [PATCH 6/6] Fix takeUntilDestroyed https://stackoverflow.com/questions/76264067/takeuntildestroyed-can-only-be-used-within-an-injection-context --- frontend/src/_services/authentication.service.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/_services/authentication.service.ts b/frontend/src/_services/authentication.service.ts index 524cb76116..8458fac1c7 100644 --- a/frontend/src/_services/authentication.service.ts +++ b/frontend/src/_services/authentication.service.ts @@ -1,5 +1,5 @@ // Modified from https://jasonwatmore.com/post/2022/11/15/angular-14-jwt-authentication-example-tutorial#login-component-ts -import { Injectable } from '@angular/core'; +import { DestroyRef, inject, Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable, timer } from 'rxjs'; @@ -14,6 +14,8 @@ import { ToastService } from './toast.service'; export class AuthenticationService extends ApiService { protected apiPath = 'user'; + private destroyRef = inject(DestroyRef); + private userSubject: BehaviorSubject; public user$: Observable; @@ -140,14 +142,14 @@ export class AuthenticationService extends ApiService { const timeLeft = tokenExpirationTime - Date.now(); if (timeLeft > 5 * oneMinute) { timer(timeLeft - 5 * oneMinute) - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => this.displaySessionExpiryWarning()); } else { this.displaySessionExpiryWarning(); } timer(timeLeft) - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { alert('Your session has expired. Please log in again.'); this.logout();