diff --git a/lib/core/api/src/index.ts b/lib/core/api/src/index.ts index e5403a15a9..1dcc418e9e 100644 --- a/lib/core/api/src/index.ts +++ b/lib/core/api/src/index.ts @@ -18,3 +18,4 @@ export * from './lib/types'; export * from './lib/adf-http-client.service'; export * from './lib/interfaces'; +export * from './lib/tokens'; diff --git a/lib/core/api/src/lib/adf-http-client.service.ts b/lib/core/api/src/lib/adf-http-client.service.ts index 4dc23e5ed8..1030a21ceb 100644 --- a/lib/core/api/src/lib/adf-http-client.service.ts +++ b/lib/core/api/src/lib/adf-http-client.service.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { SHOULD_ADD_AUTH_TOKEN } from '@alfresco/adf-core/auth'; import { Emitters as JsApiEmitters, HttpClient as JsApiHttpClient } from '@alfresco/js-api'; import { HttpClient, HttpContext, HttpErrorResponse, HttpEvent, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; @@ -35,6 +34,7 @@ import { AlfrescoApiResponseError } from './alfresco-api/alfresco-api.response-e import { Constructor } from './types'; import { RequestOptions, SecurityOptions } from './interfaces'; import ee, { Emitter } from 'event-emitter'; +import { SHOULD_ADD_AUTH_TOKEN } from './tokens'; export interface Emitters { readonly eventEmitter: Emitter; diff --git a/lib/core/auth/src/index.ts b/lib/core/api/src/lib/tokens.ts similarity index 82% rename from lib/core/auth/src/index.ts rename to lib/core/api/src/lib/tokens.ts index f3aafc7924..57cdea7099 100644 --- a/lib/core/auth/src/index.ts +++ b/lib/core/api/src/lib/tokens.ts @@ -15,5 +15,6 @@ * limitations under the License. */ -export * from './authentication'; -export * from './authentication-interceptor/authentication.interceptor'; +import { HttpContextToken } from '@angular/common/http'; + +export const SHOULD_ADD_AUTH_TOKEN = new HttpContextToken(() => false); diff --git a/lib/core/auth/README.md b/lib/core/auth/README.md deleted file mode 100644 index a33492ea53..0000000000 --- a/lib/core/auth/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @alfresco/adf-core/auth - -Secondary entry point of `@alfresco/adf-core`. It can be used by importing from `@alfresco/adf-core/auth`. diff --git a/lib/core/auth/ng-package.json b/lib/core/auth/ng-package.json deleted file mode 100644 index c781f0df46..0000000000 --- a/lib/core/auth/ng-package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lib": { - "entryFile": "src/index.ts" - } -} diff --git a/lib/core/auth/src/authentication-interceptor/README.md b/lib/core/auth/src/authentication-interceptor/README.md deleted file mode 100644 index 169e3a63a9..0000000000 --- a/lib/core/auth/src/authentication-interceptor/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# AuthenticationInterceptor - -This interceptor is responsible for providing authentication to angular HttpClient requests when a context `SHOULD_ADD_AUTH_TOKEN` is set to true. -By default, the interceptor won't do anything to the intercepted request. - -## Usage - -```typescript -import { SHOULD_ADD_AUTH_TOKEN } from '@alfresco/adf-core/auth'; -import { HttpClient, HttpContext } from '@angular/common/http'; - -getSth() { - return this.httpClient.get('http://example.com', { context: new HttpContext().set(SHOULD_ADD_AUTH_TOKEN, true)}); -} - -// or - -getSth() { - const someRequest = this.httpClient.get('GET', 'http://example.com'); - someRequest.context.set(SHOULD_ADD_AUTH_TOKEN, true); - - return someRequest; -} - -``` diff --git a/lib/core/auth/src/authentication-interceptor/authentication.interceptor.spec.ts b/lib/core/auth/src/authentication-interceptor/authentication.interceptor.spec.ts deleted file mode 100644 index 58af3fe8ff..0000000000 --- a/lib/core/auth/src/authentication-interceptor/authentication.interceptor.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*! - * @license - * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { HttpHandler, HttpHeaders, HttpRequest } from '@angular/common/http'; -import { TestBed } from '@angular/core/testing'; -import { Observable, of } from 'rxjs'; -import { Authentication } from '../authentication'; -import { AuthenticationInterceptor, SHOULD_ADD_AUTH_TOKEN } from './authentication.interceptor'; - -class MockAuthentication extends Authentication { - addTokenToHeader(_: string, httpHeaders: HttpHeaders): Observable { - return of(httpHeaders); - } -} - -const mockNext: HttpHandler = { - handle: () => new Observable(subscriber => { - subscriber.complete(); - }) -}; - -const request = new HttpRequest('GET', 'http://localhost:4200'); - -describe('AuthenticationInterceptor', () => { - let interceptor: AuthenticationInterceptor; - let addTokenToHeaderSpy: jasmine.Spy; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AuthenticationInterceptor, {provide: Authentication, useClass: MockAuthentication}] - }); - interceptor = TestBed.inject(AuthenticationInterceptor); - addTokenToHeaderSpy = spyOn(interceptor['authService'], 'addTokenToHeader'); - }); - - it('should call add auth token method when SHOULD_ADD_AUTH_TOKEN context is set to true', () => { - addTokenToHeaderSpy.and.callThrough(); - request.context.set(SHOULD_ADD_AUTH_TOKEN, true); - interceptor.intercept(request, mockNext); - expect(addTokenToHeaderSpy).toHaveBeenCalled(); - }); - - it('should not call add auth token method when SHOULD_ADD_AUTH_TOKEN context is set to false', () => { - request.context.set(SHOULD_ADD_AUTH_TOKEN, false); - interceptor.intercept(request, mockNext); - expect(addTokenToHeaderSpy).not.toHaveBeenCalled(); - }); - - it('should not call add auth token method when SHOULD_ADD_AUTH_TOKEN context is not provided', () => { - interceptor.intercept(request, mockNext); - expect(addTokenToHeaderSpy).not.toHaveBeenCalled(); - }); -}); diff --git a/lib/core/auth/src/authentication-interceptor/authentication.interceptor.ts b/lib/core/auth/src/authentication-interceptor/authentication.interceptor.ts deleted file mode 100644 index 6720e02ceb..0000000000 --- a/lib/core/auth/src/authentication-interceptor/authentication.interceptor.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * @license - * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - HttpContextToken, - HttpHandler, - HttpHeaderResponse, - HttpHeaders, - HttpInterceptor, - HttpProgressEvent, - HttpRequest, - HttpResponse, - HttpSentEvent, - HttpUserEvent -} from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable, throwError as observableThrowError } from 'rxjs'; -import { catchError, mergeMap } from 'rxjs/operators'; -import { Authentication } from '../authentication'; - -export const SHOULD_ADD_AUTH_TOKEN = new HttpContextToken(() => false); - -@Injectable() -export class AuthenticationInterceptor implements HttpInterceptor { - - constructor( private authService: Authentication) { } - - intercept(req: HttpRequest, next: HttpHandler): - Observable | HttpUserEvent> { - - if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) { - return this.authService.addTokenToHeader(req.url, req.headers).pipe( - mergeMap((headersWithBearer) => { - const headerWithContentType = this.appendJsonContentType(headersWithBearer); - const kcReq = req.clone({ headers: headerWithContentType}); - return next.handle(kcReq) - .pipe( - catchError((error) => observableThrowError(error)) - ); - }) - ); - } - - return next.handle(req).pipe(catchError((error) => observableThrowError(error))); - } - - private appendJsonContentType(headers: HttpHeaders): HttpHeaders { - - // prevent adding any content type, to properly handle formData with boundary browser generated value, - // as adding any Content-Type its going to break the upload functionality - - if (headers.get('Content-Type') === 'multipart/form-data') { - return headers.delete('Content-Type'); - } - - if (!headers.get('Content-Type')) { - return headers.set('Content-Type', 'application/json;charset=UTF-8'); - } - - return headers; - } - -} diff --git a/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.spec.ts b/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.spec.ts new file mode 100644 index 0000000000..989d200259 --- /dev/null +++ b/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.spec.ts @@ -0,0 +1,69 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HttpHandler, HttpHeaders, HttpRequest } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { Observable, of } from 'rxjs'; +import { Authentication } from '../authentication'; +import { AuthenticationInterceptor } from './authentication.interceptor'; +import { SHOULD_ADD_AUTH_TOKEN } from '@alfresco/adf-core/api'; + +class MockAuthentication extends Authentication { + addTokenToHeader(_: string, httpHeaders: HttpHeaders): Observable { + return of(httpHeaders); + } +} + +const mockNext: HttpHandler = { + handle: () => + new Observable((subscriber) => { + subscriber.complete(); + }) +}; + +const request = new HttpRequest('GET', 'http://localhost:4200'); + +describe('AuthenticationInterceptor', () => { + let interceptor: AuthenticationInterceptor; + let addTokenToHeaderSpy: jasmine.Spy; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthenticationInterceptor, { provide: Authentication, useClass: MockAuthentication }] + }); + interceptor = TestBed.inject(AuthenticationInterceptor); + addTokenToHeaderSpy = spyOn(interceptor['authService'], 'addTokenToHeader'); + }); + + it('should call add auth token method when SHOULD_ADD_AUTH_TOKEN context is set to true', () => { + addTokenToHeaderSpy.and.callThrough(); + request.context.set(SHOULD_ADD_AUTH_TOKEN, true); + interceptor.intercept(request, mockNext); + expect(addTokenToHeaderSpy).toHaveBeenCalled(); + }); + + it('should not call add auth token method when SHOULD_ADD_AUTH_TOKEN context is set to false', () => { + request.context.set(SHOULD_ADD_AUTH_TOKEN, false); + interceptor.intercept(request, mockNext); + expect(addTokenToHeaderSpy).not.toHaveBeenCalled(); + }); + + it('should not call add auth token method when SHOULD_ADD_AUTH_TOKEN context is not provided', () => { + interceptor.intercept(request, mockNext); + expect(addTokenToHeaderSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.ts b/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.ts new file mode 100644 index 0000000000..7e6093ae92 --- /dev/null +++ b/lib/core/src/lib/auth/authentication-interceptor/authentication.interceptor.ts @@ -0,0 +1,70 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + HttpHandler, + HttpHeaderResponse, + HttpHeaders, + HttpInterceptor, + HttpProgressEvent, + HttpRequest, + HttpResponse, + HttpSentEvent, + HttpUserEvent +} from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, throwError as observableThrowError } from 'rxjs'; +import { catchError, mergeMap } from 'rxjs/operators'; +import { Authentication } from '../authentication'; +import { SHOULD_ADD_AUTH_TOKEN } from '@alfresco/adf-core/api'; + +@Injectable() +export class AuthenticationInterceptor implements HttpInterceptor { + constructor(private authService: Authentication) {} + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable | HttpUserEvent> { + if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) { + return this.authService.addTokenToHeader(req.url, req.headers).pipe( + mergeMap((headersWithBearer) => { + const headerWithContentType = this.appendJsonContentType(headersWithBearer); + const kcReq = req.clone({ headers: headerWithContentType }); + return next.handle(kcReq).pipe(catchError((error) => observableThrowError(error))); + }) + ); + } + + return next.handle(req).pipe(catchError((error) => observableThrowError(error))); + } + + private appendJsonContentType(headers: HttpHeaders): HttpHeaders { + // prevent adding any content type, to properly handle formData with boundary browser generated value, + // as adding any Content-Type its going to break the upload functionality + + if (headers.get('Content-Type') === 'multipart/form-data') { + return headers.delete('Content-Type'); + } + + if (!headers.get('Content-Type')) { + return headers.set('Content-Type', 'application/json;charset=UTF-8'); + } + + return headers; + } +} diff --git a/lib/core/auth/src/authentication.ts b/lib/core/src/lib/auth/authentication.ts similarity index 100% rename from lib/core/auth/src/authentication.ts rename to lib/core/src/lib/auth/authentication.ts diff --git a/lib/core/src/lib/auth/public-api.ts b/lib/core/src/lib/auth/public-api.ts index a457bd9530..82cafe4e23 100644 --- a/lib/core/src/lib/auth/public-api.ts +++ b/lib/core/src/lib/auth/public-api.ts @@ -15,8 +15,9 @@ * limitations under the License. */ +export * from './authentication'; export * from './authentication-interceptor/auth-bearer.interceptor'; - +export * from './authentication-interceptor/authentication.interceptor'; export * from './guard/auth-guard.service'; export * from './guard/auth-guard'; export * from './guard/auth-guard-ecm.service'; diff --git a/lib/core/src/lib/core.module.ts b/lib/core/src/lib/core.module.ts index fcc5acac55..137dba16e5 100644 --- a/lib/core/src/lib/core.module.ts +++ b/lib/core/src/lib/core.module.ts @@ -41,7 +41,7 @@ import { TranslationService } from './translation/translation.service'; import { TranslateLoaderService } from './translation/translate-loader.service'; import { SEARCH_TEXT_INPUT_DIRECTIVES } from './search-text/search-text-input.module'; import { AdfHttpClient } from '@alfresco/adf-core/api'; -import { AuthenticationInterceptor, Authentication } from '@alfresco/adf-core/auth'; +import { AuthenticationInterceptor, Authentication } from './auth'; import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthenticationService } from './auth/services/authentication.service'; import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; diff --git a/lib/core/tsconfig.lib.json b/lib/core/tsconfig.lib.json index 5235439297..895019b170 100644 --- a/lib/core/tsconfig.lib.json +++ b/lib/core/tsconfig.lib.json @@ -7,7 +7,6 @@ "paths": { "@alfresco/adf-extensions": ["../../../dist/libs/extensions"], "@alfresco/adf-extensions/*": ["../../../dist/libs/extensions/*"], - "@alfresco/adf-core/auth": ["../auth/src/index.ts"], "@alfresco/adf-core/shell": ["../shell/src/index.ts"], "@alfresco/adf-core/api": ["../api/src/index.ts"], "@alfresco/adf-core/feature-flags": ["../feature-flags/src/index.ts"], diff --git a/tsconfig.json b/tsconfig.json index d8ecf9dbb3..cb12c78f2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,6 @@ "@alfresco/adf-core": ["lib/core/src/public-api.ts"], "@alfresco/adf-core/*": ["lib/core/*/public-api.ts"], "@alfresco/adf-core/api": ["lib/core/api/src/index.ts"], - "@alfresco/adf-core/auth": ["lib/core/auth/src/index.ts"], "@alfresco/adf-core/breadcrumbs": ["lib/core/breadcrumbs/src/index.ts"], "@alfresco/adf-core/feature-flags": ["lib/core/feature-flags/src/index.ts"], "@alfresco/adf-core/shell": ["lib/core/shell/src/index.ts"],