From eba5e10c4f9f0323f56b7bbb7c708f8575413be5 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Tue, 18 Jun 2024 15:34:16 +0100 Subject: [PATCH] HXCS-3521 emit token_received upon user login (#9710) * Add unit tests for redirect auth service * add unit test for authentication service --- .../auth-bearer.interceptor.spec.ts | 2 +- lib/core/src/lib/auth/oidc/auth.service.ts | 2 + .../auth/oidc/redirect-auth.service.spec.ts | 69 +++++++++++++++++++ .../lib/auth/oidc/redirect-auth.service.ts | 9 ++- .../services/authentication.service.spec.ts | 27 ++++++++ .../auth/services/authentication.service.ts | 5 ++ 6 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts diff --git a/lib/core/src/lib/auth/authentication-interceptor/auth-bearer.interceptor.spec.ts b/lib/core/src/lib/auth/authentication-interceptor/auth-bearer.interceptor.spec.ts index 041b2afe6c..4e9cedcb5e 100644 --- a/lib/core/src/lib/auth/authentication-interceptor/auth-bearer.interceptor.spec.ts +++ b/lib/core/src/lib/auth/authentication-interceptor/auth-bearer.interceptor.spec.ts @@ -42,7 +42,7 @@ describe('AuthBearerInterceptor', () => { HttpHandler, AuthBearerInterceptor, AuthenticationService, - { provide: RedirectAuthService, useValue: { onLogin: EMPTY } } + { provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: of() } } ] }); diff --git a/lib/core/src/lib/auth/oidc/auth.service.ts b/lib/core/src/lib/auth/oidc/auth.service.ts index 504616086b..4d44aa2d37 100644 --- a/lib/core/src/lib/auth/oidc/auth.service.ts +++ b/lib/core/src/lib/auth/oidc/auth.service.ts @@ -24,6 +24,8 @@ import { Observable } from 'rxjs'; export abstract class AuthService { abstract onLogin: Observable; + abstract onTokenReceived: Observable; + /** Subscribe to whether the user has valid Id/Access tokens. */ abstract authenticated$: Observable; diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts new file mode 100644 index 0000000000..7b439a536a --- /dev/null +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.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 { TestBed } from '@angular/core/testing'; +import { OAuthService, OAuthEvent, OAuthStorage, AUTH_CONFIG } from 'angular-oauth2-oidc'; +import { Subject } from 'rxjs'; +import { RedirectAuthService } from './redirect-auth.service'; +import { AUTH_MODULE_CONFIG } from './auth-config'; + +describe('RedirectAuthService', () => { + let service: RedirectAuthService; + const mockOAuthStorage: Partial = { + getItem: jasmine.createSpy('getItem'), + removeItem: jasmine.createSpy('removeItem'), + setItem: jasmine.createSpy('setItem') + }; + const oauthEvents$ = new Subject(); + const mockOauthService: Partial = { + clearHashAfterLogin: false, + events: oauthEvents$ + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + RedirectAuthService, + { provide: OAuthService, useValue: mockOauthService }, + { provide: OAuthStorage, useValue: mockOAuthStorage }, + { provide: AUTH_CONFIG, useValue: {} }, + { provide: AUTH_MODULE_CONFIG, useValue: {} } + ] + }); + + service = TestBed.inject(RedirectAuthService); + TestBed.inject(OAuthService); + }); + + it('should emit event when token_received event is received', () => { + const onTokenReceivedSpy = jasmine.createSpy(); + service.onTokenReceived.subscribe(onTokenReceivedSpy); + + oauthEvents$.next({ type: 'token_received' } as OAuthEvent); + + expect(onTokenReceivedSpy).toHaveBeenCalled(); + }); + + it('should not emit event when a different event is received', () => { + const onTokenReceivedSpy = jasmine.createSpy(); + service.onTokenReceived.subscribe(onTokenReceivedSpy); + + oauthEvents$.next({ type: 'user_profile_loaded' } as OAuthEvent); + + expect(onTokenReceivedSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts index 2ff37d2f8d..0efd0d127a 100644 --- a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts @@ -16,7 +16,7 @@ */ import { Inject, Injectable, inject } from '@angular/core'; -import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthService, OAuthStorage, TokenResponse, LoginOptions } from 'angular-oauth2-oidc'; +import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthEvent, OAuthService, OAuthStorage, TokenResponse, LoginOptions } from 'angular-oauth2-oidc'; import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks'; import { from, Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators'; @@ -32,6 +32,8 @@ export class RedirectAuthService extends AuthService { onLogin: Observable; + onTokenReceived: Observable; + private _loadDiscoveryDocumentPromise = Promise.resolve(false); /** Subscribe to whether the user has valid Id/Access tokens. */ @@ -72,6 +74,11 @@ export class RedirectAuthService extends AuthService { map(() => undefined) ); + this.onTokenReceived = this.oauthService.events.pipe( + filter((event: OAuthEvent) => event.type === 'token_received'), + map(() => undefined) + ); + this.idpUnreachable$ = this.oauthService.events.pipe( filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'), map((event) => event.reason as Error) diff --git a/lib/core/src/lib/auth/services/authentication.service.spec.ts b/lib/core/src/lib/auth/services/authentication.service.spec.ts index b1c88e94ee..5032f98cac 100644 --- a/lib/core/src/lib/auth/services/authentication.service.spec.ts +++ b/lib/core/src/lib/auth/services/authentication.service.spec.ts @@ -23,6 +23,10 @@ import { setupTestBed } from '../../testing/setup-test-bed'; import { CoreTestingModule } from '../../testing/core.testing.module'; import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service'; import { OidcAuthenticationService } from '../oidc/oidc-authentication.service'; +import { OAuthEvent } from 'angular-oauth2-oidc'; +import { Subject } from 'rxjs'; +import { RedirectAuthService } from '../oidc/redirect-auth.service'; +import { Injector } from '@angular/core'; declare let jasmine: any; @@ -507,4 +511,27 @@ describe('AuthenticationService', () => { expect(username).toEqual('john.petrucci'); }); }); + + describe('onTokenReceived', () => { + let redirectAuthService: RedirectAuthService; + let authenticationService: AuthenticationService; + const onTokenReceived$: Subject = new Subject(); + + beforeEach(() => { + redirectAuthService = TestBed.inject(RedirectAuthService); + redirectAuthService.onTokenReceived = onTokenReceived$; + + const injector = TestBed.inject(Injector); + authenticationService = new AuthenticationService(injector, redirectAuthService); + }); + + it('should emit event when RedirectAuthService onTokenReceived emits', () => { + const onTokenReceivedSpy = jasmine.createSpy(); + authenticationService.onTokenReceived.subscribe(onTokenReceivedSpy); + + onTokenReceived$.next(); + + expect(onTokenReceivedSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/lib/core/src/lib/auth/services/authentication.service.ts b/lib/core/src/lib/auth/services/authentication.service.ts index fb247f1429..3a37cd24d6 100644 --- a/lib/core/src/lib/auth/services/authentication.service.ts +++ b/lib/core/src/lib/auth/services/authentication.service.ts @@ -30,10 +30,15 @@ import { RedirectAuthService } from '../oidc/redirect-auth.service'; export class AuthenticationService implements AuthenticationServiceInterface, ee.Emitter { onLogin: Subject = new Subject(); onLogout: Subject = new Subject(); + onTokenReceived: Subject = new Subject(); constructor(private injector: Injector, private redirectAuthService: RedirectAuthService) { this.redirectAuthService.onLogin.subscribe((value) => this.onLogin.next(value)); + this.redirectAuthService.onTokenReceived.subscribe( + (value) => this.onTokenReceived.next(value) + ); + this.basicAlfrescoAuthService.onLogin.subscribe((value) => this.onLogin.next(value)); if (this.isOauth()) {