From ea8c18c4dacf4e0e150620fb2278a68740818179 Mon Sep 17 00:00:00 2001 From: christophercr Date: Thu, 25 Oct 2018 15:47:49 +0200 Subject: [PATCH] refactor(stark-core): refactor logic to validate StarkSessionConfig to prevent Angular AOT error "Function calls are not supported in decorators" in StarkSessionModule.forRoot() ISSUES CLOSED: #797 --- .../session/services/session.service.spec.ts | 71 +++++++++++++++++-- .../session/services/session.service.ts | 48 +++++++++++++ .../src/modules/session/session.module.ts | 33 +-------- 3 files changed, 114 insertions(+), 38 deletions(-) diff --git a/packages/stark-core/src/modules/session/services/session.service.spec.ts b/packages/stark-core/src/modules/session/services/session.service.spec.ts index c3c5360f97..692e577627 100644 --- a/packages/stark-core/src/modules/session/services/session.service.spec.ts +++ b/packages/stark-core/src/modules/session/services/session.service.spec.ts @@ -1,4 +1,4 @@ -/*tslint:disable:completed-docs no-undefined-argument*/ +/*tslint:disable:completed-docs no-big-function*/ import { HttpHeaders, HttpRequest } from "@angular/common/http"; import { EventEmitter, Injector } from "@angular/core"; import { DEFAULT_INTERRUPTSOURCES, Idle, InterruptSource } from "@ng-idle/core"; @@ -36,9 +36,8 @@ import { MockStarkRoutingService } from "../../routing/testing"; import { StarkCoreApplicationState } from "../../../common/store"; import Spy = jasmine.Spy; import SpyObj = jasmine.SpyObj; -import { starkSessionExpiredStateName } from "../routes"; +import { starkAppExitStateName, starkAppInitStateName, starkSessionExpiredStateName } from "../routes"; -// tslint:disable-next-line:no-big-function describe("Service: StarkSessionService", () => { let mockStore: SpyObj>; let appConfig: StarkApplicationConfig; @@ -64,7 +63,7 @@ describe("Service: StarkSessionService", () => { roles: ["a role", "another role", "yet another role"] }; const mockSessionConfig: StarkSessionConfig = { - sessionExpiredStateName: "mock-session-expired-state-name" + sessionExpiredStateName: starkAppExitStateName + ".mock-session-expired-state-name" }; // Inject module dependencies @@ -142,6 +141,67 @@ describe("Service: StarkSessionService", () => { } }); + it("should throw an error in case the session config object contains invalid initial states", () => { + const invalidSessionConfigValues: StarkSessionConfig[] = [ + { loginStateName: "someLoginState" }, + { loginStateName: "" }, + { loginStateName: starkAppInitStateName }, + { loginStateName: starkAppInitStateName + "." }, + { loginStateName: starkAppExitStateName + ".someLoginState" }, + { preloadingStateName: "somePreloadingState" }, + { preloadingStateName: "" }, + { preloadingStateName: starkAppInitStateName }, + { preloadingStateName: starkAppInitStateName + "." }, + { preloadingStateName: starkAppExitStateName + ".somePreloadingState" } + ]; + + for (const invalidSessionConfig of invalidSessionConfigValues) { + expect(() => { + return new SessionServiceHelper( + mockStore, + mockLogger, + mockRoutingService, + appConfig, + mockIdleService, + mockInjectorService, + mockTranslateService, + invalidSessionConfig + ); + }).toThrowError(/invalid StarkSessionConfig(.*)initial state/); + } + }); + + it("should throw an error in case the session config object contains invalid exit states", () => { + const invalidSessionConfigValues: StarkSessionConfig[] = [ + { sessionExpiredStateName: "someSessionExpiredState" }, + { sessionExpiredStateName: "" }, + { sessionExpiredStateName: starkAppExitStateName }, + { sessionExpiredStateName: starkAppExitStateName + "." }, + { sessionExpiredStateName: starkAppInitStateName + ".someSessionExpiredState" }, + { sessionLogoutStateName: "someSessionExpiredState" }, + { sessionLogoutStateName: "" }, + { sessionLogoutStateName: starkAppExitStateName }, + { sessionLogoutStateName: starkAppExitStateName + "." }, + { sessionLogoutStateName: starkAppInitStateName + ".someSessionExpiredState" } + ]; + + for (const invalidSessionConfig of invalidSessionConfigValues) { + expect( + () => + new SessionServiceHelper( + mockStore, + mockLogger, + mockRoutingService, + appConfig, + mockIdleService, + mockInjectorService, + mockTranslateService, + invalidSessionConfig + ) + ).toThrowError(/invalid StarkSessionConfig(.*)exit state/); + } + }); + it("should throw an error in case the warning period in the app config is invalid", () => { const invalidSessionTimeoutWarningPeriodValues: number[] = [undefined, -1]; @@ -374,8 +434,6 @@ describe("Service: StarkSessionService", () => { }); }); - // FIXME rewrite those tests to reduce function - /* tslint:disable-next-line:no-big-function */ describe("configureIdleService", () => { it("should set the necessary options of the idle service", () => { const interruptsToBeSet: InterruptSource[] = DEFAULT_INTERRUPTSOURCES; @@ -865,6 +923,7 @@ describe("Service: StarkSessionService", () => { }); it("should return an empty map if the pre-authentication headers were not constructed", () => { + /* tslint:disable-next-line:no-undefined-argument */ sessionService.setInternalDevAuthenticationHeaders(undefined); const devAuthenticationHeaders: Map = sessionService.devAuthenticationHeaders; diff --git a/packages/stark-core/src/modules/session/services/session.service.ts b/packages/stark-core/src/modules/session/services/session.service.ts index 0a3e657c46..d7fef2f242 100644 --- a/packages/stark-core/src/modules/session/services/session.service.ts +++ b/packages/stark-core/src/modules/session/services/session.service.ts @@ -73,6 +73,11 @@ export class StarkSessionServiceImpl implements StarkSessionService { // ensuring that the app config is valid before doing anything StarkConfigurationUtil.validateConfig(this.appConfig, ["session"], starkSessionServiceName); + // ensuring that the session config is valid before doing anything + if (this.sessionConfig) { + this.validateSessionConfig(this.sessionConfig); + } + if (this.idle.getKeepaliveEnabled() && !this.appConfig.keepAliveDisabled) { this.keepalive = injector.get(Keepalive); } @@ -102,6 +107,49 @@ export class StarkSessionServiceImpl implements StarkSessionService { this.logger.debug(starkSessionServiceName + " loaded"); } + /** + * Validates the StarkSessionConfig provided. + * @param customConfig - Custom configuration object passed via the StarkSessionModule.forRoot() method + * @throws In case the configuration object passed via the StarkSessionModule.forRoot() method is not valid + */ + protected validateSessionConfig(customConfig: StarkSessionConfig): void { + const invalidConfigErrorPrefix: string = starkSessionServiceName + ": invalid StarkSessionConfig object. "; + const invalidConfigErrorAppInitSuffix: string = + " should have the prefix '" + starkAppInitStateName + ".' in order to be configured correctly as an application initial state"; + const invalidConfigErrorAppExitSuffix: string = + " should have the prefix '" + starkAppExitStateName + ".' in order to be configured correctly as an application exit state"; + + // validate config to ensure that the init/exit states have the correct StarkAppInit or StarkAppExit parent + if ( + typeof customConfig.loginStateName !== "undefined" && + (!customConfig.loginStateName.startsWith(starkAppInitStateName) || + customConfig.loginStateName.replace(starkAppInitStateName, "").length < 2) + ) { + throw new Error(invalidConfigErrorPrefix + "'loginStateName' value" + invalidConfigErrorAppInitSuffix); + } + if ( + typeof customConfig.preloadingStateName !== "undefined" && + (!customConfig.preloadingStateName.startsWith(starkAppInitStateName) || + customConfig.preloadingStateName.replace(starkAppInitStateName, "").length < 2) + ) { + throw new Error(invalidConfigErrorPrefix + "'preloadingStateName' value" + invalidConfigErrorAppInitSuffix); + } + if ( + typeof customConfig.sessionExpiredStateName !== "undefined" && + (!customConfig.sessionExpiredStateName.startsWith(starkAppExitStateName) || + customConfig.sessionExpiredStateName.replace(starkAppExitStateName, "").length < 2) + ) { + throw new Error(invalidConfigErrorPrefix + "'sessionExpiredStateName' value" + invalidConfigErrorAppExitSuffix); + } + if ( + typeof customConfig.sessionLogoutStateName !== "undefined" && + (!customConfig.sessionLogoutStateName.startsWith(starkAppExitStateName) || + customConfig.sessionLogoutStateName.replace(starkAppExitStateName, "").length < 2) + ) { + throw new Error(invalidConfigErrorPrefix + "'sessionLogoutStateName' value" + invalidConfigErrorAppExitSuffix); + } + } + protected registerTransitionHook(): void { this.routingService.addKnownNavigationRejectionCause(starkUnauthenticatedUserError); diff --git a/packages/stark-core/src/modules/session/session.module.ts b/packages/stark-core/src/modules/session/session.module.ts index 3c46a96d04..d56e4d6388 100644 --- a/packages/stark-core/src/modules/session/session.module.ts +++ b/packages/stark-core/src/modules/session/session.module.ts @@ -4,37 +4,6 @@ import { starkSessionReducers } from "./reducers"; import { StarkSessionConfig, STARK_SESSION_CONFIG } from "./entities"; import { STARK_SESSION_SERVICE, StarkSessionServiceImpl } from "./services"; import { StarkUserModule } from "../user/user.module"; -import { starkAppExitStateName, starkAppInitStateName } from "./routes"; - -/** - * Validates and creates the StarkSessionConfig to be provided for the Stark Session Service - * @param customConfig - Custom configuration object passed via the StarkSessionModule.forRoot() method - * @returns The StarkSessionConfig to be provided for the Stark Session Service. - * @throws In case the configuration object passed via the StarkSessionModule.forRoot() method is not valid - */ -export function starkSessionConfigFactory(customConfig: StarkSessionConfig): StarkSessionConfig { - const invalidConfigErrorPrefix: string = "StarkSessionModule: invalid StarkSessionConfig object. "; - const invalidConfigErrorAppInitSuffix: string = - " should have the prefix '" + starkAppInitStateName + ".' in order to be configured correctly as an application initial state"; - const invalidConfigErrorAppExitSuffix: string = - " should have the prefix '" + starkAppExitStateName + ".' in order to be configured correctly as an application exit state"; - - // validate config to ensure that the init/exit states have the correct StarkAppInit or StarkAppExit parent - if (customConfig.loginStateName && !customConfig.loginStateName.startsWith(starkAppInitStateName)) { - throw new Error(invalidConfigErrorPrefix + "'loginStateName' value" + invalidConfigErrorAppInitSuffix); - } - if (customConfig.preloadingStateName && !customConfig.preloadingStateName.startsWith(starkAppInitStateName)) { - throw new Error(invalidConfigErrorPrefix + "'preloadingStateName' value" + invalidConfigErrorAppInitSuffix); - } - if (customConfig.sessionExpiredStateName && !customConfig.sessionExpiredStateName.startsWith(starkAppExitStateName)) { - throw new Error(invalidConfigErrorPrefix + "'sessionExpiredStateName' value" + invalidConfigErrorAppExitSuffix); - } - if (customConfig.sessionLogoutStateName && !customConfig.sessionLogoutStateName.startsWith(starkAppExitStateName)) { - throw new Error(invalidConfigErrorPrefix + "'sessionLogoutStateName' value" + invalidConfigErrorAppExitSuffix); - } - - return customConfig; -} @NgModule({ imports: [StoreModule.forFeature("StarkSession", starkSessionReducers), StarkUserModule] @@ -52,7 +21,7 @@ export class StarkSessionModule { ngModule: StarkSessionModule, providers: [ { provide: STARK_SESSION_SERVICE, useClass: StarkSessionServiceImpl }, - sessionConfig ? { provide: STARK_SESSION_CONFIG, useValue: starkSessionConfigFactory(sessionConfig) } : [] + sessionConfig ? { provide: STARK_SESSION_CONFIG, useValue: sessionConfig } : [] ] }; }