feat: add the settings window

This commit is contained in:
Kevin Yue
2023-07-09 10:06:44 +08:00
parent 963b7d5407
commit bf96a88e21
45 changed files with 1470 additions and 641 deletions

View File

@@ -181,7 +181,7 @@ fn setup_window(window: &Window, event_tx: mpsc::Sender<AuthEvent>) -> EventHand
window.listen_global(AUTH_REQUEST_EVENT, move |event| {
if let Ok(payload) = TryInto::<AuthRequest>::try_into(event.payload()) {
let event_tx = event_tx.clone();
send_auth_event(event_tx.clone(), AuthEvent::Request(payload));
send_auth_event(event_tx, AuthEvent::Request(payload));
} else {
warn!("Invalid auth request payload");
}
@@ -198,7 +198,7 @@ async fn process(
process_request(window, auth_request)?;
let handle = tokio::spawn(show_window_after_timeout(window.clone()));
let auth_data = monitor_events(&window, event_rx).await;
let auth_data = monitor_events(window, event_rx).await;
if !handle.is_finished() {
handle.abort();
@@ -254,12 +254,12 @@ async fn monitor_auth_event(window: &Window, mut event_rx: mpsc::Receiver<AuthEv
if let Some(auth_event) = event_rx.recv().await {
match auth_event {
AuthEvent::Request(auth_request) => {
attempt_times = attempt_times + 1;
attempt_times += 1;
info!(
"Got auth request from auth-request event, attempt #{}",
attempt_times
);
if let Err(err) = process_request(&window, auth_request) {
if let Err(err) = process_request(window, auth_request) {
warn!("Error processing auth request: {}", err);
}
}
@@ -316,7 +316,7 @@ async fn monitor_window_close_event(window: &Window) {
if matches!(event, WindowEvent::CloseRequested { .. }) {
if let Ok(mut close_tx_locked) = close_tx.try_lock() {
if let Some(close_tx) = close_tx_locked.take() {
if let Err(_) = close_tx.send(()) {
if close_tx.send(()).is_err() {
println!("Error sending close event");
}
}
@@ -352,14 +352,23 @@ async fn handle_token_not_found(window: Window, cancel_timeout_rx: Arc<Mutex<mps
/// and send it to the event channel
fn parse_auth_data(main_res: &WebResource, auth_event_tx: mpsc::Sender<AuthEvent>) {
if let Some(response) = main_res.response() {
if let Some(auth_data) = read_auth_data_from_response(&response) {
debug!("Got auth data from HTTP headers: {:?}", auth_data);
send_auth_data(auth_event_tx, auth_data);
return;
match read_auth_data_from_response(&response) {
Ok(auth_data) => {
debug!("Got auth data from HTTP headers: {:?}", auth_data);
send_auth_data(auth_event_tx, auth_data);
return;
}
Err(AuthError::TokenInvalid) => {
debug!("Received invalid token from HTTP headers");
send_auth_error(auth_event_tx, AuthError::TokenInvalid);
return;
}
Err(AuthError::TokenNotFound) => {
debug!("Token not found in HTTP headers, trying to read from HTML");
}
}
}
let auth_event_tx = auth_event_tx.clone();
main_res.data(Cancellable::NONE, move |data| {
if let Ok(data) = data {
let html = String::from_utf8_lossy(&data);
@@ -378,20 +387,27 @@ fn parse_auth_data(main_res: &WebResource, auth_event_tx: mpsc::Sender<AuthEvent
}
/// Read the authentication data from the response headers
fn read_auth_data_from_response(response: &webkit2gtk::URIResponse) -> Option<AuthData> {
response.http_headers().and_then(|mut headers| {
let auth_data = AuthData::new(
headers.get("saml-username").map(GString::into),
headers.get("prelogin-cookie").map(GString::into),
headers.get("portal-userauthcookie").map(GString::into),
);
fn read_auth_data_from_response(response: &webkit2gtk::URIResponse) -> Result<AuthData, AuthError> {
response
.http_headers()
.map_or(Err(AuthError::TokenNotFound), |mut headers| {
let saml_status: Option<String> = headers.get("saml-auth-status").map(GString::into);
if saml_status == Some("-1".to_string()) {
return Err(AuthError::TokenInvalid);
}
if auth_data.check() {
Some(auth_data)
} else {
None
}
})
let auth_data = AuthData::new(
headers.get("saml-username").map(GString::into),
headers.get("prelogin-cookie").map(GString::into),
headers.get("portal-userauthcookie").map(GString::into),
);
if auth_data.check() {
Ok(auth_data)
} else {
Err(AuthError::TokenNotFound)
}
})
}
/// Read the authentication data from the HTML content
@@ -441,7 +457,7 @@ fn send_auth_error(auth_event_tx: mpsc::Sender<AuthEvent>, err: AuthError) {
}
fn send_auth_event(auth_event_tx: mpsc::Sender<AuthEvent>, auth_event: AuthEvent) {
let _ = tauri::async_runtime::spawn(async move {
tauri::async_runtime::spawn(async move {
if let Err(err) = auth_event_tx.send(auth_event).await {
warn!("Error sending event: {}", err);
}

View File

@@ -1,7 +1,12 @@
use crate::auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams};
use crate::{
auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams},
utils::get_openssl_conf,
utils::get_openssl_conf_path,
};
use gpcommon::{Client, ServerApiError, VpnStatus};
use std::sync::Arc;
use tauri::{AppHandle, State};
use tokio::fs;
#[tauri::command]
pub(crate) async fn service_online<'a>(client: State<'a, Arc<Client>>) -> Result<bool, ()> {
@@ -47,3 +52,22 @@ pub(crate) async fn saml_login(
};
auth::saml_login(params).await
}
#[tauri::command]
pub(crate) fn os_version() -> String {
whoami::distro()
}
#[tauri::command]
pub(crate) fn openssl_config() -> String {
get_openssl_conf()
}
#[tauri::command]
pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Result<()> {
let openssl_conf = get_openssl_conf();
let openssl_conf_path = get_openssl_conf_path(&app_handle);
fs::write(openssl_conf_path, openssl_conf).await?;
Ok(())
}

View File

@@ -3,13 +3,15 @@
windows_subsystem = "windows"
)]
use crate::utils::get_openssl_conf_path;
use env_logger::Env;
use gpcommon::{Client, ClientStatus, VpnStatus};
use log::warn;
use log::{info, warn};
use serde::Serialize;
use std::sync::Arc;
use tauri::Manager;
use std::{path::PathBuf, sync::Arc};
use tauri::{Manager, Wry};
use tauri_plugin_log::LogTarget;
use tauri_plugin_store::{with_store, StoreCollection};
mod auth;
mod commands;
@@ -25,8 +27,24 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let client_clone = client.clone();
let app_handle = app.handle();
let stores = app.state::<StoreCollection<Wry>>();
let path = PathBuf::from(".settings.dat");
let _ = with_store(app_handle.clone(), stores, path, |store| {
let settings_data = store.get("SETTINGS_DATA");
let custom_openssl = settings_data.map_or(false, |data| {
data["customOpenSSL"].as_bool().unwrap_or(false)
});
if custom_openssl {
info!("Using custom OpenSSL config");
let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string();
std::env::set_var("OPENSSL_CONF", openssl_conf);
}
Ok(())
});
tauri::async_runtime::spawn(async move {
let _ = client_clone.subscribe_status(move |client_status| match client_status {
client_clone.subscribe_status(move |client_status| match client_status {
ClientStatus::Vpn(vpn_status) => {
let payload = VpnStatusPayload { status: vpn_status };
if let Err(err) = app_handle.emit_all("vpn-status-received", payload) {
@@ -45,15 +63,12 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
app.manage(client);
match std::env::var("XDG_CURRENT_DESKTOP") {
Ok(desktop) => {
if desktop == "KDE" {
if let Some(main_window) = app.get_window("main") {
let _ = main_window.set_decorations(false);
}
if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") {
if desktop == "KDE" {
if let Some(main_window) = app.get_window("main") {
let _ = main_window.set_decorations(false);
}
}
Err(_) => (),
}
Ok(())
@@ -61,7 +76,6 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
fn main() {
// env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::default()
@@ -73,13 +87,17 @@ fn main() {
.with_colors(Default::default())
.build(),
)
.plugin(tauri_plugin_store::Builder::default().build())
.setup(setup)
.invoke_handler(tauri::generate_handler![
commands::service_online,
commands::vpn_status,
commands::vpn_connect,
commands::vpn_disconnect,
commands::saml_login
commands::saml_login,
commands::os_version,
commands::openssl_config,
commands::update_openssl_config,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -1,6 +1,6 @@
use log::{info, warn};
use std::time::Instant;
use tauri::Window;
use std::{path::PathBuf, time::Instant};
use tauri::{AppHandle, Window};
use tokio::sync::oneshot;
use url::{form_urlencoded, Url};
use webkit2gtk::{
@@ -9,7 +9,7 @@ use webkit2gtk::{
};
pub(crate) fn redact_url(url: &str) -> String {
if let Ok(mut url) = Url::parse(&url) {
if let Ok(mut url) = Url::parse(url) {
if let Err(err) = url.set_host(Some("redacted")) {
warn!("Error redacting URL: {}", err);
}
@@ -20,7 +20,7 @@ pub(crate) fn redact_url(url: &str) -> String {
let redacted_query = redact_query(url.query().unwrap_or(""));
url.set_query(Some(&redacted_query));
}
return url.to_string();
url.to_string()
} else {
warn!("Error parsing URL: {}", url);
url.to_string()
@@ -86,3 +86,40 @@ fn send_result(tx: oneshot::Sender<()>) {
warn!("Error sending clear cookies result");
}
}
pub(crate) fn get_openssl_conf() -> String {
// OpenSSL version number format: 0xMNN00PP0L
// https://www.openssl.org/docs/man3.0/man3/OPENSSL_VERSION_NUMBER.html
let version_3_0_4: i64 = 0x30000040;
let openssl_version = openssl::version::number();
// See: https://stackoverflow.com/questions/75763525/curl-35-error0a000152ssl-routinesunsafe-legacy-renegotiation-disabled
let option = if openssl_version >= version_3_0_4 {
"UnsafeLegacyServerConnect"
} else {
"UnsafeLegacyRenegotiation"
};
format!(
"openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = {}",
option
)
}
pub(crate) fn get_openssl_conf_path(app_handle: &AppHandle) -> PathBuf {
let app_dir = app_handle
.path_resolver()
.app_data_dir()
.expect("failed to resolve app dir");
app_dir.join("openssl.cnf")
}