Added schema validation for config.json file
ADD
src/assets/schemas/configuration/configuration.json
+{
+ "$id": "config",
+ "title": "e-Mobility Configuration",
+ "type": "object",
+ "properties": {
+ "Migration": {
+ "type": "object",
+ "properties": {
+ "active": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "active"
+ ]
+ },
+ "Crypto": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "algorithm": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "key",
+ "algorithm"
+ ]
+ },
+ "EVDatabase": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "url",
+ "key"
+ ]
+ },
+ "OICPService": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string"
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "debug": {
+ "type": "boolean"
+ },
+ "externalProtocol": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port",
+ "debug",
+ "externalProtocol"
+ ]
+ },
+ "ODataService": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string"
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "externalProtocol": {
+ "type": "string"
+ },
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port",
+ "externalProtocol",
+ "debug"
+ ]
+ },
+ "CentralSystemServer": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string"
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port"
+ ]
+ },
+ "CentralSystems": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["ocpp"]
+ },
+ "implementation": {
+ "type": "string",
+ "enum": ["soap", "json"]
+ },
+ "protocol": {
+ "type": "string",
+ "enum": ["http", "https", "ws", "wss"]
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "type",
+ "implementation",
+ "protocol",
+ "host",
+ "port",
+ "debug"
+ ]
+ }
+ },
+ "CentralSystemRestService": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string",
+ "enum": ["http", "https"]
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "userTokenKey": {
+ "type": "string"
+ },
+ "userTokenLifetimeHours": {
+ "type": "integer"
+ },
+ "userDemoTokenLifetimeDays": {
+ "type": "integer"
+ },
+ "userTechnicalTokenLifetimeDays": {
+ "type": "integer"
+ },
+ "passwordWrongNumberOfTrial": {
+ "type": "integer"
+ },
+ "passwordBlockedWaitTimeMin": {
+ "type": "integer"
+ },
+ "captchaSecretKeyUnitTests": {
+ "type": "string"
+ },
+ "captchaSecretKeyUI": {
+ "type": "string"
+ },
+ "captchaSecretKey": {
+ "type": "string"
+ },
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port",
+ "userTokenKey",
+ "userTokenLifetimeHours",
+ "userDemoTokenLifetimeDays",
+ "userTechnicalTokenLifetimeDays",
+ "passwordWrongNumberOfTrial",
+ "passwordBlockedWaitTimeMin",
+ "captchaSecretKeyUnitTests",
+ "captchaSecretKeyUI",
+ "captchaSecretKey",
+ "debug"
+ ]
+ },
+ "CentralSystemFrontEnd": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string",
+ "enum": ["http", "https"]
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port"
+ ]
+ },
+ "WSDLEndpoint": {
+ "type": "object",
+ "properties": {
+ "baseSecureUrl": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "baseSecureUrl"
+ ]
+ },
+ "JsonEndpoint": {
+ "type": "object",
+ "properties": {
+ "baseSecureUrl": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "baseSecureUrl"
+ ]
+ },
+ "OCPIEndpoint": {
+ "type": "object",
+ "properties": {
+ "baseUrl": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "baseUrl"
+ ]
+ },
+ "OICPEndpoint": {
+ "type": "object",
+ "properties": {
+ "baseUrl": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "baseUrl"
+ ]
+ },
+ "Storage": {
+ "type": "object",
+ "properties": {
+ "implementation": {
+ "type": "string"
+ },
+ "uri": {
+ "type": "string"
+ },
+ "poolSize": {
+ "type": "integer"
+ },
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "implementation",
+ "uri",
+ "poolSize",
+ "debug"
+ ]
+ },
+ "OCPIService": {
+ "type": "object",
+ "properties": {
+ "protocol": {
+ "type": "string",
+ "enum": ["http", "https"]
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "protocol",
+ "host",
+ "port",
+ "debug"
+ ]
+ },
+ "Notification": {
+ "type": "object",
+ "properties": {
+ "Email": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "enabled"
+ ]
+ },
+ "RemotePushNotification": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "enabled"
+ ]
+ }
+ },
+ "required": [
+ "Email",
+ "RemotePushNotification"
+ ]
+ },
+ "Firebase": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "projectID": {
+ "type": "string"
+ },
+ "privateKeyID": {
+ "type": "string"
+ },
+ "privateKey": {
+ "type": "string"
+ },
+ "clientEmail": {
+ "type": "string"
+ },
+ "clientID": {
+ "type": "string"
+ },
+ "authURI": {
+ "type": "string"
+ },
+ "tokenURI": {
+ "type": "string"
+ },
+ "authProviderX509CertURL": {
+ "type": "string"
+ },
+ "clientX509CertURL": {
+ "type": "string"
+ },
+ "databaseURL": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "projectID",
+ "privateKeyID",
+ "privateKey",
+ "clientEmail",
+ "clientID",
+ "authURI",
+ "tokenURI",
+ "authProviderX509CertURL",
+ "clientX509CertURL",
+ "databaseURL"
+ ]
+ },
+ "Axios": {
+ "type": "object",
+ "properties": {
+ "retries": {
+ "type": "integer"
+ },
+ "timeoutSecs": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "retries",
+ "timeoutSecs"
+ ]
+ },
+ "Email": {
+ "type": "object",
+ "properties": {
+ "disableBackup": {
+ "type": "boolean"
+ },
+ "smtp": {
+ "type": "object",
+ "properties": {
+ "from": {
+ "type": "string"
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "secure": {
+ "type": "boolean"
+ },
+ "requireTLS": {
+ "type": "boolean"
+ },
+ "user": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "host",
+ "port",
+ "secure",
+ "requireTLS",
+ "user",
+ "password"
+ ]
+ },
+ "smtpBackup": {
+ "type": "object",
+ "properties": {
+ "from": {
+ "type": "string"
+ },
+ "host": {
+ "type": "string"
+ },
+ "port": {
+ "type": "integer"
+ },
+ "secure": {
+ "type": "boolean"
+ },
+ "requireTLS": {
+ "type": "boolean"
+ },
+ "user": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "host",
+ "port",
+ "secure",
+ "requireTLS",
+ "user",
+ "password"
+ ]
+ }
+ },
+ "required": [
+ "disableBackup",
+ "smtp",
+ "smtpBackup"
+ ]
+ },
+ "Authorization": {
+ "type": "object",
+ "properties": {
+ "debug": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "debug"
+ ]
+ },
+ "ChargingStation": {
+ "type": "object",
+ "properties": {
+ "heartbeatIntervalOCPPSSecs": {
+ "type": "integer"
+ },
+ "heartbeatIntervalOCPPJSecs": {
+ "type": "integer"
+ },
+ "pingIntervalOCPPJSecs": {
+ "type": "integer"
+ },
+ "monitoringIntervalOCPPJSecs": {
+ "type": "integer"
+ },
+ "notifBeforeEndOfChargeEnabled": {
+ "type": "boolean"
+ },
+ "notifBeforeEndOfChargePercent": {
+ "type": "integer"
+ },
+ "notifEndOfChargeEnabled": {
+ "type": "boolean"
+ },
+ "notifStopTransactionAndUnlockConnector": {
+ "type": "boolean"
+ },
+ "maxLastSeenIntervalSecs": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "heartbeatIntervalOCPPSSecs",
+ "heartbeatIntervalOCPPJSecs",
+ "pingIntervalOCPPJSecs",
+ "monitoringIntervalOCPPJSecs",
+ "notifBeforeEndOfChargeEnabled",
+ "notifBeforeEndOfChargePercent",
+ "notifEndOfChargeEnabled",
+ "notifStopTransactionAndUnlockConnector",
+ "maxLastSeenIntervalSecs"
+ ]
+ },
+ "AsyncTask": {
+ "type": "object",
+ "properties": {
+ "active": {
+ "type": "boolean"
+ },
+ "nbrTasksInParallel": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "active",
+ "nbrTasksInParallel"
+ ]
+ },
+ "Scheduler": {
+ "type": "object",
+ "properties": {
+ "active": {
+ "type": "boolean"
+ },
+ "tasks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "periodicity": {
+ "type": "string"
+ },
+ "config": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "name",
+ "active",
+ "periodicity"
+ ]
+ }
+ }
+ },
+ "required": [
+ "active",
+ "tasks"
+ ]
+ },
+ "Logging": {
+ "type": "object",
+ "properties": {
+ "logLevel": {
+ "type": "string",
+ "enum": ["NONE", "D", "I", "W", "E"]
+ },
+ "trace": {
+ "type": "boolean"
+ },
+ "moduleDetails": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "logLevel",
+ "trace"
+ ]
+ }
+ }
+}
\ No newline at end of file

