Commit 02550204 authored by Ryan Diehl's avatar Ryan Diehl

fix(security): add event listener class interface

parent 62205ce1
Pipeline #109668 passed with stages
in 3 minutes and 23 seconds
export * from './lib/providers';
export * from './lib/security-event.listener';
export * from './lib/security-ngrx.module';
export * from './lib/security.actions';
export * from './lib/security.facade';
......
import { Provider, Type } from '@angular/core';
import { SecurityEventListener } from './security-event.listener';
export function provideSecurityEventListener(impl: Type<SecurityEventListener>): Provider {
return { provide: SecurityEventListener, useExisting: impl };
}
import { User } from '@psu/utils/security';
import { Observable } from 'rxjs';
/**
* This is a "class interface" for a security event service.
*
* This allows our authentication implementation (oauth2-server, azure, hydra, etc) to be
* decoupled from the internal logic in this library.
*/
export abstract class SecurityEventListener {
/**
* An observable of type User that should emit whenever a user has succesfully logged in.
*/
readonly loginSuccessful$: Observable<User>;
/**
* An observable of type string that should emit whenever a user has failed to log in.
*/
readonly loginFailure$: Observable<string>;
}
import { createAction, props } from '@ngrx/store';
import { User } from './security.model';
import { User } from '@psu/utils/security';
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`);
export const loginFailure = createAction(`${PREFIX} Login Failure`, props<{ message?: string }>());
import { TestBed } from '@angular/core/testing';
import { Store } from '@ngrx/store';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
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 { SecurityFacade } from './security.facade';
import { initialSecurityState, SecurityPartialState, securitySliceName } from './security.reducer';
describe('SecurityFacade', () => {
let facade: SecurityFacade;
let store: MockStore<SecurityPartialState>;
let listener: Mock<SecurityEventListener>;
let loginSuccess$: Subject<User>;
let loginFailed$: Subject<string>;
beforeEach(() => {
loginSuccess$ = new Subject();
loginFailed$ = new Subject();
listener = new Mock<SecurityEventListener>({
loginSuccessful$: loginSuccess$.asObservable(),
loginFailure$: loginFailed$.asObservable()
});
TestBed.configureTestingModule({
providers: [
SecurityFacade,
provideMockStore({ initialState: { [securitySliceName]: initialSecurityState } }),
{ provide: SecurityEventListener, useValue: listener.Object }
]
});
facade = TestBed.get(SecurityFacade);
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
});
it('should create', () => {
expect(facade).toBeDefined();
});
it('login should dispatch login action', () => {
facade.login('/app');
expect(store.dispatch).toHaveBeenCalledWith(login({ targetUrl: '/app' }));
});
it('listener loginSuccessful should dispatch loginSuccessful action', () => {
const user = {
userName: 'me',
other: 'stuff',
identityClaims: {}
};
loginSuccess$.next(user);
expect(store.dispatch).toHaveBeenCalledWith(loginSuccessful({ user }));
});
it('listener loginFailure should dispatch loginFailure action', () => {
loginFailed$.next('no');
expect(store.dispatch).toHaveBeenCalledWith(loginFailure({ message: 'no' }));
loginFailed$.next(undefined);
  • Remove this redundant "undefined". 📘

Please register or sign in to reply
expect(store.dispatch).toHaveBeenCalledWith(loginFailure({}));
});
});
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 { User } from './security.model';
import { SecurityPartialState } from './security.reducer';
import {
selectSecurityCurrentUserName,
......@@ -15,7 +16,10 @@ export class SecurityFacade {
public readonly isLoggingIn$ = this.store.pipe(select(selectSecurityLoginPending));
public readonly userName$ = this.store.pipe(select(selectSecurityCurrentUserName));
constructor(private store: Store<SecurityPartialState>) {}
constructor(private store: Store<SecurityPartialState>, private listener: SecurityEventListener) {
this.listener.loginFailure$.subscribe(message => this.loginFailure(message));
this.listener.loginSuccessful$.subscribe(user => this.loginSuccessful(user));
}
public login(targetUrl: string): void {
this.store.dispatch(login({ targetUrl }));
......@@ -25,7 +29,7 @@ export class SecurityFacade {
this.store.dispatch(loginSuccessful({ user }));
}
public loginFailure(): void {
this.store.dispatch(loginFailure());
public loginFailure(message?: string): void {
this.store.dispatch(loginFailure({ message }));
}
}
......@@ -22,7 +22,7 @@ describe('securityReducer', () => {
});
it('loginFailure should clear loggingIn', () => {
expect(securityReducer({ ...initialSecurityState, loggingIn: true }, loginFailure())).toEqual({
expect(securityReducer({ ...initialSecurityState, loggingIn: true }, loginFailure({}))).toEqual({
...initialSecurityState,
loggingIn: false
});
......
import { Action, createReducer, on } from '@ngrx/store';
import { User } from '@psu/utils/security';
import { login, loginFailure, loginSuccessful } from './security.actions';
import { User } from './security.model';
export interface SecurityState {
user: User;
......
export { AlreadyLoggedInGuard } from './lib/already-logged-in.guard';
export { AuthInterceptor } from './lib/auth.interceptor';
export { User } from './lib/auth.model';
export { AuthService } from './lib/auth.service';
export { HTTP_AUTHORIZATION_HEADER, REQUIRE_AUTH_HEADER } from './lib/constants';
export { provideAuthService, provideTokenService } from './lib/providers';
......
  • SonarQube analysis indicates that quality gate is failed.

    • Bugs is failed: Actual value 1 > 0
    • high_severity_vulns is passed: Actual value 0
    • medium_severity_vulns is passed: Actual value 0

    SonarQube analysis reported 18 issues

    • 6 major
    • 🔽 7 minor
    • 5 info

    Watch the comments in this conversation to review them.

    Top 10 extra issues

    Note: The following issues were found on lines that were not modified in the commit. Because these issues can't be reported as line comments, they are summarized here:

    1. Unexpected empty source 📘
    2. Remove this useless assignment to variable "result". 📘
    3. Remove this useless assignment to variable "complete". 📘
    4. Remove this useless assignment to variable "loadResult". 📘
    5. Remove this useless assignment to variable "loadComplete". 📘
    6. 🔽 Add an "alt" attribute to this image. 📘
    7. 🔽 This assertion is unnecessary since it does not change the type of the expression. 📘
    8. 🔽 This assertion is unnecessary since it does not change the type of the expression. 📘
    9. 🔽 This assertion is unnecessary since it does not change the type of the expression. 📘
    10. 🔽 This assertion is unnecessary since it does not change the type of the expression. 📘
    • ... 7 more
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment