feat: add a basic authentication scheme for UI WebSocket server
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 15 Feb 2024 21:25:48 +0000 (22:25 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 15 Feb 2024 21:25:48 +0000 (22:25 +0100)
closes #980

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
14 files changed:
docker/config.json
package.json
pnpm-lock.yaml
src/assets/config-template.json
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIServerFactory.ts
src/types/UIProtocol.ts
src/utils/Configuration.ts
ui/web/README.md
ui/web/package.json
ui/web/pnpm-lock.yaml
ui/web/src/assets/config-template.json
ui/web/src/composables/UIClient.ts
ui/web/src/types/UIProtocol.ts

index 7808b07c0bc995a93720e9ec1ef2b53a91ba7968..c207106a03271e955b4566ee3fa8279f8e4cf612 100644 (file)
     "poolMaxSize": 16
   },
   "uiServer": {
-    "enabled": true
+    "enabled": true,
+    "type": "ws",
+    "authentication": {
+      "enabled": true,
+      "type": "protocol-basic-auth",
+      "username": "admin",
+      "password": "admin"
+    }
   },
   "stationTemplateUrls": [
     {
index 642afda9b7d9cf78f84bacbdae529ec5955fbcf0..83f3a659539e68b331acf6fd99811641cba41a5c 100644 (file)
     "@commitlint/config-conventional": "^18.6.2",
     "@mikro-orm/cli": "^6.1.3",
     "@release-it/bumper": "^6.0.1",
-    "@types/node": "^20.11.18",
+    "@types/node": "^20.11.19",
     "@types/semver": "^7.5.7",
     "@types/tar": "^6.1.11",
     "@types/ws": "^8.5.10",
index d8b6cdfa1451c4e59012c0ba96d459cd5091c4dc..1af56baa9d6ace5fa7bf46c76ae7fe326ce35f83 100644 (file)
@@ -83,7 +83,7 @@ optionalDependencies:
 devDependencies:
   '@commitlint/cli':
     specifier: ^18.6.1
-    version: 18.6.1(@types/node@20.11.18)(typescript@5.3.3)
+    version: 18.6.1(@types/node@20.11.19)(typescript@5.3.3)
   '@commitlint/config-conventional':
     specifier: ^18.6.2
     version: 18.6.2
@@ -94,8 +94,8 @@ devDependencies:
     specifier: ^6.0.1
     version: 6.0.1(release-it@17.0.5)
   '@types/node':
-    specifier: ^20.11.18
-    version: 20.11.18
+    specifier: ^20.11.19
+    version: 20.11.19
   '@types/semver':
     specifier: ^7.5.7
     version: 7.5.7
@@ -185,7 +185,7 @@ devDependencies:
     version: 7.6.0
   ts-node:
     specifier: ^10.9.2
-    version: 10.9.2(@types/node@20.11.18)(typescript@5.3.3)
+    version: 10.9.2(@types/node@20.11.19)(typescript@5.3.3)
   tsx:
     specifier: ^4.7.1
     version: 4.7.1
@@ -412,14 +412,14 @@ packages:
     engines: {node: '>=0.1.90'}
     dev: false
 
-  /@commitlint/cli@18.6.1(@types/node@20.11.18)(typescript@5.3.3):
+  /@commitlint/cli@18.6.1(@types/node@20.11.19)(typescript@5.3.3):
     resolution: {integrity: sha512-5IDE0a+lWGdkOvKH892HHAZgbAjcj1mT5QrfA/SVbLJV/BbBMGyKN0W5mhgjekPJJwEQdVNvhl9PwUacY58Usw==}
     engines: {node: '>=v18'}
     hasBin: true
     dependencies:
       '@commitlint/format': 18.6.1
       '@commitlint/lint': 18.6.1
-      '@commitlint/load': 18.6.1(@types/node@20.11.18)(typescript@5.3.3)
+      '@commitlint/load': 18.6.1(@types/node@20.11.19)(typescript@5.3.3)
       '@commitlint/read': 18.6.1
       '@commitlint/types': 18.6.1
       execa: 5.1.1
@@ -491,7 +491,7 @@ packages:
       '@commitlint/types': 18.6.1
     dev: true
 
-  /@commitlint/load@18.6.1(@types/node@20.11.18)(typescript@5.3.3):
+  /@commitlint/load@18.6.1(@types/node@20.11.19)(typescript@5.3.3):
     resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==}
     engines: {node: '>=v18'}
     dependencies:
@@ -501,7 +501,7 @@ packages:
       '@commitlint/types': 18.6.1
       chalk: 4.1.2
       cosmiconfig: 8.3.6(typescript@5.3.3)
-      cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.18)(cosmiconfig@8.3.6)(typescript@5.3.3)
+      cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.19)(cosmiconfig@8.3.6)(typescript@5.3.3)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -1127,7 +1127,7 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       '@types/yargs': 17.0.32
       chalk: 4.1.2
     dev: true
