diff --git a/.gitignore b/.gitignore index 3b954cf..eb92dae 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ playwright/.cache/ /.idea/vcs.xml /.settings/ /.vscode/ +/config.toml diff --git a/Cargo.lock b/Cargo.lock index 08f3c50..8bef0e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,7 +740,7 @@ dependencies = [ "nom", "pathdiff", "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -1021,6 +1021,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1224,6 +1237,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1360,6 +1382,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hex" version = "0.4.3" @@ -1452,6 +1480,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.26" @@ -1577,6 +1611,17 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1930,9 +1975,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" @@ -2110,7 +2155,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -2275,7 +2320,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -2518,17 +2563,21 @@ dependencies = [ "cfg-if", "chrono", "console_error_panic_hook", + "env_logger", "futures-util", + "getopts", "lazy_static", "leptos", "leptos_actix", "leptos_meta", "leptos_router", + "log", "pwhash", "regex", "rust_decimal", "serde", "sqlx", + "toml 0.8.8", "uuid", "validator", "wasm-bindgen", @@ -2801,6 +2850,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + [[package]] name = "serde_test" version = "1.0.164" @@ -3307,6 +3365,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -3420,6 +3487,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -3527,6 +3628,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -3857,6 +3964,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 724fcb1..d8fc8e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,10 @@ validator = {version = "0.16.1", features = ["derive"]} pwhash = "1.0.0" futures-util = "0.3.28" regex = "1.10.2" +toml = "0.8.8" +log = "0.4.20" +env_logger = "0.10.1" +getopts = "0.2.21" [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] diff --git a/config_sample.toml b/config_sample.toml new file mode 100644 index 0000000..0deced5 --- /dev/null +++ b/config_sample.toml @@ -0,0 +1,10 @@ +[network] +bind_ip = '127.0.0.1' +port = 3000 + +[database] +user = 'user' +password = 'password' +db_name = 'rezervator' +host = 'localhost' +port = 5432 \ No newline at end of file diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 4c13b9b..7b456ee 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,5 +1,4 @@ use cfg_if::cfg_if; - pub mod data; pub mod company; pub mod user; @@ -10,13 +9,17 @@ pub mod opening_hours; macro_rules! perm_check { ($check:ident) => { use crate::backend::user::$check; + use crate::backend::user::logged_in_user; use actix_web::http::StatusCode; use leptos_actix::ResponseOptions; + use log::warn; if !$check().await { let response = expect_context::(); response.set_status(StatusCode::FORBIDDEN); + warn!("Permission denied for user: {}", logged_in_user().await.unwrap_or_default().login); + return Ok(ApiResponse::Error("Forbidden".to_string())) } } @@ -26,7 +29,6 @@ macro_rules! perm_check { macro_rules! user_check { ($check:expr) => { use crate::perm_check; - use crate::backend::user::logged_in_user; perm_check!(is_logged_in); let user = logged_in_user().await.unwrap_or(User::default()); @@ -35,6 +37,8 @@ macro_rules! user_check { let response = expect_context::(); response.set_status(StatusCode::FORBIDDEN); + warn!("Try to update not owned data. User: {}", user.login); + return Ok(ApiResponse::Error("You can change your own profile only".to_string())) } } diff --git a/src/backend/user.rs b/src/backend/user.rs index 240877e..773e542 100644 --- a/src/backend/user.rs +++ b/src/backend/user.rs @@ -8,6 +8,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use sqlx::{query_as, Error, PgPool, query}; use actix_session::*; use leptos_actix::{extract, redirect}; + use log::{info, warn}; pub async fn has_admin_user(pool: &PgPool) -> Result { let count: (i64,) = query_as(r#"SELECT COUNT(id) FROM "user" WHERE admin = $1"#) @@ -58,7 +59,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { } }} -#[server(Login, "/api")] +#[server] pub async fn login(username: String, password: String) -> Result, ServerFnError> { use actix_session::*; use leptos_actix::extract; @@ -75,10 +76,13 @@ pub async fn login(username: String, password: String) -> Result }) .await?; + info!("User {} logged in", username); + redirect("/admin"); return Ok(ApiResponse::Data(())); } + warn!("Login failed for user {}", username); let response = expect_context::(); response.set_status(StatusCode::UNAUTHORIZED); @@ -230,6 +234,8 @@ pub async fn create_user(user: UserProfile) -> Result, ServerFnE .execute(&pool) .await?; + info!("Created user {}", user.login()); + Ok(ApiResponse::Data(())) } @@ -259,5 +265,7 @@ pub async fn delete_user(id: i32) -> Result, ServerFnError> { .execute(&get_pool().await?) .await?; + info!("User deleted"); + Ok(ApiResponse::Data(())) } \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..33d6e5a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,62 @@ +#[cfg(feature = "ssr")] +use std::fs::read_to_string; +#[cfg(feature = "ssr")] +use std::net::SocketAddr; +#[cfg(feature = "ssr")] +use std::str::FromStr; +#[cfg(feature = "ssr")] +use serde::Deserialize; + +#[cfg(feature = "ssr")] +#[derive(Deserialize)] +pub struct Network { + bind_ip: String, + port: u16 +} + +#[cfg(feature = "ssr")] +impl Network { + pub fn bind_address(&self) -> SocketAddr { + SocketAddr::from_str(format!("{}:{}", &self.bind_ip, self.port).as_str()).expect("Can't parse bind IP address") + } +} + +#[cfg(feature = "ssr")] +#[derive(Deserialize)] +pub struct Database { + user: String, + password: String, + db_name: String, + host: String, + port: Option +} + +#[cfg(feature = "ssr")] +impl Database { + pub fn con_string(&self) -> String { + format!("postgres://{}:{}@{}:{}/{}", self.user, self.password, self.host, self.port.unwrap_or(5432), self.db_name) + } +} + +#[cfg(feature = "ssr")] +#[derive(Deserialize)] +pub struct Configuration { + network: Network, + database: Database +} + +#[cfg(feature = "ssr")] +impl Configuration { + pub fn network(&self) -> &Network { + &self.network + } + pub fn database(&self) -> &Database { + &self.database + } +} + +#[cfg(feature = "ssr")] +pub fn load_config(path: &str) -> Configuration { + toml::from_str(read_to_string(path).expect("Can not open config file").as_str()).expect("Can not parse config file") +} + diff --git a/src/lib.rs b/src/lib.rs index 86ad729..a5c8934 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod pages; mod components; mod validator; pub mod error; +pub mod config; use cfg_if::cfg_if; diff --git a/src/main.rs b/src/main.rs index a00fd6e..3207e14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,16 +14,38 @@ async fn main() -> std::io::Result<()> { use sqlx::migrate; use sqlx::postgres::PgPoolOptions; use rezervator::backend::auth_middleware::Authentication; + use rezervator::config::load_config; + use env_logger::Env; + use std::env::args; + use getopts::Options; + use log::info; + + let args: Vec = args().collect(); + let mut opts = Options::new(); + opts.optopt("c", "config", "Path to config file. Default is ./config.toml", ""); + opts.optflag("h", "help", "Show help"); + let matches = opts.parse(&args[1..]).expect("Can't parse commandline"); + + if matches.opt_present("h") { + println!("{}",opts.usage(format!("Usage: {} [options]", + args.get(0).unwrap_or(&"".to_string())).as_str())); + return Ok(()); + } + + let cfg_path = matches.opt_str("c").unwrap_or("config.toml".to_string()); + + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + info!("Starting server"); let conf = get_configuration(None).await.unwrap(); - let addr = conf.leptos_options.site_addr; + let srv_conf = load_config(&cfg_path); // Generate the list of routes in your Leptos App let routes = generate_route_list(|| view! { }); let key = Key::generate(); let pool = PgPoolOptions::new() .max_connections(10) - .connect("postgres://pepa:mkoijn@localhost/rezervator").await.unwrap(); + .connect(&srv_conf.database().con_string()).await.unwrap(); migrate!().run(&pool).await.expect("could not run SQLx migrations"); @@ -47,7 +69,7 @@ async fn main() -> std::io::Result<()> { .service(Files::new("/", site_root)) //.wrap(middleware::Compress::default()) }) - .bind(&addr)? + .bind(srv_conf.network().bind_address())? .run() .await }