From 6e1997fd9dcde1dcb4886231f2ee34d852fa5579 Mon Sep 17 00:00:00 2001 From: filipriec Date: Sun, 25 May 2025 21:33:24 +0200 Subject: [PATCH] storage in the system is now storing log in details properly well --- Cargo.lock | 1 + client/Cargo.toml | 1 + client/src/config/mod.rs | 1 + client/src/config/storage.rs | 4 + client/src/config/storage/storage.rs | 101 ++++++++++++++++++++++ client/src/tui/functions/common.rs | 1 + client/src/tui/functions/common/login.rs | 15 ++++ client/src/tui/functions/common/logout.rs | 47 ++++++++++ client/src/ui/handlers/ui.rs | 33 +++++++ 9 files changed, 204 insertions(+) create mode 100644 client/src/config/storage.rs create mode 100644 client/src/config/storage/storage.rs create mode 100644 client/src/tui/functions/common/logout.rs diff --git a/Cargo.lock b/Cargo.lock index df45210..21415c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,7 @@ dependencies = [ "prost", "ratatui", "serde", + "serde_json", "time", "tokio", "toml", diff --git a/client/Cargo.toml b/client/Cargo.toml index e6e86c2..af2a07b 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -16,6 +16,7 @@ lazy_static = "1.5.0" prost = "0.13.5" ratatui = { version = "0.29.0", features = ["crossterm"] } serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" time = "0.3.41" tokio = { version = "1.44.2", features = ["full", "macros"] } toml = "0.8.20" diff --git a/client/src/config/mod.rs b/client/src/config/mod.rs index 7668248..917ed57 100644 --- a/client/src/config/mod.rs +++ b/client/src/config/mod.rs @@ -2,3 +2,4 @@ pub mod binds; pub mod colors; +pub mod storage; diff --git a/client/src/config/storage.rs b/client/src/config/storage.rs new file mode 100644 index 0000000..1c4e7c8 --- /dev/null +++ b/client/src/config/storage.rs @@ -0,0 +1,4 @@ +// src/config/storage.rs +pub mod storage; + +pub use storage::*; diff --git a/client/src/config/storage/storage.rs b/client/src/config/storage/storage.rs new file mode 100644 index 0000000..e7ab0d6 --- /dev/null +++ b/client/src/config/storage/storage.rs @@ -0,0 +1,101 @@ +// src/config/storage/storage.rs +use serde::{Deserialize, Serialize}; +use std::fs::{self, File}; +use std::io::Write; +use std::path::PathBuf; +use anyhow::{Context, Result}; +use tracing::{error, info}; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +pub const APP_NAME: &str = "multieko2_client"; +pub const TOKEN_FILE_NAME: &str = "auth.token"; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct StoredAuthData { + pub access_token: String, + pub user_id: String, + pub role: String, + pub username: String, +} + +pub fn get_token_storage_path() -> Result { + let state_dir = dirs::state_dir() + .or_else(|| dirs::home_dir().map(|home| home.join(".local").join("state"))) + .ok_or_else(|| anyhow::anyhow!("Could not determine state directory"))?; + + let app_state_dir = state_dir.join(APP_NAME); + fs::create_dir_all(&app_state_dir) + .with_context(|| format!("Failed to create app state directory at {:?}", app_state_dir))?; + + Ok(app_state_dir.join(TOKEN_FILE_NAME)) +} + +pub fn save_auth_data(data: &StoredAuthData) -> Result<()> { + let path = get_token_storage_path()?; + + let json_data = serde_json::to_string(data) + .context("Failed to serialize auth data")?; + + let mut file = File::create(&path) + .with_context(|| format!("Failed to create token file at {:?}", path))?; + + file.write_all(json_data.as_bytes()) + .context("Failed to write token data to file")?; + + // Set file permissions to 600 (owner read/write only) on Unix + #[cfg(unix)] + { + file.set_permissions(std::fs::Permissions::from_mode(0o600)) + .context("Failed to set token file permissions")?; + } + + info!("Auth data saved to {:?}", path); + Ok(()) +} + +pub fn load_auth_data() -> Result> { + let path = get_token_storage_path()?; + + if !path.exists() { + info!("Token file not found at {:?}", path); + return Ok(None); + } + + let json_data = fs::read_to_string(&path) + .with_context(|| format!("Failed to read token file at {:?}", path))?; + + if json_data.trim().is_empty() { + info!("Token file is empty at {:?}", path); + return Ok(None); + } + + match serde_json::from_str::(&json_data) { + Ok(data) => { + info!("Auth data loaded from {:?}", path); + Ok(Some(data)) + } + Err(e) => { + error!("Failed to deserialize token data from {:?}: {}. Deleting corrupt file.", path, e); + if let Err(del_e) = fs::remove_file(&path) { + error!("Failed to delete corrupt token file: {}", del_e); + } + Ok(None) + } + } +} + +pub fn delete_auth_data() -> Result<()> { + let path = get_token_storage_path()?; + + if path.exists() { + fs::remove_file(&path) + .with_context(|| format!("Failed to delete token file at {:?}", path))?; + info!("Token file deleted from {:?}", path); + } else { + info!("Token file not found for deletion at {:?}", path); + } + + Ok(()) +} diff --git a/client/src/tui/functions/common.rs b/client/src/tui/functions/common.rs index c8fd3fa..2b349a6 100644 --- a/client/src/tui/functions/common.rs +++ b/client/src/tui/functions/common.rs @@ -2,5 +2,6 @@ pub mod form; pub mod login; +pub mod logout; pub mod register; pub mod add_table; diff --git a/client/src/tui/functions/common/login.rs b/client/src/tui/functions/common/login.rs index 52f0e37..cbaaf2f 100644 --- a/client/src/tui/functions/common/login.rs +++ b/client/src/tui/functions/common/login.rs @@ -5,6 +5,7 @@ use crate::state::pages::auth::AuthState; use crate::state::pages::auth::LoginState; use crate::state::app::state::AppState; use crate::state::app::buffer::{AppView, BufferState}; +use crate::config::storage::storage::{StoredAuthData, save_auth_data}; use crate::state::pages::canvas_state::CanvasState; use crate::ui::handlers::context::DialogPurpose; use common::proto::multieko2::auth::LoginResponse; @@ -200,6 +201,20 @@ pub fn handle_login_result( auth_state.role = Some(response.role.clone()); auth_state.decoded_username = Some(response.username.clone()); + // --- NEW: Save auth data to file --- + let data_to_store = StoredAuthData { + access_token: response.access_token.clone(), + user_id: response.user_id.clone(), + role: response.role.clone(), + username: response.username.clone(), + }; + + if let Err(e) = save_auth_data(&data_to_store) { + error!("Failed to save auth data to file: {}", e); + // Continue anyway - user is still logged in for this session + } + // --- END NEW --- + let success_message = format!( "Login Successful!\n\nUsername: {}\nUser ID: {}\nRole: {}", response.username, response.user_id, response.role diff --git a/client/src/tui/functions/common/logout.rs b/client/src/tui/functions/common/logout.rs new file mode 100644 index 0000000..b2df449 --- /dev/null +++ b/client/src/tui/functions/common/logout.rs @@ -0,0 +1,47 @@ +// src/tui/functions/common/logout.rs +use crate::config::storage::delete_auth_data; +use crate::state::pages::auth::AuthState; +use crate::state::app::state::AppState; +use crate::state::app::buffer::{AppView, BufferState}; +use crate::ui::handlers::context::DialogPurpose; +use tracing::{error, info}; + +pub fn logout( + auth_state: &mut AuthState, + app_state: &mut AppState, + buffer_state: &mut BufferState, +) -> String { + // Clear auth state in memory + auth_state.auth_token = None; + auth_state.user_id = None; + auth_state.role = None; + auth_state.decoded_username = None; + + // Delete stored auth data + if let Err(e) = delete_auth_data() { + error!("Failed to delete stored auth data: {}", e); + // Continue anyway - user is logged out in memory + } + + // Navigate to intro screen + buffer_state.history = vec![AppView::Intro]; + buffer_state.active_index = 0; + + // Reset UI state + app_state.ui.focus_outside_canvas = false; + app_state.focused_button_index = 0; + + // Hide any open dialogs + app_state.hide_dialog(); + + // Show logout confirmation dialog + app_state.show_dialog( + "Logged Out", + "You have been successfully logged out.", + vec!["OK".to_string()], + DialogPurpose::LoginSuccess, // Reuse or create a new purpose + ); + + info!("User logged out successfully."); + "Logged out successfully".to_string() +} diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 9172513..37e28e9 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -4,6 +4,7 @@ use crate::config::binds::config::Config; use crate::config::colors::themes::Theme; use crate::services::grpc_client::GrpcClient; use crate::services::ui_service::UiService; +use crate::config::storage::storage::load_auth_data; use crate::modes::common::commands::CommandHandler; use crate::modes::handlers::event::{EventHandler, EventOutcome}; use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; @@ -68,6 +69,28 @@ pub async fn run_ui() -> Result<()> { let mut buffer_state = BufferState::default(); let mut app_state = AppState::new().context("Failed to create initial app state")?; + // --- DATA: Load auth data from file at startup --- + let mut auto_logged_in = false; + match load_auth_data() { + Ok(Some(stored_data)) => { + // TODO: Optionally validate token with server here + // For now, assume valid if successfully loaded + auth_state.auth_token = Some(stored_data.access_token); + auth_state.user_id = Some(stored_data.user_id); + auth_state.role = Some(stored_data.role); + auth_state.decoded_username = Some(stored_data.username); + auto_logged_in = true; + info!("Auth data loaded from file. User is auto-logged in."); + } + Ok(None) => { + info!("No stored auth data found. User will see intro/login."); + } + Err(e) => { + error!("Failed to load auth data: {}", e); + } + } + // --- END DATA --- + // Initialize app state with profile tree and table structure let column_names = UiService::initialize_app_state(&mut grpc_client, &mut app_state) @@ -78,6 +101,16 @@ pub async fn run_ui() -> Result<()> { UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?; form_state.reset_to_empty(); + // --- DATA2: Adjust initial view based on auth status --- + if auto_logged_in { + // User is auto-logged in, go to main app view + buffer_state.history = vec![AppView::Form("Adresar".to_string())]; + buffer_state.active_index = 0; + info!("Initial view set to Form due to auto-login."); + } + // If not auto-logged in, BufferState default (Intro) will be used + // --- END DATA2 --- + // --- FPS Calculation State --- let mut last_frame_time = Instant::now(); let mut current_fps = 0.0;