@@ -1346,7 +1346,7 @@ packages:
       '@octokit/graphql': 7.0.2
       '@octokit/request': 8.2.0
       '@octokit/request-error': 5.0.1
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
       before-after-hook: 2.2.3
       universal-user-agent: 6.0.1
     dev: true
@@ -1355,7 +1355,7 @@ packages:
     resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==}
     engines: {node: '>= 18'}
     dependencies:
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1364,7 +1364,7 @@ packages:
     engines: {node: '>= 18'}
     dependencies:
       '@octokit/request': 8.2.0
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1379,7 +1379,7 @@ packages:
       '@octokit/core': '>=5'
     dependencies:
       '@octokit/core': 5.1.0
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
     dev: true
 
   /@octokit/plugin-request-log@4.0.0(@octokit/core@5.1.0):
@@ -1391,21 +1391,21 @@ packages:
       '@octokit/core': 5.1.0
     dev: true
 
-  /@octokit/plugin-rest-endpoint-methods@10.2.0(@octokit/core@5.1.0):
-    resolution: {integrity: sha512-ePbgBMYtGoRNXDyKGvr9cyHjQ163PbwD0y1MkDJCpkO2YH4OeXX40c4wYHKikHGZcpGPbcRLuy0unPUuafco8Q==}
+  /@octokit/plugin-rest-endpoint-methods@10.3.0(@octokit/core@5.1.0):
+    resolution: {integrity: sha512-c/fjpoHispRvBZuRoTVt/uALg7pXa9RQbXWJiDMk6NDkGNomuAZG7YuYYpZoxeoXv+kVRjIDTsO0e1z0pei+PQ==}
     engines: {node: '>= 18'}
     peerDependencies:
       '@octokit/core': '>=5'
     dependencies:
       '@octokit/core': 5.1.0
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
     dev: true
 
   /@octokit/request-error@5.0.1:
     resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==}
     engines: {node: '>= 18'}
     dependencies:
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
       deprecation: 2.3.1
       once: 1.4.0
     dev: true
@@ -1416,7 +1416,7 @@ packages:
     dependencies:
       '@octokit/endpoint': 9.0.4
       '@octokit/request-error': 5.0.1
-      '@octokit/types': 12.4.0
+      '@octokit/types': 12.5.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1427,11 +1427,11 @@ packages:
       '@octokit/core': 5.1.0
       '@octokit/plugin-paginate-rest': 9.1.5(@octokit/core@5.1.0)
       '@octokit/plugin-request-log': 4.0.0(@octokit/core@5.1.0)
-      '@octokit/plugin-rest-endpoint-methods': 10.2.0(@octokit/core@5.1.0)
+      '@octokit/plugin-rest-endpoint-methods': 10.3.0(@octokit/core@5.1.0)
     dev: true
 
-  /@octokit/types@12.4.0:
-    resolution: {integrity: sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==}
+  /@octokit/types@12.5.0:
+    resolution: {integrity: sha512-YJEKcb0KkJlIUNU/zjnZwHEP8AoVh/OoIcP/1IyR4UHxExz7fzpe/a8IG4wBtQi7QDEqiomVLX88S6FpxxAJtg==}
     dependencies:
       '@octokit/openapi-types': 19.1.0
     dev: true
