|
|
|
@ -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<boolean>(false); |
|
|
|
const [privateKeyBitCount, setPrivateKeyBitCount] = useState<number>( |
|
|
|
config.security?.privateKey.length ?? 16, |
|
|
|
); |
|
|
|
const [privateKeyValidationText, setPrivateKeyValidationText] = |
|
|
|
useState<string>(); |
|
|
|
const [publicKey, setPublicKey] = useState<string>( |
|
|
|
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<boolean>(false); |
|
|
|
const [adminKeyBitCount, setAdminKeyBitCount] = useState<number>( |
|
|
|
config.security?.adminKey.length ?? 16, |
|
|
|
); |
|
|
|
const [adminKeyValidationText, setAdminKeyValidationText] = |
|
|
|
useState<string>(); |
|
|
|
|
|
|
|
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<string>) => void, |
|
|
|
bitCount: number, |
|
|
|
setValidationText: ( |
|
|
|
value: React.SetStateAction<string | undefined>, |
|
|
|
) => void, |
|
|
|
) => { |
|
|
|
setKey( |
|
|
|
btoa( |
|
|
|
cryptoRandomString({ |
|
|
|
length: bitCount ?? 0, |
|
|
|
type: "alphanumeric", |
|
|
|
}), |
|
|
|
), |
|
|
|
); |
|
|
|
setValidationText(undefined); |
|
|
|
}; |
|
|
|
|
|
|
|
const validatePass = ( |
|
|
|
input: string, |
|
|
|
count: number, |
|
|
|
setValidationText: ( |
|
|
|
value: React.SetStateAction<string | undefined>, |
|
|
|
) => 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<HTMLInputElement>, |
|
|
|
) => { |
|
|
|
const psk = e.currentTarget?.value; |
|
|
|
setPrivateKey(psk); |
|
|
|
validatePass(psk, privateKeyBitCount, setPrivateKeyValidationText); |
|
|
|
}; |
|
|
|
|
|
|
|
const adminKeyInputChangeEvent = (e: React.ChangeEvent<HTMLInputElement>) => { |
|
|
|
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 ( |
|
|
|
<DynamicForm<SecurityValidation> |
|
|
|
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", |
|
|
|
}, |
|
|
|
], |
|
|
|
}, |
|
|
|
|