git.psu.edu will be upgraded to critical security release 13.7.4 Monday, 11/18 between 9 and 10pm. Please email support@git.psu.edu if you have trouble with anything Gitlab-related. Please see the git.psu.edu Yammer group for more information.

Commit abfbc7c4 authored by Ryan Diehl's avatar Ryan Diehl

feat(security): add ngrx state for security

parent 97e369ef
Pipeline #109525 passed with stages
in 3 minutes and 43 seconds
......@@ -476,6 +476,34 @@
"styleext": "scss"
}
}
},
"utils-security-ngrx": {
"projectType": "library",
"root": "libs/utils/security-ngrx",
"sourceRoot": "libs/utils/security-ngrx/src",
"prefix": "ut",
"architect": {
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["libs/utils/security-ngrx/tsconfig.lib.json", "libs/utils/security-ngrx/tsconfig.spec.json"],
"exclude": ["**/node_modules/**", "!libs/utils/security-ngrx/**"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/utils/security-ngrx/jest.config.js",
"tsConfig": "libs/utils/security-ngrx/tsconfig.spec.json",
"setupFile": "libs/utils/security-ngrx/src/test-setup.ts"
}
}
},
"schematics": {
"@nrwl/angular:component": {
"styleext": "scss"
}
}
}
},
"cli": {
......
# utils-security-ngrx
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test utils-security-ngrx` to execute the unit tests.
module.exports = {
name: 'utils-security-ngrx',
preset: '../../../jest.config.js',
coverageDirectory: '../../../coverage/libs/utils/security-ngrx',
snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js'
]
};
{
"lib": {
"entryFile": "src/index.ts"
}
}
export * from './lib/security-ngrx.module';
export * from './lib/security.facade';
import { async, TestBed } from '@angular/core/testing';
import { SecurityNgrxModule } from './security-ngrx.module';
describe('SecurityNgrxModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [SecurityNgrxModule]
}).compileComponents();
}));
it('should create', () => {
expect(SecurityNgrxModule).toBeDefined();
});
});
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { SecurityEffects } from './security.effects';
import { securityReducer, securitySliceName } from './security.reducer';
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature(securitySliceName, securityReducer),
EffectsModule.forFeature([SecurityEffects])
]
})
export class SecurityNgrxModule {}
import { createAction, props } from '@ngrx/store';
import { User } from './security.model';
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`);
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
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 { SecurityEffects } from './security.effects';
describe('SecurityEffects', () => {
let effects: SecurityEffects;
let actions$: ReplaySubject<Action>;
let authService: Mock<AuthService>;
beforeEach(() => {
actions$ = new ReplaySubject(1);
authService = new Mock<AuthService>({
login: () => {}
});
TestBed.configureTestingModule({
providers: [SecurityEffects, provideMockActions(actions$), { provide: AuthService, useValue: authService.Object }]
});
effects = TestBed.get(SecurityEffects);
});
it('should create', () => {
expect(effects).toBeDefined();
});
it('login action should call auth service login method', () => {
let result: Action;
effects.login$.subscribe(r => (result = r));
actions$.next(login({ targetUrl: '/authed' }));
expect(result).toEqual({ type: 'NO_ACTION' });
expect(authService.Object.login).toHaveBeenCalledWith('/authed');
});
});
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';
@Injectable({ providedIn: 'root' })
export class SecurityEffects {
public login$ = createEffect(
() =>
this.actions$.pipe(
ofType(login),
map(action => action.targetUrl),
tap(targetUrl => this.authService.login(targetUrl)),
map(() => ({ type: 'NO_ACTION' }))
),
{ dispatch: false }
);
constructor(private actions$: Actions, private authService: AuthService) {}
}
import { Injectable } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { login, loginFailure, loginSuccessful } from './security.actions';
import { User } from './security.model';
import { SecurityPartialState } from './security.reducer';
import {
selectSecurityCurrentUserName,
selectSecurityIsLoggedIn,
selectSecurityLoginPending
} from './security.selectors';
@Injectable({ providedIn: 'root' })
export class SecurityFacade {
public readonly isLoggedIn$ = this.store.pipe(select(selectSecurityIsLoggedIn));
public readonly isLoggingIn$ = this.store.pipe(select(selectSecurityLoginPending));
public readonly userName$ = this.store.pipe(select(selectSecurityCurrentUserName));
constructor(private store: Store<SecurityPartialState>) {}
public login(targetUrl: string): void {
this.store.dispatch(login({ targetUrl }));
}
public loginSuccessful(user: User): void {
this.store.dispatch(loginSuccessful({ user }));
}
public loginFailure(): void {
this.store.dispatch(loginFailure());
}
}
export interface User {
userName: string;
}
import { login, loginFailure, loginSuccessful } from './security.actions';
import { initialSecurityState, securityReducer } from './security.reducer';
describe('securityReducer', () => {
it('login should set loggingIn to true', () => {
expect(securityReducer(initialSecurityState, login({ targetUrl: 'any' }))).toEqual({
...initialSecurityState,
loggingIn: true
});
});
it('loginSuccessful should set user and loggingIn to false', () => {
const user = {
userName: 'me',
other: 'stuff'
};
expect(securityReducer({ ...initialSecurityState, loggingIn: true }, loginSuccessful({ user }))).toEqual({
...initialSecurityState,
loggingIn: false,
user
});
});
it('loginFailure should clear loggingIn', () => {
expect(securityReducer({ ...initialSecurityState, loggingIn: true }, loginFailure())).toEqual({
...initialSecurityState,
loggingIn: false
});
});
});
import { Action, createReducer, on } from '@ngrx/store';
import { login, loginFailure, loginSuccessful } from './security.actions';
import { User } from './security.model';
export interface SecurityState {
user: User;
loggingIn: boolean;
}
export const securitySliceName = 'utils-security';
export interface SecurityPartialState {
[securitySliceName]: SecurityState;
}
export const initialSecurityState: SecurityState = {
user: undefined,
loggingIn: false
};
const reducer = createReducer(
initialSecurityState,
on(login, state => ({ ...state, loggingIn: true })),
on(loginSuccessful, (state, { user }) => ({
...state,
loggingIn: false,
user
})),
on(loginFailure, state => ({ ...state, loggingIn: false }))
);
export function securityReducer(state: SecurityState | undefined, action: Action): SecurityState {
return reducer(state, action);
}
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { securitySliceName, SecurityState } from './security.reducer';
const securityFeatureSelector = createFeatureSelector<SecurityState>(securitySliceName);
export const selectSecurityIsLoggedIn = createSelector(
securityFeatureSelector,
s => !!s.user
);
export const selectSecurityLoginPending = createSelector(
securityFeatureSelector,
s => s.loggingIn
);
export const selectSecurityCurrentUser = createSelector(
securityFeatureSelector,
s => s.user
);
export const selectSecurityCurrentUserName = createSelector(
selectSecurityCurrentUser,
s => (s ? s.userName : undefined)
);
import 'jest-preset-angular';
import '../../../../jestGlobalMocks';
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"types": ["node", "jest"]
},
"include": ["**/*.ts"]
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": ["src/test-setup.ts", "**/*.spec.ts"]
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [true, "attribute", "ut", "camelCase"],
"component-selector": [true, "element", "ut", "kebab-case"]
}
}
......@@ -55,6 +55,9 @@
},
"utils-analytics": {
"tags": []
},
"utils-security-ngrx": {
"tags": []
}
}
}
......@@ -29,7 +29,8 @@
"@psu/utils/ngrx": ["libs/utils/ngrx/src/index.ts"],
"@psu/utils/cdn": ["libs/utils/cdn/src/index.ts"],
"@psu/utils/form": ["libs/utils/form/src/index.ts"],
"@psu/utils/analytics": ["libs/utils/analytics/src/index.ts"]
"@psu/utils/analytics": ["libs/utils/analytics/src/index.ts"],
"@psu/utils/security-ngrx": ["libs/utils/security-ngrx/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
......
  • 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 17 issues

    • 5 major
    • 🔽 7 minor
    • 5 info

    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