//! # peach-web //! //! `peach-web` provides a web interface for monitoring and interacting with the //! PeachCloud device. This allows administration of the single-board computer //! (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related //! plugins. //! //! ## Design //! //! `peach-web` is written primarily in Rust and presents a web interface for //! interacting with the device. The stack currently consists of Rouille (Rust //! micro-web-framework), Maud (an HTML template engine for Rust), HTML and //! CSS. mod config; pub mod error; mod private_router; mod public_router; mod routes; mod templates; pub mod utils; use std::{ collections::HashMap, sync::{Mutex, RwLock}, }; use lazy_static::lazy_static; use log::info; // crate-local dependencies use config::ServerConfig; use utils::theme::Theme; // load the application configuration and create the theme switcher lazy_static! { static ref SERVER_CONFIG: ServerConfig = ServerConfig::new().expect("Failed to load rouille configuration values on server startup"); static ref THEME: RwLock = RwLock::new(Theme::Light); } /// Session data for each authenticated client. #[derive(Debug, Clone)] pub struct SessionData { _login: String, } /// Launch the peach-web server. fn main() { // initialize logger env_logger::init(); // set ip address / hostname and port for the webserver // defaults to "127.0.0.1:8000" let addr_and_port = format!("{}:{}", SERVER_CONFIG.addr, SERVER_CONFIG.port); // store the session data for each session and a hashmap that associates // each session id with the data // note: we are storing this data in memory. all sessions are erased when // the program is restarted. let sessions_storage: Mutex> = Mutex::new(HashMap::new()); info!("Launching web server on {}", addr_and_port); // the `start_server` starts listening forever on the given address rouille::start_server(addr_and_port, move |request| { // assign a unique id to each client (appends a cookie to the response // with a name of "SID" and a duration of one hour (3600 seconds) rouille::session::session(request, "SID", 3600, |session| { // if the "DISABLE_AUTH" env var is true, authenticate the session let mut session_data = if SERVER_CONFIG.disable_auth { Some(SessionData { _login: "success".to_string(), }) // if the client already has an identifier from a previous request, // try to load the existing session data. if successful, make a // copy of the data in order to avoid locking the session for too // long } else if session.client_has_sid() { sessions_storage.lock().unwrap().get(session.id()).cloned() } else { None }; // pass the request to the public router // // the public router includes authentication-related routes which // do not require the user to be authenticated (ie. login and reset // password) // // if the user is already authenticated, their request will be // passed to the private router by public_router::handle_route() // // we pass a mutable reference to the `Option` so that // the function is free to modify it let response = public_router::handle_route(request, &mut session_data); // since the function call to `handle_route` can modify the session // data, we have to store it back in the `sessions_storage` after // the request has been handled if let Some(data) = session_data { sessions_storage .lock() .unwrap() .insert(session.id().to_owned(), data); } else if session.client_has_sid() { // if the content of the `Option` was erased (ie. due to // deauthentication on logout), remove the session from the // storage. this is only done if the client already has an // identifier, otherwise calling `session.id()` will assign one sessions_storage.lock().unwrap().remove(session.id()); } response }) }); }