From 05f01a5d94d24df6767bc6be81b99349511b4347 Mon Sep 17 00:00:00 2001 From: Jesse Lucas Date: Fri, 20 Mar 2020 19:44:21 -0400 Subject: [PATCH] start of caching, csrf interceptor, and device service --- src/app/app.module.ts | 4 ++- .../folder-chart/folder-chart.component.ts | 2 +- src/app/cookie.service.ts | 9 ------ src/app/db-status.service.ts | 24 +++++++-------- src/app/device.service.spec.ts | 16 ++++++++++ src/app/device.service.ts | 9 ++++++ src/app/folder.service.ts | 6 ++-- src/app/http-interceptors/csrf-intercepor.ts | 29 +++++++++++++++++++ src/app/http-interceptors/index.ts | 8 +++++ src/app/in-memory-config-data.service.ts | 4 +-- src/app/system-config.service.ts | 7 ++--- 11 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 src/app/device.service.spec.ts create mode 100644 src/app/device.service.ts create mode 100644 src/app/http-interceptors/csrf-intercepor.ts create mode 100644 src/app/http-interceptors/index.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4428ad605..8a454f47a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,7 @@ import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; import { FlexLayoutModule } from '@angular/flex-layout'; +import { httpInterceptorProviders } from './http-interceptors'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -28,6 +29,7 @@ import { InMemoryConfigDataService } from './in-memory-config-data.service'; import { deviceID } from './api-utils'; import { environment } from '../environments/environment'; import { ChartItemComponent } from './charts/chart-item/chart-item.component'; +import { CSRFInterceptor } from './http-interceptors/csrf-intercepor'; @NgModule({ declarations: [ @@ -61,7 +63,7 @@ import { ChartItemComponent } from './charts/chart-item/chart-item.component'; [] : HttpClientInMemoryWebApiModule.forRoot(InMemoryConfigDataService, { dataEncapsulation: false, delay: 200 }), ], - providers: [], + providers: [httpInterceptorProviders], bootstrap: [AppComponent] }) diff --git a/src/app/charts/folder-chart/folder-chart.component.ts b/src/app/charts/folder-chart/folder-chart.component.ts index 0b2079970..8261b1c67 100644 --- a/src/app/charts/folder-chart/folder-chart.component.ts +++ b/src/app/charts/folder-chart/folder-chart.component.ts @@ -21,7 +21,7 @@ export class FolderChartComponent implements OnInit { ngOnInit(): void { for (let state in Folder.StateType) { - console.log(state); + // console.log(state); } } diff --git a/src/app/cookie.service.ts b/src/app/cookie.service.ts index a3bb17f3a..f6d3cf467 100644 --- a/src/app/cookie.service.ts +++ b/src/app/cookie.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { deviceID } from './api-utils'; @Injectable({ providedIn: 'root' @@ -8,14 +7,6 @@ export class CookieService { constructor() { } - getCSRFHeader(): any { - const dID: String = deviceID(); - const csrfCookie = 'CSRF-Token-' + dID - const csrfHeader = {}; - csrfHeader['X-CSRF-Token-' + dID] = this.getCookie(csrfCookie); - return csrfHeader; - } - getCookie(name: string): string { let ca: Array = document.cookie.split(';'); let caLen: number = ca.length; diff --git a/src/app/db-status.service.ts b/src/app/db-status.service.ts index 11da2e7f6..4b7474b37 100644 --- a/src/app/db-status.service.ts +++ b/src/app/db-status.service.ts @@ -13,25 +13,25 @@ import Folder from './folder' providedIn: 'root' }) export class DbStatusService { - private folderStatus: Object = {}; - private headers: HttpHeaders; private dbStatusUrl = environment.production ? apiURL + 'rest/db/status' : 'api/dbStatus'; + private statuses: Map; constructor(private http: HttpClient, private cookieService: CookieService) { - this.headers = new HttpHeaders(this.cookieService.getCSRFHeader()) + this.statuses = new Map(); } getFolderStatus(id: string): Observable { - let httpOptions: { headers: HttpHeaders } | - { headers: HttpHeaders, params: HttpParams }; + // First check to see if we have a cached value + if (this.statuses.has(id)) { + return of(this.statuses.get(id)); + } + + let httpOptions: { params: HttpParams }; if (id) { httpOptions = { - headers: this.headers, params: new HttpParams().set('folder', id) }; - } else { - httpOptions = { headers: this.headers }; - } + } else { } return this.http .get(this.dbStatusUrl, httpOptions) @@ -41,13 +41,13 @@ export class DbStatusService { // Remove from array in developement // in-memory-web-api returns arrays if (!environment.production) { - console.log("status res!", res); const a: any = res as any; if (a.length > 0) { - return res[0]; + res = res[0]; } - return {}; } + // cache result + this.statuses.set(id, res) return res; }) ); diff --git a/src/app/device.service.spec.ts b/src/app/device.service.spec.ts new file mode 100644 index 000000000..d55f5fb7d --- /dev/null +++ b/src/app/device.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DeviceService } from './device.service'; + +describe('DeviceService', () => { + let service: DeviceService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DeviceService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/device.service.ts b/src/app/device.service.ts new file mode 100644 index 000000000..204dbb6f5 --- /dev/null +++ b/src/app/device.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DeviceService { + + constructor() { } +} diff --git a/src/app/folder.service.ts b/src/app/folder.service.ts index 3c16be317..72508c380 100644 --- a/src/app/folder.service.ts +++ b/src/app/folder.service.ts @@ -26,7 +26,7 @@ export class FolderService { startIndex = startIndex + 1; this.dbStatusService.getFolderStatus(folder.id).subscribe( status => { - folder["status"] = status; + folder.status = status; observer.next(folder); // recursively get the status of the next folder @@ -41,6 +41,7 @@ export class FolderService { */ getAll(): Observable { const folderObservable: Observable = new Observable((observer) => { + this.systemConfigService.getFolders().subscribe( folders => { this.folders = folders; @@ -53,5 +54,4 @@ export class FolderService { }); return folderObservable } -} - +} \ No newline at end of file diff --git a/src/app/http-interceptors/csrf-intercepor.ts b/src/app/http-interceptors/csrf-intercepor.ts new file mode 100644 index 000000000..a76cb0146 --- /dev/null +++ b/src/app/http-interceptors/csrf-intercepor.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { deviceID } from '../api-utils'; +import { + HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders +} from '@angular/common/http'; + +import { CookieService } from '../cookie.service'; + + +@Injectable() +export class CSRFInterceptor implements HttpInterceptor { + + constructor(private cookieService: CookieService) { } + + intercept(req: HttpRequest, next: HttpHandler) { + const dID: String = deviceID(); + const csrfCookie = 'CSRF-Token-' + dID + + // Clone the request and replace the original headers with + // cloned headers, updated with the CSRF information. + const csrfReq = req.clone({ + headers: req.headers.set('X-CSRF-Token-' + dID, + this.cookieService.getCookie(csrfCookie)) + }); + + // send cloned request with header to the next handler. + return next.handle(csrfReq); + } +} \ No newline at end of file diff --git a/src/app/http-interceptors/index.ts b/src/app/http-interceptors/index.ts new file mode 100644 index 000000000..3056fbff4 --- /dev/null +++ b/src/app/http-interceptors/index.ts @@ -0,0 +1,8 @@ +/* "Barrel" of Http Interceptors */ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { CSRFInterceptor } from './csrf-intercepor'; + +/** Http interceptor providers in outside-in order */ +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true }, +]; \ No newline at end of file diff --git a/src/app/in-memory-config-data.service.ts b/src/app/in-memory-config-data.service.ts index 29c3d58bd..eba1f726f 100644 --- a/src/app/in-memory-config-data.service.ts +++ b/src/app/in-memory-config-data.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { config } from './mock/mock-config' -import { dbStatus } from './mock/mock-db-status' +import { config } from './mocks/mock-config' +import { dbStatus } from './mocks/mock-db-status' @Injectable({ providedIn: 'root' diff --git a/src/app/system-config.service.ts b/src/app/system-config.service.ts index bf6305fd8..de5435010 100644 --- a/src/app/system-config.service.ts +++ b/src/app/system-config.service.ts @@ -20,17 +20,14 @@ export class SystemConfigService { private devicesSubject: Subject = new Subject(); private systemConfigUrl = environment.production ? apiURL + 'rest/system/config' : 'api/config'; - private httpOptions: any; private checkInterval: number = 100; - constructor(private http: HttpClient, private cookieService: CookieService) { - this.httpOptions = { headers: new HttpHeaders(this.cookieService.getCSRFHeader()) }; - } + constructor(private http: HttpClient) { } getSystemConfig(): Observable { return this.http - .get(this.systemConfigUrl, this.httpOptions) + .get(this.systemConfigUrl) .pipe( retry(apiRetry), map(res => {