Browse Source

Refactoring Security Config

pull/619/head
philon- 1 year ago
parent
commit
d8d21ad58a
  1. 17
      src/components/Form/FormToggle.tsx
  2. 143
      src/components/PageComponents/Config/Security/Security.tsx
  3. 4
      src/components/PageComponents/Config/Security/securityReducer.tsx
  4. 16
      src/components/PageComponents/Config/Security/types.ts

17
src/components/Form/FormToggle.tsx

@ -3,11 +3,11 @@ import type {
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx"; } from "@components/Form/DynamicForm.tsx";
import { Switch } from "@components/UI/Switch.tsx"; import { Switch } from "@components/UI/Switch.tsx";
import type { ChangeEvent } from "react";
import { Controller, type FieldValues } from "react-hook-form"; import { Controller, type FieldValues } from "react-hook-form";
export interface ToggleFieldProps<T> extends BaseFormBuilderProps<T> { export interface ToggleFieldProps<T> extends BaseFormBuilderProps<T> {
type: "toggle"; type: "toggle";
inputChange?: (value: boolean) => void;
} }
export function ToggleInput<T extends FieldValues>({ export function ToggleInput<T extends FieldValues>({
@ -15,16 +15,6 @@ export function ToggleInput<T extends FieldValues>({
disabled, disabled,
field, field,
}: GenericFormElementProps<T, ToggleFieldProps<T>>) { }: GenericFormElementProps<T, ToggleFieldProps<T>>) {
const onChangeHandler = (e: (event: ChangeEvent) => void) => {
return (value: boolean) => {
e({
target: {
value: value,
},
} as unknown as ChangeEvent);
};
};
return ( return (
<Controller <Controller
name={field.name} name={field.name}
@ -32,7 +22,10 @@ export function ToggleInput<T extends FieldValues>({
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { value, onChange, ...rest } }) => (
<Switch <Switch
checked={value} checked={value}
onCheckedChange={onChangeHandler(onChange)} onCheckedChange={(v) => {
onChange(v);
field.inputChange?.(v);
}}
id={field.name} id={field.name}
disabled={disabled} disabled={disabled}
{...field.properties} {...field.properties}

143
src/components/PageComponents/Config/Security/Security.tsx

@ -9,6 +9,7 @@ import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { useReducer } from "react"; import { useReducer } from "react";
import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx"; import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
import type { SecurityConfigInit } from "./types.ts";
export const Security = () => { export const Security = () => {
const { config, setWorkingConfig, setDialogOpen } = useDevice(); const { config, setWorkingConfig, setDialogOpen } = useDevice();
@ -33,6 +34,10 @@ export const Security = () => {
fromByteArray(config.security?.adminKey?.at(2) ?? new Uint8Array(0)), fromByteArray(config.security?.adminKey?.at(2) ?? new Uint8Array(0)),
], ],
privateKeyDialogOpen: false, privateKeyDialogOpen: false,
isManaged: config.security?.isManaged ?? false,
adminChannelEnabled: config.security?.adminChannelEnabled ?? false,
debugLogApiEnabled: config.security?.debugLogApiEnabled ?? false,
serialEnabled: config.security?.serialEnabled ?? false,
}); });
const validateKey = ( const validateKey = (
@ -44,7 +49,6 @@ export const Security = () => {
const fieldNameKey = fieldName + (fieldIndex ?? ""); const fieldNameKey = fieldName + (fieldIndex ?? "");
try { try {
removeError(fieldNameKey); removeError(fieldNameKey);
if (fieldName === "privateKey" && input === "") { if (fieldName === "privateKey" && input === "") {
addError(fieldNameKey, "Private Key is required"); addError(fieldNameKey, "Private Key is required");
return; return;
@ -80,29 +84,24 @@ export const Security = () => {
} }
}; };
const onSubmit = (data: SecurityValidation) => { function buildSecurityPayload(
if (hasErrors()) { overrides: SecurityConfigInit,
return; ) {
} const base: SecurityConfigInit = {
isManaged: state.isManaged,
setWorkingConfig( adminChannelEnabled: state.adminChannelEnabled,
create(Protobuf.Config.ConfigSchema, { debugLogApiEnabled: state.debugLogApiEnabled,
payloadVariant: { serialEnabled: state.serialEnabled,
case: "security", privateKey: overrides?.privateKey ?? toByteArray(state.privateKey),
value: { publicKey: overrides?.publicKey ?? toByteArray(state.publicKey),
...data,
adminKey: [ adminKey: [
toByteArray(state.adminKey[0]), overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
toByteArray(state.adminKey[1]), overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
toByteArray(state.adminKey[2]), overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
], ],
privateKey: toByteArray(state.privateKey),
publicKey: toByteArray(state.publicKey),
},
},
}),
);
}; };
return { ...base, ...overrides };
}
const pkiRegenerate = () => { const pkiRegenerate = () => {
clearErrors(); clearErrors();
@ -122,6 +121,20 @@ export const Security = () => {
state.privateKeyBitCount, state.privateKeyBitCount,
"privateKey", "privateKey",
); );
if (!hasErrors()) {
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: buildSecurityPayload({
privateKey: privateKey,
publicKey: publicKey,
}),
},
}),
);
}
}; };
const privateKeyInputChangeEvent = ( const privateKeyInputChangeEvent = (
@ -133,6 +146,20 @@ export const Security = () => {
const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String)); const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String));
dispatch({ type: "SET_PUBLIC_KEY", payload: fromByteArray(publicKey) }); dispatch({ type: "SET_PUBLIC_KEY", payload: fromByteArray(publicKey) });
if (!hasErrors()) {
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: buildSecurityPayload({
privateKey: toByteArray(privateKeyB64String),
publicKey: publicKey,
}),
},
}),
);
}
}; };
const adminKeyInputChangeEvent = ( const adminKeyInputChangeEvent = (
@ -150,18 +177,62 @@ export const Security = () => {
dispatch({ type: "SET_ADMIN_KEY", payload: payload }); dispatch({ type: "SET_ADMIN_KEY", payload: payload });
validateKey(psk, state.privateKeyBitCount, "adminKey", fieldIndex); validateKey(psk, state.privateKeyBitCount, "adminKey", fieldIndex);
if (!hasErrors()) {
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: buildSecurityPayload({
adminKey: payload.map(toByteArray) as [
Uint8Array,
Uint8Array,
Uint8Array,
],
}),
},
}),
);
}
}; };
const privateKeySelectChangeEvent = (e: string) => { const onToggleChange = (
const count = Number.parseInt(e); field:
dispatch({ type: "SET_PRIVATE_KEY_BIT_COUNT", payload: count }); | "isManaged"
validateKey(state.privateKey, count, "privateKey"); | "adminChannelEnabled"
| "debugLogApiEnabled"
| "serialEnabled",
next: boolean,
) => {
dispatch({ type: "SET_TOGGLE", field, payload: next });
if (!hasErrors()) {
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: buildSecurityPayload({
isManaged: field === "isManaged" ? next : state.isManaged,
adminChannelEnabled: field === "adminChannelEnabled"
? next
: state.adminChannelEnabled,
debugLogApiEnabled: field === "debugLogApiEnabled"
? next
: state.debugLogApiEnabled,
serialEnabled: field === "serialEnabled"
? next
: state.serialEnabled,
}),
},
}),
);
}
}; };
return ( return (
<> <>
<DynamicForm<SecurityValidation> <DynamicForm<SecurityValidation>
onSubmit={onSubmit} onSubmit={() => {}}
submitType="onChange" submitType="onChange"
defaultValues={{ defaultValues={{
...config.security, ...config.security,
@ -192,7 +263,7 @@ export const Security = () => {
: "", : "",
devicePSKBitCount: state.privateKeyBitCount, devicePSKBitCount: state.privateKeyBitCount,
inputChange: privateKeyInputChangeEvent, inputChange: privateKeyInputChangeEvent,
selectChange: privateKeySelectChangeEvent, selectChange: () => {},
hide: !state.privateKeyVisible, hide: !state.privateKeyVisible,
actionButtons: [ actionButtons: [
{ {
@ -315,6 +386,10 @@ export const Security = () => {
label: "Managed", label: "Managed",
description: description:
"If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.", "If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.",
inputChange: (e: boolean) => onToggleChange("isManaged", e),
properties: {
checked: state.isManaged,
},
}, },
{ {
type: "toggle", type: "toggle",
@ -322,6 +397,11 @@ export const Security = () => {
label: "Allow Legacy Admin", label: "Allow Legacy Admin",
description: description:
"Allow incoming device control over the insecure legacy admin channel", "Allow incoming device control over the insecure legacy admin channel",
inputChange: (e: boolean) =>
onToggleChange("adminChannelEnabled", e),
properties: {
checked: state.adminChannelEnabled,
},
}, },
], ],
}, },
@ -335,12 +415,21 @@ export const Security = () => {
label: "Enable Debug Log API", label: "Enable Debug Log API",
description: description:
"Output live debug logging over serial, view and export position-redacted device logs over Bluetooth", "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
inputChange: (e: boolean) =>
onToggleChange("debugLogApiEnabled", e),
properties: {
checked: state.debugLogApiEnabled,
},
}, },
{ {
type: "toggle", type: "toggle",
name: "serialEnabled", name: "serialEnabled",
label: "Serial Output Enabled", label: "Serial Output Enabled",
description: "Serial Console over the Stream API", description: "Serial Console over the Stream API",
inputChange: (e: boolean) => onToggleChange("serialEnabled", e),
properties: {
checked: state.serialEnabled,
},
}, },
], ],
}, },

4
src/components/PageComponents/Config/Security/securityReducer.tsx

@ -7,8 +7,6 @@ export function securityReducer(
switch (action.type) { switch (action.type) {
case "SET_PRIVATE_KEY": case "SET_PRIVATE_KEY":
return { ...state, privateKey: action.payload }; return { ...state, privateKey: action.payload };
case "TOGGLE_PRIVATE_KEY_VISIBILITY":
return { ...state, privateKeyVisible: !state.privateKeyVisible };
case "SET_PRIVATE_KEY_BIT_COUNT": case "SET_PRIVATE_KEY_BIT_COUNT":
return { ...state, privateKeyBitCount: action.payload }; return { ...state, privateKeyBitCount: action.payload };
case "SET_PUBLIC_KEY": case "SET_PUBLIC_KEY":
@ -24,6 +22,8 @@ export function securityReducer(
publicKey: action.payload.publicKey, publicKey: action.payload.publicKey,
privateKeyDialogOpen: false, privateKeyDialogOpen: false,
}; };
case "SET_TOGGLE":
return { ...state, [action.field]: action.payload };
default: default:
return state; return state;
} }

16
src/components/PageComponents/Config/Security/types.ts

@ -1,3 +1,6 @@
import { type MessageInitShape } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/core";
export interface SecurityState { export interface SecurityState {
privateKey: string; privateKey: string;
privateKeyVisible: boolean; privateKeyVisible: boolean;
@ -6,16 +9,27 @@ export interface SecurityState {
publicKey: string; publicKey: string;
adminKey: [string, string, string]; adminKey: [string, string, string];
privateKeyDialogOpen: boolean; privateKeyDialogOpen: boolean;
isManaged: boolean;
adminChannelEnabled: boolean;
debugLogApiEnabled: boolean;
serialEnabled: boolean;
} }
export type SecurityAction = export type SecurityAction =
| { type: "SET_PRIVATE_KEY"; payload: string } | { type: "SET_PRIVATE_KEY"; payload: string }
| { type: "TOGGLE_PRIVATE_KEY_VISIBILITY" }
| { type: "SET_PRIVATE_KEY_BIT_COUNT"; payload: number } | { type: "SET_PRIVATE_KEY_BIT_COUNT"; payload: number }
| { type: "SET_PUBLIC_KEY"; payload: string } | { type: "SET_PUBLIC_KEY"; payload: string }
| { type: "SET_ADMIN_KEY"; payload: [string, string, string] } | { type: "SET_ADMIN_KEY"; payload: [string, string, string] }
| { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean } | { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean }
| { type: "SET_TOGGLE"; payload: boolean; field: string }
| { | {
type: "REGENERATE_PRIV_PUB_KEY"; type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: string }; payload: { privateKey: string; publicKey: string };
}; };
export type SecurityConfigInit = Extract<
MessageInitShape<
typeof Protobuf.Config.ConfigSchema
>["payloadVariant"],
{ case: "security" }
>["value"];

Loading…
Cancel
Save