From 329eab0e2561adc801877a95885f3ad15cbb90f6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 15 Feb 2024 22:25:48 +0100 Subject: [PATCH] feat: add a basic authentication scheme for UI WebSocket server MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit closes #980 Signed-off-by: Jérôme Benoit --- docker/config.json | 9 +- package.json | 2 +- pnpm-lock.yaml | 76 ++++++++--------- src/assets/config-template.json | 2 +- .../ui-server/AbstractUIServer.ts | 61 ++++++++++++-- .../ui-server/UIServerFactory.ts | 21 ++++- src/types/UIProtocol.ts | 3 +- src/utils/Configuration.ts | 2 + ui/web/README.md | 4 +- ui/web/package.json | 4 +- ui/web/pnpm-lock.yaml | 84 ++++++++++--------- ui/web/src/assets/config-template.json | 4 +- ui/web/src/composables/UIClient.ts | 11 ++- ui/web/src/types/UIProtocol.ts | 2 +- 14 files changed, 183 insertions(+), 102 deletions(-) diff --git a/docker/config.json b/docker/config.json index 7808b07c..c207106a 100644 --- a/docker/config.json +++ b/docker/config.json @@ -14,7 +14,14 @@ "poolMaxSize": 16 }, "uiServer": { - "enabled": true + "enabled": true, + "type": "ws", + "authentication": { + "enabled": true, + "type": "protocol-basic-auth", + "username": "admin", + "password": "admin" + } }, "stationTemplateUrls": [ { diff --git a/package.json b/package.json index 642afda9..83f3a659 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8b6cdfa..1af56baa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/assets/config-template.json b/src/assets/config-template.json index 641117ea..0946258f 100644 --- a/src/assets/config-template.json +++ b/src/assets/config-template.json @@ -19,7 +19,7 @@ "type": "ws", "authentication": { "enabled": true, - "type": "basic-auth", + "type": "protocol-basic-auth", "username": "admin", "password": "admin" } diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index a1375cea..49ac6359 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -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 ) } diff --git a/src/charging-station/ui-server/UIServerFactory.ts b/src/charging-station/ui-server/UIServerFactory.ts index df542ba6..3df45663 100644 --- a/src/charging-station/ui-server/UIServerFactory.ts +++ b/src/charging-station/ui-server/UIServerFactory.ts @@ -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 diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index e2d65804..6e120337 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -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 { diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 30ae873e..d577ded4 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -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 diff --git a/ui/web/README.md b/ui/web/README.md index 7ae92537..1a129e14 100644 --- a/ui/web/README.md +++ b/ui/web/README.md @@ -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" } diff --git a/ui/web/package.json b/ui/web/package.json index 84eda920..d5ab1c0c 100644 --- a/ui/web/package.json +++ b/ui/web/package.json @@ -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" diff --git a/ui/web/pnpm-lock.yaml b/ui/web/pnpm-lock.yaml index f9a397e8..08427fa3 100644 --- a/ui/web/pnpm-lock.yaml +++ b/ui/web/pnpm-lock.yaml @@ -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 diff --git a/ui/web/src/assets/config-template.json b/ui/web/src/assets/config-template.json index 1997f9d1..1542583a 100644 --- a/ui/web/src/assets/config-template.json +++ b/ui/web/src/assets/config-template.json @@ -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" } diff --git a/ui/web/src/composables/UIClient.ts b/ui/web/src/composables/UIClient.ts index 0251512d..0ed5d82e 100644 --- a/ui/web/src/composables/UIClient.ts +++ b/ui/web/src/composables/UIClient.ts @@ -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 => { diff --git a/ui/web/src/types/UIProtocol.ts b/ui/web/src/types/UIProtocol.ts index 1f8d1e6f..6883cf8b 100644 --- a/ui/web/src/types/UIProtocol.ts +++ b/ui/web/src/types/UIProtocol.ts @@ -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] -- 2.34.1