Browse Source

Refactoring Security Config

pull/619/head
philon- 1 year ago
parent
commit
d8d21ad58a
  1. 17
      src/components/Form/FormToggle.tsx
  2. 149
      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,
} from "@components/Form/DynamicForm.tsx";
import { Switch } from "@components/UI/Switch.tsx";
import type { ChangeEvent } from "react";
import { Controller, type FieldValues } from "react-hook-form";
export interface ToggleFieldProps<T> extends BaseFormBuilderProps<T> {
type: "toggle";
inputChange?: (value: boolean) => void;
}
export function ToggleInput<T extends FieldValues>({
@ -15,16 +15,6 @@ export function ToggleInput<T extends FieldValues>({
disabled,
field,
}: GenericFormElementProps<T, ToggleFieldProps<T>>) {
const onChangeHandler = (e: (event: ChangeEvent) => void) => {
return (value: boolean) => {
e({
target: {
value: value,
},
} as unknown as ChangeEvent);
};
};
return (
<Controller
name={field.name}
@ -32,7 +22,10 @@ export function ToggleInput<T extends FieldValues>({
render={({ field: { value, onChange, ...rest } }) => (
<Switch
checked={value}
onCheckedChange={onChangeHandler(onChange)}
onCheckedChange={(v) => {
onChange(v);
field.inputChange?.(v);
}}
id={field.name}
disabled={disabled}
{...field.properties}

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

@ -9,6 +9,7 @@ import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js";
import { useReducer } from "react";
import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
import type { SecurityConfigInit } from "./types.ts";
export const Security = () => {
const { config, setWorkingConfig, setDialogOpen } = useDevice();
@ -33,6 +34,10 @@ export const Security = () => {
fromByteArray(config.security?.adminKey?.at(2) ?? new Uint8Array(0)),
],
privateKeyDialogOpen: false,
isManaged: config.security?.isManaged ?? false,
adminChannelEnabled: config.security?.adminChannelEnabled ?? false,
debugLogApiEnabled: config.security?.debugLogApiEnabled ?? false,
serialEnabled: config.security?.serialEnabled ?? false,
});
const validateKey = (
@ -44,7 +49,6 @@ export const Security = () => {
const fieldNameKey = fieldName + (fieldIndex ?? "");
try {
removeError(fieldNameKey);
if (fieldName === "privateKey" && input === "") {
addError(fieldNameKey, "Private Key is required");
return;
@ -80,29 +84,24 @@ export const Security = () => {
}
};
const onSubmit = (data: SecurityValidation) => {
if (hasErrors()) {
return;
}
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: {
...data,
adminKey: [
toByteArray(state.adminKey[0]),
toByteArray(state.adminKey[1]),
toByteArray(state.adminKey[2]),
],
privateKey: toByteArray(state.privateKey),
publicKey: toByteArray(state.publicKey),
},
},
}),
);
};
function buildSecurityPayload(
overrides: SecurityConfigInit,
) {
const base: SecurityConfigInit = {
isManaged: state.isManaged,
adminChannelEnabled: state.adminChannelEnabled,
debugLogApiEnabled: state.debugLogApiEnabled,
serialEnabled: state.serialEnabled,
privateKey: overrides?.privateKey ?? toByteArray(state.privateKey),
publicKey: overrides?.publicKey ?? toByteArray(state.publicKey),
adminKey: [
overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
overrides?.adminKey?.[0] ?? toByteArray(state.adminKey[0]),
],
};
return { ...base, ...overrides };
}
const pkiRegenerate = () => {
clearErrors();
@ -122,6 +121,20 @@ export const Security = () => {
state.privateKeyBitCount,
"privateKey",
);
if (!hasErrors()) {
setWorkingConfig(
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "security",
value: buildSecurityPayload({
privateKey: privateKey,
publicKey: publicKey,
}),
},
}),
);
}
};
const privateKeyInputChangeEvent = (
@ -133,6 +146,20 @@ export const Security = () => {
const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String));
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 = (
@ -150,18 +177,62 @@ export const Security = () => {
dispatch({ type: "SET_ADMIN_KEY", payload: payload });
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 count = Number.parseInt(e);
dispatch({ type: "SET_PRIVATE_KEY_BIT_COUNT", payload: count });
validateKey(state.privateKey, count, "privateKey");
const onToggleChange = (
field:
| "isManaged"
| "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 (
<>
<DynamicForm<SecurityValidation>
onSubmit={onSubmit}
onSubmit={() => {}}
submitType="onChange"
defaultValues={{
...config.security,
@ -192,7 +263,7 @@ export const Security = () => {
: "",
devicePSKBitCount: state.privateKeyBitCount,
inputChange: privateKeyInputChangeEvent,
selectChange: privateKeySelectChangeEvent,
selectChange: () => {},
hide: !state.privateKeyVisible,
actionButtons: [
{
@ -315,6 +386,10 @@ export const Security = () => {
label: "Managed",
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.",
inputChange: (e: boolean) => onToggleChange("isManaged", e),
properties: {
checked: state.isManaged,
},
},
{
type: "toggle",
@ -322,6 +397,11 @@ export const Security = () => {
label: "Allow Legacy Admin",
description:
"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",
description:
"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",
name: "serialEnabled",
label: "Serial Output Enabled",
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) {
case "SET_PRIVATE_KEY":
return { ...state, privateKey: action.payload };
case "TOGGLE_PRIVATE_KEY_VISIBILITY":
return { ...state, privateKeyVisible: !state.privateKeyVisible };
case "SET_PRIVATE_KEY_BIT_COUNT":
return { ...state, privateKeyBitCount: action.payload };
case "SET_PUBLIC_KEY":
@ -24,6 +22,8 @@ export function securityReducer(
publicKey: action.payload.publicKey,
privateKeyDialogOpen: false,
};
case "SET_TOGGLE":
return { ...state, [action.field]: action.payload };
default:
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 {
privateKey: string;
privateKeyVisible: boolean;
@ -6,16 +9,27 @@ export interface SecurityState {
publicKey: string;
adminKey: [string, string, string];
privateKeyDialogOpen: boolean;
isManaged: boolean;
adminChannelEnabled: boolean;
debugLogApiEnabled: boolean;
serialEnabled: boolean;
}
export type SecurityAction =
| { type: "SET_PRIVATE_KEY"; payload: string }
| { type: "TOGGLE_PRIVATE_KEY_VISIBILITY" }
| { type: "SET_PRIVATE_KEY_BIT_COUNT"; payload: number }
| { type: "SET_PUBLIC_KEY"; payload: string }
| { type: "SET_ADMIN_KEY"; payload: [string, string, string] }
| { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean }
| { type: "SET_TOGGLE"; payload: boolean; field: string }
| {
type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: string };
};
export type SecurityConfigInit = Extract<
MessageInitShape<
typeof Protobuf.Config.ConfigSchema
>["payloadVariant"],
{ case: "security" }
>["value"];

Loading…
Cancel
Save