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 c2e8aff..3014b28 100644 Binary files a/frontend/src/assets/images/favicon.ico and b/frontend/src/assets/images/favicon.ico differ diff --git a/frontend/src/favicon.ico b/frontend/src/favicon.ico index c2e8aff..3014b28 100644 Binary files a/frontend/src/favicon.ico and b/frontend/src/favicon.ico differ