From 0b86bc4d7d196ad0d161dcea17b27e7a7291a2d0 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Fri, 14 Aug 2020 22:09:07 +0100 Subject: [PATCH] Remove Regex support from the HTTP interceptor (#29) * Remove regex support from Http Interceptor * Reinstate tests that were no longer covered * Remove redundant test * Trim the route value before processing --- projects/auth0-angular/src/lib/auth.config.ts | 16 ++++--- .../src/lib/auth.interceptor.spec.ts | 44 +++++++++---------- .../auth0-angular/src/lib/auth.interceptor.ts | 33 ++++++++++---- 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/projects/auth0-angular/src/lib/auth.config.ts b/projects/auth0-angular/src/lib/auth.config.ts index 527d97a2..d7772835 100644 --- a/projects/auth0-angular/src/lib/auth.config.ts +++ b/projects/auth0-angular/src/lib/auth.config.ts @@ -6,9 +6,8 @@ import { InjectionToken } from '@angular/core'; * * - an object of type HttpInterceptorConfig * - a string - * - a regular expression */ -export type ApiRouteDefinition = HttpInterceptorRouteConfig | string | RegExp; +export type ApiRouteDefinition = HttpInterceptorRouteConfig | string; /** * A custom type guard to help identify route definitions that are actually HttpInterceptorRouteConfig types. @@ -32,14 +31,21 @@ export interface HttpInterceptorConfig { */ export interface HttpInterceptorRouteConfig { /** - * The URL to test, either by using a regex or by supplying the whole URL to match. - * If `test` is a match for the current request URL from the HTTP client, then + * The URL to test, by supplying the URL to match. + * If `test` is a match for the current request path from the HTTP client, then * an access token is attached to the request in the * ["Authorization" header](https://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-20#section-2.1). * * If the test does not pass, the request proceeds without the access token attached. + * + * A wildcard character can be used to match only the start of the URL. + * + * @usagenotes + * + * '/api' - exactly match the route /api + * '/api/*' - match any route that starts with /api/ */ - uri: string | RegExp; + uri: string; /** * The options that are passed to the SDK when retrieving the diff --git a/projects/auth0-angular/src/lib/auth.interceptor.spec.ts b/projects/auth0-angular/src/lib/auth.interceptor.spec.ts index 6d0264d5..fce82535 100644 --- a/projects/auth0-angular/src/lib/auth.interceptor.spec.ts +++ b/projects/auth0-angular/src/lib/auth.interceptor.spec.ts @@ -44,9 +44,8 @@ describe('The Auth HTTP Interceptor', () => { '', '/api/photos', '/api/people*', - /^\/basic-api-regex/, + 'https://my-api.com/orders', { uri: '/api/orders' }, - { uri: /^\/regex-api/ }, { uri: '/api/addresses', tokenOptions: { @@ -107,39 +106,33 @@ describe('The Auth HTTP Interceptor', () => { assertAuthorizedApiCallTo('/api/photos', done); })); - it('attach the access token when the configuration uri is a regex', fakeAsync(( - done - ) => { - assertAuthorizedApiCallTo('/basic-api-regex?value=123', done); - })); - it('attach the access token when the configuration uri is a string with a wildcard', fakeAsync(( done ) => { // Testing /api/people* (wildcard match) assertAuthorizedApiCallTo('/api/people/profile', done); })); - }); - describe('Requests that are configured using a complex object', () => { - it('attach the access token when the uri is configured using a string', fakeAsync(( - done - ) => { - // Testing { uri: /api/addresses } (exact match) - assertAuthorizedApiCallTo('/api/addresses', done); + it('matches a full url to an API', fakeAsync((done) => { + // Testing 'https://my-api.com/orders' (exact) + assertAuthorizedApiCallTo('https://my-api.com/orders', done); })); - it('attach the access token when the configuration uri is a string with a wildcard', fakeAsync(( - done - ) => { - // Testing { uri: /api/calendar* } (wildcard match) - assertAuthorizedApiCallTo('/api/calendar/events', done); + it('matches a URL that contains a query string', fakeAsync((done) => { + assertAuthorizedApiCallTo('/api/people?name=test', done); })); - it('attach the access token when the uri is configured using a regex', fakeAsync(( + it('matches a URL that contains a hash fragment', fakeAsync((done) => { + assertAuthorizedApiCallTo('/api/people#hash-fragment', done); + })); + }); + + describe('Requests that are configured using a complex object', () => { + it('attach the access token when the uri is configured using a string', fakeAsync(( done ) => { - assertAuthorizedApiCallTo('/regex-api?my-param=42', done); + // Testing { uri: /api/orders } (exact match) + assertAuthorizedApiCallTo('/api/orders', done); })); it('pass through the route options to getTokenSilently, without additional properties', fakeAsync(( @@ -153,5 +146,12 @@ describe('The Auth HTTP Interceptor', () => { scope: 'scope', }); })); + + it('attach the access token when the configuration uri is a string with a wildcard', fakeAsync(( + done + ) => { + // Testing { uri: /api/calendar* } (wildcard match) + assertAuthorizedApiCallTo('/api/calendar/events', done); + })); }); }); diff --git a/projects/auth0-angular/src/lib/auth.interceptor.ts b/projects/auth0-angular/src/lib/auth.interceptor.ts index 005635bb..e90a0e8f 100644 --- a/projects/auth0-angular/src/lib/auth.interceptor.ts +++ b/projects/auth0-angular/src/lib/auth.interceptor.ts @@ -62,6 +62,22 @@ export class AuthHttpInterceptor implements HttpInterceptor { ); } + /** + * Strips the query and fragment from the given uri + * @param uri The uri to remove the query and fragment from + */ + private stripQueryFrom(uri: string): string { + if (uri.indexOf('?') > -1) { + uri = uri.substr(0, uri.indexOf('?')); + } + + if (uri.indexOf('#') > -1) { + uri = uri.substr(0, uri.indexOf('#')); + } + + return uri; + } + /** * Determines whether the specified route can have an access token attached to it, based on matching the HTTP request against * the interceptor route configuration. @@ -72,34 +88,35 @@ export class AuthHttpInterceptor implements HttpInterceptor { route: ApiRouteDefinition, request: HttpRequest ): boolean { - const testPrimitive = (value: string | RegExp) => { + const testPrimitive = (value: string) => { + if (value) { + value.trim(); + } + if (!value) { return false; } - if (value === request.url) { + const requestPath = this.stripQueryFrom(request.url); + + if (value === requestPath) { return true; } // If the URL ends with an asterisk, match using startsWith. if ( - typeof value === 'string' && value.indexOf('*') === value.length - 1 && request.url.startsWith(value.substr(0, value.length - 1)) ) { return true; } - - if (value instanceof RegExp && value.test(request.url)) { - return true; - } }; if (isHttpInterceptorRouteConfig(route)) { return testPrimitive(route.uri); } - return testPrimitive(route as string | RegExp); + return testPrimitive(route); } /**