@@ -1607,8 +1607,8 @@ packages:
     resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
     dev: false
 
-  /@types/node@20.11.18:
-    resolution: {integrity: sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==}
+  /@types/node@20.11.19:
+    resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==}
     dependencies:
       undici-types: 5.26.5
     dev: true
@@ -1636,7 +1636,7 @@ packages:
   /@types/tar@6.1.11:
     resolution: {integrity: sha512-ThA1WD8aDdVU4VLuyq5NEqriwXErF5gEIJeyT6gHBWU7JtSmW2a5qjNv3/vR82O20mW+1vhmeZJfBQPT3HCugg==}
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       minipass: 4.2.8
     dev: true
 
@@ -1661,7 +1661,7 @@ packages:
   /@types/ws@8.5.10:
     resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
     dev: true
 
   /@types/yargs-parser@21.0.3:
@@ -3217,7 +3217,7 @@ packages:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
     dev: true
 
-  /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.18)(cosmiconfig@8.3.6)(typescript@5.3.3):
+  /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.19)(cosmiconfig@8.3.6)(typescript@5.3.3):
     resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
     engines: {node: '>=v16'}
     peerDependencies:
@@ -3225,7 +3225,7 @@ packages:
       cosmiconfig: '>=8.2'
       typescript: '>=4'
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       cosmiconfig: 8.3.6(typescript@5.3.3)
       jiti: 1.21.0
       typescript: 5.3.3
@@ -5322,8 +5322,8 @@ packages:
       - supports-color
     optional: true
 
-  /http-proxy-agent@7.0.1:
-    resolution: {integrity: sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==}
+  /http-proxy-agent@7.0.2:
+    resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
@@ -5368,8 +5368,8 @@ packages:
       - supports-color
     optional: true
 
-  /https-proxy-agent@7.0.3:
-    resolution: {integrity: sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==}
+  /https-proxy-agent@7.0.4:
+    resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
@@ -6084,7 +6084,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       chalk: 4.1.2
       ci-info: 3.9.0
       graceful-fs: 4.2.11
@@ -7570,8 +7570,8 @@ packages:
       agent-base: 7.1.0
       debug: 4.3.4
       get-uri: 6.0.3
-      http-proxy-agent: 7.0.1
-      https-proxy-agent: 7.0.3
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.4
       pac-resolver: 7.0.1
       socks-proxy-agent: 8.0.2
     transitivePeerDependencies:
@@ -7935,8 +7935,8 @@ packages:
     dependencies:
       agent-base: 7.1.0
       debug: 4.3.4
-      http-proxy-agent: 7.0.1
-      https-proxy-agent: 7.0.3
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.4
       lru-cache: 7.18.3
       pac-proxy-agent: 7.0.1
       proxy-from-env: 1.1.0
@@ -9311,7 +9311,7 @@ packages:
       code-block-writer: 12.0.0
     dev: false
 
-  /ts-node@10.9.2(@types/node@20.11.18)(typescript@5.3.3):
+  /ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3):
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
     hasBin: true
     peerDependencies:
@@ -9330,7 +9330,7 @@ packages:
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       acorn: 8.11.3
       acorn-walk: 8.3.2
       arg: 4.1.3
index 641117eaa2bab27a1ad6b7b9c6d3a58ef0f3791c..0946258fc1b483a4adedfa4b98d70484eae7f703 100644 (file)
@@ -19,7 +19,7 @@
     "type": "ws",
     "authentication": {
       "enabled": true,
-      "type": "basic-auth",
+      "type": "protocol-basic-auth",
       "username": "admin",
       "password": "admin"
     }
