Skip to content

Commit

Permalink
refactor(stark-core): refactor logic to validate StarkSessionConfig t…
Browse files Browse the repository at this point in the history
…o prevent Angular AOT error "Function calls are not supported in decorators" in StarkSessionModule.forRoot()

ISSUES CLOSED: #797
  • Loading branch information
christophercr committed Oct 25, 2018
1 parent ccade91 commit ea8c18c
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<Store<StarkCoreApplicationState>>;
let appConfig: StarkApplicationConfig;
Expand All @@ -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
Expand Down Expand Up @@ -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[] = [<any>undefined, -1];

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, string> = sessionService.devAuthenticationHeaders;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>(Keepalive);
}
Expand Down Expand Up @@ -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);

Expand Down
33 changes: 1 addition & 32 deletions packages/stark-core/src/modules/session/session.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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 } : []
]
};
}
Expand Down

0 comments on commit ea8c18c

Please sign in to comment.