|
|
@ -1,5 +1,8 @@ |
|
|
import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx"; |
|
|
import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx"; |
|
|
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; |
|
|
import { |
|
|
|
|
|
DynamicForm, |
|
|
|
|
|
type DynamicFormFormInit, |
|
|
|
|
|
} from "@components/Form/DynamicForm.tsx"; |
|
|
import { useAppStore } from "@core/stores/appStore.ts"; |
|
|
import { useAppStore } from "@core/stores/appStore.ts"; |
|
|
import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts"; |
|
|
import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts"; |
|
|
import { |
|
|
import { |
|
|
@ -7,37 +10,75 @@ import { |
|
|
type RawSecurity, |
|
|
type RawSecurity, |
|
|
RawSecuritySchema, |
|
|
RawSecuritySchema, |
|
|
} from "@app/validation/config/security.ts"; |
|
|
} from "@app/validation/config/security.ts"; |
|
|
import { useState } from "react"; |
|
|
import { useEffect, useState } from "react"; |
|
|
import { create } from "@bufbuild/protobuf"; |
|
|
import { create } from "@bufbuild/protobuf"; |
|
|
import { useDevice } from "@core/stores/deviceStore.ts"; |
|
|
import { useDevice } from "@core/stores/deviceStore.ts"; |
|
|
import { Protobuf } from "@meshtastic/core"; |
|
|
import { Protobuf } from "@meshtastic/core"; |
|
|
import { fromByteArray, toByteArray } from "base64-js"; |
|
|
import { fromByteArray, toByteArray } from "base64-js"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
|
import { type DefaultValues, useForm } from "react-hook-form"; |
|
|
|
|
|
import { createZodResolver } from "@components/Form/createZodResolver.ts"; |
|
|
|
|
|
interface SecurityConfigProps { |
|
|
|
|
|
onFormInit: DynamicFormFormInit<RawSecurity>; |
|
|
|
|
|
} |
|
|
|
|
|
export const Security = ({ onFormInit }: SecurityConfigProps) => { |
|
|
|
|
|
const { |
|
|
|
|
|
setWorkingConfig, |
|
|
|
|
|
setDialogOpen, |
|
|
|
|
|
getEffectiveConfig, |
|
|
|
|
|
} = useDevice(); |
|
|
|
|
|
|
|
|
type KeyState = { |
|
|
|
|
|
publicKey: string; |
|
|
|
|
|
privateKey: string; |
|
|
|
|
|
privateKeyDialogOpen: boolean; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const Security = () => { |
|
|
|
|
|
const { config, setWorkingConfig, setDialogOpen } = useDevice(); |
|
|
|
|
|
const { removeError } = useAppStore(); |
|
|
const { removeError } = useAppStore(); |
|
|
const { t } = useTranslation("deviceConfig"); |
|
|
const { t } = useTranslation("deviceConfig"); |
|
|
|
|
|
|
|
|
const [keyState, setKeyState] = useState<KeyState>(() => ({ |
|
|
const securityConfig = getEffectiveConfig("security"); |
|
|
publicKey: fromByteArray(config?.security?.publicKey ?? new Uint8Array(0)), |
|
|
const defaultValues = { |
|
|
|
|
|
...securityConfig, |
|
|
|
|
|
...{ |
|
|
privateKey: fromByteArray( |
|
|
privateKey: fromByteArray( |
|
|
config?.security?.privateKey ?? new Uint8Array(0), |
|
|
securityConfig?.privateKey ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
publicKey: fromByteArray( |
|
|
|
|
|
securityConfig?.publicKey ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
adminKey: [ |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
securityConfig?.adminKey?.at(0) ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
securityConfig?.adminKey?.at(1) ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
securityConfig?.adminKey?.at(2) ?? new Uint8Array(0), |
|
|
), |
|
|
), |
|
|
privateKeyDialogOpen: false, |
|
|
], |
|
|
})); |
|
|
}, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const formMethods = useForm<RawSecurity>({ |
|
|
|
|
|
mode: "onChange", |
|
|
|
|
|
defaultValues: defaultValues as DefaultValues<RawSecurity>, |
|
|
|
|
|
resolver: createZodResolver(RawSecuritySchema), |
|
|
|
|
|
shouldFocusError: false, |
|
|
|
|
|
resetOptions: { keepDefaultValues: true }, |
|
|
|
|
|
}); |
|
|
|
|
|
const { setValue, formState } = formMethods; |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
onFormInit?.(formMethods); |
|
|
|
|
|
}, [onFormInit, formMethods]); |
|
|
|
|
|
|
|
|
|
|
|
const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState<boolean>( |
|
|
|
|
|
false, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
const onSubmit = (data: RawSecurity) => { |
|
|
const onSubmit = (data: RawSecurity) => { |
|
|
|
|
|
if (!formState.isReady) return; |
|
|
|
|
|
|
|
|
const payload: ParsedSecurity = { |
|
|
const payload: ParsedSecurity = { |
|
|
...data, |
|
|
...data, |
|
|
privateKey: toByteArray(keyState.privateKey), |
|
|
privateKey: toByteArray(data.privateKey), |
|
|
publicKey: toByteArray(keyState.publicKey), |
|
|
publicKey: toByteArray(data.publicKey), |
|
|
adminKey: [ |
|
|
adminKey: [ |
|
|
toByteArray(data.adminKey.at(0) ?? ""), |
|
|
toByteArray(data.adminKey.at(0) ?? ""), |
|
|
toByteArray(data.adminKey.at(1) ?? ""), |
|
|
toByteArray(data.adminKey.at(1) ?? ""), |
|
|
@ -54,18 +95,10 @@ export const Security = () => { |
|
|
}), |
|
|
}), |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const pkiRegenerate = () => { |
|
|
const pkiRegenerate = () => { |
|
|
const privateKey = getX25519PrivateKey(); |
|
|
const privateKey = getX25519PrivateKey(); |
|
|
|
|
|
|
|
|
updatePublicKey(fromByteArray(privateKey)); |
|
|
updatePublicKey(fromByteArray(privateKey)); |
|
|
|
|
|
|
|
|
setKeyState((prev) => ({ |
|
|
|
|
|
...prev, |
|
|
|
|
|
privateKey: fromByteArray(privateKey), |
|
|
|
|
|
privateKeyDialogOpen: false, |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
removeError("privateKey"); |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const updatePublicKey = (privateKey: string) => { |
|
|
const updatePublicKey = (privateKey: string) => { |
|
|
@ -73,18 +106,14 @@ export const Security = () => { |
|
|
const publicKey = fromByteArray( |
|
|
const publicKey = fromByteArray( |
|
|
getX25519PublicKey(toByteArray(privateKey)), |
|
|
getX25519PublicKey(toByteArray(privateKey)), |
|
|
); |
|
|
); |
|
|
setKeyState((prev) => ({ |
|
|
setValue("privateKey", privateKey); |
|
|
...prev, |
|
|
setValue("publicKey", publicKey); |
|
|
privateKey: privateKey, |
|
|
|
|
|
publicKey: publicKey, |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
removeError("privateKey"); |
|
|
removeError("publicKey"); |
|
|
removeError("publicKey"); |
|
|
|
|
|
setPrivateKeyDialogOpen(false); |
|
|
} catch (_e) { |
|
|
} catch (_e) { |
|
|
setKeyState((prev) => ({ |
|
|
setValue("privateKey", privateKey); |
|
|
...prev, |
|
|
|
|
|
privateKey: privateKey, |
|
|
|
|
|
})); |
|
|
|
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@ -99,31 +128,9 @@ export const Security = () => { |
|
|
return ( |
|
|
return ( |
|
|
<> |
|
|
<> |
|
|
<DynamicForm<RawSecurity> |
|
|
<DynamicForm<RawSecurity> |
|
|
|
|
|
propMethods={formMethods} |
|
|
onSubmit={onSubmit} |
|
|
onSubmit={onSubmit} |
|
|
validationSchema={RawSecuritySchema} |
|
|
|
|
|
formId="Config_SecurityConfig" |
|
|
formId="Config_SecurityConfig" |
|
|
defaultValues={{ |
|
|
|
|
|
...config.security, |
|
|
|
|
|
...{ |
|
|
|
|
|
privateKey: fromByteArray( |
|
|
|
|
|
config?.security?.privateKey ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
publicKey: fromByteArray( |
|
|
|
|
|
config?.security?.publicKey ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
adminKey: [ |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
config?.security?.adminKey.at(0) ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
config?.security?.adminKey.at(1) ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
fromByteArray( |
|
|
|
|
|
config?.security?.adminKey.at(2) ?? new Uint8Array(0), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
}, |
|
|
|
|
|
}} |
|
|
|
|
|
fieldGroups={[ |
|
|
fieldGroups={[ |
|
|
{ |
|
|
{ |
|
|
label: t("security.title"), |
|
|
label: t("security.title"), |
|
|
@ -144,11 +151,7 @@ export const Security = () => { |
|
|
actionButtons: [ |
|
|
actionButtons: [ |
|
|
{ |
|
|
{ |
|
|
text: t("button.generate"), |
|
|
text: t("button.generate"), |
|
|
onClick: () => |
|
|
onClick: () => setPrivateKeyDialogOpen(true), |
|
|
setKeyState((prev) => ({ |
|
|
|
|
|
...prev, |
|
|
|
|
|
privateKeyDialogOpen: true, |
|
|
|
|
|
})), |
|
|
|
|
|
variant: "success", |
|
|
variant: "success", |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
@ -160,8 +163,6 @@ export const Security = () => { |
|
|
properties: { |
|
|
properties: { |
|
|
showCopyButton: true, |
|
|
showCopyButton: true, |
|
|
showPasswordToggle: true, |
|
|
showPasswordToggle: true, |
|
|
|
|
|
|
|
|
value: keyState.privateKey, |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
@ -172,7 +173,6 @@ export const Security = () => { |
|
|
description: t("security.publicKey.description"), |
|
|
description: t("security.publicKey.description"), |
|
|
properties: { |
|
|
properties: { |
|
|
showCopyButton: true, |
|
|
showCopyButton: true, |
|
|
value: keyState.publicKey, |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
], |
|
|
], |
|
|
@ -275,12 +275,8 @@ export const Security = () => { |
|
|
title: t("pkiRegenerate.title"), |
|
|
title: t("pkiRegenerate.title"), |
|
|
description: t("pkiRegenerate.description"), |
|
|
description: t("pkiRegenerate.description"), |
|
|
}} |
|
|
}} |
|
|
open={keyState.privateKeyDialogOpen} |
|
|
open={privateKeyDialogOpen} |
|
|
onOpenChange={() => |
|
|
onOpenChange={() => setPrivateKeyDialogOpen(true)} |
|
|
setKeyState((prev) => ({ |
|
|
|
|
|
...prev, |
|
|
|
|
|
privateKeyDialogOpen: false, |
|
|
|
|
|
}))} |
|
|
|
|
|
onSubmit={pkiRegenerate} |
|
|
onSubmit={pkiRegenerate} |
|
|
/> |
|
|
/> |
|
|
</> |
|
|
</> |
|
|
|