From a3b757abad7c55e717a44809c7d9357b9f74872a Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 11 Apr 2021 13:14:11 +0200 Subject: [PATCH 1/8] initial port to tracing --- Cargo.lock | 288 ++++++++++++++++++++++++---------------- Cargo.toml | 8 +- src/bin/pslink/cli.rs | 99 +++++--------- src/bin/pslink/main.rs | 69 ++++++---- src/bin/pslink/views.rs | 29 ++-- src/lib.rs | 15 --- src/models.rs | 2 +- src/queries.rs | 34 ++--- 8 files changed, 279 insertions(+), 265 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c83b89..e18936c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ checksum = "c697a62a2f51c5c26af6b1dded0622f15bec690da191615947e0c1b2b7b75198" dependencies = [ "actix-web", "chrono", - "futures 0.3.13", + "futures 0.3.14", "pin-project 0.4.28", "slog", ] @@ -307,7 +307,7 @@ dependencies = [ "actix-service", "actix-web", "derive_more", - "futures 0.3.13", + "futures 0.3.14", "mime_guess", "path-slash", ] @@ -422,6 +422,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.40" @@ -829,7 +838,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim", @@ -920,9 +929,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1038,27 +1047,6 @@ dependencies = [ "generic-array 0.14.4", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - [[package]] name = "discard" version = "1.0.4" @@ -1242,9 +1230,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a685ab99b8f60a271b44d5dd1a76e55124a8c9fa0407b7a8e9cd172d5b588" +checksum = "859e0fa5d4a9b5f73671712ce3e400fa17133c9b9faac607ada8e469f0b6be6c" dependencies = [ "futures-core", "futures-sink", @@ -1305,9 +1293,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" +checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" dependencies = [ "futures-channel", "futures-core", @@ -1320,9 +1308,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" dependencies = [ "futures-core", "futures-sink", @@ -1330,9 +1318,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" [[package]] name = "futures-cpupool" @@ -1346,9 +1334,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" +checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" dependencies = [ "futures-core", "futures-task", @@ -1357,15 +1345,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" [[package]] name = "futures-macro" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.26", @@ -1375,21 +1363,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" +checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" [[package]] name = "futures-task" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" +checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" [[package]] name = "futures-util" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" dependencies = [ "futures-channel", "futures-core", @@ -1433,6 +1421,16 @@ dependencies = [ "version_check 0.9.3", ] +[[package]] +name = "gethostname" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1896,6 +1894,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.8" @@ -2397,12 +2404,14 @@ dependencies = [ "rand 0.8.3", "rpassword", "serde", - "slog", - "slog-async", - "slog-term", "sqlx", "tera", "thiserror", + "tracing", + "tracing-actix-web", + "tracing-bunyan-formatter", + "tracing-log", + "tracing-subscriber", ] [[package]] @@ -2688,16 +2697,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom 0.2.2", - "redox_syscall", -] - [[package]] name = "regex" version = "1.4.5" @@ -2709,6 +2708,16 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.23" @@ -2793,12 +2802,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustversion" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" - [[package]] name = "ryu" version = "1.0.5" @@ -2834,9 +2837,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ "ring", "untrusted", @@ -2964,6 +2967,15 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sharded-slab" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -2985,31 +2997,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" -[[package]] -name = "slog-async" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60813879f820c85dbc4eabf3269befe374591289019775898d56a81a804fbdc" -dependencies = [ - "crossbeam-channel", - "slog", - "take_mut", - "thread_local", -] - -[[package]] -name = "slog-term" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c1e7e5aab61ced6006149ea772770b84a0d16ce0f7885def313e4829946d76" -dependencies = [ - "atty", - "chrono", - "slog", - "term", - "thread_local", -] - [[package]] name = "slug" version = "0.1.4" @@ -3148,7 +3135,7 @@ dependencies = [ "cargo_metadata", "dotenv", "either", - "futures 0.3.13", + "futures 0.3.14", "heck", "hex", "lazy_static", @@ -3291,12 +3278,6 @@ dependencies = [ "unicode-xid 0.2.1", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tap" version = "1.0.1" @@ -3335,17 +3316,6 @@ dependencies = [ "unic-segment", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -3550,9 +3520,51 @@ dependencies = [ "cfg-if 1.0.0", "log", "pin-project-lite 0.2.6", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-actix-web" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc36fc2f840643e49d220d07cd7ca81bc31c7f6df25f164d4257971533dab354" +dependencies = [ + "actix-web", + "futures 0.3.14", + "tracing", + "tracing-futures", + "uuid", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +dependencies = [ + "proc-macro2 1.0.26", + "quote 1.0.9", + "syn", +] + +[[package]] +name = "tracing-bunyan-formatter" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6b1b6c038da11d8d704f93b3974b0394824cdcc8c818c48255183cf315fd5" +dependencies = [ + "chrono", + "gethostname", + "log", + "serde", + "serde_json", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "tracing-core" version = "0.1.17" @@ -3572,6 +3584,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705096c6f83bf68ea5d357a6aa01829ddbdac531b357b45abeca842938085baa" +dependencies = [ + "ansi_term 0.12.1", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + [[package]] name = "trust-dns-proto" version = "0.19.7" @@ -3581,7 +3636,7 @@ dependencies = [ "async-trait", "cfg-if 1.0.0", "enum-as-inner", - "futures 0.3.13", + "futures 0.3.14", "idna", "lazy_static", "log", @@ -3599,7 +3654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" dependencies = [ "cfg-if 0.1.10", - "futures 0.3.13", + "futures 0.3.14", "ipconfig", "lazy_static", "log", @@ -3810,6 +3865,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.2", +] + [[package]] name = "vcpkg" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index 83f9c3a..f93dbee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,11 @@ dotenv = "0.15.0" actix-identity = "0.3" chrono = { version = "0.4", features = ["serde"] } argonautica = "0.2" -slog = { version = "2", features = ["max_level_trace", "release_max_level_info"] } -slog-term = "2" -slog-async = "2" +tracing = { version = "0.1", features = ["log"] } +tracing-bunyan-formatter = "0.2.0" +tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter"] } +tracing-log = "0.1" +tracing-actix-web = "0.2.1" qrcode = "0.12" image = "0.23" rand="0.8" diff --git a/src/bin/pslink/cli.rs b/src/bin/pslink/cli.rs index d51731d..4b13919 100644 --- a/src/bin/pslink/cli.rs +++ b/src/bin/pslink/cli.rs @@ -12,7 +12,7 @@ use std::{ use pslink::{models::NewUser, models::User, ServerConfig, ServerError}; -use slog::{Drain, Logger}; +use tracing::{error, info, trace, warn}; static MIGRATOR: Migrator = sqlx::migrate!(); @@ -122,7 +122,7 @@ fn generate_cli() -> App<'static, 'static> { ) } -async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConfig { +async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig { let secret = config .value_of("secret") .expect("Failed to read the secret") @@ -132,20 +132,11 @@ async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConf use rand::{thread_rng, Rng}; if secret.is_empty() { - slog_warn!( - log, - "No secret was found! Use the environment variable PSLINK_SECRET to set one." - ); - slog_warn!( - log, - "If you change the secret all passwords will be invalid" - ); - slog_warn!(log, "Using an auto generated one for this run."); + warn!("No secret was found! Use the environment variable PSLINK_SECRET to set one."); + warn!("If you change the secret all passwords will be invalid"); + warn!("Using an auto generated one for this run."); } else { - slog_warn!( - log, - "The provided secret was too short. Using an autogenerated one." - ) + warn!("The provided secret was too short. Using an autogenerated one.") } thread_rng() @@ -195,8 +186,6 @@ async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConf .parse::() .expect("Failed to parse the protocol"); - let log = log.new(slog_o!("host" => public_url.clone())); - crate::ServerConfig { secret, db, @@ -205,7 +194,6 @@ async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConf internal_ip, port, protocol, - log, empty_forward_url, brand_name, } @@ -214,16 +202,9 @@ async fn parse_args_to_config(config: ArgMatches<'_>, log: Logger) -> ServerConf pub(crate) async fn setup() -> Result, ServerError> { dotenv().ok(); - // initiallize the logger - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - let log = slog::Logger::root(drain, slog_o!("name" => "Pslink")); - // Print launch info - slog_info!(log, "Launching Pslink a 'Private short link generator'"); - slog_trace!(log, "logging initialized"); + info!("Launching Pslink a 'Private short link generator'"); + trace!("logging initialized"); let app = generate_cli(); @@ -239,7 +220,7 @@ pub(crate) async fn setup() -> Result, ServerError> .parse::() .expect("Failed to parse Database path."); if !db.exists() { - slog_trace!(log, "No database file found {}", db.display()); + trace!("No database file found {}", db.display()); if config.subcommand_matches("migrate-database").is_none() { let msg = format!( concat!( @@ -249,17 +230,17 @@ pub(crate) async fn setup() -> Result, ServerError> ), db.display() ); - slog_error!(log, "{}", msg); + error!("{}", msg); eprintln!("{}", msg); return Ok(None); } - slog_trace!(log, "Creating database: {}", db.display()); + trace!("Creating database: {}", db.display()); // create an empty database file. The if above makes sure that this file does not exist. File::create(db)?; }; - let server_config: crate::ServerConfig = parse_args_to_config(config.clone(), log).await; + let server_config: crate::ServerConfig = parse_args_to_config(config.clone()).await; if let Some(_migrate_config) = config.subcommand_matches("generate-env") { return match generate_env_file(&server_config) { @@ -284,21 +265,15 @@ pub(crate) async fn setup() -> Result, ServerError> let num_users = User::count_admins(&server_config).await?; if num_users.number < 1 { - slog_warn!( - &server_config.log, - concat!( - "No admin user created you will not be", - " able to do anything as the service is invite only.", - " Create a user with `pslink create-admin`" - ) - ); + warn!(concat!( + "No admin user created you will not be", + " able to do anything as the service is invite only.", + " Create a user with `pslink create-admin`" + )); } else { - slog_trace!(&server_config.log, "At least one admin user is found."); + trace!("At least one admin user is found."); } - slog_trace!( - &server_config.log, - "Initialization finished starting the service." - ); + trace!("Initialization finished starting the service."); Ok(Some(server_config)) } else { println!("{}", config.usage()); @@ -308,7 +283,7 @@ pub(crate) async fn setup() -> Result, ServerError> /// Interactively create a new admin user. async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> { - slog_info!(&config.log, "Creating an admin user."); + info!("Creating an admin user."); let sin = io::stdin(); // wait for logging: @@ -325,11 +300,9 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> { print!("Please enter the password for {}: ", new_username); io::stdout().flush().unwrap(); let password = rpassword::read_password().unwrap(); - slog_info!( - &config.log, + info!( "Creating {} ({}) with given password ", - &new_username, - &new_email + &new_username, &new_email ); let new_admin = NewUser::new(new_username.clone(), new_email.clone(), &password, config)?; @@ -338,14 +311,13 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> { let created_user = User::get_user_by_name(&new_username, config).await?; created_user.toggle_admin(config).await?; - slog_info!(&config.log, "Admin user created: {}", new_username); + info!("Admin user created: {}", new_username); Ok(()) } async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> { - slog_info!( - config.log, + info!( "Creating a database file and running the migrations in the file {}:", &config.db.display() ); @@ -355,22 +327,13 @@ async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> { fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> { if std::path::Path::new(".env").exists() { - slog_error!( - server_config.log, - "ERROR: There already is a .env file - ABORT!" - ) + error!("ERROR: There already is a .env file - ABORT!") } else { - slog_info!( - server_config.log, - "Creating a .env file with default options" - ); - slog_info!( - server_config.log, - concat!( - "The SECRET_KEY variable is used for password encryption.", - "If it is changed all existing passwords are invalid." - ) - ); + info!("Creating a .env file with default options"); + info!(concat!( + "The SECRET_KEY variable is used for password encryption.", + "If it is changed all existing passwords are invalid." + )); let mut file = std::fs::File::create(".env")?; let conf_file_content = server_config.to_env_strings(); @@ -379,7 +342,7 @@ fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> { file.write_all(l.as_bytes()) .expect("failed to write .env file") }); - slog_info!(server_config.log, "Successfully created the env file!") + info!("Successfully created the env file!") } Ok(()) } diff --git a/src/bin/pslink/main.rs b/src/bin/pslink/main.rs index 50e38a7..ff771f6 100644 --- a/src/bin/pslink/main.rs +++ b/src/bin/pslink/main.rs @@ -1,19 +1,4 @@ extern crate sqlx; -#[allow(unused_imports)] -#[macro_use( - slog_o, - slog_trace, - slog_info, - slog_warn, - slog_error, - slog_log, - slog_record, - slog_record_static, - slog_b, - slog_kv -)] -extern crate slog; -extern crate slog_async; mod cli; mod views; @@ -26,6 +11,33 @@ use tera::Tera; use pslink::{ServerConfig, ServerError}; +use tracing::instrument; +use tracing::{error, info, trace}; +use tracing::{subscriber::set_global_default, Subscriber}; +use tracing_actix_web::TracingLogger; +use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; +use tracing_log::LogTracer; +use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; + +/// Compose multiple layers into a `tracing`'s subscriber. +pub fn get_subscriber(name: String, env_filter: String) -> impl Subscriber + Send + Sync { + let env_filter = + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter)); + let formatting_layer = BunyanFormattingLayer::new(name, std::io::stdout); + Registry::default() + .with(env_filter) + .with(JsonStorageLayer) + .with(formatting_layer) +} + +/// Register a subscriber as global default to process span data. +/// +/// It should only be called once! +pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) { + LogTracer::init().expect("Failed to set logger"); + set_global_default(subscriber).expect("Failed to set subscriber"); +} + include!(concat!(env!("OUT_DIR"), "/generated.rs")); static_loader! { @@ -35,8 +47,10 @@ static_loader! { }; } +#[instrument] fn build_tera() -> Result { let mut tera = Tera::default(); + tracing::info!("Tracing activated!"); // Add translation support tera.register_function("fluent", FluentLoader::new(&*LOCALES)); @@ -86,29 +100,22 @@ fn build_tera() -> Result { #[allow(clippy::future_not_send, clippy::too_many_lines)] async fn webservice(server_config: ServerConfig) -> Result<()> { let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); - let cfg = server_config.clone(); - slog_info!( - cfg.log, + info!( "Running on: {}://{}/admin/login/", - &server_config.protocol, - host_port + &server_config.protocol, host_port ); - slog_info!( - cfg.log, + info!( "If the public url is set up correctly it should be accessible via: {}://{}/admin/login/", - &server_config.protocol, - &server_config.public_url + &server_config.protocol, &server_config.public_url ); let tera = build_tera()?; - slog_trace!(cfg.log, "The tera templates are ready"); + trace!("The tera templates are ready"); HttpServer::new(move || { let generated = generate(); App::new() .data(server_config.clone()) - .wrap(actix_slog::StructuredLogger::new( - server_config.log.new(slog_o!("log_type" => "access")), - )) + .wrap(TracingLogger) .wrap(IdentityService::new( CookieIdentityPolicy::new(&[0; 32]) .name("auth-cookie") @@ -191,7 +198,7 @@ async fn webservice(server_config: ServerConfig) -> Result<()> { .bind(host_port) .context("Failed to bind to port") .map_err(|e| { - slog_error!(cfg.log, "Failed to bind to port!"); + error!("Failed to bind to port!"); e })? .run() @@ -199,8 +206,12 @@ async fn webservice(server_config: ServerConfig) -> Result<()> { .context("Failed to run the webservice") } +#[instrument] #[actix_web::main] async fn main() -> std::result::Result<(), ServerError> { + let subscriber = get_subscriber("app".into(), "info".into()); + init_subscriber(subscriber); + match cli::setup().await { Ok(Some(server_config)) => webservice(server_config).await.map_err(|e| { println!("{:?}", e); diff --git a/src/bin/pslink/views.rs b/src/bin/pslink/views.rs index 06398a9..10a8969 100644 --- a/src/bin/pslink/views.rs +++ b/src/bin/pslink/views.rs @@ -15,6 +15,7 @@ use image::{DynamicImage, ImageOutputFormat, Luma}; use qrcode::{render::svg, QrCode}; use queries::{authenticate, Role}; use tera::{Context, Tera}; +use tracing::{info, trace, warn}; use pslink::forms::LinkForm; use pslink::models::{LoginUser, NewUser}; @@ -150,7 +151,7 @@ pub async fn view_profile( id: Identity, user_id: web::Path, ) -> Result { - slog_info!(config.log, "Viewing Profile!"); + info!("Viewing Profile!"); if let Ok(query) = queries::get_user(&id, &user_id.0, &config).await { let mut data = Context::new(); data.insert("user", &query.user); @@ -177,7 +178,7 @@ pub async fn edit_profile( id: Identity, user_id: web::Path, ) -> Result { - slog_info!(config.log, "Editing Profile!"); + info!("Editing Profile!"); if let Ok(query) = queries::get_user(&id, &user_id.0, &config).await { let mut data = Context::new(); data.insert("user", &query.user); @@ -259,7 +260,7 @@ pub async fn process_signup( config: web::Data, id: Identity, ) -> Result { - slog_info!(config.log, "Creating a User: {:?}", &data); + info!("Creating a User: {:?}", &data); match queries::create_user(&id, &data, &config).await { Ok(item) => { Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", item.item.username))) @@ -296,7 +297,7 @@ pub async fn login( req: HttpRequest, ) -> Result { let language_code = detect_language(&req)?; - slog_info!(config.log, "Detected languagecode: {}", &language_code); + info!("Detected languagecode: {}", &language_code); let mut data = Context::new(); data.insert("title", "Login"); data.insert("language", &language_code); @@ -305,8 +306,7 @@ pub async fn login( if let Ok(r) = authenticate(&id, &config).await { match r { Role::Admin { user } | Role::Regular { user } => { - slog_trace!( - config.log, + trace!( "This user ({}) is already logged in redirecting to /admin/index/", user.username ); @@ -315,7 +315,7 @@ pub async fn login( Role::Disabled | Role::NotAuthenticated => (), } } - slog_warn!(config.log, "Invalid user session. The user might be deleted or something tampered with the cookies."); + warn!("Invalid user session. The user might be deleted or something tampered with the cookies."); id.forget(); } @@ -340,7 +340,7 @@ pub async fn process_login( .verify()?; if valid { - slog_info!(config.log, "Log-in of user: {}", &u.username); + info!("Log-in of user: {}", &u.username); let session_token = u.username; id.remember(session_token); Ok(redirect_builder("/admin/index/")) @@ -349,7 +349,7 @@ pub async fn process_login( } } Err(e) => { - slog_info!(config.log, "Failed to login: {}", e); + info!("Failed to login: {}", e); Ok(redirect_builder("/admin/login/")) } } @@ -366,21 +366,18 @@ pub async fn redirect( data: web::Path, req: HttpRequest, ) -> Result { - slog_info!(config.log, "Redirecting to {:?}", data); + info!("Redirecting to {:?}", data); let link = queries::get_link_simple(&data.0, &config).await; - slog_info!(config.log, "link: {:?}", link); + info!("link: {:?}", link); match link { Ok(link) => { queries::click_link(link.id, &config).await?; Ok(redirect_builder(&link.target)) } Err(ServerError::Database(e)) => { - slog_info!( - config.log, + info!( "Link was not found: http://{}/{} \n {}", - &config.public_url, - &data.0, - e + &config.public_url, &data.0, e ); let mut data = Context::new(); data.insert("title", "Wurde gel\u{f6}scht"); diff --git a/src/lib.rs b/src/lib.rs index d5d034a..d35be74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,4 @@ extern crate sqlx; -#[allow(unused_imports)] -#[macro_use( - slog_o, - slog_info, - slog_warn, - slog_error, - slog_log, - slog_record, - slog_record_static, - slog_b, - slog_kv -)] -extern crate slog; -extern crate slog_async; pub mod forms; pub mod models; @@ -171,7 +157,6 @@ pub struct ServerConfig { pub internal_ip: String, pub port: u32, pub protocol: Protocol, - pub log: slog::Logger, pub empty_forward_url: String, pub brand_name: String, } diff --git a/src/models.rs b/src/models.rs index 2cdad31..033460d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -197,7 +197,7 @@ impl Link { let link = sqlx::query_as!(Self, "Select * from links where code = ? ", code) .fetch_one(&server_config.db_pool) .await; - slog_info!(server_config.log, "Found link: {:?}", &link); + tracing::info!("Found link: {:?}", &link); link.map_err(ServerError::Database) } diff --git a/src/queries.rs b/src/queries.rs index f40dc20..8f2168a 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -1,6 +1,7 @@ use actix_identity::Identity; use actix_web::web; use serde::Serialize; +use tracing::info; use super::models::{Count, Link, NewUser, User}; use crate::{ @@ -170,7 +171,7 @@ pub async fn get_user( server_config: &ServerConfig, ) -> Result, ServerError> { if let Ok(uid) = user_id.parse::() { - slog_info!(server_config.log, "Getting user {}", uid); + info!("Getting user {}", uid); let auth = authenticate(id, server_config).await?; if auth.admin_or_self(uid) { match auth { @@ -214,7 +215,7 @@ pub async fn create_user( data: &web::Form, server_config: &ServerConfig, ) -> Result, ServerError> { - slog_info!(server_config.log, "Creating a User: {:?}", &data); + info!("Creating a User: {:?}", &data); let auth = authenticate(id, server_config).await?; match auth { Role::Admin { user } => { @@ -258,7 +259,7 @@ pub async fn update_user( if auth.admin_or_self(uid) { match auth { Role::Admin { .. } | Role::Regular { .. } => { - slog_info!(server_config.log, "Updating userinfo: "); + info!("Updating userinfo: "); let password = if data.password.len() > 3 { NewUser::hash_password(&data.password, server_config)? } else { @@ -303,21 +304,17 @@ pub async fn toggle_admin( let auth = authenticate(id, server_config).await?; match auth { Role::Admin { .. } => { - slog_info!(server_config.log, "Changing administrator priviledges: "); + info!("Changing administrator priviledges: "); let unchanged_user = User::get_user(uid, server_config).await?; let old = unchanged_user.role; unchanged_user.toggle_admin(server_config).await?; - slog_info!(server_config.log, "Toggling role: old was {}", old); + info!("Toggling role: old was {}", old); let changed_user = User::get_user(uid, server_config).await?; - slog_info!( - server_config.log, - "Toggled role: new is {}", - changed_user.role - ); + info!("Toggled role: new is {}", changed_user.role); Ok(Item { user: changed_user.clone(), item: changed_user, @@ -382,10 +379,10 @@ pub async fn get_link_simple( link_code: &str, server_config: &ServerConfig, ) -> Result { - slog_info!(server_config.log, "Getting link for {:?}", link_code); + info!("Getting link for {:?}", link_code); let link = Link::get_link_by_code(link_code, server_config).await?; - slog_info!(server_config.log, "Foun d link for {:?}", link); + info!("Foun d link for {:?}", link); Ok(link) } @@ -394,7 +391,7 @@ pub async fn get_link_simple( /// # Errors /// Fails with [`ServerError`] if access to the database fails. pub async fn click_link(link_id: i64, server_config: &ServerConfig) -> Result<(), ServerError> { - slog_info!(server_config.log, "Clicking on {:?}", link_id); + info!("Clicking on {:?}", link_id); let new_click = NewClick::new(link_id); new_click.insert_click(server_config).await?; Ok(()) @@ -429,12 +426,7 @@ pub async fn update_link( data: web::Form, server_config: &ServerConfig, ) -> Result, ServerError> { - slog_info!( - server_config.log, - "Changing link to: {:?} {:?}", - &data, - &link_code - ); + info!("Changing link to: {:?} {:?}", &data, &link_code); let auth = authenticate(id, server_config).await?; match auth { Role::Admin { .. } | Role::Regular { .. } => { @@ -472,9 +464,9 @@ pub async fn create_link( match auth { Role::Admin { user } | Role::Regular { user } => { let code = data.code.clone(); - slog_info!(server_config.log, "Creating link for: {}", &code); + info!("Creating link for: {}", &code); let new_link = NewLink::from_link_form(data.into_inner(), user.id); - slog_info!(server_config.log, "Creating link for: {:?}", &new_link); + info!("Creating link for: {:?}", &new_link); new_link.insert(server_config).await?; let new_link = get_link_simple(&code, server_config).await?; From ac172670bec681074957eea9432fbb611dc0b4e9 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Mon, 12 Apr 2021 16:30:18 +0200 Subject: [PATCH 2/8] simplifying apply_migrations --- src/bin/pslink/cli.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/bin/pslink/cli.rs b/src/bin/pslink/cli.rs index 4b13919..db4b6a4 100644 --- a/src/bin/pslink/cli.rs +++ b/src/bin/pslink/cli.rs @@ -327,22 +327,25 @@ async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> { fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> { if std::path::Path::new(".env").exists() { - error!("ERROR: There already is a .env file - ABORT!") - } else { - info!("Creating a .env file with default options"); - info!(concat!( - "The SECRET_KEY variable is used for password encryption.", - "If it is changed all existing passwords are invalid." + return Err(ServerError::User( + "ERROR: There already is a .env file - ABORT!".to_string(), )); - let mut file = std::fs::File::create(".env")?; - - let conf_file_content = server_config.to_env_strings(); - - conf_file_content.iter().for_each(|l| { - file.write_all(l.as_bytes()) - .expect("failed to write .env file") - }); - info!("Successfully created the env file!") } + + info!( + r#"Creating a .env file with default options + The SECRET_KEY variable is used for password encryption. + If it is changed all existing passwords are invalid."# + ); + + let mut file = std::fs::File::create(".env")?; + let conf_file_content = server_config.to_env_strings(); + + for line in &conf_file_content { + file.write_all(line.as_bytes()) + .expect("failed to write .env file") + } + info!("Successfully created the env file!"); + Ok(()) } From 6fd36936a3cf4ca66dbd9286cbbd3ebbf3fbe86b Mon Sep 17 00:00:00 2001 From: Dietrich Date: Mon, 12 Apr 2021 16:32:59 +0200 Subject: [PATCH 3/8] Enable jaeger + opentracing logging --- Cargo.lock | 74 ++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 ++-- src/bin/pslink/main.rs | 25 +++++++++----- src/bin/pslink/views.rs | 28 +++++++++++++++- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e18936c..1825e85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1713,6 +1713,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "integer-encoding" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dc51180a9b377fd75814d0cc02199c20f8e99433d6762f650d39cdbbd3b56f" + [[package]] name = "intl-memoizer" version = "0.5.1" @@ -2119,6 +2125,44 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "opentelemetry" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91cea1dfd50064e52db033179952d18c770cbc5dfefc8eba45d619357ba3914" +dependencies = [ + "async-trait", + "futures 0.3.14", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project 1.0.6", + "rand 0.8.3", + "thiserror", +] + +[[package]] +name = "opentelemetry-jaeger" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd4984441954f9ebbe3eebdfc6fd4fa95be6400d403171228779b949f3cd918" +dependencies = [ + "async-trait", + "lazy_static", + "opentelemetry", + "thiserror", + "thrift", +] + +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", +] + [[package]] name = "ouroboros" version = "0.8.3" @@ -2400,6 +2444,8 @@ dependencies = [ "fluent-langneg", "fluent-templates", "image", + "opentelemetry", + "opentelemetry-jaeger", "qrcode", "rand 0.8.3", "rpassword", @@ -2410,7 +2456,7 @@ dependencies = [ "tracing", "tracing-actix-web", "tracing-bunyan-formatter", - "tracing-log", + "tracing-opentelemetry", "tracing-subscriber", ] @@ -3372,6 +3418,19 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "thrift" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6d965454947cc7266d22716ebfd07b18d84ebaf35eec558586bbb2a8cb6b5b" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float", + "threadpool", +] + [[package]] name = "tiff" version = "0.6.1" @@ -3595,6 +3654,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99003208b647dae59dcefc49c98aecaa3512fbc29351685d4b9ef23a9218458e" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "tracing-serde" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index f93dbee..fe55603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,11 @@ chrono = { version = "0.4", features = ["serde"] } argonautica = "0.2" tracing = { version = "0.1", features = ["log"] } tracing-bunyan-formatter = "0.2.0" -tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter"] } -tracing-log = "0.1" +tracing-subscriber = { version = "0.2.17", features = ["registry", "env-filter"] } tracing-actix-web = "0.2.1" +tracing-opentelemetry = "0.12" +opentelemetry = "0.13" +opentelemetry-jaeger="0.12" qrcode = "0.12" image = "0.23" rand="0.8" diff --git a/src/bin/pslink/main.rs b/src/bin/pslink/main.rs index ff771f6..f47d891 100644 --- a/src/bin/pslink/main.rs +++ b/src/bin/pslink/main.rs @@ -15,18 +15,29 @@ use tracing::instrument; use tracing::{error, info, trace}; use tracing::{subscriber::set_global_default, Subscriber}; use tracing_actix_web::TracingLogger; -use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; -use tracing_log::LogTracer; +use tracing_opentelemetry::OpenTelemetryLayer; use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; /// Compose multiple layers into a `tracing`'s subscriber. -pub fn get_subscriber(name: String, env_filter: String) -> impl Subscriber + Send + Sync { +#[must_use] +pub fn get_subscriber(name: &str, env_filter: &str) -> impl Subscriber + Send + Sync { let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter)); - let formatting_layer = BunyanFormattingLayer::new(name, std::io::stdout); + // Create a jaeger exporter pipeline for a `trace_demo` service. + let tracer = opentelemetry_jaeger::new_pipeline() + .with_service_name(name) + .install_simple() + .expect("Error initializing Jaeger exporter"); + let formatting_layer = tracing_subscriber::fmt::layer().with_target(false); + + // Create a layer with the configured tracer + let otel_layer = OpenTelemetryLayer::new(tracer); + + // Use the tracing subscriber `Registry`, or any other subscriber + // that impls `LookupSpan` Registry::default() + .with(otel_layer) .with(env_filter) - .with(JsonStorageLayer) .with(formatting_layer) } @@ -34,7 +45,6 @@ pub fn get_subscriber(name: String, env_filter: String) -> impl Subscriber + Sen /// /// It should only be called once! pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) { - LogTracer::init().expect("Failed to set logger"); set_global_default(subscriber).expect("Failed to set subscriber"); } @@ -50,7 +60,6 @@ static_loader! { #[instrument] fn build_tera() -> Result { let mut tera = Tera::default(); - tracing::info!("Tracing activated!"); // Add translation support tera.register_function("fluent", FluentLoader::new(&*LOCALES)); @@ -209,7 +218,7 @@ async fn webservice(server_config: ServerConfig) -> Result<()> { #[instrument] #[actix_web::main] async fn main() -> std::result::Result<(), ServerError> { - let subscriber = get_subscriber("app".into(), "info".into()); + let subscriber = get_subscriber("fhs.li", "info"); init_subscriber(subscriber); match cli::setup().await { diff --git a/src/bin/pslink/views.rs b/src/bin/pslink/views.rs index 10a8969..5695010 100644 --- a/src/bin/pslink/views.rs +++ b/src/bin/pslink/views.rs @@ -15,13 +15,14 @@ use image::{DynamicImage, ImageOutputFormat, Luma}; use qrcode::{render::svg, QrCode}; use queries::{authenticate, Role}; use tera::{Context, Tera}; -use tracing::{info, trace, warn}; +use tracing::{info, instrument, trace, warn}; use pslink::forms::LinkForm; use pslink::models::{LoginUser, NewUser}; use pslink::queries; use pslink::ServerError; +#[instrument] fn redirect_builder(target: &str) -> HttpResponse { HttpResponse::SeeOther() .set(CacheControl(vec![ @@ -34,6 +35,7 @@ fn redirect_builder(target: &str) -> HttpResponse { .body(format!("Redirect to {}", target)) } +#[instrument] fn detect_language(request: &HttpRequest) -> Result { let requested = parse_accepted_languages( request @@ -63,6 +65,8 @@ fn detect_language(request: &HttpRequest) -> Result { } /// Show the list of all available links if a user is authenticated + +#[instrument(skip(id, tera))] pub async fn index( tera: web::Data, config: web::Data, @@ -81,6 +85,7 @@ pub async fn index( } /// Show the list of all available links if a user is authenticated +#[instrument(skip(id, tera))] pub async fn index_users( tera: web::Data, config: web::Data, @@ -98,6 +103,8 @@ pub async fn index_users( Ok(redirect_builder("/admin/login")) } } + +#[instrument(skip(id, tera))] pub async fn view_link_empty( tera: web::Data, config: web::Data, @@ -106,6 +113,7 @@ pub async fn view_link_empty( view_link(tera, config, id, web::Path::from("".to_owned())).await } +#[instrument(skip(id, tera))] pub async fn view_link( tera: web::Data, config: web::Data, @@ -145,6 +153,7 @@ pub async fn view_link( } } +#[instrument(skip(id, tera))] pub async fn view_profile( tera: web::Data, config: web::Data, @@ -172,6 +181,7 @@ pub async fn view_profile( } } +#[instrument(skip(id, tera))] pub async fn edit_profile( tera: web::Data, config: web::Data, @@ -198,6 +208,7 @@ pub async fn edit_profile( } } +#[instrument(skip(id))] pub async fn process_edit_profile( data: web::Form, config: web::Data, @@ -211,6 +222,7 @@ pub async fn process_edit_profile( ))) } +#[instrument(skip(id))] pub async fn download_png( id: Identity, config: web::Data, @@ -235,6 +247,7 @@ pub async fn download_png( } } +#[instrument(skip(id, tera))] pub async fn signup( tera: web::Data, config: web::Data, @@ -255,6 +268,7 @@ pub async fn signup( } } +#[instrument(skip(id))] pub async fn process_signup( data: web::Form, config: web::Data, @@ -269,6 +283,7 @@ pub async fn process_signup( } } +#[instrument(skip(id))] pub async fn toggle_admin( data: web::Path, config: web::Data, @@ -281,6 +296,7 @@ pub async fn toggle_admin( ))) } +#[instrument(skip(id))] pub async fn set_language( data: web::Path, config: web::Data, @@ -290,6 +306,7 @@ pub async fn set_language( Ok(redirect_builder("/admin/index/")) } +#[instrument(skip(id))] pub async fn login( tera: web::Data, id: Identity, @@ -323,6 +340,7 @@ pub async fn login( Ok(HttpResponse::Ok().body(rendered)) } +#[instrument(skip(id))] pub async fn process_login( data: web::Form, config: web::Data, @@ -355,11 +373,14 @@ pub async fn process_login( } } +#[instrument(skip(id))] pub async fn logout(id: Identity) -> Result { + info!("Logging out the user"); id.forget(); Ok(redirect_builder("/admin/login/")) } +#[instrument] pub async fn redirect( tera: web::Data, config: web::Data, @@ -390,12 +411,14 @@ pub async fn redirect( } } +#[instrument] pub async fn redirect_empty( config: web::Data, ) -> Result { Ok(redirect_builder(&config.empty_forward_url)) } +#[instrument(skip(id))] pub async fn create_link( tera: web::Data, config: web::Data, @@ -416,6 +439,7 @@ pub async fn create_link( } } +#[instrument(skip(id))] pub async fn process_link_creation( data: web::Form, config: web::Data, @@ -428,6 +452,7 @@ pub async fn process_link_creation( ))) } +#[instrument(skip(id))] pub async fn edit_link( tera: web::Data, config: web::Data, @@ -460,6 +485,7 @@ pub async fn process_link_edit( } } +#[instrument(skip(id))] pub async fn process_link_delete( id: Identity, config: web::Data, From ce315c429cb9160fc7292cb4e075efb43e341740 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 18 Apr 2021 09:41:17 +0200 Subject: [PATCH 4/8] Add integration tests, do not show secret in logs The code is restructured so that the library contains the actix-web code and the binary only does commandline parsing and running of the lib. --- Cargo.lock | 291 +++++++++++++++++++++++++++++++++- Cargo.toml | 99 +++++++----- src/bin/pslink/cli.rs | 7 +- src/bin/pslink/main.rs | 196 ++--------------------- src/lib.rs | 208 +++++++++++++++++++++++- src/models.rs | 2 +- src/{bin/pslink => }/views.rs | 54 +++---- tests/integration-tests.rs | 215 +++++++++++++++++++++++++ 8 files changed, 813 insertions(+), 259 deletions(-) rename src/{bin/pslink => }/views.rs (92%) create mode 100644 tests/integration-tests.rs diff --git a/Cargo.lock b/Cargo.lock index 1825e85..7d0b35f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,6 +897,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + [[package]] name = "cpuid-bool" version = "0.1.2" @@ -1247,6 +1263,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1625,12 +1656,28 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.6", + "http", +] + [[package]] name = "httparse" version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc35c995b9d93ec174cf9a27d425c7892722101e14993cd227fdb51d70cf9589" +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + [[package]] name = "humansize" version = "1.1.0" @@ -1646,6 +1693,43 @@ dependencies = [ "quick-error", ] +[[package]] +name = "hyper" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" +dependencies = [ + "bytes 0.5.6", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.6", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" +dependencies = [ + "bytes 0.5.6", + "hyper", + "native-tls", + "tokio", + "tokio-tls", +] + [[package]] name = "idna" version = "0.2.2" @@ -1757,9 +1841,15 @@ dependencies = [ "socket2", "widestring", "winapi 0.3.9", - "winreg", + "winreg 0.6.2", ] +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + [[package]] name = "itoa" version = "0.4.7" @@ -2016,6 +2106,24 @@ dependencies = [ "getrandom 0.2.2", ] +[[package]] +name = "native-tls" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -2125,6 +2233,39 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" +dependencies = [ + "autocfg 1.0.1", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.13.0" @@ -2433,6 +2574,7 @@ name = "pslink" version = "0.3.1" dependencies = [ "actix-identity", + "actix-rt", "actix-slog", "actix-web", "actix-web-static-files", @@ -2448,10 +2590,13 @@ dependencies = [ "opentelemetry-jaeger", "qrcode", "rand 0.8.3", + "reqwest", "rpassword", "serde", "sqlx", + "tempdir", "tera", + "test_bin", "thiserror", "tracing", "tracing-actix-web", @@ -2779,6 +2924,41 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "reqwest" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c" +dependencies = [ + "base64 0.13.0", + "bytes 0.5.6", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite 0.2.6", + "serde", + "serde_urlencoded", + "tokio", + "tokio-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.7.0", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -2863,6 +3043,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -2891,6 +3081,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -3340,6 +3553,20 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "tera" version = "1.7.0" @@ -3371,6 +3598,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_bin" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1937bab04b0bd0f2c19628d3408fb6dd78faf5aa60f4bdb03212507a9b7314ba" + [[package]] name = "textwrap" version = "0.11.0" @@ -3518,6 +3751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ "bytes 0.5.6", + "fnv", "futures-core", "iovec", "lazy_static", @@ -3556,6 +3790,16 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.3.1" @@ -3570,6 +3814,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + [[package]] name = "tracing" version = "0.1.25" @@ -3738,6 +3988,12 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "type-map" version = "0.4.0" @@ -3981,6 +4237,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -4000,6 +4266,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" dependencies = [ "cfg-if 1.0.0", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -4018,6 +4286,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.73" @@ -4160,6 +4440,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index fe55603..b6b77ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,68 @@ [package] -name = "pslink" -version = "0.3.1" -description = "A simple webservice that allows registered users to create short links including qr-codes.\nAnyone can visit the shortened links. This is an ideal setup for small busines or for publishing papers." authors = ["Dietrich "] -edition = "2018" -license = "MIT OR Apache-2.0" -keywords = ["url", "link", "webpage", "actix", "web"] +build = "build.rs" categories = ["web-programming", "network-programming", "web-programming::http-server", "command-line-utilities"] +description = "A simple webservice that allows registered users to create short links including qr-codes.\nAnyone can visit the shortened links. This is an ideal setup for small busines or for publishing papers." +edition = "2018" +keywords = ["url", "link", "webpage", "actix", "web"] +license = "MIT OR Apache-2.0" +name = "pslink" readme = "README.md" repository = "https://github.com/enaut/pslink/" -build = "build.rs" - - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -actix-web = "3" -actix-web-static-files = "3.0" -actix-slog = "0.2" -tera = "1.6" -serde = "1.0" -sqlx={version="0.4", features = [ "sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline" ]} -dotenv = "0.15.0" -actix-identity = "0.3" -chrono = { version = "0.4", features = ["serde"] } -argonautica = "0.2" -tracing = { version = "0.1", features = ["log"] } -tracing-bunyan-formatter = "0.2.0" -tracing-subscriber = { version = "0.2.17", features = ["registry", "env-filter"] } -tracing-actix-web = "0.2.1" -tracing-opentelemetry = "0.12" -opentelemetry = "0.13" -opentelemetry-jaeger="0.12" -qrcode = "0.12" -image = "0.23" -rand="0.8" -rpassword = "5.0" -clap = "2.33" -fluent-templates = { version = "0.6", features = ["tera"] } -fluent-langneg = "0.13" -thiserror = "1.0" -anyhow = "1.0" - +version = "0.3.1" [build-dependencies] actix-web-static-files = "3.0" -# optimize for size at cost of compilation speed. +[dependencies] +actix-identity = "0.3" +actix-rt = "1.1" +actix-slog = "0.2" +actix-web = "3" +actix-web-static-files = "3.0" +anyhow = "1.0" +argonautica = "0.2" +clap = "2.33" +dotenv = "0.15.0" +fluent-langneg = "0.13" +image = "0.23" +opentelemetry = "0.13" +opentelemetry-jaeger = "0.12" +qrcode = "0.12" +rand = "0.8" +rpassword = "5.0" +serde = "1.0" +tera = "1.6" +thiserror = "1.0" +tracing-actix-web = "0.2.1" +tracing-bunyan-formatter = "0.2.0" +tracing-opentelemetry = "0.12" + +[dependencies.chrono] +features = ["serde"] +version = "0.4" + +[dependencies.fluent-templates] +features = ["tera"] +version = "0.6" + +[dependencies.sqlx] +features = ["sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline"] +version = "0.4" + +[dependencies.tracing] +features = ["log"] +version = "0.1" + +[dependencies.tracing-subscriber] +features = ["registry", "env-filter"] +version = "0.2.17" + +[dev-dependencies] +reqwest = "0.10.10" +tempdir = "0.3" +test_bin = "0.3" + +[profile] [profile.release] -lto = true +lto = true #timize for size at cost of compilation speed. #codegen-units = 1 \ No newline at end of file diff --git a/src/bin/pslink/cli.rs b/src/bin/pslink/cli.rs index db4b6a4..e92ed8d 100644 --- a/src/bin/pslink/cli.rs +++ b/src/bin/pslink/cli.rs @@ -147,6 +147,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig { } else { secret }; + let secret = pslink::Secret::new(secret); let db = config .value_of("database") .expect(concat!( @@ -219,9 +220,12 @@ pub(crate) async fn setup() -> Result, ServerError> )) .parse::() .expect("Failed to parse Database path."); + if !db.exists() { trace!("No database file found {}", db.display()); - if config.subcommand_matches("migrate-database").is_none() { + if !(config.subcommand_matches("migrate-database").is_none() + | config.subcommand_matches("generate-env").is_none()) + { let msg = format!( concat!( "Database not found at {}!", @@ -239,7 +243,6 @@ pub(crate) async fn setup() -> Result, ServerError> // create an empty database file. The if above makes sure that this file does not exist. File::create(db)?; }; - let server_config: crate::ServerConfig = parse_args_to_config(config.clone()).await; if let Some(_migrate_config) = config.subcommand_matches("generate-env") { diff --git a/src/bin/pslink/main.rs b/src/bin/pslink/main.rs index f47d891..4122311 100644 --- a/src/bin/pslink/main.rs +++ b/src/bin/pslink/main.rs @@ -1,20 +1,11 @@ extern crate sqlx; mod cli; -mod views; -use actix_identity::{CookieIdentityPolicy, IdentityService}; -use actix_web::{web, App, HttpServer}; -use anyhow::{Context, Result}; -use fluent_templates::{static_loader, FluentLoader}; -use tera::Tera; - -use pslink::{ServerConfig, ServerError}; +use pslink::ServerConfig; use tracing::instrument; -use tracing::{error, info, trace}; use tracing::{subscriber::set_global_default, Subscriber}; -use tracing_actix_web::TracingLogger; use tracing_opentelemetry::OpenTelemetryLayer; use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; @@ -48,185 +39,24 @@ pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) { set_global_default(subscriber).expect("Failed to set subscriber"); } -include!(concat!(env!("OUT_DIR"), "/generated.rs")); - -static_loader! { - static LOCALES = { - locales: "./locales", - fallback_language: "en", - }; -} - -#[instrument] -fn build_tera() -> Result { - let mut tera = Tera::default(); - - // Add translation support - tera.register_function("fluent", FluentLoader::new(&*LOCALES)); - - tera.add_raw_templates(vec![ - ("admin.html", include_str!("../../../templates/admin.html")), - ("base.html", include_str!("../../../templates/base.html")), - ( - "edit_link.html", - include_str!("../../../templates/edit_link.html"), - ), - ( - "edit_profile.html", - include_str!("../../../templates/edit_profile.html"), - ), - ( - "index_users.html", - include_str!("../../../templates/index_users.html"), - ), - ("index.html", include_str!("../../../templates/index.html")), - ("login.html", include_str!("../../../templates/login.html")), - ( - "not_found.html", - include_str!("../../../templates/not_found.html"), - ), - ( - "signup.html", - include_str!("../../../templates/signup.html"), - ), - ( - "submission.html", - include_str!("../../../templates/submission.html"), - ), - ( - "view_link.html", - include_str!("../../../templates/view_link.html"), - ), - ( - "view_profile.html", - include_str!("../../../templates/view_profile.html"), - ), - ]) - .context("Failed to load Templates")?; - Ok(tera) -} - -#[allow(clippy::future_not_send, clippy::too_many_lines)] -async fn webservice(server_config: ServerConfig) -> Result<()> { - let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); - info!( - "Running on: {}://{}/admin/login/", - &server_config.protocol, host_port - ); - info!( - "If the public url is set up correctly it should be accessible via: {}://{}/admin/login/", - &server_config.protocol, &server_config.public_url - ); - let tera = build_tera()?; - trace!("The tera templates are ready"); - - HttpServer::new(move || { - let generated = generate(); - App::new() - .data(server_config.clone()) - .wrap(TracingLogger) - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&[0; 32]) - .name("auth-cookie") - .secure(false), - )) - .data(tera.clone()) - .service(actix_web_static_files::ResourceFiles::new( - "/static", generated, - )) - // directly go to the main page set the target with the environment variable. - .route("/", web::get().to(views::redirect_empty)) - // admin block - .service( - web::scope("/admin") - // list all links - .route("/index/", web::get().to(views::index)) - // invite users - .route("/signup/", web::get().to(views::signup)) - .route("/signup/", web::post().to(views::process_signup)) - // logout - .route("/logout/", web::to(views::logout)) - // submit a new url for shortening - .route("/submit/", web::get().to(views::create_link)) - .route("/submit/", web::post().to(views::process_link_creation)) - // view an existing url - .service( - web::scope("/view") - .service( - web::scope("/link") - .route("/{redirect_id}", web::get().to(views::view_link)) - .route("/", web::get().to(views::view_link_empty)), - ) - .service( - web::scope("/profile") - .route("/{user_id}", web::get().to(views::view_profile)), - ) - .route("/users/", web::get().to(views::index_users)), - ) - .service( - web::scope("/edit") - .service( - web::scope("/link") - .route("/{redirect_id}", web::get().to(views::edit_link)) - .route( - "/{redirect_id}", - web::post().to(views::process_link_edit), - ), - ) - .service( - web::scope("/profile") - .route("/{user_id}", web::get().to(views::edit_profile)) - .route( - "/{user_id}", - web::post().to(views::process_edit_profile), - ), - ) - .route("/set_admin/{user_id}", web::get().to(views::toggle_admin)) - .route( - "/set_language/{language}", - web::get().to(views::set_language), - ), - ) - .service( - web::scope("/delete").service( - web::scope("/link") - .route("/{redirect_id}", web::get().to(views::process_link_delete)), - ), - ) - .service( - web::scope("/download") - .route("/png/{redirect_id}", web::get().to(views::download_png)), - ) - // login to the admin area - .route("/login/", web::get().to(views::login)) - .route("/login/", web::post().to(views::process_login)), - ) - // redirect to the url hidden behind the code - .route("/{redirect_id}", web::get().to(views::redirect)) - }) - .bind(host_port) - .context("Failed to bind to port") - .map_err(|e| { - error!("Failed to bind to port!"); - e - })? - .run() - .await - .context("Failed to run the webservice") -} - #[instrument] #[actix_web::main] -async fn main() -> std::result::Result<(), ServerError> { +async fn main() -> std::result::Result<(), std::io::Error> { let subscriber = get_subscriber("fhs.li", "info"); init_subscriber(subscriber); match cli::setup().await { - Ok(Some(server_config)) => webservice(server_config).await.map_err(|e| { - println!("{:?}", e); - std::thread::sleep(std::time::Duration::from_millis(100)); - std::process::exit(0); - }), + Ok(Some(server_config)) => { + pslink::webservice(server_config) + .await + .map_err(|e| { + println!("{:?}", e); + std::thread::sleep(std::time::Duration::from_millis(100)); + std::process::exit(0); + }) + .expect("Failed to launch the service") + .await + } Ok(None) => { std::thread::sleep(std::time::Duration::from_millis(100)); std::process::exit(0); diff --git a/src/lib.rs b/src/lib.rs index d35be74..7402241 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,14 +3,21 @@ extern crate sqlx; pub mod forms; pub mod models; pub mod queries; +mod views; -use std::{fmt::Display, path::PathBuf, str::FromStr}; - +use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_web::HttpResponse; - +use actix_web::{web, App, HttpServer}; +use fluent_templates::{static_loader, FluentLoader}; use qrcode::types::QrError; use sqlx::{Pool, Sqlite}; +use std::{fmt::Display, path::PathBuf, str::FromStr}; +use tera::Tera; use thiserror::Error; +use tracing::instrument; +use tracing::{error, info, trace}; +use tracing_actix_web::TracingLogger; + #[derive(Error, Debug)] pub enum ServerError { #[error("Failed to encrypt the password {0} - aborting!")] @@ -148,9 +155,33 @@ impl FromStr for Protocol { } } +#[derive(Clone)] +pub struct Secret { + secret: String, +} + +impl Secret { + #[must_use] + pub const fn new(secret: String) -> Self { + Self { secret } + } +} + +impl std::fmt::Debug for Secret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("*****SECRET*****") + } +} + +impl std::fmt::Display for Secret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("*****SECRET*****") + } +} + #[derive(Debug, Clone)] pub struct ServerConfig { - pub secret: String, + pub secret: Secret, pub db: PathBuf, pub db_pool: Pool, pub public_url: String, @@ -181,3 +212,172 @@ impl ServerConfig { ] } } + +include!(concat!(env!("OUT_DIR"), "/generated.rs")); + +static_loader! { + static LOCALES = { + locales: "./locales", + fallback_language: "en", + }; +} + +#[instrument] +fn build_tera() -> Result { + let mut tera = Tera::default(); + + // Add translation support + tera.register_function("fluent", FluentLoader::new(&*LOCALES)); + + tera.add_raw_templates(vec![ + ("admin.html", include_str!("../templates/admin.html")), + ("base.html", include_str!("../templates/base.html")), + ( + "edit_link.html", + include_str!("../templates/edit_link.html"), + ), + ( + "edit_profile.html", + include_str!("../templates/edit_profile.html"), + ), + ( + "index_users.html", + include_str!("../templates/index_users.html"), + ), + ("index.html", include_str!("../templates/index.html")), + ("login.html", include_str!("../templates/login.html")), + ( + "not_found.html", + include_str!("../templates/not_found.html"), + ), + ("signup.html", include_str!("../templates/signup.html")), + ( + "submission.html", + include_str!("../templates/submission.html"), + ), + ( + "view_link.html", + include_str!("../templates/view_link.html"), + ), + ( + "view_profile.html", + include_str!("../templates/view_profile.html"), + ), + ])?; + Ok(tera) +} + +/// Launch the pslink-webservice +/// +/// # Errors +/// This produces a [`ServerError`] if: +/// * Tera failed to build its templates +/// * The server failed to bind to the designated port. +#[allow(clippy::future_not_send, clippy::too_many_lines)] +pub async fn webservice( + server_config: ServerConfig, +) -> Result { + let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); + info!( + "Running on: {}://{}/admin/login/", + &server_config.protocol, host_port + ); + info!( + "If the public url is set up correctly it should be accessible via: {}://{}/admin/login/", + &server_config.protocol, &server_config.public_url + ); + let tera = build_tera()?; + trace!("The tera templates are ready"); + + let server = HttpServer::new(move || { + let generated = generate(); + App::new() + .data(server_config.clone()) + .wrap(TracingLogger) + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .name("auth-cookie") + .secure(false), + )) + .data(tera.clone()) + .service(actix_web_static_files::ResourceFiles::new( + "/static", generated, + )) + // directly go to the main page set the target with the environment variable. + .route("/", web::get().to(views::redirect_empty)) + // admin block + .service( + web::scope("/admin") + // list all links + .route("/index/", web::get().to(views::index)) + // invite users + .route("/signup/", web::get().to(views::signup)) + .route("/signup/", web::post().to(views::process_signup)) + // logout + .route("/logout/", web::to(views::logout)) + // submit a new url for shortening + .route("/submit/", web::get().to(views::create_link)) + .route("/submit/", web::post().to(views::process_link_creation)) + // view an existing url + .service( + web::scope("/view") + .service( + web::scope("/link") + .route("/{redirect_id}", web::get().to(views::view_link)) + .route("/", web::get().to(views::view_link_empty)), + ) + .service( + web::scope("/profile") + .route("/{user_id}", web::get().to(views::view_profile)), + ) + .route("/users/", web::get().to(views::index_users)), + ) + .service( + web::scope("/edit") + .service( + web::scope("/link") + .route("/{redirect_id}", web::get().to(views::edit_link)) + .route( + "/{redirect_id}", + web::post().to(views::process_link_edit), + ), + ) + .service( + web::scope("/profile") + .route("/{user_id}", web::get().to(views::edit_profile)) + .route( + "/{user_id}", + web::post().to(views::process_edit_profile), + ), + ) + .route("/set_admin/{user_id}", web::get().to(views::toggle_admin)) + .route( + "/set_language/{language}", + web::get().to(views::set_language), + ), + ) + .service( + web::scope("/delete").service( + web::scope("/link") + .route("/{redirect_id}", web::get().to(views::process_link_delete)), + ), + ) + .service( + web::scope("/download") + .route("/png/{redirect_id}", web::get().to(views::download_png)), + ) + // login to the admin area + .route("/login/", web::get().to(views::login)) + .route("/login/", web::post().to(views::process_login)), + ) + // redirect to the url hidden behind the code + .route("/{redirect_id}", web::get().to(views::redirect)) + }) + .bind(host_port) + .map_err(|e| { + error!("Failed to bind to port!"); + e + })? + .run(); + Ok(server) +} diff --git a/src/models.rs b/src/models.rs index 033460d..c88f826 100644 --- a/src/models.rs +++ b/src/models.rs @@ -146,7 +146,7 @@ impl NewUser { let hash = Hasher::default() .with_password(password) - .with_secret_key(secret) + .with_secret_key(&secret.secret) .hash()?; Ok(hash) diff --git a/src/bin/pslink/views.rs b/src/views.rs similarity index 92% rename from src/bin/pslink/views.rs rename to src/views.rs index 5695010..7963ece 100644 --- a/src/bin/pslink/views.rs +++ b/src/views.rs @@ -17,10 +17,10 @@ use queries::{authenticate, Role}; use tera::{Context, Tera}; use tracing::{info, instrument, trace, warn}; -use pslink::forms::LinkForm; -use pslink::models::{LoginUser, NewUser}; -use pslink::queries; -use pslink::ServerError; +use crate::forms::LinkForm; +use crate::models::{LoginUser, NewUser}; +use crate::queries; +use crate::ServerError; #[instrument] fn redirect_builder(target: &str) -> HttpResponse { @@ -69,7 +69,7 @@ fn detect_language(request: &HttpRequest) -> Result { #[instrument(skip(id, tera))] pub async fn index( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { if let Ok(links) = queries::list_all_allowed(&id, &config).await { @@ -88,7 +88,7 @@ pub async fn index( #[instrument(skip(id, tera))] pub async fn index_users( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { if let Ok(users) = queries::list_users(&id, &config).await { @@ -107,7 +107,7 @@ pub async fn index_users( #[instrument(skip(id, tera))] pub async fn view_link_empty( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { view_link(tera, config, id, web::Path::from("".to_owned())).await @@ -116,7 +116,7 @@ pub async fn view_link_empty( #[instrument(skip(id, tera))] pub async fn view_link( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, link_id: web::Path, ) -> Result { @@ -156,7 +156,7 @@ pub async fn view_link( #[instrument(skip(id, tera))] pub async fn view_profile( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, user_id: web::Path, ) -> Result { @@ -184,7 +184,7 @@ pub async fn view_profile( #[instrument(skip(id, tera))] pub async fn edit_profile( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, user_id: web::Path, ) -> Result { @@ -211,7 +211,7 @@ pub async fn edit_profile( #[instrument(skip(id))] pub async fn process_edit_profile( data: web::Form, - config: web::Data, + config: web::Data, id: Identity, user_id: web::Path, ) -> Result { @@ -225,7 +225,7 @@ pub async fn process_edit_profile( #[instrument(skip(id))] pub async fn download_png( id: Identity, - config: web::Data, + config: web::Data, link_code: web::Path, ) -> Result { match queries::get_link(&id, &link_code.0, &config).await { @@ -250,7 +250,7 @@ pub async fn download_png( #[instrument(skip(id, tera))] pub async fn signup( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { match queries::authenticate(&id, &config).await? { @@ -271,7 +271,7 @@ pub async fn signup( #[instrument(skip(id))] pub async fn process_signup( data: web::Form, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { info!("Creating a User: {:?}", &data); @@ -286,7 +286,7 @@ pub async fn process_signup( #[instrument(skip(id))] pub async fn toggle_admin( data: web::Path, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { let update = queries::toggle_admin(&id, &data.0, &config).await?; @@ -299,18 +299,18 @@ pub async fn toggle_admin( #[instrument(skip(id))] pub async fn set_language( data: web::Path, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { queries::set_language(&id, &data.0, &config).await?; Ok(redirect_builder("/admin/index/")) } -#[instrument(skip(id))] +#[instrument(skip(tera, id))] pub async fn login( tera: web::Data, id: Identity, - config: web::Data, + config: web::Data, req: HttpRequest, ) -> Result { let language_code = detect_language(&req)?; @@ -343,7 +343,7 @@ pub async fn login( #[instrument(skip(id))] pub async fn process_login( data: web::Form, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { let user = queries::get_user_by_name(&data.username, &config).await; @@ -354,7 +354,7 @@ pub async fn process_login( let valid = Verifier::default() .with_hash(&u.password) .with_password(&data.password) - .with_secret_key(secret) + .with_secret_key(&secret.secret) .verify()?; if valid { @@ -383,7 +383,7 @@ pub async fn logout(id: Identity) -> Result { #[instrument] pub async fn redirect( tera: web::Data, - config: web::Data, + config: web::Data, data: web::Path, req: HttpRequest, ) -> Result { @@ -413,7 +413,7 @@ pub async fn redirect( #[instrument] pub async fn redirect_empty( - config: web::Data, + config: web::Data, ) -> Result { Ok(redirect_builder(&config.empty_forward_url)) } @@ -421,7 +421,7 @@ pub async fn redirect_empty( #[instrument(skip(id))] pub async fn create_link( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { match queries::authenticate(&id, &config).await? { @@ -442,7 +442,7 @@ pub async fn create_link( #[instrument(skip(id))] pub async fn process_link_creation( data: web::Form, - config: web::Data, + config: web::Data, id: Identity, ) -> Result { let new_link = queries::create_link(&id, data, &config).await?; @@ -455,7 +455,7 @@ pub async fn process_link_creation( #[instrument(skip(id))] pub async fn edit_link( tera: web::Data, - config: web::Data, + config: web::Data, id: Identity, link_id: web::Path, ) -> Result { @@ -472,7 +472,7 @@ pub async fn edit_link( } pub async fn process_link_edit( data: web::Form, - config: web::Data, + config: web::Data, id: Identity, link_code: web::Path, ) -> Result { @@ -488,7 +488,7 @@ pub async fn process_link_edit( #[instrument(skip(id))] pub async fn process_link_delete( id: Identity, - config: web::Data, + config: web::Data, link_code: web::Path, ) -> Result { queries::delete_link(&id, &link_code.0, &config).await?; diff --git a/tests/integration-tests.rs b/tests/integration-tests.rs new file mode 100644 index 0000000..d9ee3f6 --- /dev/null +++ b/tests/integration-tests.rs @@ -0,0 +1,215 @@ +#[test] +fn test_help_of_command_for_breaking_changes() { + let output = test_bin::get_test_bin("pslink") + .output() + .expect("Failed to start pslink"); + assert!(String::from_utf8_lossy(&output.stdout).contains("USAGE")); + + let output = test_bin::get_test_bin("pslink") + .args(&["--help"]) + .output() + .expect("Failed to start pslink"); + let outstring = String::from_utf8_lossy(&output.stdout); + + let args = &[ + "USAGE", + "-h", + "--help", + "-b", + "-e", + "-i", + "-p", + "-t", + "-u", + "runserver", + "create-admin", + "generate-env", + "migrate-database", + "help", + ]; + + for s in args { + assert!( + outstring.contains(s), + "{} was not found in the help - this is a breaking change", + s + ); + } +} + +#[test] +fn test_generate_env() { + use std::io::BufRead; + let tmp_dir = tempdir::TempDir::new("pslink_test_env").expect("create temp dir"); + let output = test_bin::get_test_bin("pslink") + .args(&["generate-env"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed to start pslink"); + let envfile = tmp_dir.path().join(".env"); + let dbfile = tmp_dir.path().join("links.db"); + println!("{}", envfile.display()); + println!("{}", dbfile.display()); + println!("{}", String::from_utf8_lossy(&output.stdout)); + assert!(envfile.exists(), "No .env-file was created!"); + assert!(dbfile.exists(), "No database-file was created!"); + + let envfile = std::fs::File::open(envfile).unwrap(); + let mut envcontent = std::io::BufReader::new(envfile).lines(); + assert!( + envcontent.any(|s| s.as_ref().unwrap().starts_with("PSLINK_PORT=")), + "Failed to find DATABASE_URL in the generated .env file." + ); + let output = test_bin::get_test_bin("pslink") + .args(&["generate-env"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed to start pslink"); + let second_out = String::from_utf8_lossy(&output.stdout); + assert!(!second_out.contains("secret")); +} + +#[actix_rt::test] +async fn test_migrate_database() { + use std::io::Write; + #[derive(serde::Serialize, Debug)] + pub struct Count { + pub number: i32, + } + + let tmp_dir = tempdir::TempDir::new("pslink_test_env").expect("create temp dir"); + // generate .env file + let _output = test_bin::get_test_bin("pslink") + .args(&["generate-env"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed generate .env"); + + // migrate the database + let output = test_bin::get_test_bin("pslink") + .args(&["migrate-database"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed to migrate the database"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + + // check if the users table exists by counting the number of admins. + let db_pool = sqlx::pool::Pool::::connect( + &tmp_dir.path().join("links.db").display().to_string(), + ) + .await + .expect("Error: Failed to connect to database!"); + let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2") + .fetch_one(&db_pool) + .await + .unwrap(); + // initially no admin is present + assert_eq!(num.number, 0, "Failed to create the database!"); + + // create a new admin + let mut input = test_bin::get_test_bin("pslink") + .args(&["create-admin"]) + .current_dir(&tmp_dir) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .expect("Failed to migrate the database"); + let mut procin = input.stdin.take().unwrap(); + + procin.write_all(b"test\n").unwrap(); + procin.write_all(b"test@mail.test\n").unwrap(); + procin.write_all(b"testpw\n").unwrap(); + + let r = input.wait().unwrap(); + println!("Exitstatus is: {}", r); + + println!("{}", String::from_utf8_lossy(&output.stdout)); + let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2") + .fetch_one(&db_pool) + .await + .unwrap(); + // now 1 admin is there + assert_eq!(num.number, 1, "Failed to create an admin!"); +} + +/* async fn run_server() { + use std::io::Write; + #[derive(serde::Serialize, Debug)] + pub struct Count { + pub number: i32, + } + let tmp_dir = tempdir::TempDir::new("pslink_test_env").expect("create temp dir"); + // generate .env file + let _output = test_bin::get_test_bin("pslink") + .args(&["generate-env"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed generate .env"); + // migrate the database + let output = test_bin::get_test_bin("pslink") + .args(&["migrate-database"]) + .current_dir(&tmp_dir) + .output() + .expect("Failed to migrate the database"); + + // create a database connection. + let db_pool = sqlx::pool::Pool::::connect( + &tmp_dir.path().join("links.db").display().to_string(), + ) + .await + .expect("Error: Failed to connect to database!"); // create a new admin + let mut input = test_bin::get_test_bin("pslink") + .args(&["create-admin"]) + .current_dir(&tmp_dir) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .expect("Failed to migrate the database"); + let mut procin = input.stdin.take().unwrap(); + + procin.write_all(b"test\n").unwrap(); + procin.write_all(b"test@mail.test\n").unwrap(); + procin.write_all(b"testpw\n").unwrap(); + + let r = input.wait().unwrap(); + println!("Exitstatus is: {}", r); + + println!("{}", String::from_utf8_lossy(&output.stdout)); + let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2") + .fetch_one(&db_pool) + .await + .unwrap(); + // now 1 admin is there + assert_eq!( + num.number, 1, + "Failed to create an admin! See previous tests!" + ); + let output = test_bin::get_test_bin("pslink") + .args(&["runserver"]) + .current_dir(&tmp_dir) + .spawn() + .expect("Failed to migrate the database"); + let out = output.wait_with_output().unwrap(); + println!("{}", String::from_utf8_lossy(&out.stdout)); +} + +#[actix_rt::test] +async fn test_web_paths() { + actix_rt::spawn(run_server()); + + std::thread::sleep(std::time::Duration::new(5, 0)); + // We need to bring in `reqwest` + // to perform HTTP requests against our application. + let client = reqwest::Client::new(); + + // Act + let response = client + .get("http://127.0.0.1:8080/admin/login/") + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + //assert_eq!(Some(0), response.content_length()); +} */ From 322c867e945c5255d29d6c232ad708f48ba44fc1 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 18 Apr 2021 10:29:09 +0200 Subject: [PATCH 5/8] Fix: real secret instead of censored in .env --- src/lib.rs | 2 +- tests/integration-tests.rs | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7402241..5044571 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,7 +208,7 @@ impl ServerConfig { "# If it is changed all existing passwords are invalid.\n" ) .to_owned(), - format!("PSLINK_SECRET=\"{}\"\n", self.secret), + format!("PSLINK_SECRET=\"{}\"\n", self.secret.secret), ] } } diff --git a/tests/integration-tests.rs b/tests/integration-tests.rs index d9ee3f6..e6d226f 100644 --- a/tests/integration-tests.rs +++ b/tests/integration-tests.rs @@ -55,10 +55,25 @@ fn test_generate_env() { assert!(dbfile.exists(), "No database-file was created!"); let envfile = std::fs::File::open(envfile).unwrap(); - let mut envcontent = std::io::BufReader::new(envfile).lines(); + let envcontent: Vec> = std::io::BufReader::new(envfile).lines().collect(); assert!( - envcontent.any(|s| s.as_ref().unwrap().starts_with("PSLINK_PORT=")), - "Failed to find DATABASE_URL in the generated .env file." + envcontent + .iter() + .any(|s| s.as_ref().unwrap().starts_with("PSLINK_PORT=")), + "Failed to find PSLINK_PORT in the generated .env file." + ); + assert!( + envcontent + .iter() + .any(|s| s.as_ref().unwrap().starts_with("PSLINK_SECRET=")), + "Failed to find PSLINK_SECRET in the generated .env file." + ); + assert!( + !envcontent.iter().any(|s| { + let r = s.as_ref().unwrap().contains("***SECRET***"); + r + }), + "It seems that a censored secret was used in the .env file." ); let output = test_bin::get_test_bin("pslink") .args(&["generate-env"]) From 7690d301f15de8223388131077712c62d3f113f9 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 18 Apr 2021 11:37:02 +0200 Subject: [PATCH 6/8] Properly handle absense of a prefered language --- src/views.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views.rs b/src/views.rs index 7963ece..a4cf069 100644 --- a/src/views.rs +++ b/src/views.rs @@ -313,7 +313,7 @@ pub async fn login( config: web::Data, req: HttpRequest, ) -> Result { - let language_code = detect_language(&req)?; + let language_code = detect_language(&req).unwrap_or_else(|_| "en".to_string()); info!("Detected languagecode: {}", &language_code); let mut data = Context::new(); data.insert("title", "Login"); @@ -402,7 +402,7 @@ pub async fn redirect( ); let mut data = Context::new(); data.insert("title", "Wurde gel\u{f6}scht"); - let language = detect_language(&req)?; + let language = detect_language(&req).unwrap_or_else(|_| "en".to_string()); data.insert("language", &language); let rendered = tera.render("not_found.html", &data)?; Ok(HttpResponse::NotFound().body(rendered)) From 04170079d66c35d5d40414a002bd958153d18fa6 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 18 Apr 2021 11:38:07 +0200 Subject: [PATCH 7/8] Add integration test for runserver --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 4 ++-- tests/integration-tests.rs | 43 ++++++++++++++++++++++++++------------ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d0b35f..15d09bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2598,6 +2598,7 @@ dependencies = [ "tera", "test_bin", "thiserror", + "tokio", "tracing", "tracing-actix-web", "tracing-bunyan-formatter", diff --git a/Cargo.toml b/Cargo.toml index b6b77ce..ed552da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ version = "0.2.17" reqwest = "0.10.10" tempdir = "0.3" test_bin = "0.3" +tokio="0.2.25" [profile] [profile.release] diff --git a/src/lib.rs b/src/lib.rs index 5044571..b5ee2b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -276,7 +276,7 @@ fn build_tera() -> Result { #[allow(clippy::future_not_send, clippy::too_many_lines)] pub async fn webservice( server_config: ServerConfig, -) -> Result { +) -> Result { let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); info!( "Running on: {}://{}/admin/login/", @@ -286,7 +286,7 @@ pub async fn webservice( "If the public url is set up correctly it should be accessible via: {}://{}/admin/login/", &server_config.protocol, &server_config.public_url ); - let tera = build_tera()?; + let tera = build_tera().expect("Failed to build Templates"); trace!("The tera templates are ready"); let server = HttpServer::new(move || { diff --git a/tests/integration-tests.rs b/tests/integration-tests.rs index e6d226f..efd4d71 100644 --- a/tests/integration-tests.rs +++ b/tests/integration-tests.rs @@ -42,7 +42,7 @@ fn test_generate_env() { use std::io::BufRead; let tmp_dir = tempdir::TempDir::new("pslink_test_env").expect("create temp dir"); let output = test_bin::get_test_bin("pslink") - .args(&["generate-env"]) + .args(&["generate-env", "--secret", "abcdefghijklmnopqrstuvw"]) .current_dir(&tmp_dir) .output() .expect("Failed to start pslink"); @@ -75,6 +75,13 @@ fn test_generate_env() { }), "It seems that a censored secret was used in the .env file." ); + assert!( + envcontent.iter().any(|s| { + let r = s.as_ref().unwrap().contains("abcdefghijklmnopqrstuvw"); + r + }), + "The secret has not made it into the .env file!" + ); let output = test_bin::get_test_bin("pslink") .args(&["generate-env"]) .current_dir(&tmp_dir) @@ -147,7 +154,7 @@ async fn test_migrate_database() { assert_eq!(num.number, 1, "Failed to create an admin!"); } -/* async fn run_server() { +async fn run_server() { use std::io::Write; #[derive(serde::Serialize, Debug)] pub struct Count { @@ -156,7 +163,7 @@ async fn test_migrate_database() { let tmp_dir = tempdir::TempDir::new("pslink_test_env").expect("create temp dir"); // generate .env file let _output = test_bin::get_test_bin("pslink") - .args(&["generate-env"]) + .args(&["generate-env", "--secret", "abcdefghijklmnopqrstuvw"]) .current_dir(&tmp_dir) .output() .expect("Failed generate .env"); @@ -199,18 +206,28 @@ async fn test_migrate_database() { num.number, 1, "Failed to create an admin! See previous tests!" ); - let output = test_bin::get_test_bin("pslink") - .args(&["runserver"]) - .current_dir(&tmp_dir) - .spawn() - .expect("Failed to migrate the database"); - let out = output.wait_with_output().unwrap(); - println!("{}", String::from_utf8_lossy(&out.stdout)); + + let server_config = pslink::ServerConfig { + secret: pslink::Secret::new("abcdefghijklmnopqrstuvw".to_string()), + db: std::path::PathBuf::from("links.db"), + db_pool, + public_url: "localhost:8080".to_string(), + internal_ip: "localhost".to_string(), + port: 8080, + protocol: pslink::Protocol::Http, + empty_forward_url: "https://github.com/enaut/pslink".to_string(), + brand_name: "Pslink".to_string(), + }; + + let server = pslink::webservice(server_config); + + let neveruse = tokio::spawn(server); + println!("Never used: {:?}", neveruse); } #[actix_rt::test] async fn test_web_paths() { - actix_rt::spawn(run_server()); + run_server().await; std::thread::sleep(std::time::Duration::new(5, 0)); // We need to bring in `reqwest` @@ -219,7 +236,7 @@ async fn test_web_paths() { // Act let response = client - .get("http://127.0.0.1:8080/admin/login/") + .get("http://localhost:8080/admin/login/") .send() .await .expect("Failed to execute request."); @@ -227,4 +244,4 @@ async fn test_web_paths() { // Assert assert!(response.status().is_success()); //assert_eq!(Some(0), response.content_length()); -} */ +} From 84625939de9a053479410c4fc1a7fbea74ed0c63 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 18 Apr 2021 16:11:43 +0200 Subject: [PATCH 8/8] Add more testcases --- Cargo.lock | 30 ++++++++++ Cargo.toml | 10 +++- tests/integration-tests.rs | 114 +++++++++++++++++++++++++++++++++++-- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15d09bf..23c5037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -891,6 +891,22 @@ dependencies = [ "version_check 0.9.3", ] +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.2.26", + "url", +] + [[package]] name = "copyless" version = "0.1.5" @@ -2575,6 +2591,7 @@ version = "0.3.1" dependencies = [ "actix-identity", "actix-rt", + "actix-server", "actix-slog", "actix-web", "actix-web-static-files", @@ -2606,6 +2623,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna", + "url", +] + [[package]] name = "qrcode" version = "0.12.0" @@ -2933,6 +2960,8 @@ checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c" dependencies = [ "base64 0.13.0", "bytes 0.5.6", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -2951,6 +2980,7 @@ dependencies = [ "pin-project-lite 0.2.6", "serde", "serde_urlencoded", + "time 0.2.26", "tokio", "tokio-tls", "url", diff --git a/Cargo.toml b/Cargo.toml index ed552da..6e836d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,12 +58,16 @@ features = ["registry", "env-filter"] version = "0.2.17" [dev-dependencies] -reqwest = "0.10.10" +actix-server = "1.0.4" tempdir = "0.3" test_bin = "0.3" -tokio="0.2.25" +tokio = "0.2.25" + +[dev-dependencies.reqwest] +features = ["cookies"] +version = "0.10.10" [profile] [profile.release] -lto = true #timize for size at cost of compilation speed. +lto = true #codegen-units = 1 \ No newline at end of file diff --git a/tests/integration-tests.rs b/tests/integration-tests.rs index efd4d71..56da9a9 100644 --- a/tests/integration-tests.rs +++ b/tests/integration-tests.rs @@ -221,18 +221,32 @@ async fn run_server() { let server = pslink::webservice(server_config); - let neveruse = tokio::spawn(server); - println!("Never used: {:?}", neveruse); + let _neveruse = tokio::spawn(server); } #[actix_rt::test] async fn test_web_paths() { run_server().await; - std::thread::sleep(std::time::Duration::new(5, 0)); // We need to bring in `reqwest` // to perform HTTP requests against our application. - let client = reqwest::Client::new(); + let client = reqwest::Client::builder() + .cookie_store(true) + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap(); + + // Act + let response = client + .get("http://localhost:8080/") + .send() + .await + .expect("Failed to execute request."); + + // The basic redirection is working! + assert!(response.status().is_redirection()); + let location = response.headers().get("location").unwrap(); + assert!(location.to_str().unwrap().contains("github")); // Act let response = client @@ -241,7 +255,95 @@ async fn test_web_paths() { .await .expect("Failed to execute request."); - // Assert + // The Loginpage is reachable and contains a password field! assert!(response.status().is_success()); - //assert_eq!(Some(0), response.content_length()); + let content = response.text().await.unwrap(); + assert!( + content.contains(r#""#), + "No Logout Button was found on /admin/index/!" + ); + + // Act title=haupt&target=http%3A%2F%2Fdas.geht%2Fjetzt%2F&code=tpuah + let formdata = &[ + ("title", "haupt"), + ("target", "https://das.geht/jetzt/"), + ("code", "tpuah"), + ]; + let response = client + .post("http://localhost:8080/admin/submit/") + .form(formdata) + .send() + .await + .expect("Failed to execute request."); + + // It is possible to login + assert!(response.status().is_redirection()); + let location = response.headers().get("location").unwrap(); + assert_eq!("/admin/view/link/tpuah", location.to_str().unwrap()); + + // Act + let response = client + .get("http://localhost:8080/tpuah") + .send() + .await + .expect("Failed to execute request."); + + // The basic redirection is working! + assert!(response.status().is_redirection()); + let location = response.headers().get("location").unwrap(); + assert!(location + .to_str() + .unwrap() + .contains("https://das.geht/jetzt/")); }