ADD
src/storage/mongodb/validator/ConfigurationValidatorStorage.ts
+import { Configuration } from '../../../types/configuration/Configuration';
+import Schema from '../../../types/validator/Schema';
+import SchemaValidator from '../../../validator/SchemaValidator';
+import fs from 'fs';
+import global from '../../../types/GlobalType';
+
+export default class ConfigurationValidatorStorage extends SchemaValidator {
+ private static instance: ConfigurationValidatorStorage | null = null;
+ private configuration: Schema;
+
+ private constructor() {
+ super('ConfigurationValidatorStorage', {
+ strict: false, // When 'true', it fails with anyOf required fields: https://github.com/ajv-validator/ajv/issues/1571
+ allErrors: true,
+ removeAdditional: false, // 'all' fails with anyOf documents: Manually added 'additionalProperties: false' in schema due filtering of data in anyOf/oneOf/allOf array (it's standard): https://github.com/ajv-validator/ajv/issues/1784
+ allowUnionTypes: true,
+ coerceTypes: true,
+ verbose: true,
+ });
+ this.configuration = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/schemas/configuration/configuration.json`, 'utf8'));
+ }
+
+ public static getInstance(): ConfigurationValidatorStorage {
+ if (!ConfigurationValidatorStorage.instance) {
+ ConfigurationValidatorStorage.instance = new ConfigurationValidatorStorage();
+ }
+ return ConfigurationValidatorStorage.instance;
+ }
+
+ public validateConfiguration(data: Record<string, unknown>): Configuration {
+ return this.validate(this.configuration, data);
+ }
+}

MODIFY
src/utils/Configuration.ts
src/utils/Configuration.ts
@@ -7,6 +7,7 @@ import CentralSystemRestServiceConfiguration from '../types/configuration/Centra
import CentralSystemServerConfiguration from '../types/configuration/CentralSystemServerConfiguration';
import ChargingStationConfiguration from '../types/configuration/ChargingStationConfiguration';
import { Configuration as ConfigurationData } from '../types/configuration/Configuration';
+import ConfigurationValidatorStorage from '../storage/mongodb/validator/ConfigurationValidatorStorage';
import Constants from './Constants';
import CryptoConfiguration from '../types/configuration/CryptoConfiguration';
import EVDatabaseConfiguration from '../types/configuration/EVDatabaseConfiguration';
@@ -234,7 +235,9 @@ export default class Configuration {
private static getConfig(): ConfigurationData {
if (!Configuration.config) {
- Configuration.config = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/config.json`, 'utf8')) as ConfigurationData;
+ const config = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/config.json`, 'utf8')) as ConfigurationData;
+ Configuration.config = ConfigurationValidatorStorage.getInstance().validateConfiguration(
+ config as unknown as Record<string, unknown>);
}
return Configuration.config;
}

Added schema validation for config.json file