index a1375cea3786d131013685e39786032e7f1e4d6f..49ac635969b47388734fac8c7a85280d488774c0 100644 (file)
@@ -91,11 +91,19 @@ export abstract class AbstractUIServer {
   }
 
   protected authenticate (req: IncomingMessage, next: (err?: Error) => void): void {
+    const authorizationError = new BaseError('Unauthorized')
     if (this.isBasicAuthEnabled()) {
-      if (!this.isValidBasicAuth(req)) {
-        next(new BaseError('Unauthorized'))
+      if (!this.isValidBasicAuth(req, next)) {
+        next(authorizationError)
       }
       next()
+    } else if (this.isProtocolBasicAuthEnabled()) {
+      if (!this.isValidProtocolBasicAuth(req, next)) {
+        next(authorizationError)
+      }
+      next()
+    } else if (this.uiServerConfiguration.authentication?.enabled === true) {
+      next(authorizationError)
     }
     next()
   }
@@ -109,21 +117,56 @@ export abstract class AbstractUIServer {
   private isBasicAuthEnabled (): boolean {
     return (
       this.uiServerConfiguration.authentication?.enabled === true &&
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
       this.uiServerConfiguration.authentication.type === AuthenticationType.BASIC_AUTH
     )
   }
 
-  private isValidBasicAuth (req: IncomingMessage): boolean {
-    const authorizationHeader = req.headers.authorization ?? ''
-    const authorizationToken = authorizationHeader.split(/\s+/).pop() ?? ''
+  private isProtocolBasicAuthEnabled (): boolean {
+    return (
+      this.uiServerConfiguration.authentication?.enabled === true &&
+      this.uiServerConfiguration.authentication.type === AuthenticationType.PROTOCOL_BASIC_AUTH
+    )
+  }
+
+  private isValidBasicAuth (req: IncomingMessage, next: (err?: Error) => void): boolean {
+    const [username, password] = this.getUsernameAndPasswordFromAuthorizationToken(
+      req.headers.authorization?.split(/\s+/).pop() ?? '',
+      next
+    )
+    return this.isValidUsernameAndPassword(username, password)
+  }
+
+  private isValidProtocolBasicAuth (req: IncomingMessage, next: (err?: Error) => void): boolean {
+    const authorizationProtocol = req.headers['sec-websocket-protocol']?.split(',').pop()?.trim()
+    const [username, password] = this.getUsernameAndPasswordFromAuthorizationToken(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      `${authorizationProtocol}${Array(((4 - (authorizationProtocol!.length % 4)) % 4) + 1).join('=')}`
+        .split('.')
+        .pop() ?? '',
+      next
+    )
+    return this.isValidUsernameAndPassword(username, password)
+  }
+
+  private getUsernameAndPasswordFromAuthorizationToken (
+    authorizationToken: string,
+    next: (err?: Error) => void
+  ): [string, string] {
+    if (
+      !/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/.test(authorizationToken)
+    ) {
+      next(new BaseError('Invalid basic authentication token format'))
+    }
     const authentication = Buffer.from(authorizationToken, 'base64').toString()
     const authenticationParts = authentication.split(/:/)
-    const username = authenticationParts.shift()
-    const password = authenticationParts.join(':')
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return [authenticationParts.shift()!, authenticationParts.join(':')]
+  }
+
+  private isValidUsernameAndPassword (username: string, password: string): boolean {
     return (
       this.uiServerConfiguration.authentication?.username === username &&
-      this.uiServerConfiguration.authentication?.password === password
+      this.uiServerConfiguration.authentication.password === password
     )
   }
 
index df542ba6773a31271684c4b2ea1f6b51b54f7cb0..3df4566356cee97e6c33893aa6fe7d36003d6aac 100644 (file)
@@ -4,9 +4,11 @@ import type { AbstractUIServer } from './AbstractUIServer.js'
 import { UIHttpServer } from './UIHttpServer.js'
 import { UIServerUtils } from './UIServerUtils.js'
 import { UIWebSocketServer } from './UIWebSocketServer.js'
+import { BaseError } from '../../exception/BaseError.js'
 import {
   ApplicationProtocol,
   ApplicationProtocolVersion,
+  AuthenticationType,
   type UIServerConfiguration
 } from '../../types/index.js'
 
@@ -19,6 +21,21 @@ export class UIServerFactory {
   public static getUIServerImplementation (
     uiServerConfiguration: UIServerConfiguration
   ): AbstractUIServer | undefined {
+    if (
+      uiServerConfiguration.authentication?.enabled === true &&
+      !Object.values(AuthenticationType).includes(uiServerConfiguration.authentication.type)
+    ) {
+      throw new BaseError(
+        `Unknown authentication type '${uiServerConfiguration.authentication.type}' for UI server`
+      )
+    }
+    if (
+      uiServerConfiguration.type === ApplicationProtocol.HTTP &&
+      uiServerConfiguration.authentication?.enabled === true &&
+      uiServerConfiguration.authentication.type === AuthenticationType.PROTOCOL_BASIC_AUTH
+    ) {
+      throw new BaseError('Protocol basic authentication is not supported for HTTP UI server')
+    }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     if (!UIServerUtils.isLoopback(uiServerConfiguration.options!.host!)) {
       console.warn(
@@ -27,10 +44,6 @@ export class UIServerFactory {
         )
       )
     }
-    uiServerConfiguration = {
-      version: ApplicationProtocolVersion.VERSION_11,
-      ...uiServerConfiguration
-    }
     if (
       uiServerConfiguration.type === ApplicationProtocol.WS &&
       uiServerConfiguration.version !== ApplicationProtocolVersion.VERSION_11
index e2d65804b78be82cc0dc74af1282ca25c5db960a..6e120337197d3ea39b65a48517b6e9f554e9eaa2 100644 (file)
@@ -11,7 +11,8 @@ export enum ApplicationProtocol {
 }
 
 export enum AuthenticationType {
-  BASIC_AUTH = 'basic-auth'
+  BASIC_AUTH = 'basic-auth',
+  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth'
 }
 
 export enum ProtocolVersion {
index 30ae873e672a595e362b3c9f68d8818c5b291ee7..d577ded4141858bbc08213615f116fd199bfc2c8 100644 (file)
@@ -18,6 +18,7 @@ import { Constants } from './Constants.js'
 import { hasOwnProp, isCFEnvironment, once } from './Utils.js'
 import {
   ApplicationProtocol,
+  ApplicationProtocolVersion,
   type ConfigurationData,
   ConfigurationSection,
   FileType,
@@ -155,6 +156,7 @@ export class Configuration {
     let uiServerConfiguration: UIServerConfiguration = {
       enabled: false,
       type: ApplicationProtocol.WS,
+      version: ApplicationProtocolVersion.VERSION_11,
       options: {
         host: Constants.DEFAULT_UI_SERVER_HOST,
         port: Constants.DEFAULT_UI_SERVER_PORT
index 7ae92537e2424bb50efc43ffa603a96c6b3452a3..1a129e14132574deb40be07d82f4352c8659a7a8 100644 (file)
@@ -23,8 +23,8 @@ The simulator UI server must be enabled, use WebSocket transport type and have a
     "enabled": true,
     "type": "ws",
     "authentication": {
-      "enabled": false,
-      "type": "basic-auth",
+      "enabled": true,
+      "type": "protocol-basic-auth",
       "username": "admin",
       "password": "admin"
     }
index 84eda920110989bf2c6f4a8ae1de3283d93c4865..d5ab1c0c5a0d72ad2e810f717d09f7161683f6b6 100644 (file)
@@ -41,7 +41,7 @@
     "@rushstack/eslint-patch": "^1.7.2",
     "@tsconfig/node20": "^20.1.2",
     "@types/jsdom": "^21.1.6",
-    "@types/node": "^20.11.18",
+    "@types/node": "^20.11.19",
     "@typescript-eslint/eslint-plugin": "^7.0.1",
     "@typescript-eslint/parser": "^7.0.1",
     "@vitejs/plugin-vue": "^5.0.4",
@@ -61,7 +61,7 @@
     "prettier": "^3.2.5",
     "rimraf": "^5.0.5",
     "typescript": "~5.3.3",
-    "vite": "^5.1.2",
+    "vite": "^5.1.3",
     "vitest": "^1.2.2"
   },
   "_id": "webui@0.1.1"
index f9a397e896214f2030afc29b5a305afb70438e4b..08427fa365ddd4c6a8cf4d0583a30496f8bd734b 100644 (file)
@@ -32,8 +32,8 @@ devDependencies:
     specifier: ^21.1.6
     version: 21.1.6
   '@types/node':
-    specifier: ^20.11.18
-    version: 20.11.18
+    specifier: ^20.11.19
+    version: 20.11.19
   '@typescript-eslint/eslint-plugin':
     specifier: ^7.0.1
     version: 7.0.1(@typescript-eslint/parser@7.0.1)(eslint@8.56.0)(typescript@5.3.3)
@@ -42,10 +42,10 @@ devDependencies:
     version: 7.0.1(eslint@8.56.0)(typescript@5.3.3)
   '@vitejs/plugin-vue':
     specifier: ^5.0.4
-    version: 5.0.4(vite@5.1.2)(vue@3.4.19)
+    version: 5.0.4(vite@5.1.3)(vue@3.4.19)
   '@vitejs/plugin-vue-jsx':
     specifier: ^3.1.0
-    version: 3.1.0(vite@5.1.2)(vue@3.4.19)
+    version: 3.1.0(vite@5.1.3)(vue@3.4.19)
   '@vitest/coverage-v8':
     specifier: ^1.2.2
     version: 1.2.2(vitest@1.2.2)
@@ -92,11 +92,11 @@ devDependencies:
     specifier: ~5.3.3
     version: 5.3.3
   vite:
-    specifier: ^5.1.2
-    version: 5.1.2(@types/node@20.11.18)
+    specifier: ^5.1.3
+    version: 5.1.3(@types/node@20.11.19)
   vitest:
     specifier: ^1.2.2
-    version: 1.2.2(@types/node@20.11.18)(jsdom@24.0.0)
+    version: 1.2.2(@types/node@20.11.19)(jsdom@24.0.0)
 
 packages:
 
@@ -881,7 +881,7 @@ packages:
   /@types/jsdom@21.1.6:
     resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       '@types/tough-cookie': 4.0.5
       parse5: 7.1.2
     dev: true
@@ -894,8 +894,8 @@ packages:
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
     dev: true
 
-  /@types/node@20.11.18:
-    resolution: {integrity: sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==}
+  /@types/node@20.11.19:
+    resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==}
     dependencies:
       undici-types: 5.26.5
     dev: true
@@ -1176,7 +1176,7 @@ packages:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: true
 
-  /@vitejs/plugin-vue-jsx@3.1.0(vite@5.1.2)(vue@3.4.19):
+  /@vitejs/plugin-vue-jsx@3.1.0(vite@5.1.3)(vue@3.4.19):
     resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -1186,20 +1186,20 @@ packages:
       '@babel/core': 7.23.9
       '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.9)
       '@vue/babel-plugin-jsx': 1.2.1(@babel/core@7.23.9)
-      vite: 5.1.2(@types/node@20.11.18)
+      vite: 5.1.3(@types/node@20.11.19)
       vue: 3.4.19(typescript@5.3.3)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@5.0.4(vite@5.1.2)(vue@3.4.19):
+  /@vitejs/plugin-vue@5.0.4(vite@5.1.3)(vue@3.4.19):
     resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
       vite: ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.1.2(@types/node@20.11.18)
+      vite: 5.1.3(@types/node@20.11.19)
       vue: 3.4.19(typescript@5.3.3)
     dev: true
 
@@ -1221,7 +1221,7 @@ packages:
       std-env: 3.7.0
       test-exclude: 6.0.0
       v8-to-istanbul: 9.2.0
-      vitest: 1.2.2(@types/node@20.11.18)(jsdom@24.0.0)
+      vitest: 1.2.2(@types/node@20.11.19)(jsdom@24.0.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -1340,8 +1340,8 @@ packages:
       '@vue/compiler-dom': 3.4.19
       '@vue/shared': 3.4.19
 
-  /@vue/devtools-api@6.5.1:
-    resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
+  /@vue/devtools-api@6.6.1:
+    resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
     dev: false
 
   /@vue/eslint-config-prettier@9.0.0(eslint@8.56.0)(prettier@3.2.5):
@@ -1418,7 +1418,7 @@ packages:
       '@vue/server-renderer':
         optional: true
     dependencies:
-      js-beautify: 1.14.11
+      js-beautify: 1.15.0
       vue: 3.4.19(typescript@5.3.3)
       vue-component-type-helpers: 1.8.27
     dev: true
@@ -1634,7 +1634,7 @@ packages:
     hasBin: true
     dependencies:
       caniuse-lite: 1.0.30001587
-      electron-to-chromium: 1.4.670
+      electron-to-chromium: 1.4.671
       node-releases: 2.0.14
       update-browserslist-db: 1.0.13(browserslist@4.23.0)
     dev: true
@@ -1921,8 +1921,8 @@ packages:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
     dev: false
 
-  /electron-to-chromium@1.4.670:
-    resolution: {integrity: sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A==}
+  /electron-to-chromium@1.4.671:
+    resolution: {integrity: sha512-UUlE+/rWbydmp+FW8xlnnTA5WNA0ZZd2XL8CuMS72rh+k4y1f8+z6yk3UQhEwqHQWj6IBdL78DwWOdGMvYfQyA==}
     dev: true
 
   /emoji-regex@8.0.0:
@@ -2694,8 +2694,8 @@ packages:
       toidentifier: 1.0.1
     dev: false
 
-  /http-proxy-agent@7.0.1:
-    resolution: {integrity: sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==}
+  /http-proxy-agent@7.0.2:
+    resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
@@ -2704,8 +2704,8 @@ packages:
       - supports-color
     dev: true
 
-  /https-proxy-agent@7.0.3:
-    resolution: {integrity: sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==}
+  /https-proxy-agent@7.0.4:
+    resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
@@ -2946,17 +2946,23 @@ packages:
       '@pkgjs/parseargs': 0.11.0
     dev: true
 
-  /js-beautify@1.14.11:
-    resolution: {integrity: sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==}
+  /js-beautify@1.15.0:
+    resolution: {integrity: sha512-U1f+LPtn13M0OS0ChNMpM7wA7J47ECqwIcvayrZu+o0FLLt9FckoT6XOO1grhBS2vZjSt79K+vkUuP0o+BIdsA==}
     engines: {node: '>=14'}
     hasBin: true
     dependencies:
       config-chain: 1.1.13
       editorconfig: 1.0.4
       glob: 10.3.10
+      js-cookie: 3.0.5
       nopt: 7.2.0
     dev: true
 
+  /js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+    dev: true
+
   /js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
     dev: true
@@ -2982,8 +2988,8 @@ packages:
       decimal.js: 10.4.3
       form-data: 4.0.0
       html-encoding-sniffer: 4.0.0
-      http-proxy-agent: 7.0.1
-      https-proxy-agent: 7.0.3
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.4
       is-potential-custom-element-name: 1.0.1
       nwsapi: 2.2.7
       parse5: 7.1.2
@@ -4063,7 +4069,7 @@ packages:
       convert-source-map: 2.0.0
     dev: true
 
-  /vite-node@1.2.2(@types/node@20.11.18):
+  /vite-node@1.2.2(@types/node@20.11.19):
     resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
@@ -4072,7 +4078,7 @@ packages:
       debug: 4.3.4
       pathe: 1.1.2
       picocolors: 1.0.0
-      vite: 5.1.2(@types/node@20.11.18)
+      vite: 5.1.3(@types/node@20.11.19)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -4084,8 +4090,8 @@ packages:
       - terser
     dev: true
 
-  /vite@5.1.2(@types/node@20.11.18):
-    resolution: {integrity: sha512-uwiFebQbTWRIGbCaTEBVAfKqgqKNKMJ2uPXsXeLIZxM8MVMjoS3j0cG8NrPxdDIadaWnPSjrkLWffLSC+uiP3Q==}
+  /vite@5.1.3(@types/node@20.11.19):
+    resolution: {integrity: sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -4112,7 +4118,7 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       esbuild: 0.19.12
       postcss: 8.4.35
       rollup: 4.11.0
@@ -4120,7 +4126,7 @@ packages:
       fsevents: 2.3.3
     dev: true
 
-  /vitest@1.2.2(@types/node@20.11.18)(jsdom@24.0.0):
+  /vitest@1.2.2(@types/node@20.11.19)(jsdom@24.0.0):
     resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
@@ -4145,7 +4151,7 @@ packages:
       jsdom:
         optional: true
     dependencies:
-      '@types/node': 20.11.18
+      '@types/node': 20.11.19
       '@vitest/expect': 1.2.2
       '@vitest/runner': 1.2.2
       '@vitest/snapshot': 1.2.2
@@ -4165,8 +4171,8 @@ packages:
       strip-literal: 1.3.0
       tinybench: 2.6.0
       tinypool: 0.8.2
-      vite: 5.1.2(@types/node@20.11.18)
-      vite-node: 1.2.2(@types/node@20.11.18)
+      vite: 5.1.3(@types/node@20.11.19)
+      vite-node: 1.2.2(@types/node@20.11.19)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -4205,7 +4211,7 @@ packages:
     peerDependencies:
       vue: ^3.2.0
     dependencies:
-      '@vue/devtools-api': 6.5.1
+      '@vue/devtools-api': 6.6.1
       vue: 3.4.19(typescript@5.3.3)
     dev: false
 
index 1997f9d10aeb2ecc72cec5dacfda0d0e254af357..1542583a2b0f9f96784e5fcadc20d53378235f38 100644 (file)
@@ -5,8 +5,8 @@
     "protocol": "ui",
     "version": "0.0.1",
     "authentication": {
-      "enabled": false,
-      "type": "basic-auth",
+      "enabled": true,
+      "type": "protocol-basic-auth",
       "username": "admin",
       "password": "admin"
     }
index 0251512d2f233df9e551948d1de3f1bd7054e188..0ed5d82e995474772f774f4082d9e207e7e0ea2c 100644 (file)
@@ -1,5 +1,6 @@
 import {
   ApplicationProtocol,
+  AuthenticationType,
   type ConfigurationData,
   ProcedureName,
   type ProtocolResponse,
@@ -111,9 +112,17 @@ export class UIClient {
   }
 
   private openWS(): void {
+    const protocols =
+      this.configuration.uiServer.authentication?.enabled === true &&
+      this.configuration.uiServer.authentication?.type === AuthenticationType.PROTOCOL_BASIC_AUTH
+        ? [
+            `${this.configuration.uiServer.protocol}${this.configuration.uiServer.version}`,
+            `authorization.basic.${btoa(`${this.configuration.uiServer.authentication.username}:${this.configuration.uiServer.authentication.password}`).replace(/={1,2}$/, '')}`
+          ]
+        : `${this.configuration.uiServer.protocol}${this.configuration.uiServer.version}`
     this.ws = new WebSocket(
       `${this.configuration.uiServer.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${this.configuration.uiServer.host}:${this.configuration.uiServer.port}`,
-      `${this.configuration.uiServer.protocol}${this.configuration.uiServer.version}`
+      protocols
     )
     this.ws.onmessage = this.responseHandler.bind(this)
     this.ws.onerror = errorEvent => {
index 1f8d1e6fe7ba432fa02516cb4da851757297a47f..6883cf8b1d9c0bb03dd3e931424847a8c472662d 100644 (file)
@@ -14,7 +14,7 @@ export enum ProtocolVersion {
 }
 
 export enum AuthenticationType {
-  BASIC_AUTH = 'basic-auth'
+  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth'
 }
 
 export type ProtocolRequest = [string, ProcedureName, RequestPayload]