Warn if UI server is configured to listen on something else than the
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 13 Jul 2022 20:53:41 +0000 (22:53 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 13 Jul 2022 20:53:41 +0000 (22:53 +0200)
loopback

Close #64

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStationConfigurationUtils.ts
src/charging-station/ChargingStationUtils.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ui-server/UIServerFactory.ts
src/charging-station/ui-server/ui-services/UIServiceUtils.ts
src/utils/Configuration.ts
src/utils/ElectricUtils.ts
src/utils/FileUtils.ts
src/utils/Utils.ts
src/worker/WorkerUtils.ts

index 9c182ea5733e0aae7a3e135106f44fb9da6f89d9..7f1ff980e4ac75dd60bc7c289fe747cdb8ce716d 100644 (file)
@@ -4,6 +4,10 @@ import { StandardParametersKey } from '../types/ocpp/Configuration';
 import logger from '../utils/Logger';
 
 export class ChargingStationConfigurationUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static getConfigurationKey(
     chargingStation: ChargingStation,
     key: string | StandardParametersKey,
index 7208fb80fe383fa916836306d63c33d9405f634a..2ac8a81bd4feb8f3df01972d8b473a435d04caa0 100644 (file)
@@ -26,6 +26,10 @@ import moment from 'moment';
 import path from 'path';
 
 export class ChargingStationUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static getChargingStationId(
     index: number,
     stationTemplate: ChargingStationTemplate
index 7a1fde29cc1c9af7eb4171fdbaf9ae4ad3042b6d..c9bfdd564cfa33cfe275cb386fe863ee0a1e4c12 100644 (file)
@@ -1,4 +1,8 @@
 export class OCPPServiceUtils {
+  protected constructor() {
+    // This is intentional
+  }
+
   protected static getLimitFromSampledValueTemplateCustomValue(
     value: string,
     limit: number,
index 1ad64014219ace91a0bdcf3c4afe24e4fb00f8ba..26bbe6dd48519806862bd21fccad1233384853c9 100644 (file)
@@ -2,7 +2,9 @@ import { AbstractUIServer } from './AbstractUIServer';
 import { ApplicationProtocol } from '../../types/UIProtocol';
 import Configuration from '../../utils/Configuration';
 import { ServerOptions } from '../../types/ConfigurationData';
+import { UIServiceUtils } from './ui-services/UIServiceUtils';
 import UIWebSocketServer from './UIWebSocketServer';
+import chalk from 'chalk';
 
 export default class UIServerFactory {
   private constructor() {
@@ -13,6 +15,13 @@ export default class UIServerFactory {
     applicationProtocol: ApplicationProtocol,
     options?: ServerOptions
   ): AbstractUIServer | null {
+    if (!UIServiceUtils.isLoopback(options?.host)) {
+      console.warn(
+        chalk.magenta(
+          'Loopback address not detected in UI server configuration. This is not recommended.'
+        )
+      );
+    }
     switch (applicationProtocol) {
       case ApplicationProtocol.WS:
         return new UIWebSocketServer(options ?? Configuration.getUIServer().options);
index dabb5d474e88a78aebe5ea965b5b591e9f20246e..974d6fc4fb0ac16be896e62f4a43fbd06685437e 100644 (file)
@@ -5,6 +5,10 @@ import Utils from '../../../utils/Utils';
 import logger from '../../../utils/Logger';
 
 export class UIServiceUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static handleProtocols = (
     protocols: Set<string>,
     request: IncomingMessage
@@ -33,4 +37,13 @@ export class UIServiceUtils {
     );
     return false;
   };
+
+  public static isLoopback(address: string): boolean {
+    const isLoopbackRegExp = new RegExp(
+      // eslint-disable-next-line no-useless-escape
+      /^localhost$|^127(?:\.\d+){0,2}\.\d+$|^(?:0*\:)*?:?0*1$/,
+      'i'
+    );
+    return isLoopbackRegExp.test(address);
+  }
 }
index c7689f36a8604767b32135bd338f5358a2f1d0b1..67cf501b02e24eb2e7356affc556d79bca311aff 100644 (file)
@@ -29,6 +29,10 @@ export default class Configuration {
   private static configuration: ConfigurationData | null = null;
   private static configurationChangeCallback: () => Promise<void>;
 
+  private constructor() {
+    // This is intentional
+  }
+
   static setConfigurationChangeCallback(cb: () => Promise<void>): void {
     Configuration.configurationChangeCallback = cb;
   }
@@ -59,10 +63,10 @@ export default class Configuration {
       },
     };
     if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiServer')) {
-      uiServerConfiguration = {
-        ...uiServerConfiguration,
-        ...Configuration.getConfig().uiServer,
-      };
+      uiServerConfiguration = Configuration.deepMerge(
+        uiServerConfiguration,
+        Configuration.getConfig().uiServer
+      );
     }
     return uiServerConfiguration;
   }
@@ -383,6 +387,33 @@ export default class Configuration {
     }
   }
 
+  private static isObject(item): boolean {
+    return item && typeof item === 'object' && !Array.isArray(item);
+  }
+
+  private static deepMerge(target: object, ...sources: object[]): object {
+    if (!sources.length) {
+      return target;
+    }
+    const source = sources.shift();
+
+    if (Configuration.isObject(target) && Configuration.isObject(source)) {
+      for (const key in source) {
+        if (Configuration.isObject(source[key])) {
+          if (!target[key]) {
+            Object.assign(target, { [key]: {} });
+          }
+          // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+          Configuration.deepMerge(target[key], source[key]);
+        } else {
+          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+          Object.assign(target, { [key]: source[key] });
+        }
+      }
+    }
+    return Configuration.deepMerge(target, ...sources);
+  }
+
   private static objectHasOwnProperty(object: unknown, property: string): boolean {
     return Object.prototype.hasOwnProperty.call(object, property) as boolean;
   }
index 448d11ebb1dc205716f2e3bf3d2bd5c53c852fa0..9f0c680ac890ed02e2b277776c63e66c3c7f9ea8 100644 (file)
@@ -8,6 +8,10 @@
  * Targeted to AC related values calculation.
  */
 export class ACElectricUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   static amperageTotal(nbOfPhases: number, Iph: number): number {
     return nbOfPhases * Iph;
   }
index f38bde6c8ea66218887438eefa23832c7e6aa0e9..e36e85f73f5b11393899e4f8f9d83cf36dd346e5 100644 (file)
@@ -8,6 +8,10 @@ import fs from 'fs';
 import logger from './Logger';
 
 export default class FileUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static watchJsonFile<T extends JsonType>(
     logPrefix: string,
     fileType: FileType,
index c7fd875f8f7f09130164ee3126eaebd192280e00..f4a6221a473e93dc460d70bcd40add75843725d0 100644 (file)
@@ -2,6 +2,10 @@ import crypto from 'crypto';
 import { v4 as uuid } from 'uuid';
 
 export default class Utils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static logPrefix(prefixString = ''): string {
     return new Date().toLocaleString() + prefixString;
   }
index a181d80e205a2d6050e0f148311846d9a38db97a..00feba445ff939cbc933eac0daf1a12fc0cfb4c4 100644 (file)
@@ -1,6 +1,10 @@
 import chalk from 'chalk';
 
 export class WorkerUtils {
+  private constructor() {
+    // This is intentional
+  }
+
   public static defaultExitHandler = (code: number): void => {
     if (code !== 0) {
       console.error(chalk.red(`Worker stopped with exit code ${code}`));