From c31a3839525c8c58570ff81d50fc95f686b58e1e Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 20 Aug 2019 13:23:08 +0200 Subject: [PATCH] feat(admin-ui): Support bearer token auth method Relates to #138 --- .../local-storage/local-storage.service.ts | 2 +- admin-ui/src/app/data/data.module.ts | 14 ++++++++++- .../src/app/data/providers/interceptor.ts | 24 ++++++++++++++++++- packages/common/src/shared-types.ts | 2 ++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/admin-ui/src/app/core/providers/local-storage/local-storage.service.ts b/admin-ui/src/app/core/providers/local-storage/local-storage.service.ts index 38dcd05a21..6459fde5d2 100644 --- a/admin-ui/src/app/core/providers/local-storage/local-storage.service.ts +++ b/admin-ui/src/app/core/providers/local-storage/local-storage.service.ts @@ -1,7 +1,7 @@ import { Location } from '@angular/common'; import { Injectable } from '@angular/core'; -export type LocalStorageKey = 'activeChannelToken'; +export type LocalStorageKey = 'activeChannelToken' | 'authToken'; export type LocalStorageLocationBasedKey = 'shippingTestOrder' | 'shippingTestAddress'; const PREFIX = 'vnd_'; diff --git a/admin-ui/src/app/data/data.module.ts b/admin-ui/src/app/data/data.module.ts index e1ebb81445..408a124770 100644 --- a/admin-ui/src/app/data/data.module.ts +++ b/admin-ui/src/app/data/data.module.ts @@ -25,7 +25,7 @@ export function createApollo( localStorageService: LocalStorageService, fetchAdapter: FetchAdapter, ): ApolloClientOptions { - const { apiHost, apiPort, adminApiPath } = getAppConfig(); + const { apiHost, apiPort, adminApiPath, tokenMethod } = getAppConfig(); const host = apiHost === 'auto' ? `${location.protocol}//${location.hostname}` : apiHost; const port = apiPort === 'auto' ? (location.port === '' ? '' : `:${location.port}`) : `:${apiPort}`; const apolloCache = new InMemoryCache({ @@ -54,6 +54,18 @@ export function createApollo( }; } }), + setContext(() => { + if (tokenMethod === 'bearer') { + const authToken = localStorageService.get('authToken'); + if (authToken) { + return { + headers: { + authorization: `Bearer ${authToken}`, + }, + }; + } + } + }), createUploadLink({ uri: `${host}${port}/${adminApiPath}`, fetch: fetchAdapter.fetch, diff --git a/admin-ui/src/app/data/providers/interceptor.ts b/admin-ui/src/app/data/providers/interceptor.ts index ba847767fe..745cb4f17f 100644 --- a/admin-ui/src/app/data/providers/interceptor.ts +++ b/admin-ui/src/app/data/providers/interceptor.ts @@ -10,6 +10,8 @@ import { Injectable, Injector } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; +import { DEFAULT_AUTH_TOKEN_HEADER_KEY } from 'shared/shared-constants'; +import { AdminUiConfig } from 'shared/shared-types'; import { getAppConfig } from '../../app.config'; import { AuthService } from '../../core/providers/auth/auth.service'; @@ -27,13 +29,19 @@ export const AUTH_REDIRECT_PARAM = 'redirectTo'; */ @Injectable() export class DefaultInterceptor implements HttpInterceptor { + private readonly tokenMethod: AdminUiConfig['tokenMethod'] = 'cookie'; + private readonly authTokenHeaderKey: string; + constructor( private dataService: DataService, private injector: Injector, private authService: AuthService, private router: Router, private localStorageService: LocalStorageService, - ) {} + ) { + this.tokenMethod = getAppConfig().tokenMethod; + this.authTokenHeaderKey = getAppConfig().authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY; + } intercept(req: HttpRequest, next: HttpHandler): Observable> { this.dataService.client.startRequest().subscribe(); @@ -41,6 +49,7 @@ export class DefaultInterceptor implements HttpInterceptor { tap( event => { if (event instanceof HttpResponse) { + this.checkForAuthToken(event); this.notifyOnError(event); this.dataService.client.completeRequest().subscribe(); } @@ -105,4 +114,17 @@ export class DefaultInterceptor implements HttpInterceptor { const notificationService = this.injector.get(NotificationService); notificationService.error(message, vars); } + + /** + * If the server is configured to use the "bearer" tokenMethod, each response should be checked + * for the existence of an auth token. + */ + private checkForAuthToken(response: HttpResponse) { + if (this.tokenMethod === 'bearer') { + const authToken = response.headers.get(this.authTokenHeaderKey); + if (authToken) { + this.localStorageService.set('authToken', authToken); + } + } + } } diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index 437b646732..324cee52f3 100644 --- a/packages/common/src/shared-types.ts +++ b/packages/common/src/shared-types.ts @@ -72,4 +72,6 @@ export interface AdminUiConfig { apiHost: string | 'auto'; apiPort: number | 'auto'; adminApiPath: string; + tokenMethod: 'cookie' | 'bearer'; + authTokenHeaderKey: string; }