From 049f3de919d79e71fefd4da077d6b9b58d083c1a Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Mon, 19 Aug 2024 23:50:10 +0200 Subject: [PATCH 01/47] Added security tab --- src/components/PageComponents/Config/Security.tsx | 5 +++++ src/pages/Config/DeviceConfig.tsx | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/components/PageComponents/Config/Security.tsx diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx new file mode 100644 index 00000000..71346d1a --- /dev/null +++ b/src/components/PageComponents/Config/Security.tsx @@ -0,0 +1,5 @@ +export const Security = (): JSX.Element => { + return ( +

Security

+ ) +}; \ No newline at end of file diff --git a/src/pages/Config/DeviceConfig.tsx b/src/pages/Config/DeviceConfig.tsx index 66b8559d..45973e60 100644 --- a/src/pages/Config/DeviceConfig.tsx +++ b/src/pages/Config/DeviceConfig.tsx @@ -5,6 +5,7 @@ import { LoRa } from "@components/PageComponents/Config/LoRa.js"; import { Network } from "@components/PageComponents/Config/Network.js"; import { Position } from "@components/PageComponents/Config/Position.js"; import { Power } from "@components/PageComponents/Config/Power.js"; +import { Security } from "@components/PageComponents/Config/Security.js"; import { Tabs, TabsContent, @@ -47,6 +48,10 @@ export const DeviceConfig = (): JSX.Element => { label: "Bluetooth", element: Bluetooth, }, + { + label: "Security", + element: Security, + }, ]; return ( From a8dcab0844cde6f3a8a3467b0980cb195ce3f787 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 00:07:32 +0200 Subject: [PATCH 02/47] Added security input fields --- .../PageComponents/Config/Security.tsx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 71346d1a..4adb899e 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,5 +1,38 @@ +import { DynamicForm } from "@app/components/Form/DynamicForm"; + export const Security = (): JSX.Element => { + const onSubmit = (data: any) => { + console.log(data); + }; return ( -

Security

+ ) }; \ No newline at end of file From f6be57224e2e11704a222420185a00acd96f159f Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 15:04:10 +0200 Subject: [PATCH 03/47] Updated inputs --- .../PageComponents/Config/Security.tsx | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 4adb899e..b2f7d5f5 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,4 +1,4 @@ -import { DynamicForm } from "@app/components/Form/DynamicForm"; +import { DynamicForm } from "@app/components/Form/DynamicForm.js"; export const Security = (): JSX.Element => { const onSubmit = (data: any) => { @@ -9,26 +9,68 @@ export const Security = (): JSX.Element => { onSubmit={onSubmit} fieldGroups={[ { - label: "Security", - description: "Admin and direct messages keys", + label: "Security Settings", + description: "Settings for the Security module", fields: [ { type: "text", name: "publicKey", label: "Public Key", - description: "Sent out to other nodes on the mesh to allow them to compute a shared secret key.", + description: "Sent out to other nodes on the mesh to allow them to compute a shared secret key", }, { type: "text", name: "privateKey", label: "Private Key", - description: "Used to create a shared key with a remote device." + description: "Used to create a shared key with a remote device", + }, + ], + }, + { + label: "Admin Settings", + description: "Settings for Admin ", + fields: [ + { + type: "toggle", + name: "adminChannelEnabled", + label: "Admin Channel", + description: "Enable 'admin' channel", + }, + { + type: "toggle", + name: "isManaged", + label: "Is Managed", + description: "Enable if you want to manage this node from other nodes", }, { type: "text", name: "adminKey", label: "Admin Key", - description: "The public key authorized to send admin messages to this node." + description: "The public key authorized to send admin messages to this node", + } + ], + }, + { + label: "Logging Settings", + description: "Settings for Logging", + fields: [ + { + type: "toggle", + name: "bluetoothLoggingEnabled", + label: "Bluetooth Logging", + description: "Enable Bluetooth Logging", + }, + { + type: "toggle", + name: "debugLogApiEnabled", + label: "Debug Log API", + description: "Enable Log API", + }, + { + type: "toggle", + name: "serialEnabled", + label: "Serial", + description: "Enable Serial" } ], }, From be9169f56f0f370d12ebdade5ab980f7046841d2 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 15:08:13 +0200 Subject: [PATCH 04/47] Use protobuf pki branch, tmp for dev --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e56bdaed..b49b2041 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.2", - "@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240613143006-244927bc441a.1", + "@buf/meshtastic_protobufs.bufbuild_es": "2.0.0-00000000000000-aaea594f36a5.2", "@types/chrome": "^0.0.263", "@types/node": "^20.14.9", "@types/react": "^18.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 938ea82e..6bb4dad7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,8 +130,8 @@ importers: specifier: ^1.8.2 version: 1.8.2 '@buf/meshtastic_protobufs.bufbuild_es': - specifier: 1.10.0-20240613143006-244927bc441a.1 - version: 1.10.0-20240613143006-244927bc441a.1(@bufbuild/protobuf@1.10.0) + specifier: 2.0.0-00000000000000-aaea594f36a5.2 + version: 2.0.0-00000000000000-aaea594f36a5.2(@bufbuild/protobuf@1.10.0) '@types/chrome': specifier: ^0.0.263 version: 0.0.263 @@ -354,10 +354,10 @@ packages: cpu: [x64] os: [win32] - '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240613143006-244927bc441a.1': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-1.10.0-20240613143006-244927bc441a.1.tgz} + '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-00000000000000-aaea594f36a5.2': + resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-2.0.0-00000000000000-aaea594f36a5.2.tgz} peerDependencies: - '@bufbuild/protobuf': ^1.10.0 + '@bufbuild/protobuf': ^2.0.0 '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} @@ -3306,7 +3306,7 @@ snapshots: '@biomejs/cli-win32-x64@1.8.2': optional: true - '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240613143006-244927bc441a.1(@bufbuild/protobuf@1.10.0)': + '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-00000000000000-aaea594f36a5.2(@bufbuild/protobuf@1.10.0)': dependencies: '@bufbuild/protobuf': 1.10.0 From f64b96527e0806d9dce99f7d3bf7c1872117a6c8 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 17:18:55 +0200 Subject: [PATCH 05/47] Add security to device config store --- src/core/stores/deviceStore.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index 29ac6ee0..a716a85b 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -191,6 +191,9 @@ export const useDeviceStore = create((set, get) => ({ device.config.bluetooth = config.payloadVariant.value; break; } + case "security": { + device.config.security = config.payloadVariant.value; + } } } }), From d3836a7250ffc909bbf005b65dc975678b2518b5 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 17:19:36 +0200 Subject: [PATCH 06/47] Add class SecurityValidation --- src/validation/config/security.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/validation/config/security.ts diff --git a/src/validation/config/security.ts b/src/validation/config/security.ts new file mode 100644 index 00000000..4d501537 --- /dev/null +++ b/src/validation/config/security.ts @@ -0,0 +1,31 @@ +import type { Message } from "@bufbuild/protobuf"; +import type { Protobuf } from "@meshtastic/js"; +import { IsBoolean, IsString } from "class-validator"; + +export class SecurityValidation + implements Omit +{ + @IsBoolean() + adminChannelEnabled: boolean; + + @IsString() + adminKey: string; + + @IsBoolean() + bluetoothLoggingEnabled: boolean; + + @IsBoolean() + debugLogApiEnabled: boolean; + + @IsBoolean() + isManaged: boolean; + + @IsString() + privateKey: string; + + @IsString() + publicKey: string; + + @IsBoolean() + serialEnabled: boolean; +} \ No newline at end of file From 7d5950d6cc127237a637bd550c1ba3b1589d9615 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 17:23:42 +0200 Subject: [PATCH 07/47] Add SecurityValidation to security channel --- .../PageComponents/Config/Security.tsx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index b2f7d5f5..8553cf33 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,12 +1,47 @@ +import type { SecurityValidation } from "@app/validation/config/security.js" import { DynamicForm } from "@app/components/Form/DynamicForm.js"; +import { useDevice } from "@core/stores/deviceStore.js"; +import { Protobuf } from "@meshtastic/js"; +import { fromByteArray, toByteArray } from "base64-js"; +import { useState } from "react"; export const Security = (): JSX.Element => { - const onSubmit = (data: any) => { - console.log(data); + const { config, nodes, hardware, setWorkingConfig } = useDevice(); + + const [adminKey, setAdminKey] = useState( + fromByteArray(config.security?.adminKey ?? new Uint8Array(0)) + ); + const [privateKey, setPrivateKey] = useState( + fromByteArray(config.security?.privateKey ?? new Uint8Array(0)) + ); + const [publicKey, setPublicKey] = useState( + fromByteArray(config.security?.publicKey ?? new Uint8Array(0)) + ); + + const onSubmit = (data: SecurityValidation) => { + setWorkingConfig( + new Protobuf.Config.Config({ + payloadVariant: { + case: "security", + value: { + ...data, + adminKey: toByteArray(adminKey), + privateKey: toByteArray(privateKey), + publicKey: toByteArray(publicKey), + } + }, + }) + ) }; return ( - onSubmit={onSubmit} + defaultValues={{ + ...config.security, + adminKey: adminKey, + privateKey: privateKey, + publicKey: publicKey + }} fieldGroups={[ { label: "Security Settings", @@ -77,4 +112,4 @@ export const Security = (): JSX.Element => { ]} /> ) -}; \ No newline at end of file +}; From 6d3bf39b76542e61edad4f6e9c650f1e99016baa Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 18:06:27 +0200 Subject: [PATCH 08/47] Updated @buf/meshtastic_protobufs.bufbuild_es to lastest --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b49b2041..5c260975 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.2", - "@buf/meshtastic_protobufs.bufbuild_es": "2.0.0-00000000000000-aaea594f36a5.2", + "@buf/meshtastic_protobufs.bufbuild_es": "2.0.0-20240820152623-fac6975bbc78.2", "@types/chrome": "^0.0.263", "@types/node": "^20.14.9", "@types/react": "^18.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6bb4dad7..605247d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,8 +130,8 @@ importers: specifier: ^1.8.2 version: 1.8.2 '@buf/meshtastic_protobufs.bufbuild_es': - specifier: 2.0.0-00000000000000-aaea594f36a5.2 - version: 2.0.0-00000000000000-aaea594f36a5.2(@bufbuild/protobuf@1.10.0) + specifier: 2.0.0-20240820152623-fac6975bbc78.2 + version: 2.0.0-20240820152623-fac6975bbc78.2(@bufbuild/protobuf@1.10.0) '@types/chrome': specifier: ^0.0.263 version: 0.0.263 @@ -354,8 +354,8 @@ packages: cpu: [x64] os: [win32] - '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-00000000000000-aaea594f36a5.2': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-2.0.0-00000000000000-aaea594f36a5.2.tgz} + '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-20240820152623-fac6975bbc78.2': + resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-2.0.0-20240820152623-fac6975bbc78.2.tgz} peerDependencies: '@bufbuild/protobuf': ^2.0.0 @@ -3306,7 +3306,7 @@ snapshots: '@biomejs/cli-win32-x64@1.8.2': optional: true - '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-00000000000000-aaea594f36a5.2(@bufbuild/protobuf@1.10.0)': + '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-20240820152623-fac6975bbc78.2(@bufbuild/protobuf@1.10.0)': dependencies: '@bufbuild/protobuf': 1.10.0 From f0eae444c7ca2391c3b291cd12b496729276c0c6 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 18:10:39 +0200 Subject: [PATCH 09/47] Update descriptions in Security --- src/components/PageComponents/Config/Security.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 8553cf33..17ec99fa 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -45,7 +45,7 @@ export const Security = (): JSX.Element => { fieldGroups={[ { label: "Security Settings", - description: "Settings for the Security module", + description: "Settings for the Security configuration", fields: [ { type: "text", @@ -69,13 +69,13 @@ export const Security = (): JSX.Element => { type: "toggle", name: "adminChannelEnabled", label: "Admin Channel", - description: "Enable 'admin' channel", + description: "Allow incoming device control over the insecure legacy admin channel", }, { type: "toggle", name: "isManaged", label: "Is Managed", - description: "Enable if you want to manage this node from other nodes", + description: 'If true, device is considered to be "managed" by a mesh administrator via admin messages', }, { type: "text", @@ -93,19 +93,19 @@ export const Security = (): JSX.Element => { type: "toggle", name: "bluetoothLoggingEnabled", label: "Bluetooth Logging", - description: "Enable Bluetooth Logging", + description: "Enables device (serial style logs) over Bluetooth", }, { type: "toggle", name: "debugLogApiEnabled", label: "Debug Log API", - description: "Enable Log API", + description: "Output live debug logging over serial", }, { type: "toggle", name: "serialEnabled", label: "Serial", - description: "Enable Serial" + description: "Serial Console over the Stream API" } ], }, From af51659e71b6ce43856db93e7fcbc906289b7abe Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 18:50:19 +0200 Subject: [PATCH 10/47] Add always to disabled by in dynamics form --- src/components/Form/DynamicForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx index 61c4c0e7..9affc0c7 100644 --- a/src/components/Form/DynamicForm.tsx +++ b/src/components/Form/DynamicForm.tsx @@ -16,7 +16,7 @@ import { } from "react-hook-form"; interface DisabledBy { - fieldName: Path; + fieldName: Path | "always"; selector?: number; invert?: boolean; } @@ -66,7 +66,9 @@ export function DynamicForm({ if (!disabledBy) return false; return disabledBy.some((field) => { + if (field.fieldName === "always") return true const value = getValues(field.fieldName); + if (value === "always") return true; if (typeof value === "boolean") return field.invert ? value : !value; if (typeof value === "number") return field.invert From 65247c4f351bb7f41268298d8b3ac5b84acef0ec Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 20 Aug 2024 18:51:02 +0200 Subject: [PATCH 11/47] Update Security tab --- src/components/PageComponents/Config/Security.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 17ec99fa..ea3b2f17 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -47,18 +47,19 @@ export const Security = (): JSX.Element => { label: "Security Settings", description: "Settings for the Security configuration", fields: [ - { - type: "text", - name: "publicKey", - label: "Public Key", - description: "Sent out to other nodes on the mesh to allow them to compute a shared secret key", - }, { type: "text", name: "privateKey", label: "Private Key", description: "Used to create a shared key with a remote device", }, + { + type: "text", + name: "publicKey", + label: "Public Key", + description: "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + disabledBy: [ { fieldName: "always" } ] + }, ], }, { From 8bb0a9674479b9dda9d023fca416f1c547d11007 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Wed, 21 Aug 2024 13:13:42 +0200 Subject: [PATCH 12/47] Downgraded @buf/meshtastic_protobufs.bufbuild_es from 2.0 to 1.10 --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 5c260975..b65359d9 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.2", - "@buf/meshtastic_protobufs.bufbuild_es": "2.0.0-20240820152623-fac6975bbc78.2", + "@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240820152623-fac6975bbc78.1", "@types/chrome": "^0.0.263", "@types/node": "^20.14.9", "@types/react": "^18.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 605247d7..62578557 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,8 +130,8 @@ importers: specifier: ^1.8.2 version: 1.8.2 '@buf/meshtastic_protobufs.bufbuild_es': - specifier: 2.0.0-20240820152623-fac6975bbc78.2 - version: 2.0.0-20240820152623-fac6975bbc78.2(@bufbuild/protobuf@1.10.0) + specifier: 1.10.0-20240820152623-fac6975bbc78.1 + version: 1.10.0-20240820152623-fac6975bbc78.1(@bufbuild/protobuf@1.10.0) '@types/chrome': specifier: ^0.0.263 version: 0.0.263 @@ -354,10 +354,10 @@ packages: cpu: [x64] os: [win32] - '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-20240820152623-fac6975bbc78.2': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-2.0.0-20240820152623-fac6975bbc78.2.tgz} + '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240820152623-fac6975bbc78.1': + resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-1.10.0-20240820152623-fac6975bbc78.1.tgz} peerDependencies: - '@bufbuild/protobuf': ^2.0.0 + '@bufbuild/protobuf': ^1.10.0 '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} @@ -3306,7 +3306,7 @@ snapshots: '@biomejs/cli-win32-x64@1.8.2': optional: true - '@buf/meshtastic_protobufs.bufbuild_es@2.0.0-20240820152623-fac6975bbc78.2(@bufbuild/protobuf@1.10.0)': + '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240820152623-fac6975bbc78.1(@bufbuild/protobuf@1.10.0)': dependencies: '@bufbuild/protobuf': 1.10.0 From cf423620c4e7a8da32d1b1484f8122fc07593172 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Wed, 21 Aug 2024 17:41:24 +0200 Subject: [PATCH 13/47] Error & formating fixes --- src/components/Form/DynamicForm.tsx | 2 +- .../PageComponents/Config/Security.tsx | 40 ++++++++++--------- src/components/Sidebar.tsx | 2 +- src/pages/Map.tsx | 8 ++-- src/validation/config/bluetooth.ts | 6 ++- src/validation/config/lora.ts | 3 +- src/validation/config/power.ts | 3 +- src/validation/config/security.ts | 8 +++- src/validation/moduleConfig/storeForward.ts | 5 ++- 9 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx index 9affc0c7..58bf241e 100644 --- a/src/components/Form/DynamicForm.tsx +++ b/src/components/Form/DynamicForm.tsx @@ -66,7 +66,7 @@ export function DynamicForm({ if (!disabledBy) return false; return disabledBy.some((field) => { - if (field.fieldName === "always") return true + if (field.fieldName === "always") return true; const value = getValues(field.fieldName); if (value === "always") return true; if (typeof value === "boolean") return field.invert ? value : !value; diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index ea3b2f17..2834574c 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,5 +1,5 @@ -import type { SecurityValidation } from "@app/validation/config/security.js" import { DynamicForm } from "@app/components/Form/DynamicForm.js"; +import type { SecurityValidation } from "@app/validation/config/security.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; @@ -9,13 +9,13 @@ export const Security = (): JSX.Element => { const { config, nodes, hardware, setWorkingConfig } = useDevice(); const [adminKey, setAdminKey] = useState( - fromByteArray(config.security?.adminKey ?? new Uint8Array(0)) + fromByteArray(config.security?.adminKey ?? new Uint8Array(0)), ); const [privateKey, setPrivateKey] = useState( - fromByteArray(config.security?.privateKey ?? new Uint8Array(0)) + fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), ); const [publicKey, setPublicKey] = useState( - fromByteArray(config.security?.publicKey ?? new Uint8Array(0)) + fromByteArray(config.security?.publicKey ?? new Uint8Array(0)), ); const onSubmit = (data: SecurityValidation) => { @@ -28,10 +28,10 @@ export const Security = (): JSX.Element => { adminKey: toByteArray(adminKey), privateKey: toByteArray(privateKey), publicKey: toByteArray(publicKey), - } + }, }, - }) - ) + }), + ); }; return ( @@ -40,7 +40,7 @@ export const Security = (): JSX.Element => { ...config.security, adminKey: adminKey, privateKey: privateKey, - publicKey: publicKey + publicKey: publicKey, }} fieldGroups={[ { @@ -57,8 +57,9 @@ export const Security = (): JSX.Element => { type: "text", name: "publicKey", label: "Public Key", - description: "Sent out to other nodes on the mesh to allow them to compute a shared secret key", - disabledBy: [ { fieldName: "always" } ] + description: + "Sent out to other nodes on the mesh to allow them to compute a shared secret key", + disabledBy: [{ fieldName: "always" }], }, ], }, @@ -70,20 +71,23 @@ export const Security = (): JSX.Element => { type: "toggle", name: "adminChannelEnabled", label: "Admin Channel", - description: "Allow incoming device control over the insecure legacy admin channel", + description: + "Allow incoming device control over the insecure legacy admin channel", }, { type: "toggle", name: "isManaged", label: "Is Managed", - description: 'If true, device is considered to be "managed" by a mesh administrator via admin messages', + description: + 'If true, device is considered to be "managed" by a mesh administrator via admin messages', }, { type: "text", name: "adminKey", label: "Admin Key", - description: "The public key authorized to send admin messages to this node", - } + description: + "The public key authorized to send admin messages to this node", + }, ], }, { @@ -106,11 +110,11 @@ export const Security = (): JSX.Element => { type: "toggle", name: "serialEnabled", label: "Serial", - description: "Serial Console over the Stream API" - } + description: "Serial Console over the Stream API", + }, ], }, ]} - /> - ) + /> + ); }; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 96d820bb..116b6647 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -85,7 +85,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
- {myNode?.deviceMetrics?.voltage.toPrecision(3) ?? "UNK"} volts + {myNode?.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"} volts
diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx index 8c86480e..53df0cd4 100644 --- a/src/pages/Map.tsx +++ b/src/pages/Map.tsx @@ -144,8 +144,8 @@ export const MapPage = (): JSX.Element => { {waypoints.map((wp) => (
@@ -163,8 +163,8 @@ export const MapPage = (): JSX.Element => { return ( { diff --git a/src/validation/config/bluetooth.ts b/src/validation/config/bluetooth.ts index 65a0d0be..efaca6e9 100644 --- a/src/validation/config/bluetooth.ts +++ b/src/validation/config/bluetooth.ts @@ -3,7 +3,11 @@ import { Protobuf } from "@meshtastic/js"; import { IsBoolean, IsEnum, IsInt } from "class-validator"; export class BluetoothValidation - implements Omit + implements + Omit< + Protobuf.Config.Config_BluetoothConfig, + keyof Message | "deviceLoggingEnabled" + > { @IsBoolean() enabled: boolean; diff --git a/src/validation/config/lora.ts b/src/validation/config/lora.ts index 291a42d0..f5ef49d6 100644 --- a/src/validation/config/lora.ts +++ b/src/validation/config/lora.ts @@ -3,7 +3,8 @@ import { Protobuf } from "@meshtastic/js"; import { IsArray, IsBoolean, IsEnum, IsInt, Max, Min } from "class-validator"; export class LoRaValidation - implements Omit + implements + Omit { @IsBoolean() usePreset: boolean; diff --git a/src/validation/config/power.ts b/src/validation/config/power.ts index 3f3d097a..67bd1cd0 100644 --- a/src/validation/config/power.ts +++ b/src/validation/config/power.ts @@ -3,7 +3,8 @@ import type { Protobuf } from "@meshtastic/js"; import { IsBoolean, IsInt, IsNumber, Max, Min } from "class-validator"; export class PowerValidation - implements Omit + implements + Omit { @IsBoolean() isPowerSaving: boolean; diff --git a/src/validation/config/security.ts b/src/validation/config/security.ts index 4d501537..d1a35808 100644 --- a/src/validation/config/security.ts +++ b/src/validation/config/security.ts @@ -3,7 +3,11 @@ import type { Protobuf } from "@meshtastic/js"; import { IsBoolean, IsString } from "class-validator"; export class SecurityValidation - implements Omit + implements + Omit< + Protobuf.Config.Config_SecurityConfig, + keyof Message | "adminKey" | "privateKey" | "publicKey" + > { @IsBoolean() adminChannelEnabled: boolean; @@ -28,4 +32,4 @@ export class SecurityValidation @IsBoolean() serialEnabled: boolean; -} \ No newline at end of file +} diff --git a/src/validation/moduleConfig/storeForward.ts b/src/validation/moduleConfig/storeForward.ts index 82d9f4c2..773a2539 100644 --- a/src/validation/moduleConfig/storeForward.ts +++ b/src/validation/moduleConfig/storeForward.ts @@ -4,7 +4,10 @@ import { IsBoolean, IsInt } from "class-validator"; export class StoreForwardValidation implements - Omit + Omit< + Protobuf.ModuleConfig.ModuleConfig_StoreForwardConfig, + keyof Message | "isServer" + > { @IsBoolean() enabled: boolean; From 66fb3005758f565c89c5e9477da9f176d08b9ba1 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Wed, 21 Aug 2024 18:52:03 +0200 Subject: [PATCH 14/47] Moved prop. from Device to Security & Hide PKI Eye --- .../PageComponents/Config/Device.tsx | 19 --------- .../PageComponents/Config/Security.tsx | 42 ++++++++++++++----- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index c0f3bfc4..ce2b5ce1 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -36,19 +36,6 @@ export const Device = (): JSX.Element => { formatEnumName: true, }, }, - { - type: "toggle", - name: "serialEnabled", - label: "Serial Output Enabled", - description: "Enable the device's serial console", - }, - { - type: "toggle", - name: "debugLogEnabled", - label: "Enabled Debug Log", - description: - "Output debugging information to the device's serial port (auto disables when serial client is connected)", - }, { type: "number", name: "buttonGpio", @@ -86,12 +73,6 @@ export const Device = (): JSX.Element => { label: "Double Tap as Button Press", description: "Treat double tap as button press", }, - { - type: "toggle", - name: "isManaged", - label: "Managed", - description: "Is this device managed by a mesh administator", - }, { type: "toggle", name: "disableTripleClick", diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 2834574c..c9e1175c 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -3,20 +3,23 @@ import type { SecurityValidation } from "@app/validation/config/security.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; +import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; export const Security = (): JSX.Element => { const { config, nodes, hardware, setWorkingConfig } = useDevice(); - const [adminKey, setAdminKey] = useState( - fromByteArray(config.security?.adminKey ?? new Uint8Array(0)), - ); const [privateKey, setPrivateKey] = useState( fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), ); + const [privateKeyVisible, setPrivateKeyVisible] = useState(false); const [publicKey, setPublicKey] = useState( fromByteArray(config.security?.publicKey ?? new Uint8Array(0)), ); + const [adminKey, setAdminKey] = useState( + fromByteArray(config.security?.adminKey ?? new Uint8Array(0)), + ); + const [adminKeyVisible, setAdminKeyVisible] = useState(false); const onSubmit = (data: SecurityValidation) => { setWorkingConfig( @@ -48,10 +51,22 @@ export const Security = (): JSX.Element => { description: "Settings for the Security configuration", fields: [ { - type: "text", + type: privateKeyVisible ? "text" : "password", name: "privateKey", label: "Private Key", description: "Used to create a shared key with a remote device", + disabledBy: [ + { + fieldName: "adminChannelEnabled", + invert: true, + }, + ], + properties: { + action: { + icon: privateKeyVisible ? EyeOff : Eye, + onClick: () => setPrivateKeyVisible(!privateKeyVisible), + }, + }, }, { type: "text", @@ -70,21 +85,28 @@ export const Security = (): JSX.Element => { { type: "toggle", name: "adminChannelEnabled", - label: "Admin Channel", + label: "Allow Legacy Admin", description: "Allow incoming device control over the insecure legacy admin channel", }, { type: "toggle", name: "isManaged", - label: "Is Managed", + label: "Managed", description: 'If true, device is considered to be "managed" by a mesh administrator via admin messages', }, { - type: "text", + type: adminKeyVisible ? "text" : "password", name: "adminKey", label: "Admin Key", + disabledBy: [{ fieldName: "adminChannelEnabled" }], + properties: { + action: { + icon: adminKeyVisible ? EyeOff : Eye, + onClick: () => setAdminKeyVisible(!adminKeyVisible), + }, + }, description: "The public key authorized to send admin messages to this node", }, @@ -97,19 +119,19 @@ export const Security = (): JSX.Element => { { type: "toggle", name: "bluetoothLoggingEnabled", - label: "Bluetooth Logging", + label: "Allow Bluetooth Logging", description: "Enables device (serial style logs) over Bluetooth", }, { type: "toggle", name: "debugLogApiEnabled", - label: "Debug Log API", + label: "Enable Debug Log API", description: "Output live debug logging over serial", }, { type: "toggle", name: "serialEnabled", - label: "Serial", + label: "Serial Output Enabled", description: "Serial Console over the Stream API", }, ], From 02a63c213eba55e835da16057e75ada4cdd0e5a4 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 22 Aug 2024 14:08:17 +0200 Subject: [PATCH 15/47] Add key verification & generation --- .../PageComponents/Config/Security.tsx | 110 ++++++++++++++++-- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index c9e1175c..c1739aba 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -3,6 +3,7 @@ import type { SecurityValidation } from "@app/validation/config/security.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; +import cryptoRandomString from "crypto-random-string"; import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; @@ -13,6 +14,11 @@ export const Security = (): JSX.Element => { fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), ); const [privateKeyVisible, setPrivateKeyVisible] = useState(false); + const [privateKeyBitCount, setPrivateKeyBitCount] = useState( + config.security?.privateKey.length ?? 16, + ); + const [privateKeyValidationText, setPrivateKeyValidationText] = + useState(); const [publicKey, setPublicKey] = useState( fromByteArray(config.security?.publicKey ?? new Uint8Array(0)), ); @@ -20,8 +26,15 @@ export const Security = (): JSX.Element => { fromByteArray(config.security?.adminKey ?? new Uint8Array(0)), ); const [adminKeyVisible, setAdminKeyVisible] = useState(false); + const [adminKeyBitCount, setAdminKeyBitCount] = useState( + config.security?.adminKey.length ?? 16, + ); + const [adminKeyValidationText, setAdminKeyValidationText] = + useState(); const onSubmit = (data: SecurityValidation) => { + if (privateKeyValidationText || adminKeyValidationText) return; + setWorkingConfig( new Protobuf.Config.Config({ payloadVariant: { @@ -36,14 +49,75 @@ export const Security = (): JSX.Element => { }), ); }; + + const clickEvent = ( + setKey: (value: React.SetStateAction) => void, + bitCount: number, + setValidationText: ( + value: React.SetStateAction, + ) => void, + ) => { + setKey( + btoa( + cryptoRandomString({ + length: bitCount ?? 0, + type: "alphanumeric", + }), + ), + ); + setValidationText(undefined); + }; + + const validatePass = ( + input: string, + count: number, + setValidationText: ( + value: React.SetStateAction, + ) => void, + ) => { + if (input.length % 4 !== 0 || toByteArray(input).length !== count) { + setValidationText(`Please enter a valid ${count * 8} bit PSK.`); + } else { + setValidationText(undefined); + } + }; + + const privateKeyInputChangeEvent = ( + e: React.ChangeEvent, + ) => { + const psk = e.currentTarget?.value; + setPrivateKey(psk); + validatePass(psk, privateKeyBitCount, setPrivateKeyValidationText); + }; + + const adminKeyInputChangeEvent = (e: React.ChangeEvent) => { + const psk = e.currentTarget?.value; + setAdminKey(psk); + validatePass(psk, privateKeyBitCount, setAdminKeyValidationText); + }; + + const privateKeySelectChangeEvent = (e: string) => { + const count = Number.parseInt(e); + setPrivateKeyBitCount(count); + validatePass(privateKey, count, setPrivateKeyValidationText); + }; + + const adminKeySelectChangeEvent = (e: string) => { + const count = Number.parseInt(e); + setAdminKeyBitCount(count); + validatePass(privateKey, count, setAdminKeyValidationText); + }; + return ( onSubmit={onSubmit} defaultValues={{ ...config.security, - adminKey: adminKey, - privateKey: privateKey, - publicKey: publicKey, + ...{ + adminKey: adminKey, + privateKey: privateKey, + publicKey: publicKey, + }, }} fieldGroups={[ { @@ -51,10 +125,20 @@ export const Security = (): JSX.Element => { description: "Settings for the Security configuration", fields: [ { - type: privateKeyVisible ? "text" : "password", + type: "passwordGenerator", name: "privateKey", label: "Private Key", description: "Used to create a shared key with a remote device", + validationText: privateKeyValidationText, + devicePSKBitCount: privateKeyBitCount, + inputChange: privateKeyInputChangeEvent, + selectChange: privateKeySelectChangeEvent, + buttonClick: () => + clickEvent( + setPrivateKey, + privateKeyBitCount, + setPrivateKeyValidationText, + ), disabledBy: [ { fieldName: "adminChannelEnabled", @@ -62,6 +146,7 @@ export const Security = (): JSX.Element => { }, ], properties: { + value: privateKey, action: { icon: privateKeyVisible ? EyeOff : Eye, onClick: () => setPrivateKeyVisible(!privateKeyVisible), @@ -97,18 +182,29 @@ export const Security = (): JSX.Element => { 'If true, device is considered to be "managed" by a mesh administrator via admin messages', }, { - type: adminKeyVisible ? "text" : "password", + type: "passwordGenerator", name: "adminKey", label: "Admin Key", + description: + "The public key authorized to send admin messages to this node", + validationText: adminKeyValidationText, + devicePSKBitCount: adminKeyBitCount, + inputChange: adminKeyInputChangeEvent, + selectChange: adminKeySelectChangeEvent, + buttonClick: () => + clickEvent( + setAdminKey, + adminKeyBitCount, + setAdminKeyValidationText, + ), disabledBy: [{ fieldName: "adminChannelEnabled" }], properties: { + value: adminKey, action: { icon: adminKeyVisible ? EyeOff : Eye, onClick: () => setAdminKeyVisible(!adminKeyVisible), }, }, - description: - "The public key authorized to send admin messages to this node", }, ], }, From e3fad3015fa489a1dbe169de12aa95e367aee324 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 22 Aug 2024 15:45:11 +0200 Subject: [PATCH 16/47] Add hide toggle to password generator --- src/components/Form/FormPasswordGenerator.tsx | 4 ++++ .../PageComponents/Config/Security.tsx | 2 ++ src/components/UI/Generator.tsx | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index d015d3af..a94b0215 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -8,6 +8,7 @@ import { Controller, type FieldValues } from "react-hook-form"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { type: "passwordGenerator"; + hide?: boolean; devicePSKBitCount: number; inputChange: ChangeEventHandler; selectChange: (event: string) => void; @@ -17,6 +18,7 @@ export interface PasswordGeneratorProps extends BaseFormBuilderProps { export function PasswordGenerator({ control, field, + disabled, }: GenericFormElementProps>) { return ( ({ control={control} render={({ field: { value, ...rest } }) => ( ({ buttonText="Generate" {...field.properties} {...rest} + disabled={disabled} /> )} /> diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index c1739aba..a82ed980 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -133,6 +133,7 @@ export const Security = (): JSX.Element => { devicePSKBitCount: privateKeyBitCount, inputChange: privateKeyInputChangeEvent, selectChange: privateKeySelectChangeEvent, + hide: !privateKeyVisible, buttonClick: () => clickEvent( setPrivateKey, @@ -191,6 +192,7 @@ export const Security = (): JSX.Element => { devicePSKBitCount: adminKeyBitCount, inputChange: adminKeyInputChangeEvent, selectChange: adminKeySelectChangeEvent, + hide: !adminKeyVisible, buttonClick: () => clickEvent( setAdminKey, diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index 344e89bb..0e8b1e0d 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -9,8 +9,10 @@ import { SelectTrigger, SelectValue, } from "@components/UI/Select.js"; +import type { LucideIcon } from "lucide-react"; export interface GeneratorProps extends React.BaseHTMLAttributes { + hide?: boolean; devicePSKBitCount?: number; value: string; variant: "default" | "invalid"; @@ -18,11 +20,17 @@ export interface GeneratorProps extends React.BaseHTMLAttributes { selectChange: (event: string) => void; inputChange: (event: React.ChangeEvent) => void; buttonClick: React.MouseEventHandler; + action?: { + icon: LucideIcon; + onClick: () => void; + }; + disabled?: boolean; } const Generator = React.forwardRef( ( { + hide = true, devicePSKBitCount, variant, value, @@ -30,6 +38,8 @@ const Generator = React.forwardRef( selectChange, inputChange, buttonClick, + action, + disabled, ...props }, ref, @@ -37,17 +47,19 @@ const Generator = React.forwardRef( return ( <>