From 882ffe70940dd6da54b703bab96a6c0241327f38 Mon Sep 17 00:00:00 2001 From: Florian THIERRY Date: Tue, 15 Oct 2024 16:11:46 +0200 Subject: [PATCH] Several changes about token refreshing. --- .../src/main/resources/application.yml | 0 frontend/src/app/app.config.ts | 9 ++- .../app/core/guard/authentication.guard.ts | 4 +- .../app/core/interceptor/jwt.interceptor.ts | 23 ++------ .../core/service/authentication.service.ts | 55 +++++++++++++++++- frontend/src/assets/images/favicon.ico | Bin 16958 -> 16958 bytes frontend/src/favicon.ico | Bin 16958 -> 16958 bytes 7 files changed, 69 insertions(+), 22 deletions(-) delete mode 100644 backend/codiki-exposition/src/main/resources/application.yml diff --git a/backend/codiki-exposition/src/main/resources/application.yml b/backend/codiki-exposition/src/main/resources/application.yml deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 58985bc..e6336f1 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,10 +1,11 @@ -import { ApplicationConfig } from '@angular/core'; +import { APP_INITIALIZER, ApplicationConfig } from '@angular/core'; import { provideRouter, withRouterConfig } from '@angular/router'; import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { routes } from './app.routes'; import { JwtInterceptor } from './core/interceptor/jwt.interceptor'; +import { AuthenticationService } from './core/service/authentication.service'; export const appConfig: ApplicationConfig = { providers: [ @@ -18,5 +19,11 @@ export const appConfig: ApplicationConfig = { provideAnimationsAsync(), provideHttpClient(withInterceptorsFromDi()), { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, + { + provide: APP_INITIALIZER, + useFactory: (authenticationService: AuthenticationService) => () => authenticationService.startAuthenticationCheckingProcess(), + deps: [AuthenticationService], + multi: true + } ] }; diff --git a/frontend/src/app/core/guard/authentication.guard.ts b/frontend/src/app/core/guard/authentication.guard.ts index d94aec9..2e653dd 100644 --- a/frontend/src/app/core/guard/authentication.guard.ts +++ b/frontend/src/app/core/guard/authentication.guard.ts @@ -3,11 +3,13 @@ import { CanActivateFn, Router } from "@angular/router"; import { AuthenticationService } from "../service/authentication.service"; import { MatSnackBar } from "@angular/material/snack-bar"; -export const authenticationGuard: CanActivateFn = () => { +export const authenticationGuard: CanActivateFn = async () => { const authenticationService = inject(AuthenticationService); const router = inject(Router); const snackBar = inject(MatSnackBar); + await authenticationService.checkIsAuthenticated(); + if (authenticationService.isAuthenticated()) { return true; } else { diff --git a/frontend/src/app/core/interceptor/jwt.interceptor.ts b/frontend/src/app/core/interceptor/jwt.interceptor.ts index 52b9f84..f4096d0 100644 --- a/frontend/src/app/core/interceptor/jwt.interceptor.ts +++ b/frontend/src/app/core/interceptor/jwt.interceptor.ts @@ -41,23 +41,12 @@ export class JwtInterceptor implements HttpInterceptor { this.isRefreshingToken = true; this.refreshTokenSubject.next(undefined); - const refreshToken = this.authenticationService.getRefreshToken(); - if (refreshToken) { - const refreshTokenRequest: RefreshTokenRequest = { - refreshTokenValue: refreshToken - }; - this.userRestService.refreshToken(refreshTokenRequest) - .then(refreshTokenResponse => { - this.authenticationService.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken); - this.refreshTokenSubject.next(refreshTokenResponse.accessToken); - }) - .catch(() => { - return this.handleNoRefreshToken(initialError); - }) - .finally(() => this.isRefreshingToken = false); - } else { - return this.handleNoRefreshToken(initialError); - } + this.authenticationService.refreshToken() + .then(refreshTokenResponse => { + this.refreshTokenSubject.next(refreshTokenResponse.accessToken); + }) + .catch(() => this.handleNoRefreshToken(initialError)) + .finally(() => this.isRefreshingToken = false); } return this.refreshTokenSubject.pipe( diff --git a/frontend/src/app/core/service/authentication.service.ts b/frontend/src/app/core/service/authentication.service.ts index d562abc..c4048a8 100644 --- a/frontend/src/app/core/service/authentication.service.ts +++ b/frontend/src/app/core/service/authentication.service.ts @@ -1,7 +1,9 @@ import { inject, Injectable } from "@angular/core"; +import { BehaviorSubject, interval } from "rxjs"; import { User } from "../model/User"; import { UserRestService } from "../rest-services/user/user.rest-service"; import { RefreshTokenRequest } from "../rest-services/user/model/refresh-token.model"; +import { LoginResponse } from "../rest-services/user/model/login.model"; const JWT_PARAM = 'jwt'; const REFRESH_TOKEN_PARAM = 'refresh-token'; @@ -18,18 +20,30 @@ interface UserDetails { providedIn: 'root' }) export class AuthenticationService { + private readonly AUTHENTICATION_CHECKING_PERIOD = 5 * 60 * 1000; + private isAuthenticatedSubject = new BehaviorSubject(false); + private readonly userRestService = inject(UserRestService); + + startAuthenticationCheckingProcess(): void { + this.checkIsAuthenticated(); + interval(this.AUTHENTICATION_CHECKING_PERIOD) + .subscribe(() => this.checkIsAuthenticated()); + } authenticate(token: string, refreshToken: string): void { localStorage.setItem(JWT_PARAM, token); localStorage.setItem(REFRESH_TOKEN_PARAM, refreshToken); + this.isAuthenticatedSubject.next(true); } unauthenticate(): void { localStorage.removeItem(JWT_PARAM); + localStorage.removeItem(REFRESH_TOKEN_PARAM); + this.isAuthenticatedSubject.next(false); } isAuthenticated(): boolean { - return !!localStorage.getItem(JWT_PARAM); + return this.isAuthenticatedSubject.value; } getAuthenticatedUser(): User | undefined { @@ -45,7 +59,7 @@ export class AuthenticationService { } isTokenExpired(): boolean { - let result = false; + let result = true; const userDetails = this.extractUserDetails(); @@ -59,8 +73,43 @@ export class AuthenticationService { return result; } + refreshToken(): Promise { + const refreshToken = this.getRefreshToken(); + if (refreshToken) { + const refreshTokenRequest: RefreshTokenRequest = { + refreshTokenValue: refreshToken + }; + return this.userRestService.refreshToken(refreshTokenRequest) + .then(refreshTokenResponse => { + this.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken); + return refreshTokenResponse; + }); + } + + return Promise.reject('No any refresh token found.'); + } + + checkIsAuthenticated(): Promise { + return new Promise((resolve, reject) => { + const isTokenExpired = this.isTokenExpired(); + if (isTokenExpired) { + if (this.getRefreshToken()) { + this.refreshToken() + .then(() => resolve()) + .catch(() => reject()); + } else { + this.isAuthenticatedSubject.next(false); + } + } else { + this.isAuthenticatedSubject.next(true); + } + resolve(); + }); + + } + private extractUserFromLocalStorage(): User | undefined { - let result: User | undefined = undefined; + let result: User | undefined; const userDetails = this.extractUserDetails(); if (userDetails) { diff --git a/frontend/src/assets/images/favicon.ico b/frontend/src/assets/images/favicon.ico index c2e8affe7d9ea9baa9c51bd16e8bbe7faef9df48..3014b28ec3ca8db0f749182b901eb26203857739 100644 GIT binary patch literal 16958 zcmeHPKWkJ$5Z@5+8X+PQCC!C^5N(14loXmMsAwUkx3J7turk(G+S^!KSZN=y^eb3g z;Rm=PgoEVn+~4ZxX4yBpdpmRQ1+(%pZ+B;A=Qq1=_TQl~3-;g6j9d3F2dr+4=^zkhtP_4D(yyN6$Px5s$!jxyAN z2fUDhY&%xhwYs)+aRV5D=j-nFwB~^h=z`8VtMsKG_OHEsd+*k_8oZn=bfXP@BxNl; zU0d1v@aWDD&UVQib65`yrz2Tju1oWKh^aJRx<0^~skD;oNP&*f3`l4Jzj{1vo+)m z^PFsMoxeBK{`N!rDbLwTJWZ67&8hQa0Pb9JWM<)MvLEFseVpv^@o`d~(+|uP1GQow zXMZjJ9C;4E9|Ljs#_`TlSBo8OrqV2Skqj_X|4X0oNZ2i*gRsUZFcW^5Y}tuV{;y`#*+5$AIJw< zSYvuie*XZU{LjCCp)sNP9mM)bqpW1y6KE$p_Nd$sZU`U5*E^O=`>&=8z(uba7sQO|`Ku#LDHn zv_2^PeJh8nqz-GUHpc?mE?w7^;LGL3zCvr?=*M(=M_ajjnOqDKe2BXmXDEaly0v1v z1b)(iID|7ciEjfsplhD7jc`a%oHJui0tVc<01xioFjwIY77yN0hC1+o7c$`)wS+zA zI!=3l_TrWr<1!;-`gj=_(*xbN|ABxd6Ue=`7Zjk|)9Da&$+0=c1LPMSyAwdxYII^& zkf;;43T!J#eWFgCRY-K;s!fmRfpKXur2D_i7uuqsJ(M39mwH4A7g54Zl>9<|B5HdD p>7QQ@khOjufZ7gn3T;17zc@kLPts>f7SK{p{YSzW@iZmrVcw delta 925 zcmZWnv2Gec5Z&W8KrG}#1PF>Tha3wx}Ldp_1h{Z zzddTbywU0>s_o~gTk*37z+LG!Rps16<;RYj?OY^3(5qaIK9+|wxAM5-Hg!hcfe$-S z>g;L{z<-;(>Lu4yGE*CEbJRUN*Xpq*tg8)n6cn^CwiIsc)wg#&)NzukWT37p6v-ZW zN4=eGeezyINd}kr;Q1*Z2WNUQo2FmK-*kjAR4}*!d5xrF_31<+IaH(QR(di8a0N_I z-E4A6m*l+}s2k$sBpsq@5wq*d=YZD6)N1N9RW5oL;fT zNc!YBV_7T|N_ZqF?NjsJ^r=Zs)b`)fXKVQX$xYIu?+y3b3Wh@1D_n03UJ7C0v+$YF zENI3WPZ2CO-~~7cg~{AmXtrZvhC*bq$pE)AE5#FYt$2e1(2b3;VK&#AGb^39f~DSI z$GSi#L_sVJLNs9JhAIvOb7wJ8Y{zZInI#Tvzzd(jTqwWH>{pmE{DL#!P6)=qP*xq# SLTs(Xp0I?TK$`B~kN*PDzUC9d3F2dr+4=^zkhtP_4D(yyN6$Px5s$!jxyAN z2fUDhY&%xhwYs)+aRV5D=j-nFwB~^h=z`8VtMsKG_OHEsd+*k_8oZn=bfXP@BxNl; zU0d1v@aWDD&UVQib65`yrz2Tju1oWKh^aJRx<0^~skD;oNP&*f3`l4Jzj{1vo+)m z^PFsMoxeBK{`N!rDbLwTJWZ67&8hQa0Pb9JWM<)MvLEFseVpv^@o`d~(+|uP1GQow zXMZjJ9C;4E9|Ljs#_`TlSBo8OrqV2Skqj_X|4X0oNZ2i*gRsUZFcW^5Y}tuV{;y`#*+5$AIJw< zSYvuie*XZU{LjCCp)sNP9mM)bqpW1y6KE$p_Nd$sZU`U5*E^O=`>&=8z(uba7sQO|`Ku#LDHn zv_2^PeJh8nqz-GUHpc?mE?w7^;LGL3zCvr?=*M(=M_ajjnOqDKe2BXmXDEaly0v1v z1b)(iID|7ciEjfsplhD7jc`a%oHJui0tVc<01xioFjwIY77yN0hC1+o7c$`)wS+zA zI!=3l_TrWr<1!;-`gj=_(*xbN|ABxd6Ue=`7Zjk|)9Da&$+0=c1LPMSyAwdxYII^& zkf;;43T!J#eWFgCRY-K;s!fmRfpKXur2D_i7uuqsJ(M39mwH4A7g54Zl>9<|B5HdD p>7QQ@khOjufZ7gn3T;17zc@kLPts>f7SK{p{YSzW@iZmrVcw delta 925 zcmZWnv2Gec5Z&W8KrG}#1PF>Tha3wx}Ldp_1h{Z zzddTbywU0>s_o~gTk*37z+LG!Rps16<;RYj?OY^3(5qaIK9+|wxAM5-Hg!hcfe$-S z>g;L{z<-;(>Lu4yGE*CEbJRUN*Xpq*tg8)n6cn^CwiIsc)wg#&)NzukWT37p6v-ZW zN4=eGeezyINd}kr;Q1*Z2WNUQo2FmK-*kjAR4}*!d5xrF_31<+IaH(QR(di8a0N_I z-E4A6m*l+}s2k$sBpsq@5wq*d=YZD6)N1N9RW5oL;fT zNc!YBV_7T|N_ZqF?NjsJ^r=Zs)b`)fXKVQX$xYIu?+y3b3Wh@1D_n03UJ7C0v+$YF zENI3WPZ2CO-~~7cg~{AmXtrZvhC*bq$pE)AE5#FYt$2e1(2b3;VK&#AGb^39f~DSI z$GSi#L_sVJLNs9JhAIvOb7wJ8Y{zZInI#Tvzzd(jTqwWH>{pmE{DL#!P6)=qP*xq# SLTs(Xp0I?TK$`B~kN*PDzUC