diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c858993d..63767beb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,7 +7,7 @@ #### Documentation ### -- [ ] If testing requires changes in the environment or deployment, please **update the documentation** (https://defguard.gitbook.io) first and **attach the link to the documentation** section in this pool request +- [ ] If testing requires changes in the environment or deployment, please **update the documentation** (https://defguard.gitbook.io) first and **attach the link to the documentation** section in this pull request - [ ] I have commented on my code, particularly in hard-to-understand areas #### Testing ### diff --git a/package.json b/package.json index 43239ac1..d3df6782 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "defguard-client", "private": false, - "version": "0.3.0", + "version": "0.4.0", "type": "module", "scripts": { "dev": "npm-run-all --parallel vite typesafe-i18n", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 87333b5f..4c479140 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -485,6 +485,12 @@ version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -1179,10 +1185,10 @@ checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" [[package]] name = "defguard-client" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", - "base64 0.21.6", + "base64 0.22.1", "chrono", "clap", "dark-light", @@ -1224,10 +1230,10 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.4.4" -source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.4#87ed3bcbc908fd68c106faa86b1a1b977015029b" +version = "0.4.5" +source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.5#35345d54e7426f2678ad120378d59cf642acce5d" dependencies = [ - "base64 0.21.6", + "base64 0.22.1", "libc", "log", "netlink-packet-core", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dc8ad310..cf0bb534 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard-client" -version = "0.3.0" +version = "0.4.0" description = "Defguard desktop client" license = "" homepage = "https://github.com/DefGuard/client" @@ -8,6 +8,7 @@ repository = "https://github.com/DefGuard/client" default-run = "defguard-client" edition = "2021" rust-version = "1.60" +authors = ["Defguard"] [build-dependencies] tauri-build = { version = "1.5", features = [] } @@ -16,10 +17,10 @@ prost-build = { version = "0.12" } [dependencies] anyhow = "1.0" -base64 = "0.21" +base64 = "0.22" clap = { version = "4.4", features = ["derive", "env"] } chrono = { version = "0.4", features = ["serde"] } -defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.4" } +defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.5" } dirs = "5.0" lazy_static = "1.4" local-ip-address = "0.5" @@ -31,13 +32,28 @@ rust-ini = "0.20" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } serde_with = "3.5" -sqlx = { version = "0.7", features = ["chrono", "sqlite", "runtime-tokio", "uuid", "macros"] } +sqlx = { version = "0.7", features = [ + "chrono", + "sqlite", + "runtime-tokio", + "uuid", + "macros", +] } struct-patch = "0.4" strum = { version = "0.25", features = ["derive"] } dark-light = "1.0" webbrowser = "0.8" -tauri = { version = "1.5", features = [ "dialog-all", "clipboard-all", "http-all", "window-all", "system-tray", "native-tls-vendored", "icon-png", "fs-all"] } +tauri = { version = "1.5", features = [ + "dialog-all", + "clipboard-all", + "http-all", + "window-all", + "system-tray", + "native-tls-vendored", + "icon-png", + "fs-all", +] } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } @@ -48,10 +64,7 @@ tonic = "0.10" tracing = "0.1" tracing-appender = "0.2" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } -x25519-dalek = { version = "2", features = [ - "getrandom", - "static_secrets", -] } +x25519-dalek = { version = "2", features = ["getrandom", "static_secrets"] } reqwest = { version = "0.11", features = ["json"] } [target.'cfg(target_os = "windows")'.dependencies] @@ -64,7 +77,7 @@ nix = { version = "0.28", features = ["net"] } # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. # DO NOT REMOVE!! -custom-protocol = [ "tauri/custom-protocol" ] +custom-protocol = ["tauri/custom-protocol"] [dev-dependencies] tokio = { version = "1.34", features = ["full"] } diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 75b21cac..1e850d5e 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index 936af86b..a2a9e582 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index 8cc5ae21..70c138ef 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 58e3cfe3..39f8a6b3 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index d69adc70..6095c406 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 4f459242..815a7418 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index 4a194519..edcc40d9 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 1b97dbbf..614bf53f 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index 83c36519..59c964b7 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index e0fa7036..749b8d9c 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 68698de6..39921e32 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index 94e1beb3..243086ba 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index f078cf54..ed6e429c 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index a6da45a0..d963baeb 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index 6ca084dd..20a0d9c3 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index 3090f8ba..cdf7bccd 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/proto b/src-tauri/proto index 29898d9c..c71f3784 160000 --- a/src-tauri/proto +++ b/src-tauri/proto @@ -1 +1 @@ -Subproject commit 29898d9cb502ef5e7bb1771b63e6a66debf31e7d +Subproject commit c71f37847279ee23220fcf9e0e45d2c365b3b8ee diff --git a/src-tauri/resources/icons/tray-32x32-black.png b/src-tauri/resources/icons/tray-32x32-black.png index e0f7e171..74946b12 100644 Binary files a/src-tauri/resources/icons/tray-32x32-black.png and b/src-tauri/resources/icons/tray-32x32-black.png differ diff --git a/src-tauri/resources/icons/tray-32x32-color.png b/src-tauri/resources/icons/tray-32x32-color.png index 9f463510..e243aafe 100644 Binary files a/src-tauri/resources/icons/tray-32x32-color.png and b/src-tauri/resources/icons/tray-32x32-color.png differ diff --git a/src-tauri/resources/icons/tray-32x32-gray.png b/src-tauri/resources/icons/tray-32x32-gray.png index 3e5f76ea..6eede45e 100644 Binary files a/src-tauri/resources/icons/tray-32x32-gray.png and b/src-tauri/resources/icons/tray-32x32-gray.png differ diff --git a/src-tauri/resources/icons/tray-32x32-white.png b/src-tauri/resources/icons/tray-32x32-white.png index e5ede2fa..93629037 100644 Binary files a/src-tauri/resources/icons/tray-32x32-white.png and b/src-tauri/resources/icons/tray-32x32-white.png differ diff --git a/src-tauri/src/appstate.rs b/src-tauri/src/appstate.rs index de763559..58b50ce7 100644 --- a/src-tauri/src/appstate.rs +++ b/src-tauri/src/appstate.rs @@ -71,6 +71,7 @@ impl AppState { info!("Removed connection from active connections: {removed_connection:#?}"); Some(removed_connection) } else { + debug!("No active connection found with location_id: {location_id}"); None } } @@ -97,11 +98,15 @@ impl AppState { let active_connections = self.get_connections(); info!("Found {} active connections", active_connections.len()); for connection in active_connections { - debug!("Found active connection"); + debug!( + "Found active connection with location {}", + connection.location_id + ); trace!("Connection: {connection:#?}"); - debug!("Removing interface"); + debug!("Removing interface {}", connection.interface_name); disconnect_interface(connection, self).await?; } + info!("All active connections closed"); Ok(()) } @@ -123,7 +128,7 @@ impl AppState { debug!("Found connection: {connection:#?}"); Some(connection.to_owned()) } else { - error!("Element with id: {id}, connection_type: {connection_type:?} not found."); + error!("Couldn't find connection with id: {id}, connection_type: {connection_type:?} in active connections."); None } } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index cb62f5b8..d0fbe9d9 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -35,6 +35,7 @@ pub async fn connect( preshared_key: Option, handle: AppHandle, ) -> Result<(), Error> { + debug!("Connecting location {location_id} using connection type {connection_type:?}"); let state = handle.state::(); if connection_type.eq(&ConnectionType::Location) { if let Some(location) = Location::find_by_id(&state.get_pool(), location_id).await? { @@ -49,6 +50,7 @@ pub async fn connect( error!("Tunnel {location_id} not found"); return Err(Error::NotFound); } + info!("Connected to location with id: {location_id}"); Ok(()) } @@ -58,7 +60,7 @@ pub async fn disconnect( connection_type: ConnectionType, handle: AppHandle, ) -> Result<(), Error> { - debug!("Disconnecting location {}", location_id); + debug!("Disconnecting location {location_id}"); let state = handle.state::(); if let Some(connection) = state.find_and_remove_connection(location_id, &connection_type) { let interface_name = connection.interface_name.clone(); @@ -73,9 +75,10 @@ pub async fn disconnect( }, )?; stop_log_watcher_task(handle, interface_name)?; + info!("Disconnected from location with id: {location_id}"); Ok(()) } else { - error!("Connection for location with id: {location_id} not found"); + error!("Error while disconnecting from location with id: {location_id} not found"); Err(Error::NotFound) } } @@ -127,7 +130,7 @@ pub async fn save_device_config( app_state: State<'_, AppState>, handle: AppHandle, ) -> Result { - debug!("Received device configuration: {response:#?}"); + debug!("Received device configuration: {response:#?}."); let mut transaction = app_state.get_pool().begin().await?; let instance_info = response @@ -165,6 +168,7 @@ pub async fn save_device_config( locations, instance, }; + info!("Device configuration saved."); Ok(res) } @@ -248,6 +252,7 @@ pub async fn all_locations( }; location_info.push(info); } + info!("Locations retrieved({})", location_info.len()); debug!( "Returning {} locations for instance {instance_id}", location_info.len(), @@ -305,6 +310,7 @@ pub async fn update_instance( let mut transaction = pool.begin().await?; // update instance + debug!("Updating instance {instance_id}."); let instance_info = response .instance .expect("Missing instance info in device config response"); @@ -315,6 +321,7 @@ pub async fn update_instance( instance.save(&mut *transaction).await?; // process locations received in response + debug!("Updating locations for instance {instance_id}."); for location in response.configs { // parse device config let mut new_location = device_config_to_location(location, instance_id); @@ -326,6 +333,10 @@ pub async fn update_instance( { // remove from list of existing locations let mut current_location = current_locations.remove(position); + debug!( + "Updating existing location {} for instance {instance_id}.", + current_location.name + ); // update existing location current_location.name = new_location.name; current_location.address = new_location.address; @@ -334,18 +345,28 @@ pub async fn update_instance( current_location.allowed_ips = new_location.allowed_ips; current_location.mfa_enabled = new_location.mfa_enabled; current_location.keepalive_interval = new_location.keepalive_interval; + current_location.dns = new_location.dns; current_location.save(&mut *transaction).await?; + info!( + "Location {} updated for instance {instance_id}.", + current_location.name + ); } else { // create new location + debug!("Creating new location for instance {instance_id}."); new_location.save(&mut *transaction).await?; + info!("New location created for instance {instance_id}."); } } + info!("Locations updated for instance {instance_id}."); // remove locations which were present in current locations // but no longer found in core response + debug!("Removing locations for instance {instance_id}."); for removed_location in current_locations { removed_location.delete(&mut *transaction).await?; } + info!("Locations removed for instance {instance_id}."); transaction.commit().await?; @@ -353,6 +374,7 @@ pub async fn update_instance( app_handle.emit_all("instance-update", ())?; Ok(()) } else { + error!("Instance with id {instance_id} not found"); Err(Error::NotFound) } } @@ -386,7 +408,11 @@ fn get_aggregation(from: NaiveDateTime) -> Result { // Use hourly aggregation for longer periods let aggregation = match Utc::now().naive_utc() - from { duration if duration >= Duration::hours(8) => Ok(DateTimeAggregation::Hour), - duration if duration < Duration::zero() => Err(Error::InternalError), + duration if duration < Duration::zero() => Err(Error::InternalError(format!( + "Negative duration between dates: now ({}) and {}", + Utc::now().naive_utc(), + from + ))), _ => Ok(DateTimeAggregation::Second), }?; Ok(aggregation) @@ -448,7 +474,7 @@ pub async fn all_connections( .collect() } }; - debug!("Connections received, returning."); + info!("Connections retrieved({})", connections.len()); trace!("Connections found:\n{:#?}", connections); Ok(connections) } @@ -461,7 +487,7 @@ pub async fn all_tunnel_connections( debug!("Retrieving connections for location {location_id}"); let connections = TunnelConnectionInfo::all_by_tunnel_id(&app_state.get_pool(), location_id).await?; - debug!("Connections received, returning."); + info!("Tunnel connections retrieved({})", connections.len()); trace!("Connections found:\n{:#?}", connections); Ok(connections) } @@ -479,8 +505,8 @@ pub async fn active_connection( if connection.is_some() { debug!("Active connection found"); } - trace!("Connection:\n{:#?}", connection); - debug!("Connection returned"); + trace!("Connection retrieved:\n{:#?}", connection); + info!("Connection retrieved"); Ok(connection) } @@ -495,7 +521,7 @@ pub async fn last_connection( if let Some(connection) = Connection::latest_by_location_id(&app_state.get_pool(), location_id).await? { - trace!("Connection found"); + info!("Found last connection at {}", connection.end); Ok(Some(connection.into())) } else { Ok(None) @@ -503,9 +529,10 @@ pub async fn last_connection( } else if let Some(connection) = TunnelConnection::latest_by_tunnel_id(&app_state.get_pool(), location_id).await? { - trace!("Connection found"); + info!("Found last connection at {}", connection.end); Ok(Some(connection.into())) } else { + info!("No last connection found"); Ok(None) } } @@ -527,6 +554,7 @@ pub async fn update_location_routing( { location.route_all_traffic = route_all_traffic; location.save(&app_state.get_pool()).await?; + info!("Location routing updated for location {location_id}"); handle.emit_all( "location-update", Payload { @@ -535,7 +563,9 @@ pub async fn update_location_routing( )?; Ok(()) } else { - error!("Location with id: {location_id} not found."); + error!( + "Couldn't update location routing: location with id {location_id} not found." + ); Err(Error::NotFound) } } @@ -544,6 +574,7 @@ pub async fn update_location_routing( { tunnel.route_all_traffic = route_all_traffic; tunnel.save(&app_state.get_pool()).await?; + info!("Tunnel routing updated for tunnel {location_id}"); handle.emit_all( "location-update", Payload { @@ -552,7 +583,7 @@ pub async fn update_location_routing( )?; Ok(()) } else { - error!("Tunnel with id: {location_id} not found."); + error!("Couldn't update tunnel routing: tunnel with id {location_id} not found."); Err(Error::NotFound) } } @@ -561,8 +592,10 @@ pub async fn update_location_routing( #[tauri::command] pub async fn get_settings(handle: AppHandle) -> Result { + debug!("Retrieving settings"); let app_state = handle.state::(); let settings = Settings::get(&app_state.get_pool()).await?; + info!("Settings retrieved"); Ok(settings) } @@ -581,7 +614,7 @@ pub async fn update_settings(data: SettingsPatch, handle: AppHandle) -> Result {} Err(e) => { error!( - "During settings update, tray configuration update failed. err : {}", + "Tray configuration update failed during settings update. err : {}", e.to_string() ); } @@ -610,11 +643,15 @@ pub async fn delete_instance(instance_id: i64, handle: AppHandle) -> Result<(), pre_down: None, post_down: None, }; - client - .remove_interface(request) - .await - .map_err(|_| Error::InternalError)?; - debug!("Connection closed and interface removed"); + client.remove_interface(request).await.map_err(|status| { + let msg = + format!("Error occured while removing interface {} for location {location_id}, status: {status}", + connection.interface_name + ); + error!("{msg}"); + Error::InternalError(msg) + })?; + info!("Connection closed and interface removed"); } } } @@ -630,10 +667,12 @@ pub async fn delete_instance(instance_id: i64, handle: AppHandle) -> Result<(), #[tauri::command(async)] pub async fn parse_tunnel_config(config: String) -> Result { debug!("Parsing config file"); - parse_wireguard_config(&config).map_err(|error| { + let tunnel_config = parse_wireguard_config(&config).map_err(|error| { error!("{error}"); Error::ConfigParseError(error.to_string()) - }) + })?; + info!("Config file parsed"); + Ok(tunnel_config) } #[tauri::command(async)] pub async fn save_tunnel(mut tunnel: Tunnel, handle: AppHandle) -> Result<(), Error> { @@ -682,6 +721,8 @@ pub async fn all_tunnels(app_state: State<'_, AppState>) -> Result Result<(), Erro client .remove_interface(request) .await - .map_err(|_| Error::InternalError)?; - debug!("Connection closed and interface removed"); + .map_err(|status| { + let msg = + format!("Error occured while removing interface {} for tunnel {tunnel_id}, status: {status}", + connection.interface_name + ); + error!("{msg}"); + Error::InternalError(msg) + })?; + info!("Connection closed and interface removed"); } tunnel.delete(pool).await?; } else { @@ -772,10 +821,13 @@ pub async fn get_latest_app_version(handle: AppHandle) -> Result = response.json::().await; - response_json.map_err(|err| { + let response = response_json.map_err(|err| { error!("Failed to deserialize latest application version response {err}"); Error::CommandError(err.to_string()) - }) + })?; + + info!("Latest application version fetched: {}", response.version); + Ok(response) } else { let err = res.err().unwrap(); error!("Failed to fetch latest application version {err}"); diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index 384a281f..11817d04 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -26,8 +26,8 @@ pub enum Error { AddrParse(#[from] AddrParseError), #[error("Local Ip Error: {0}")] LocalIpError(#[from] LocalIpError), - #[error("Internal error")] - InternalError, + #[error("Internal error: {0}")] + InternalError(String), #[error("Failed to parse timestamp")] Datetime, #[error("Object not found")] diff --git a/src-tauri/src/service/log_watcher.rs b/src-tauri/src/service/log_watcher.rs index 58af5fd7..5d67343b 100644 --- a/src-tauri/src/service/log_watcher.rs +++ b/src-tauri/src/service/log_watcher.rs @@ -142,7 +142,7 @@ impl ServiceLogWatcher { fn parse_log_dir(&mut self) -> Result<(), LogWatcherError> { // get latest log file let latest_log_file = self.get_latest_log_file()?; - info!("found latest log file: {latest_log_file:?}"); + debug!("found latest log file: {latest_log_file:?}"); // check if latest file changed if latest_log_file.is_some() && latest_log_file != self.current_log_file { @@ -180,9 +180,9 @@ impl ServiceLogWatcher { /// Deserializes the log line into a known struct and checks if the line is relevant /// to the specified interface. Also performs filtering by log level and optional timestamp. fn parse_log_line(&self, line: String) -> Result, LogWatcherError> { - debug!("Parsing log line: {line}"); + trace!("Parsing log line: {line}"); let log_line = serde_json::from_str::(&line)?; - debug!("Parsed log line into: {log_line:?}"); + trace!("Parsed log line into: {log_line:?}"); // filter by log level if log_line.level > self.log_level { @@ -205,7 +205,7 @@ impl ServiceLogWatcher { if let Some(ref span) = log_line.span { if let Some(interface_name) = &span.interface_name { if interface_name != &self.interface_name { - debug!("Interface name {interface_name} is not the configured name {}. Skipping line...", self.interface_name); + trace!("Interface name {interface_name} is not the configured name {}. Skipping line...", self.interface_name); return Ok(None); } } @@ -219,7 +219,7 @@ impl ServiceLogWatcher { /// Log files are rotated daily and have a knows naming format, /// with the last 10 characters specifying a date (e.g. `2023-12-15`). fn get_latest_log_file(&self) -> Result, LogWatcherError> { - debug!("Getting latest log file"); + debug!("Getting latest log file from directory: {:?}", self.log_dir); let entries = read_dir(&self.log_dir)?; let mut latest_log = None; @@ -241,7 +241,7 @@ impl ServiceLogWatcher { } fn extract_timestamp(filename: &str) -> Option { - debug!("Extracting timestamp from log file name: {filename}"); + trace!("Extracting timestamp from log file name: {filename}"); // we know that the date is always in the last 10 characters let split_pos = filename.char_indices().nth_back(9)?.0; let timestamp = &filename[split_pos..]; diff --git a/src-tauri/src/service/mod.rs b/src-tauri/src/service/mod.rs index 02d324fb..ccac5624 100644 --- a/src-tauri/src/service/mod.rs +++ b/src-tauri/src/service/mod.rs @@ -317,11 +317,12 @@ impl From for Peer { public_key: Key::decode(peer.public_key).expect("Failed to parse public key"), preshared_key: peer .preshared_key - .map(|key| Key::decode(key).expect("Failed to parse preshared key")), + .map(|key| Key::decode(key).expect("Failed to parse preshared key: {key}")), protocol_version: peer.protocol_version, - endpoint: peer - .endpoint - .map(|addr| addr.parse().expect("Failed to parse endpoint address")), + endpoint: peer.endpoint.map(|addr| { + addr.parse() + .expect("Failed to parse endpoint address: {addr}") + }), last_handshake: peer .last_handshake .map(|timestamp| UNIX_EPOCH.add(Duration::from_secs(timestamp))), @@ -333,7 +334,7 @@ impl From for Peer { allowed_ips: peer .allowed_ips .into_iter() - .map(|addr| addr.parse().expect("Failed to parse allowed IP")) + .map(|addr| addr.parse().expect("Failed to parse allowed IP: {addr}")) .collect(), } } diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 8365185a..9ecf5d40 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -101,7 +101,7 @@ pub fn configure_tray_icon(app: &AppHandle, theme: &TrayIconTheme) -> Result<(), Some(icon_path) => { let icon = tauri::Icon::File(icon_path); app.tray_handle().set_icon(icon)?; - debug!("Tray icon changed"); + info!("Tray icon changed"); Ok(()) } None => { diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index b0023c2f..de8baa88 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -47,14 +47,20 @@ pub async fn setup_interface( // prepare peer config debug!("Decoding location public key: {}.", location.pubkey); let peer_key: Key = Key::from_str(&location.pubkey)?; + info!("Location public key decoded."); + debug!("Location public key: {peer_key}"); let mut peer = Peer::new(peer_key); debug!("Parsing location endpoint: {}", location.endpoint); peer.set_endpoint(&location.endpoint)?; peer.persistent_keepalive_interval = Some(25); + info!("Parsed location endpoint."); + debug!("Location endpoint: {}", location.endpoint); if let Some(psk) = preshared_key { + debug!("Decoding preshared key."); let peer_psk = Key::from_str(&psk)?; + info!("Preshared key decoded."); peer.preshared_key = Some(peer_psk); } @@ -63,7 +69,10 @@ pub async fn setup_interface( debug!("Using all traffic routing: {DEFAULT_ROUTE}"); vec![DEFAULT_ROUTE.into()] } else { - debug!("Using predefined location traffic"); + debug!( + "Using predefined location traffic: {}", + location.allowed_ips + ); location .allowed_ips .split(',') @@ -83,9 +92,13 @@ pub async fn setup_interface( } } } + info!("Parsed allowed IPs for location."); + debug!("Allowed IPs: {:#?}", peer.allowed_ips); // request interface configuration + debug!("Looking for a free port for interface {interface_name}..."); if let Some(port) = find_random_free_port() { + info!("Found free port: {port} for interface {interface_name}."); let interface_config = InterfaceConfiguration { name: interface_name, prvkey: keys.prvkey, @@ -102,19 +115,28 @@ pub async fn setup_interface( post_up: None, }; if let Err(error) = client.create_interface(request).await { - error!("Failed to create interface: {error}"); - Err(Error::InternalError) + let msg = format!( + "Failed to create interface with config {interface_config:?}. Error: {error}" + ); + error!("{msg}"); + Err(Error::InternalError(msg)) } else { info!("Created interface {interface_config:#?}"); Ok(()) } } else { - error!("Error finding free port"); - Err(Error::InternalError) + let msg = format!( + "Error finding free port during interface {interface_name} setup for location {}", + location.name + ); + error!("{msg}"); + Err(Error::InternalError(msg)) } } else { error!("No keys found for instance: {}", location.instance_id); - Err(Error::InternalError) + Err(Error::InternalError( + "No keys found for instance".to_string(), + )) } } @@ -268,6 +290,8 @@ pub async fn setup_interface_tunnel( // prepare peer config debug!("Decoding location public key: {}.", tunnel.server_pubkey); let peer_key: Key = Key::from_str(&tunnel.server_pubkey)?; + info!("Location public key decoded."); + debug!("Location public key: {peer_key}"); let mut peer = Peer::new(peer_key); debug!("Parsing location endpoint: {}", tunnel.endpoint); @@ -278,9 +302,13 @@ pub async fn setup_interface_tunnel( .try_into() .expect("Failed to parse persistent keep alive"), ); + info!("Parsed location endpoint."); + debug!("Location endpoint: {}", tunnel.endpoint); if let Some(psk) = &tunnel.preshared_key { + debug!("Decoding preshared key."); let peer_psk = Key::from_str(psk)?; + debug!("Preshared key decoded."); peer.preshared_key = Some(peer_psk); } @@ -289,7 +317,11 @@ pub async fn setup_interface_tunnel( debug!("Using all traffic routing: {DEFAULT_ROUTE}"); vec![DEFAULT_ROUTE.into()] } else { - debug!("Using predefined location traffic"); + let msg = match &tunnel.allowed_ips { + Some(ips) => format!("Using predefined location traffic: {ips}"), + None => "No allowed IPs found".to_string(), + }; + debug!("{msg}"); tunnel .allowed_ips .as_ref() @@ -309,9 +341,13 @@ pub async fn setup_interface_tunnel( } } } + info!("Parsed allowed IPs."); + debug!("Allowed IPs: {:?}", peer.allowed_ips); // request interface configuration + debug!("Looking for a free port for interface {interface_name}..."); if let Some(port) = find_random_free_port() { + info!("Found free port: {port} for interface {interface_name}."); let interface_config = InterfaceConfiguration { name: interface_name, prvkey: tunnel.prvkey.clone(), @@ -328,15 +364,21 @@ pub async fn setup_interface_tunnel( post_up: tunnel.post_up.clone(), }; if let Err(error) = client.create_interface(request).await { - error!("Failed to create interface: {error}"); - Err(Error::InternalError) + let msg = format!("Failed to create interface: {error}"); + error!("{msg}"); + Err(Error::InternalError(msg)) } else { - info!("Created interface {interface_config:#?}"); + info!("Created interface {}", interface_config.name); + debug!("Created interface with config: {interface_config:?}"); Ok(()) } } else { - error!("Error finding free port"); - Err(Error::InternalError) + let msg = format!( + "Error finding free port during tunnel {} setup for interface {interface_name}", + tunnel.name + ); + error!("{msg}"); + Err(Error::InternalError(msg)) } } @@ -346,7 +388,6 @@ pub async fn get_tunnel_interface_details( ) -> Result { debug!("Fetching tunnel details for tunnel ID {tunnel_id}"); if let Some(tunnel) = Tunnel::find_by_id(pool, tunnel_id).await? { - debug!("Fetching WireGuard keys for location {}", tunnel.name); let peer_pubkey = tunnel.pubkey; // generate interface name @@ -355,6 +396,7 @@ pub async fn get_tunnel_interface_details( #[cfg(not(target_os = "macos"))] let interface_name = get_interface_name(&tunnel.name); + debug!("Fetching tunnel stats for tunnel ID {tunnel_id}"); let result = query!( r#" SELECT last_handshake, listen_port as "listen_port!: u32", @@ -366,6 +408,7 @@ pub async fn get_tunnel_interface_details( ) .fetch_optional(pool) .await?; + info!("Fetched tunnel stats for tunnel ID {tunnel_id}"); let (listen_port, persistent_keepalive_interval, last_handshake) = match result { Some(record) => ( @@ -376,6 +419,8 @@ pub async fn get_tunnel_interface_details( None => (None, None, None), }; + info!("Fetched tunnel details for tunnel ID {tunnel_id}"); + Ok(LocationInterfaceDetails { location_id: tunnel_id, name: interface_name, @@ -390,7 +435,7 @@ pub async fn get_tunnel_interface_details( last_handshake, }) } else { - error!("Tunnel ID {tunnel_id} not found"); + error!("Error while fetching tunnel details for ID {tunnel_id}: tunnel not found"); Err(Error::NotFound) } } @@ -404,6 +449,10 @@ pub async fn get_location_interface_details( let keys = WireguardKeys::find_by_instance_id(pool, location.instance_id) .await? .ok_or(Error::NotFound)?; + info!( + "Successfully fetched WireGuard keys for location {}", + location.name + ); let peer_pubkey = keys.pubkey; // generate interface name @@ -412,6 +461,7 @@ pub async fn get_location_interface_details( #[cfg(not(target_os = "macos"))] let interface_name = get_interface_name(&location.name); + debug!("Fetching location stats for location ID {location_id}"); let result = query!( r#" SELECT last_handshake, listen_port as "listen_port!: u32", @@ -423,6 +473,7 @@ pub async fn get_location_interface_details( ) .fetch_optional(pool) .await?; + info!("Fetched location stats for location ID {location_id}"); let (listen_port, persistent_keepalive_interval, last_handshake) = match result { Some(record) => ( @@ -433,6 +484,8 @@ pub async fn get_location_interface_details( None => (None, None, None), }; + info!("Fetched location details for location ID {location_id}"); + Ok(LocationInterfaceDetails { location_id, name: interface_name, @@ -447,7 +500,7 @@ pub async fn get_location_interface_details( last_handshake, }) } else { - error!("Location ID {location_id} not found"); + error!("Error while fetching location details for ID {location_id}: location not found"); Err(Error::NotFound) } } @@ -487,6 +540,10 @@ pub async fn handle_connection_for_location( .lock() .map_err(|_| Error::MutexError)? .push(connection); + info!( + "Finished creating new interface connection for location: {}", + location.name + ); debug!( "Active connections: {:#?}", state @@ -494,24 +551,27 @@ pub async fn handle_connection_for_location( .lock() .map_err(|_| Error::MutexError)? ); - debug!("Sending event connection-changed."); + debug!("Sending event connection-changed..."); handle.emit_all( "connection-changed", Payload { message: "Created new connection".into(), }, )?; + debug!("Event connection-changed sent."); // Spawn stats threads - debug!("Spawning stats thread"); + debug!("Spawning stats thread..."); spawn_stats_thread( handle.clone(), interface_name.clone(), ConnectionType::Location, ) .await; + info!("Stats thread spawned."); // spawn log watcher + debug!("Spawning log watcher..."); spawn_log_watcher_task( handle, location.id.expect("Missing Location ID"), @@ -521,6 +581,7 @@ pub async fn handle_connection_for_location( None, ) .await?; + info!("Log watcher spawned."); Ok(()) } @@ -548,6 +609,10 @@ pub async fn handle_connection_for_tunnel(tunnel: &Tunnel, handle: AppHandle) -> .lock() .map_err(|_| Error::MutexError)? .push(connection); + info!( + "Finished creating new interface connection for tunnel: {}", + tunnel.name + ); debug!( "Active connections: {:#?}", state @@ -562,17 +627,20 @@ pub async fn handle_connection_for_tunnel(tunnel: &Tunnel, handle: AppHandle) -> message: "Created new connection".into(), }, )?; + debug!("Event connection-changed sent."); // Spawn stats threads - info!("Spawning stats thread"); + debug!("Spawning stats thread"); spawn_stats_thread( handle.clone(), interface_name.clone(), ConnectionType::Tunnel, ) .await; + info!("Stats thread spawned"); //spawn log watcher + debug!("Spawning log watcher"); spawn_log_watcher_task( handle, tunnel.id.expect("Missing Tunnel ID"), @@ -582,6 +650,7 @@ pub async fn handle_connection_for_tunnel(tunnel: &Tunnel, handle: AppHandle) -> None, ) .await?; + info!("Log watcher spawned"); Ok(()) } /// Execute command passed as argument. @@ -627,7 +696,9 @@ pub async fn disconnect_interface( }; if let Err(error) = client.remove_interface(request).await { error!("Failed to remove interface: {error}"); - return Err(Error::InternalError); + return Err(Error::InternalError( + "Failed to remove interface".to_string(), + )); } let mut connection: Connection = active_connection.into(); connection.save(&state.get_pool()).await?; @@ -646,8 +717,9 @@ pub async fn disconnect_interface( post_down: tunnel.post_down, }; if let Err(error) = client.remove_interface(request).await { - error!("Failed to remove interface: {error}"); - return Err(Error::InternalError); + let msg = format!("Failed to remove interface: {error}"); + error!("{msg}"); + return Err(Error::InternalError(msg)); } let mut connection: TunnelConnection = active_connection.into(); connection.save(&state.get_pool()).await?; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0a31ab85..65258773 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "defguard-client", - "version": "0.3.0" + "version": "0.4.0" }, "tauri": { "systemTray": { diff --git a/src/pages/client/components/ClientSideBar/ClientSideBar.tsx b/src/pages/client/components/ClientSideBar/ClientSideBar.tsx index 77063541..df1dc997 100644 --- a/src/pages/client/components/ClientSideBar/ClientSideBar.tsx +++ b/src/pages/client/components/ClientSideBar/ClientSideBar.tsx @@ -7,7 +7,7 @@ import { useMatch, useNavigate } from 'react-router-dom'; import { useI18nContext } from '../../../../i18n/i18n-react'; import { IconDefguard } from '../../../../shared/components/icons/IconDefguard/IconDeguard'; -import SvgDefguadNavLogoCollapsed from '../../../../shared/components/svg/DefguardLogoCollapsed'; +import SvgDefguardLogoCollapsed from '../../../../shared/components/svg/DefguardLogoCollapsed'; import SvgDefguardLogoText from '../../../../shared/components/svg/DefguardLogoText'; import SvgIconNavConnections from '../../../../shared/components/svg/IconNavConnections'; import SvgIconNavVpn from '../../../../shared/components/svg/IconNavVpn'; @@ -47,7 +47,7 @@ export const ClientSideBar = () => { className="logo-mobile" onClick={() => navigate(routes.client.carousel, { replace: true })} > - +
diff --git a/src/pages/client/pages/ClientAddInstancePage/components/AddInstanceFormCard/components/AddInstanceInitForm/AddInstanceInitForm.tsx b/src/pages/client/pages/ClientAddInstancePage/components/AddInstanceFormCard/components/AddInstanceInitForm/AddInstanceInitForm.tsx index 369582cb..4de6c833 100644 --- a/src/pages/client/pages/ClientAddInstancePage/components/AddInstanceFormCard/components/AddInstanceInitForm/AddInstanceInitForm.tsx +++ b/src/pages/client/pages/ClientAddInstancePage/components/AddInstanceFormCard/components/AddInstanceInitForm/AddInstanceInitForm.tsx @@ -157,7 +157,7 @@ export const AddInstanceInitForm = ({ nextStep }: Props) => { } // register new instance // is user in need of full enrollment ? - if (r.user.is_active) { + if (r.user.enrolled) { //no, only create new device for desktop client debug('User already active, adding device only.'); nextStep({ diff --git a/src/pages/enrollment/steps/DeviceStep/components/DesktopSetup/DesktopSetup.tsx b/src/pages/enrollment/steps/DeviceStep/components/DesktopSetup/DesktopSetup.tsx index c8f178e2..bc5f880e 100644 --- a/src/pages/enrollment/steps/DeviceStep/components/DesktopSetup/DesktopSetup.tsx +++ b/src/pages/enrollment/steps/DeviceStep/components/DesktopSetup/DesktopSetup.tsx @@ -88,11 +88,31 @@ export const DesktopSetup = () => { const deviceResponse = await createDeviceMutation({ name: values.name, pubkey: publicKey, + }).then((res) => { + if (!res.ok) { + toaster.error(LL.common.messages.error()); + error( + `Failed to create device during the enrollment. Error details: ${JSON.stringify( + res.data, + )} Error status code: ${JSON.stringify(res.status)}`, + ); + throw Error('Failed to create device'); + } + return res; }); mutateUserActivation({ password: userPassword, phone_number: userInfo.phone_number, - }).then(() => { + }).then((res) => { + if (!res.ok) { + toaster.error(LL.common.messages.error()); + error( + `Failed to activate user during the enrollment. Error details: ${JSON.stringify( + res.data, + )} Error status code: ${JSON.stringify(res.status)}`, + ); + throw Error('Failed to activate user'); + } info('User activated'); setIsLoading(true); debug('Invoking save_device_config'); diff --git a/src/shared/components/icons/IconDefguard/IconDeguard.tsx b/src/shared/components/icons/IconDefguard/IconDeguard.tsx index e8d8d9f9..6c302bfe 100644 --- a/src/shared/components/icons/IconDefguard/IconDeguard.tsx +++ b/src/shared/components/icons/IconDefguard/IconDeguard.tsx @@ -7,34 +7,28 @@ type Props = { className?: string; }; -export const IconDefguard = ({ id, className, height = 44, width = 21 }: Props) => { - const gradientId = useId(); +export const IconDefguard = ({ id, className, height = 43, width = 21 }: Props) => { + const clipPathId = useId(); return ( - + + + - - - - + + + ); diff --git a/src/shared/components/icons/IconDefguardFull/IconDefguardFull.tsx b/src/shared/components/icons/IconDefguardFull/IconDefguardFull.tsx index e1403e0f..3c440c7b 100644 --- a/src/shared/components/icons/IconDefguardFull/IconDefguardFull.tsx +++ b/src/shared/components/icons/IconDefguardFull/IconDefguardFull.tsx @@ -7,38 +7,65 @@ type Props = { className?: string; }; -export const IconDefguardFull = ({ id, className, height = 56, width = 128 }: Props) => { - const gradientId = useId(); +export const IconDefguardFull = ({ id, className, height = 99, width = 257 }: Props) => { + const clipPathId = useId(); return ( - - + + + + + + + + + + + - - - - + + + ); diff --git a/src/shared/components/svg/DefguardLogoCollapsed.tsx b/src/shared/components/svg/DefguardLogoCollapsed.tsx index 00d04a94..d7767eef 100644 --- a/src/shared/components/svg/DefguardLogoCollapsed.tsx +++ b/src/shared/components/svg/DefguardLogoCollapsed.tsx @@ -1,38 +1,24 @@ import type { SVGProps } from 'react'; -const SvgDefguadNavLogoCollapsed = (props: SVGProps) => ( +const SvgDefguardLogoCollapsed = (props: SVGProps) => ( + + + - - - - + + + - - ); -export default SvgDefguadNavLogoCollapsed; +export default SvgDefguardLogoCollapsed; diff --git a/src/shared/components/svg/DefguardLogoDarkmode.tsx b/src/shared/components/svg/DefguardLogoDarkmode.tsx index a7ee5de9..06a23689 100644 --- a/src/shared/components/svg/DefguardLogoDarkmode.tsx +++ b/src/shared/components/svg/DefguardLogoDarkmode.tsx @@ -2,32 +2,26 @@ import type { SVGProps } from 'react'; const SvgDefguardLogoDarkmode = (props: SVGProps) => ( - - + + + + - - - - + + + ); diff --git a/src/shared/components/svg/DefguardLogoIcon.tsx b/src/shared/components/svg/DefguardLogoIcon.tsx index 4d7800a7..2ba72bd0 100644 --- a/src/shared/components/svg/DefguardLogoIcon.tsx +++ b/src/shared/components/svg/DefguardLogoIcon.tsx @@ -3,27 +3,21 @@ const SvgDefguardLogoIcon = (props: SVGProps) => ( - + + + - - - - + + + ); diff --git a/src/shared/components/svg/DefguardLogoText.tsx b/src/shared/components/svg/DefguardLogoText.tsx index 8656174d..ee0ddbaf 100644 --- a/src/shared/components/svg/DefguardLogoText.tsx +++ b/src/shared/components/svg/DefguardLogoText.tsx @@ -2,15 +2,15 @@ import type { SVGProps } from 'react'; const SvgDefguardLogoText = (props: SVGProps) => ( ); diff --git a/src/shared/hooks/api/types.ts b/src/shared/hooks/api/types.ts index c6e478a1..be2140e0 100644 --- a/src/shared/hooks/api/types.ts +++ b/src/shared/hooks/api/types.ts @@ -18,6 +18,7 @@ export type UserInfo = { is_active: boolean; phone_number: string; device_names: string[]; + enrolled: boolean; }; export type EnrollmentStartRequest = { @@ -83,6 +84,7 @@ export type EnrollmentInitialUserInfo = { email: string; phone_number?: string; is_active: boolean; + enrolled: boolean; }; export type EnrollmentInstanceInfo = { diff --git a/src/shared/images/svg/defguard-logo-collapsed.svg b/src/shared/images/svg/defguard-logo-collapsed.svg new file mode 100644 index 00000000..e13c498e --- /dev/null +++ b/src/shared/images/svg/defguard-logo-collapsed.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/shared/images/svg/defguard-logo-darkmode.svg b/src/shared/images/svg/defguard-logo-darkmode.svg index c4cdcbf9..5d09935c 100644 --- a/src/shared/images/svg/defguard-logo-darkmode.svg +++ b/src/shared/images/svg/defguard-logo-darkmode.svg @@ -1,17 +1,36 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/shared/images/svg/defguard-logo-icon.svg b/src/shared/images/svg/defguard-logo-icon.svg index 9df56861..b3dbd698 100644 --- a/src/shared/images/svg/defguard-logo-icon.svg +++ b/src/shared/images/svg/defguard-logo-icon.svg @@ -1,9 +1,12 @@ - - + + + + - - - - + + + - + \ No newline at end of file diff --git a/src/shared/images/svg/defguard-logo-text.svg b/src/shared/images/svg/defguard-logo-text.svg index c6b32f83..141b6668 100644 --- a/src/shared/images/svg/defguard-logo-text.svg +++ b/src/shared/images/svg/defguard-logo-text.svg @@ -1,10 +1,25 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file