diff --git a/frontend/src/app/shared/service/auth.service.ts b/frontend/src/app/shared/service/auth.service.ts index f73183c..4763c8b 100644 --- a/frontend/src/app/shared/service/auth.service.ts +++ b/frontend/src/app/shared/service/auth.service.ts @@ -1,38 +1,77 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; + +import { BehaviorSubject } from 'rxjs'; import { environment } from '../../../environments/environment'; import { LoginCredentials, Tokens } from '../types'; +import { LocalStorageService } from './local-storage.service'; +import { SessionStorageService } from './session-storage.service'; + @Injectable({ providedIn: 'root', }) export class AuthService { - private isAuthenticated: boolean = false; + private readonly path: string = '/api/auth'; private access_token: string | null = null; private refresh_token: string | null = null; + private isAuthenticated$: BehaviorSubject = + new BehaviorSubject(false); public constructor( private readonly httpClient: HttpClient, - private readonly router: Router + private readonly localStorageService: LocalStorageService, + private readonly sessionStorageService: SessionStorageService ) {} public signin(credentials: LoginCredentials): void { this.httpClient - .post(environment.api.base + '/api/auth/signin', credentials) + .post(environment.api.base + `${this.path}/signin`, credentials) .subscribe((response: Tokens) => { - this.access_token = response.access_token; - this.refresh_token = response.refresh_token; + this.handleSuccess(response); }); } public signup(credentials: LoginCredentials): void { this.httpClient - .post(environment.api.base + '/api/auth/signup', credentials) + .post(environment.api.base + `${this.path}/signup`, credentials) .subscribe((response: Tokens) => { - this.access_token = response.access_token; - this.refresh_token = response.refresh_token; + // The checked accept terms should be saved with a timestamp in the db + this.handleSuccess(response); }); } + + public signout(): void { + this.access_token = null; + this.refresh_token = null; + this.localStorageService.removeItem('access_token'); + this.sessionStorageService.removeItem('refresh_token'); + this.isAuthenticated$.next(false); + } + + public refreshToken(): void { + const headers = new HttpHeaders().set( + 'Authorization', + 'Bearer ' + this.refresh_token + ); + + this.httpClient + .post( + environment.api.base + `${this.path}/refresh`, + {}, + { headers: headers } + ) + .subscribe((response: Tokens) => { + this.handleSuccess(response); + }); + } + + private handleSuccess(tokens: Tokens): void { + this.access_token = tokens.access_token; + this.refresh_token = tokens.refresh_token; + this.localStorageService.setItem('access_token', tokens.access_token); + this.sessionStorageService.setItem('refresh_token', tokens.refresh_token); + this.isAuthenticated$.next(true); + } } diff --git a/frontend/src/app/shared/service/base-storage.service.ts b/frontend/src/app/shared/service/base-storage.service.ts new file mode 100644 index 0000000..24dcb19 --- /dev/null +++ b/frontend/src/app/shared/service/base-storage.service.ts @@ -0,0 +1,33 @@ +import { EncryptionService } from './encryption.service'; +import { SanitizationService } from './sanitization.service'; + +export abstract class BaseStorageService { + protected abstract getStorage(): Storage; + + public setItem(key: string, value: T): void { + const sanitizedValue = SanitizationService.sanitize(JSON.stringify(value)); + const encryptedValue = EncryptionService.encrypt(sanitizedValue); + + this.getStorage().setItem(key, encryptedValue); + } + + public getItem(key: string): T | null { + const encryptedValue = this.getStorage().getItem(key); + + if (encryptedValue) { + const decryptedValue = EncryptionService.decrypt(encryptedValue); + const sanitizedValue = SanitizationService.sanitize(decryptedValue); + + return JSON.parse(sanitizedValue) as T; + } + return null; + } + + public removeItem(key: string): void { + this.getStorage().removeItem(key); + } + + public clear(): void { + this.getStorage().clear(); + } +} diff --git a/frontend/src/app/shared/service/encryption.service.ts b/frontend/src/app/shared/service/encryption.service.ts new file mode 100644 index 0000000..8823749 --- /dev/null +++ b/frontend/src/app/shared/service/encryption.service.ts @@ -0,0 +1,15 @@ +import * as CryptoJS from 'crypto-js'; + +import { environment } from '../../../environments/environment'; + +export class EncryptionService { + private static readonly key: string = environment.security.encryptionKey; + + public static encrypt(data: string): string { + return CryptoJS.AES.encrypt(data, this.key).toString(); + } + + public static decrypt(data: string): string { + return CryptoJS.AES.decrypt(data, this.key).toString(CryptoJS.enc.Utf8); + } +} diff --git a/frontend/src/app/shared/service/index.ts b/frontend/src/app/shared/service/index.ts index 2a719d1..f11d653 100644 --- a/frontend/src/app/shared/service/index.ts +++ b/frontend/src/app/shared/service/index.ts @@ -1 +1,3 @@ export * from './auth.service'; +export * from './local-storage.service'; +export * from './session-storage.service'; diff --git a/frontend/src/app/shared/service/local-storage.service.ts b/frontend/src/app/shared/service/local-storage.service.ts new file mode 100644 index 0000000..34fd729 --- /dev/null +++ b/frontend/src/app/shared/service/local-storage.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; + +import { BaseStorageService } from './base-storage.service'; + +@Injectable({ + providedIn: 'root', +}) +export class LocalStorageService extends BaseStorageService { + protected getStorage(): Storage { + return localStorage; + } +} diff --git a/frontend/src/app/shared/service/sanitization.service.ts b/frontend/src/app/shared/service/sanitization.service.ts new file mode 100644 index 0000000..a787262 --- /dev/null +++ b/frontend/src/app/shared/service/sanitization.service.ts @@ -0,0 +1,7 @@ +import DOMPurify from 'dompurify'; + +export class SanitizationService { + public static sanitize(data: string): string { + return DOMPurify.sanitize(data); + } +} diff --git a/frontend/src/app/shared/service/session-storage.service.ts b/frontend/src/app/shared/service/session-storage.service.ts new file mode 100644 index 0000000..c31601b --- /dev/null +++ b/frontend/src/app/shared/service/session-storage.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; + +import { BaseStorageService } from './base-storage.service'; + +@Injectable({ + providedIn: 'root', +}) +export class SessionStorageService extends BaseStorageService { + protected getStorage(): Storage { + return sessionStorage; + } +}