diff --git a/runtime/backend/src/AppConfiguration.ts b/runtime/backend/src/AppConfiguration.ts index c11f6a3d..b1410aae 100644 --- a/runtime/backend/src/AppConfiguration.ts +++ b/runtime/backend/src/AppConfiguration.ts @@ -298,6 +298,44 @@ export class AppConfiguration { return dappConfig.dappName; } + /** + * This static helper method returns the configuration section value + * based on the provided section name. + * + * @access public + * @static + * @param {string} section The config section name e.g. `"dapp"`, `"statistics"`. + * @returns {object} + */ + public static getConfig(section: string): object { + switch (section) { + case "assets": + return assetsConfigLoader(); + case "dapp": + return dappConfigLoader(); + case "network": + return networkConfigLoader(); + case "oauth": + return oauthConfigLoader(); + case "payout": + return payoutConfigLoader(); + case "processor": + return processorConfigLoader(); + case "security": + return securityConfigLoader(); + case "statistics": + return statisticsConfigLoader(); + case "social": + return socialConfigLoader(); + case "monitoring": + return monitoringConfigLoader(); + case "transport": + return transportConfigLoader(); + default: + throw new Error(`Cannot find relevant config for section: ${section}`); + } + } + /** * This static helper method returns all the configuration loaders. This * method *does not* interpret the content of configuration objects. diff --git a/runtime/backend/src/common/services/AccountsService.ts b/runtime/backend/src/common/services/AccountsService.ts index 618880c4..a0d730f0 100644 --- a/runtime/backend/src/common/services/AccountsService.ts +++ b/runtime/backend/src/common/services/AccountsService.ts @@ -24,8 +24,8 @@ import { } from "../models/AccountSchema"; // configuration resources -import dappConfigLoader from "../../../config/dapp"; -import networkConfigLoader from "../../../config/network"; +import { DappConfig, NetworkConfig } from "../models"; +import { AppConfiguration } from "../../AppConfiguration"; /** * @class AccountsService @@ -218,7 +218,9 @@ export class AccountsService { */ public static createAddress(publicKeyOrAddress: string): Address { // extracts the network type from configuration - const { networkIdentifier } = networkConfigLoader().network; + const { networkIdentifier } = ( + AppConfiguration.getConfig("network") as NetworkConfig + ).network; const networkType = networkIdentifier as NetworkType; // if we have a public key (64 characters in hexadecimal format) @@ -238,7 +240,9 @@ export class AccountsService { // source input is **not** a valid address, return fallback if (sourceAddress.length !== 39) { - return AccountsService.createAddress(dappConfigLoader().dappPublicKey); + return AccountsService.createAddress( + (AppConfiguration.getConfig("dapp") as DappConfig).dappPublicKey, + ); } // source input **is a valid address format** diff --git a/runtime/backend/src/common/services/AuthService.ts b/runtime/backend/src/common/services/AuthService.ts index e788f542..b37e568f 100644 --- a/runtime/backend/src/common/services/AuthService.ts +++ b/runtime/backend/src/common/services/AuthService.ts @@ -30,7 +30,6 @@ import { NetworkService } from "./NetworkService"; import { LogService } from "./LogService"; import { AccountsService } from "./AccountsService"; import { ChallengesService } from "./ChallengesService"; -import { AppConfiguration } from "../../AppConfiguration"; import { AccountDocument, AccountQuery } from "../models/AccountSchema"; import { AccessTokenDTO } from "../models/AccessTokenDTO"; import { @@ -42,11 +41,10 @@ import { AccountSessionQuery, } from "../models/AccountSessionSchema"; import { AccountSessionsService } from "./AccountSessionsService"; +import { AccessTokenRequest } from "../requests/AccessTokenRequest"; // configuration resources -import dappConfigLoader from "../../../config/dapp"; -import { AccessTokenRequest } from "../requests/AccessTokenRequest"; -const conf = dappConfigLoader(); +import { AppConfiguration } from "../../AppConfiguration"; /** * @interface CookiePayload @@ -172,7 +170,7 @@ export class AuthService { */ public static extractToken( request: Request, - cookieName: string = conf.dappName, + cookieName: string = AppConfiguration.dappName, ): string | null { // private local helper function to extract // a key from an object only if it is defined @@ -274,7 +272,7 @@ export class AuthService { */ public async getAccount( request: Request, - cookieName: string = conf.dappName, + cookieName: string = AppConfiguration.dappName, ): Promise { // read and decode access token const token: string = AuthService.extractToken(request, cookieName); diff --git a/runtime/backend/src/common/services/LogService.ts b/runtime/backend/src/common/services/LogService.ts index e6870073..444bff33 100644 --- a/runtime/backend/src/common/services/LogService.ts +++ b/runtime/backend/src/common/services/LogService.ts @@ -8,13 +8,7 @@ * @license LGPL-3.0 */ // external dependencies -import { - Inject, - Injectable, - LoggerService, - LogLevel, - Optional, -} from "@nestjs/common"; +import { Injectable, LoggerService, LogLevel, Optional } from "@nestjs/common"; import { utilities as nestWinstonModuleUtilities, WinstonModule, @@ -26,15 +20,13 @@ import "winston-daily-rotate-file"; import { EventEmitter2 } from "@nestjs/event-emitter"; // internal dependencies -import { AppConfiguration } from "../../AppConfiguration"; import { StorageOptions } from "../models/StorageOptions"; import { DappConfig } from "../models/DappConfig"; import { MonitoringConfig } from "../models/MonitoringConfig"; import { AlertEvent } from "../events/AlertEvent"; // configuration resources -import dappConfigLoader from "../../../config/dapp"; -import monitoringConfigLoader from "../../../config/monitoring"; +import { AppConfiguration } from "../../AppConfiguration"; /** * @class LogService @@ -83,16 +75,6 @@ export class LogService implements LoggerService { */ private module: string; - /** - * An instance of {@link DappConfig} that will be used - * to get necessary dapp information for this class. - * - * @access private - * @readonly - * @var {DappConfig} - */ - private readonly dappConfig: DappConfig; - /** * An instance of {@link DappConfig} that will be used * to get necessary monitoring information for this class. @@ -115,13 +97,13 @@ export class LogService implements LoggerService { @Optional() protected context?: string, @Optional() protected eventEmitter?: EventEmitter2, ) { - // @todo should use AppConfiguration - this.dappConfig = dappConfigLoader(); - this.monitoringConfig = monitoringConfigLoader(); + this.monitoringConfig = AppConfiguration.getConfig( + "monitoring", + ) as MonitoringConfig; // set context to avoid emptiness if (!this.context || !this.context.length) { - this.context = this.dappConfig.dappName; + this.context = AppConfiguration.dappName; } // create a winston logger instance @@ -321,8 +303,8 @@ export class LogService implements LoggerService { const transports: winston.transport[] = []; // uses volume/global /logs folder - const logFile = `${this.dappConfig.dappName}.log`; - const errFile = `${this.dappConfig.dappName}-error.log`; + const logFile = `${AppConfiguration.dappName}.log`; + const errFile = `${AppConfiguration.dappName}-error.log`; const logPath = this.monitoringConfig.logDirectoryPath; // get storage options from config diff --git a/runtime/backend/src/common/traits/AuthStrategy.ts b/runtime/backend/src/common/traits/AuthStrategy.ts index e8649991..a4dfa155 100644 --- a/runtime/backend/src/common/traits/AuthStrategy.ts +++ b/runtime/backend/src/common/traits/AuthStrategy.ts @@ -14,20 +14,16 @@ import { ExtractJwt, Strategy } from "passport-jwt"; import { Request } from "express"; // internal dependencies -import { AccountDocument, AccountQuery } from "../models/AccountSchema"; -import { AccountsService } from "../services/AccountsService"; import { AuthenticationPayload } from "../services/AuthService"; - -// configuration resources -import dappConfigLoader from "../../../config/dapp"; -import securityConfigLoader from "../../../config/security"; import { AccountSessionDocument, AccountSessionQuery, } from "../models/AccountSessionSchema"; import { AccountSessionsService } from "../services/AccountSessionsService"; -const conf = dappConfigLoader(); -const auth = securityConfigLoader().auth; + +// configuration resources +import { AppConfiguration } from "../../AppConfiguration"; +import { SecurityConfig } from "../models"; /** * @class AuthStrategy @@ -51,11 +47,13 @@ export class AuthStrategy extends PassportStrategy(Strategy) { jwtFromRequest: ExtractJwt.fromExtractors([ // do we have a refresh token in the request's *signed* // httpOnly cookies? (name: "dappName:Refresh") - (request: Request) => request?.signedCookies[conf.dappName] ?? null, + (request: Request) => + request?.signedCookies[AppConfiguration.dappName] ?? null, // do we have a refresh token in the request's *unsigned* // httpOnly cookies? (name: "dappName:Refresh") - (request: Request) => request?.cookies[conf.dappName] ?? null, + (request: Request) => + request?.cookies[AppConfiguration.dappName] ?? null, // enables `Authorization` header ExtractJwt.fromAuthHeaderAsBearerToken(), @@ -63,7 +61,8 @@ export class AuthStrategy extends PassportStrategy(Strategy) { // delegates the validation of expiry to Passport ignoreExpiration: false, // defines a symmetric secret key for signing tokens - secretOrKey: auth.secret, + secretOrKey: (AppConfiguration.getConfig("security") as SecurityConfig) + .auth.secret, }); } diff --git a/runtime/backend/src/common/traits/RefreshStrategy.ts b/runtime/backend/src/common/traits/RefreshStrategy.ts index b3b1188a..8e10cbe9 100644 --- a/runtime/backend/src/common/traits/RefreshStrategy.ts +++ b/runtime/backend/src/common/traits/RefreshStrategy.ts @@ -23,10 +23,8 @@ import { } from "../models/AccountSessionSchema"; // configuration resources -import dappConfigLoader from "../../../config/dapp"; -import securityConfigLoader from "../../../config/security"; -const conf = dappConfigLoader(); -const auth = securityConfigLoader().auth; +import { AppConfiguration } from "../../AppConfiguration"; +import { SecurityConfig } from "../models"; /** * @class RefreshStrategy @@ -51,18 +49,20 @@ export class RefreshStrategy extends PassportStrategy(Strategy, "jwt-refresh") { // do we have a refresh token in the request's *signed* // httpOnly cookies? (name: "dappName:Refresh") (request: Request) => - request?.signedCookies[conf.dappName + ":Refresh"] ?? null, + request?.signedCookies[AppConfiguration.dappName + ":Refresh"] ?? + null, // do we have a refresh token in the request's *unsigned* // httpOnly cookies? (name: "dappName:Refresh") (request: Request) => - request?.cookies[conf.dappName + ":Refresh"] ?? null, + request?.cookies[AppConfiguration.dappName + ":Refresh"] ?? null, // do we have a refresh token in Authorization header? ExtractJwt.fromAuthHeaderAsBearerToken(), ]), // defines a symmetric secret key for signing tokens - secretOrKey: auth.secret, + secretOrKey: (AppConfiguration.getConfig("security") as SecurityConfig) + .auth.secret, // permits to access the cookie from validate method passReqToCallback: true, }); @@ -86,7 +86,7 @@ export class RefreshStrategy extends PassportStrategy(Strategy, "jwt-refresh") { // extracts refresh token from *signed cookies* const refreshToken = AuthService.extractToken( request, - `${conf.dappName}:Refresh`, + `${AppConfiguration.dappName}:Refresh`, ); // finds an `accounts` document using a SHA3-256 diff --git a/runtime/backend/tests/unit/AppConfiguration.spec.ts b/runtime/backend/tests/unit/AppConfiguration.spec.ts index d3b49d77..74b1de9e 100644 --- a/runtime/backend/tests/unit/AppConfiguration.spec.ts +++ b/runtime/backend/tests/unit/AppConfiguration.spec.ts @@ -293,6 +293,65 @@ describe("AppConfiguration", () => { }) }); + describe("static getConfig()", () => { + it("should return correct config section", () => { + // prepare + const configSections = [ + "assets", + "dapp", + "network", + "oauth", + "payout", + "processor", + "security", + "statistics", + "social", + "monitoring", + "transport", + ]; + const loaderCalls = [ + mockAssetsConfigLoaderCall, + mockDappConfigLoaderCall, + mockNetworkConfigLoaderCall, + mockOauthConfigLoaderCall, + mockPayoutConfigLoaderCall, + mockProcessorConfigLoaderCall, + mockSecurityConfigLoaderCall, + mockStatisticsConfigLoaderCall, + mockSocialConfigLoaderCall, + mockMonitoringConfigLoaderCall, + mockTransportConfigLoaderCall, + ] + configSections.forEach((configSection: string, index: number) => { + // act + const result = AppConfiguration.getConfig(configSection); + + // assert + expect(loaderCalls[index]).toHaveBeenCalledTimes(1); // first call was in constructor + expect(result).toBeDefined(); + }) + }); + + it("should throw error if section name is not defined/in list", () => { + // prepare + [ + undefined, + null, + "", + true, + "some-non-existing-section" + ].forEach((configSection: any) => { + const expectedError = new Error(`Cannot find relevant config for section: ${configSection}`); + + // act + const result = () => AppConfiguration.getConfig(configSection); + + // assert + expect(result).toThrowError(expectedError); + }); + }); + }); + describe("getDatabaseModule()", () => { it("should create instance using mongoose module", () => { // act diff --git a/runtime/backend/tests/unit/worker/WorkerModule.spec.ts b/runtime/backend/tests/unit/worker/WorkerModule.spec.ts index 481d6c16..10358dd1 100644 --- a/runtime/backend/tests/unit/worker/WorkerModule.spec.ts +++ b/runtime/backend/tests/unit/worker/WorkerModule.spec.ts @@ -8,7 +8,9 @@ * @license LGPL-3.0 */ // mock AppConfiguration +import testMonitoringConfigLoader from "../../../config/monitoring"; class MockAppConfiguration { + static getConfig = jest.fn().mockReturnValue(testMonitoringConfigLoader()); static getMailerModule = jest.fn(); static getEventEmitterModule = jest.fn(); static getDatabaseModule = jest.fn();