diff --git a/libs/utils/security-ngrx/src/lib/security.actions.ts b/libs/utils/security-ngrx/src/lib/security.actions.ts index 5d183bc85466c45ea7f230699278b928c35c192e..11da4bf2b5d92439098807c8cf7353c50ddce106 100644 --- a/libs/utils/security-ngrx/src/lib/security.actions.ts +++ b/libs/utils/security-ngrx/src/lib/security.actions.ts @@ -6,3 +6,4 @@ const PREFIX = '@psu/utils/security'; export const login = createAction(`${PREFIX} Login`, props<{ targetUrl: string }>()); export const loginSuccessful = createAction(`${PREFIX} Login Successful`, props<{ user: User }>()); export const loginFailure = createAction(`${PREFIX} Login Failure`, props<{ message?: string }>()); +export const logout = createAction(`${PREFIX} Logout`); diff --git a/libs/utils/security-ngrx/src/lib/security.effects.spec.ts b/libs/utils/security-ngrx/src/lib/security.effects.spec.ts index 70b9958c9efd591f99c5a8ee278f2925640b9e58..42912ff0f165fbfd4473dc3f1e063011b6ab8fc5 100644 --- a/libs/utils/security-ngrx/src/lib/security.effects.spec.ts +++ b/libs/utils/security-ngrx/src/lib/security.effects.spec.ts @@ -4,7 +4,7 @@ import { Action } from '@ngrx/store'; import { AuthService } from '@psu/utils/security'; import { ReplaySubject } from 'rxjs'; import { Mock } from 'ts-mocks'; -import { login } from './security.actions'; +import { login, logout } from './security.actions'; import { SecurityEffects } from './security.effects'; describe('SecurityEffects', () => { @@ -15,7 +15,8 @@ describe('SecurityEffects', () => { beforeEach(() => { actions$ = new ReplaySubject(1); authService = new Mock({ - login: () => {} + login: () => {}, + logout: () => {} }); TestBed.configureTestingModule({ providers: [SecurityEffects, provideMockActions(actions$), { provide: AuthService, useValue: authService.Object }] @@ -35,4 +36,12 @@ describe('SecurityEffects', () => { expect(result).toEqual({ type: 'NO_ACTION' }); expect(authService.Object.login).toHaveBeenCalledWith('/authed'); }); + + it('logout action should call auth service logout method', () => { + let result: Action; + effects.logout$.subscribe(r => (result = r)); + actions$.next(logout()); + expect(result).toEqual({ type: 'NO_ACTION' }); + expect(authService.Object.logout).toHaveBeenCalled(); + }); }); diff --git a/libs/utils/security-ngrx/src/lib/security.effects.ts b/libs/utils/security-ngrx/src/lib/security.effects.ts index f6f40fabf96c77097411a36d55ac335d34d885a6..ca2ce4e797b84fe44b87da57d762740fdaa38a31 100644 --- a/libs/utils/security-ngrx/src/lib/security.effects.ts +++ b/libs/utils/security-ngrx/src/lib/security.effects.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { AuthService } from '@psu/utils/security'; import { map, tap } from 'rxjs/operators'; -import { login } from './security.actions'; +import { login, logout } from './security.actions'; @Injectable({ providedIn: 'root' }) export class SecurityEffects { @@ -17,5 +17,13 @@ export class SecurityEffects { { dispatch: false } ); + public logout$ = createEffect(() => + this.actions$.pipe( + ofType(logout), + tap(() => this.authService.logout()), + map(() => ({ type: 'NO_ACTION' })) + ) + ); + constructor(private actions$: Actions, private authService: AuthService) {} } diff --git a/libs/utils/security-ngrx/src/lib/security.facade.spec.ts b/libs/utils/security-ngrx/src/lib/security.facade.spec.ts index a722804ec8d2bce2d3978e898ba4783dd4ae587a..eec08870b221790ee54bb4323e692c58444ae32c 100644 --- a/libs/utils/security-ngrx/src/lib/security.facade.spec.ts +++ b/libs/utils/security-ngrx/src/lib/security.facade.spec.ts @@ -5,7 +5,7 @@ import { User } from '@psu/utils/security'; import { Subject } from 'rxjs'; import { Mock } from 'ts-mocks'; import { SecurityEventListener } from './security-event.listener'; -import { login, loginFailure, loginSuccessful } from './security.actions'; +import { login, loginFailure, loginSuccessful, logout } from './security.actions'; import { SecurityFacade } from './security.facade'; import { initialSecurityState, SecurityPartialState, securitySliceName } from './security.reducer'; @@ -61,4 +61,9 @@ describe('SecurityFacade', () => { loginFailed$.next(); expect(store.dispatch).toHaveBeenCalledWith(loginFailure({})); }); + + it('logout should dispatch logout action', () => { + facade.logout(); + expect(store.dispatch).toHaveBeenCalledWith(logout()); + }); }); diff --git a/libs/utils/security-ngrx/src/lib/security.facade.ts b/libs/utils/security-ngrx/src/lib/security.facade.ts index 03d8973c832f40a3be0970bbffd8bb93b62efc34..820ca02f16fd682cc2645b7246c60fe132dfd070 100644 --- a/libs/utils/security-ngrx/src/lib/security.facade.ts +++ b/libs/utils/security-ngrx/src/lib/security.facade.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { User } from '@psu/utils/security'; import { SecurityEventListener } from './security-event.listener'; -import { login, loginFailure, loginSuccessful } from './security.actions'; +import { login, loginFailure, loginSuccessful, logout } from './security.actions'; import { SecurityPartialState } from './security.reducer'; import { selectSecurityCurrentUserName, @@ -32,4 +32,8 @@ export class SecurityFacade { public loginFailure(message?: string): void { this.store.dispatch(loginFailure({ message })); } + + public logout(): void { + this.store.dispatch(logout()); + } } diff --git a/libs/utils/security/src/lib/auth.service.spec.ts b/libs/utils/security/src/lib/auth.service.spec.ts deleted file mode 100644 index 2f21e27947cf43e05c4c71ae9bdd25ccbc8897b1..0000000000000000000000000000000000000000 --- a/libs/utils/security/src/lib/auth.service.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable } from '@angular/core'; -import { AuthService } from './auth.service'; - -@Injectable() -class MockAuthService extends AuthService { - getUserName = () => 'mock'; -} - -@Injectable() -class MockAnonymousService extends AuthService { - getUserName = () => undefined; -} - -describe('AuthService', () => { - let service: MockAuthService; - let anonymousService: MockAnonymousService; - - beforeEach(() => { - service = new MockAuthService(); - anonymousService = new MockAnonymousService(); - }); - - it('should create', () => { - expect(service).toBeDefined(); - expect(anonymousService).toBeDefined(); - }); - - it('should return mock username', () => { - expect(service.getUserName()).toEqual('mock'); - expect(anonymousService.getUserName()).toBeUndefined(); - }); - - it('isLoggedIn should be true', () => { - expect(service.isLoggedIn()).toEqual(true); - }); - - it('anonymous service isLoggedIn should be false', () => { - expect(anonymousService.isLoggedIn()).toEqual(false); - }); -}); diff --git a/libs/utils/security/src/lib/auth.service.ts b/libs/utils/security/src/lib/auth.service.ts index a3c6450ff3c45d3504ded9b79f1d5024cf330de9..5f57b64ca4d1094584978966ad9f61a346530d2f 100644 --- a/libs/utils/security/src/lib/auth.service.ts +++ b/libs/utils/security/src/lib/auth.service.ts @@ -17,10 +17,13 @@ export abstract class AuthService { */ login: (targetUrl: string) => any; + /** + * Trigger a logout request to the auth provider. + */ + logout: () => any; + /** * Determine if there is a logged in user. */ - public isLoggedIn(): boolean { - return !!this.getUserName(); - } + isLoggedIn: () => boolean; }