From e1d2b2d0c67929535853b96949fae59f89ed32bc Mon Sep 17 00:00:00 2001 From: Brendan Osborne Date: Thu, 23 Nov 2023 14:07:09 +1000 Subject: [PATCH 01/18] fix for allowed apps/folders writing incorrectly to config file --- src-tauri/src/main.rs | 71 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a57b02e..cb0b134 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -157,7 +157,10 @@ async fn enable_wiresock(tunnel: Tunnel, app_handle: tauri::AppHandle) -> Result { let state = WIRESOCK_STATE.lock().unwrap(); if state.wiresock_status != "STOPPED" { - println!("wiresock_state at start of enable_wiresock is {:?}", &*state); + println!( + "wiresock_state at start of enable_wiresock is {:?}", + &*state + ); return Err("enable_wiresock is already running".into()); } } @@ -231,25 +234,61 @@ async fn enable_wiresock(tunnel: Tunnel, app_handle: tauri::AppHandle) -> Result ) .unwrap(); } + + // Rules + + // Allowed + + // Allowed Apps + let mut allowed_apps = String::new(); if !tunnel.rules.allowed.apps.is_empty() { - writeln!( - &mut w, - "AllowedApps = {} {}", - tunnel.rules.allowed.apps, tunnel.rules.allowed.folders - ) - .unwrap(); + allowed_apps = format!("AllowedApps = {}", tunnel.rules.allowed.apps); } - if !tunnel.rules.disallowed.apps.is_empty() { - writeln!( - &mut w, - "DisallowedApps = {} {}", - tunnel.rules.disallowed.apps, tunnel.rules.disallowed.folders - ) - .unwrap(); + + // Allowed Folders + if !tunnel.rules.allowed.folders.is_empty() { + if !allowed_apps.is_empty() { + // Ensure there is comma between the allowed apps and allowed folders + allowed_apps = format!("{}, {}", allowed_apps, tunnel.rules.allowed.folders); + } else { + allowed_apps = format!("AllowedApps = {}", tunnel.rules.allowed.folders); + } } + + // Write Allowed Apps/Folders to the config file + if !allowed_apps.is_empty() { + writeln!(&mut w, "{}", allowed_apps).unwrap(); + } + + // Allowed IP Addresses if !tunnel.rules.allowed.ipAddresses.is_empty() { writeln!(&mut w, "AllowedIPs = {}", tunnel.rules.allowed.ipAddresses).unwrap(); } + + // Disallowed + + // Disallowed Apps + let mut disallowed_apps = String::new(); + if !tunnel.rules.disallowed.apps.is_empty() { + disallowed_apps = format!("DisallowedApps = {}", tunnel.rules.disallowed.apps); + } + + // Disallowed Folders + if !tunnel.rules.disallowed.folders.is_empty() { + if !disallowed_apps.is_empty() { + // Ensure there is comma between the disallowed apps and disallowed folders + disallowed_apps = format!("{}, {}", disallowed_apps, tunnel.rules.disallowed.folders); + } else { + disallowed_apps = format!("DisallowedApps = {}", tunnel.rules.disallowed.folders); + } + } + + // Write Disallowed Apps/Folders to the config file + if !disallowed_apps.is_empty() { + writeln!(&mut w, "{}", disallowed_apps).unwrap(); + } + + // Disallowed IP Addresses if !tunnel.rules.disallowed.ipAddresses.is_empty() { writeln!( &mut w, @@ -326,7 +365,9 @@ async fn enable_wiresock(tunnel: Tunnel, app_handle: tauri::AppHandle) -> Result update_state(&app_handle, |state| { state.wiresock_status = "STOPPED".to_string(); state.tunnel_status = "DISCONNECTED".to_string(); - state.logs.push("Tunnel Disabled. Wiresock process stopped".into()) + state + .logs + .push("Tunnel Disabled. Wiresock process stopped".into()) }); } Err(e) => println!("error attempting to wait: {}", e), From 7efe408ba57ac367b0cfcc0f8bf4d23790a628c1 Mon Sep 17 00:00:00 2001 From: Brendan Osborne Date: Thu, 23 Nov 2023 14:22:06 +1000 Subject: [PATCH 02/18] fix for displaying seperated rules --- src/components/containers/TunnelDisplay.tsx | 30 +++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/containers/TunnelDisplay.tsx b/src/components/containers/TunnelDisplay.tsx index 21eb064..7d5b874 100644 --- a/src/components/containers/TunnelDisplay.tsx +++ b/src/components/containers/TunnelDisplay.tsx @@ -27,13 +27,11 @@ function TunnelDisplay({ selectedTunnel, wiresockState, enableTunnel, disableTun
{/* Start of name, status and buttons */}
- {/* Tunnel name */}

{selectedTunnel?.name}

{/* Tunnel status section */}
- {/* Start of Tunnel status icon */}
-
{/* End tunnel status section */} -
{/* End of name, status and buttons */} @@ -112,21 +108,27 @@ function TunnelDisplay({ selectedTunnel, wiresockState, enableTunnel, disableTun
Allowed
- {selectedTunnel.rules.allowed.apps} - {selectedTunnel.rules.allowed.folders.length > 0 ? `, ${selectedTunnel.rules.allowed.folders}` : ''} - {selectedTunnel.rules.allowed.ipAddresses.length > 0 - ? `, ${selectedTunnel.rules.allowed.ipAddresses}` - : ''} + {/* Create an array of the values and seperate with a comma */} + {[ + selectedTunnel.rules.allowed.apps, + selectedTunnel.rules.allowed.folders, + selectedTunnel.rules.allowed.ipAddresses, + ] + .filter(Boolean) + .join(', ')}
Disallowed
- {selectedTunnel.rules.disallowed.apps} - {selectedTunnel.rules.disallowed.folders.length > 0 ? `, ${selectedTunnel.rules.disallowed.folders}` : ''} - {selectedTunnel.rules.disallowed.ipAddresses.length > 0 - ? `, ${selectedTunnel.rules.disallowed.ipAddresses}` - : ''} + {/* Create an array of the values and seperate with a comma */} + {[ + selectedTunnel.rules.disallowed.apps, + selectedTunnel.rules.disallowed.folders, + selectedTunnel.rules.disallowed.ipAddresses, + ] + .filter(Boolean) + .join(', ')}
From 121d07a4df6192f5dc9f8a4247db47fc97f5cd77 Mon Sep 17 00:00:00 2001 From: Brendan Osborne Date: Thu, 23 Nov 2023 17:41:10 +1000 Subject: [PATCH 03/18] minor change to add some commenting --- src/main.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index d238181..c355348 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -26,10 +26,16 @@ function Main(): JSX.Element { /* ------------------------- */ /* ------- useState -------- */ /* ------------------------- */ + // For checking if Wiresock is installed on app launch, so we know to show the setup component or not. const [isWiresockInstalled, setIsWiresockInstalled] = useState(null) + + // For keeping track of the state of the wiresock process and tunnel connection status emitted from Tauri. const [wiresockState, setWiresockState] = useState(null) + + // Keep track of whether auto connect has already fired. Don't want it running again on component reload. const [hasRunAutoConnect, setHasRunAutoConnect] = useState(false) + // Keep track of which tunnel the UI is showing const [selectedTunnel, setSelectedTunnel] = useState(() => { // Get the previously selected tunnel from settings on first load const selectedTunnelID = getSelectedTunnelIDFromStorage() @@ -40,8 +46,8 @@ function Main(): JSX.Element { } }) + // Get the tunnel configs from local storage const [tunnels, setTunnels] = useState>(() => { - // Get the tunnels from settings const tunnelsFromStorage = getTunnelsFromStorage() const filteredTunnels = Object.fromEntries( Object.entries(tunnelsFromStorage).filter(([_, value]) => value !== null), @@ -62,7 +68,7 @@ function Main(): JSX.Element { }, []) // Wait for wiresockState data to arrive from Tauri - // Auto connect a tunnel if there is one set + // Auto connect a tunnel if one is set useEffect(() => { if (!hasRunAutoConnect && wiresockState !== null && wiresockState.wiresock_status === 'STOPPED') { setHasRunAutoConnect(true) From b3309d059d27f7b9f80a9edb2ed333cf46a0ebe2 Mon Sep 17 00:00:00 2001 From: Brendan Osborne Date: Thu, 23 Nov 2023 17:56:27 +1000 Subject: [PATCH 04/18] minor updates to default html page --- index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.html b/index.html index 03abf55..7a420d6 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,8 @@ - - Tauri + React + TS + TunnlTo From 82376184da43f6464ad5c45ef5c97d1aceac049f Mon Sep 17 00:00:00 2001 From: Brendan Osborne Date: Sat, 25 Nov 2023 11:20:43 +1000 Subject: [PATCH 05/18] tunnel converter. improved local storage handling. --- src/components/containers/Sidebar.tsx | 6 +- src/components/containers/TunnelEditor.tsx | 137 ++++++++++++--------- src/main.tsx | 129 ++++++++++++++----- src/models/Tunnel.ts | 14 ++- src/utilities/storageUtils.ts | 62 ++++++---- 5 files changed, 223 insertions(+), 125 deletions(-) diff --git a/src/components/containers/Sidebar.tsx b/src/components/containers/Sidebar.tsx index f6e748f..cd7b9c9 100644 --- a/src/components/containers/Sidebar.tsx +++ b/src/components/containers/Sidebar.tsx @@ -4,13 +4,13 @@ import { useNavigate } from 'react-router-dom' import type WiresockStateModel from '../../models/WiresockStateModel.ts' interface SidebarProps { - childHandleTunnelSelect: (tunnel: Tunnel) => void tunnels: Record | null selectedTunnel: Tunnel | null wiresockState: WiresockStateModel | null + setSelectedTunnel: (tunnel: Tunnel) => void } -function Sidebar({ childHandleTunnelSelect, tunnels, selectedTunnel, wiresockState }: SidebarProps): JSX.Element { +function Sidebar({ tunnels, selectedTunnel, wiresockState, setSelectedTunnel }: SidebarProps): JSX.Element { const menuItems = tunnels != null ? Object.keys(tunnels) : [] const navigate = useNavigate() @@ -36,7 +36,7 @@ function Sidebar({ childHandleTunnelSelect, tunnels, selectedTunnel, wiresockSta : 'text-gray-300 hover:bg-gray-700 hover:text-white' } rounded-md px-2 cursor-pointer text-sm font-medium py-2 flex items-center align-center`} onClick={() => { - if (tunnels != null) childHandleTunnelSelect(tunnels[item]) + if (tunnels != null) setSelectedTunnel(tunnels[item]) }} > {tunnels != null ? tunnels[item].name : ''} diff --git a/src/components/containers/TunnelEditor.tsx b/src/components/containers/TunnelEditor.tsx index 6935186..d31f5e7 100644 --- a/src/components/containers/TunnelEditor.tsx +++ b/src/components/containers/TunnelEditor.tsx @@ -1,47 +1,48 @@ import { useState, useRef } from 'react' import Tunnel from '../../models/Tunnel.ts' import { useNavigate } from 'react-router-dom' -import { saveTunnelInStorage, deleteTunnelFromStorage } from '../../utilities/storageUtils.ts' import DeleteModal from '../DeleteModal.tsx' import { ExclamationTriangleIcon, EyeIcon, EyeSlashIcon, LinkIcon } from '@heroicons/react/24/outline' interface ConfigProps { tunnels: Record selectedTunnel: Tunnel | null - childHandleTunnelSelect: (tunnel: Tunnel | null) => void + setSelectedTunnel: (tunnel: Tunnel | null) => void + setTunnels: (tunnels: Record) => void } -function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: ConfigProps): JSX.Element { +function TunnelEditor({ tunnels, selectedTunnel, setSelectedTunnel, setTunnels }: ConfigProps): JSX.Element { + /* ------------------------- */ + /* ------- useState -------- */ + /* ------------------------- */ + const [wasValidated, setWasValidated] = useState(false) const [nameError, setNameError] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isPrivateKeyHidden, setIsPrivateKeyHidden] = useState(true) const [isPublicKeyHidden, setIsPublicKeyHidden] = useState(true) const [isPresharedKeyHidden, setIsPresharedKeyHidden] = useState(true) - const [tunnel, setTunnel] = useState(() => { + const [editedTunnel, setEditedTunnel] = useState(() => { + // If a tunnel is passed in we are editing it, otherwise we are creating a new tunnel if (selectedTunnel === null) { - // Generate a unique ID and return a new Tunnel with that ID - let uniqueId = '' - const characters = 'abcdefghijklmnopqrstuvwxyz0123456789' - - do { - uniqueId = '' - for (let i = 0; i < 4; i++) { - uniqueId += characters.charAt(Math.floor(Math.random() * characters.length)) - } - } while (Object.prototype.hasOwnProperty.call(tunnels, uniqueId)) - - const newTunnel = new Tunnel() - newTunnel.id = uniqueId + const newTunnel = new Tunnel(tunnels) return newTunnel } else { return selectedTunnel } }) + /* ------------------------- */ + /* ------- variables ------- */ + /* ------------------------- */ + const navigate = useNavigate() const formRef = useRef(null) + /* ------------------------- */ + /* ------- functions ------- */ + /* ------------------------- */ + function toggleKeyVisibility(key: 'privateKey' | 'publicKey' | 'presharedKey'): void { if (key === 'privateKey') { setIsPrivateKeyHidden(!isPrivateKeyHidden) @@ -58,26 +59,26 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf if (keys.length === 1) { if (keys[0] === 'name') { - setTunnel({ ...tunnel, name: value ?? '' }) + setEditedTunnel({ ...editedTunnel, name: value ?? '' }) } } else if (keys.length === 2) { if (keys[0] === 'interface') { - setTunnel({ - ...tunnel, - interface: { ...tunnel?.interface, [keys[1]]: value }, + setEditedTunnel({ + ...editedTunnel, + interface: { ...editedTunnel?.interface, [keys[1]]: value }, }) } else if (keys[0] === 'peer') { - setTunnel({ ...tunnel, peer: { ...tunnel?.peer, [keys[1]]: value } }) + setEditedTunnel({ ...editedTunnel, peer: { ...editedTunnel?.peer, [keys[1]]: value } }) } } else if (keys.length === 3) { if (keys[0] === 'rules') { if (keys[1] === 'allowed' || keys[1] === 'disallowed') { - setTunnel({ - ...tunnel, + setEditedTunnel({ + ...editedTunnel, rules: { - ...tunnel?.rules, + ...editedTunnel?.rules, [keys[1]]: { - ...tunnel?.rules[keys[1]], + ...editedTunnel?.rules[keys[1]], [keys[2]]: value, }, }, @@ -93,19 +94,37 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf function handleUserDeleteDecision(decision: 'delete' | 'cancel'): void { if (decision === 'delete') { - if (selectedTunnel !== null) deleteTunnelFromStorage(tunnels, selectedTunnel.id) - - // Notify parent that the selected tunenl no longer exists - childHandleTunnelSelect(null) - + deleteTunnel() navigate('/') } } + function deleteTunnel(): void { + // Create a copy of the tunnels object + const updatedTunnels = { ...tunnels } + + // Delete the tunnel from the copied object + Reflect.deleteProperty(updatedTunnels, editedTunnel.id) + + setTunnels(updatedTunnels) + + setSelectedTunnel(null) + } + + function saveTunnel(): void { + // Create a new object that includes all tunnels and the new tunnel + const updatedTunnels = { ...tunnels, [editedTunnel.id]: editedTunnel } + + // Update the state + setTunnels(updatedTunnels) + + setSelectedTunnel(editedTunnel) + } + function handleNameCheck(): void { // Check the desired name isn't already in use for (const x of Object.values(tunnels)) { - if (x.name === tunnel.name && x.id !== tunnel.id) { + if (x.name === editedTunnel.name && x.id !== editedTunnel.id) { // Alert the user setNameError(true) return @@ -120,13 +139,7 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf if (formRef.current?.checkValidity() === true && !nameError) { // Form is valid - - // Update or Add the new tunnel - saveTunnelInStorage(tunnels, tunnel) - - // Notify parent of a new selected tunnel - // If a user creates a new tunnel, this tells the parent to show the new tunnel on TunnelDisplay - childHandleTunnelSelect(tunnel) + saveTunnel() navigate('/') } else { @@ -155,8 +168,10 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf
Documentation @@ -165,8 +180,10 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf
Examples @@ -180,7 +197,7 @@ function TunnelEditor({ tunnels, selectedTunnel, childHandleTunnelSelect }: Conf Name