diff --git a/Cargo.lock b/Cargo.lock index 8d46b67..a3e5a9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -251,6 +261,7 @@ dependencies = [ "num-integer", "num-traits", "serde", + "time", "winapi", ] @@ -434,6 +445,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "fnv" version = "1.0.7" @@ -500,6 +520,7 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -781,9 +802,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -796,9 +817,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" [[package]] name = "lock_api" @@ -893,6 +914,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "nom" version = "7.1.1" @@ -1127,13 +1166,19 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.17" @@ -1210,6 +1255,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ring" version = "0.16.20" @@ -1244,6 +1298,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -1549,15 +1609,29 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "tera" version = "1.15.0" @@ -1609,6 +1683,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -1630,6 +1714,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-sqlx-session", + "chrono", "serde", "sqlx", "tera", @@ -1638,6 +1723,7 @@ dependencies = [ "tracing", "tracing-subscriber", "warp", + "warp-sessions", ] [[package]] @@ -1692,6 +1778,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.6.9" @@ -1800,6 +1899,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1 0.9.8", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typenum" version = "1.15.0" @@ -1931,6 +2058,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "valuable" version = "0.1.0" @@ -1979,6 +2112,7 @@ dependencies = [ "log", "mime", "mime_guess", + "multipart", "percent-encoding", "pin-project", "scoped-tls", @@ -1987,11 +2121,26 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-stream", + "tokio-tungstenite", "tokio-util 0.6.9", "tower-service", "tracing", ] +[[package]] +name = "warp-sessions" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc73864084b8deb701cbd0eab8bf0ca239ee4ebc5e4b3de43f9c29065bf2035c" +dependencies = [ + "async-session", + "async-trait", + "http", + "serde", + "tokio", + "warp", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -2006,9 +2155,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2016,9 +2165,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -2031,9 +2180,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2041,9 +2190,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -2054,15 +2203,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index adb8e4f..a9a8e90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "tmtd" version = "0.1.0" edition = "2021" +[profile.release] +strip = "debuginfo" +lto = true + [dependencies] tokio = { version = "1", features = ["rt", "sync", "signal", "macros"] } toml = "0.5" @@ -11,6 +15,8 @@ sqlx = { version = "0.5", default-features = false, features = ["runtime-tokio-r tracing = "0.1" tracing-subscriber = "0.3" warp = { version = "0.3", default-features = false } +warp-sessions = "1.0" tera = "1.15" async-sqlx-session = { version = "0.4", default-features = false, features = ["pg"] } anyhow = "1.0" +chrono = "0.4" diff --git a/src/database.rs b/src/database.rs index e7426ed..520240f 100644 --- a/src/database.rs +++ b/src/database.rs @@ -21,9 +21,7 @@ use sqlx::{ConnectOptions, PgPool}; use tracing::info; use tracing::log::LevelFilter; -pub struct Database { - pool: PgPool, -} +pub struct Database(PgPool); impl Database { pub async fn connect(conn_string: &str) -> anyhow::Result { @@ -40,16 +38,14 @@ impl Database { "Database connected, PostgreSQL version {}", pgver.as_deref().unwrap_or("unknown") ); - Ok(Self { - pool - }) + Ok(Self(pool)) } pub fn pool(&self) -> PgPool { - self.pool.clone() + self.0.clone() } pub async fn close(&self) { - self.pool.close().await; + self.0.close().await; } } diff --git a/src/main.rs b/src/main.rs index 08837f5..95ec500 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,16 +16,16 @@ * this program. If not, see . */ +use crate::web::App; use crate::{config::Config, database::Database}; -use std::{env, sync::Arc}; +use async_sqlx_session::PostgresSessionStore; use std::str::FromStr; use std::time::Duration; -use async_sqlx_session::PostgresSessionStore; +use std::{env, sync::Arc}; use tokio::sync::broadcast; use tokio::task::JoinHandle; use tokio::time::sleep; use tracing::{debug, error, info, Level}; -use crate::web::App; mod config; mod database; @@ -71,13 +71,15 @@ async fn main() -> anyhow::Result<()> { info!(concat!("Initializing - tmtd ", env!("CARGO_PKG_VERSION"))); let (ctx, _) = broadcast::channel(1); let database = Arc::new(Database::connect(&cfg.connection_string).await?); - let session_store = PostgresSessionStore::from_client(database.pool()).with_table_name("sessions"); + let session_store = + PostgresSessionStore::from_client(database.pool()).with_table_name("sessions"); session_store.migrate().await?; - let cleanup_task = spawn_session_cleanup_task(&session_store, Duration::from_secs(600), ctx.subscribe()); + let cleanup_task = + spawn_session_cleanup_task(&session_store, Duration::from_secs(600), ctx.subscribe()); info!("Started session cleanup task"); - let web_app = App::new(cfg.clone(), database.clone())?; + let web_app = App::new(cfg.clone(), database.clone(), session_store)?; let web_task = tokio::spawn(web_app.run(ctx.subscribe())); info!("Started the web app at http://{}", cfg.listen_addr); @@ -95,7 +97,11 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn spawn_session_cleanup_task(store: &PostgresSessionStore, period: Duration, mut cancel: broadcast::Receiver<()>) -> JoinHandle<()> { +fn spawn_session_cleanup_task( + store: &PostgresSessionStore, + period: Duration, + mut cancel: broadcast::Receiver<()>, +) -> JoinHandle<()> { let store = store.clone(); tokio::spawn(async move { loop { diff --git a/src/web.rs b/src/web.rs index ae562fd..7519e76 100644 --- a/src/web.rs +++ b/src/web.rs @@ -16,43 +16,116 @@ * this program. If not, see . */ -use crate::{Config, Database}; use std::sync::Arc; + +use async_sqlx_session::PostgresSessionStore; +use chrono::Duration; +use sqlx::types::chrono::Utc; +use tera::Tera; use tokio::sync::broadcast; -use warp::{Filter, reply}; -use tera::{Context, Tera}; use tracing::info; +use warp::Filter; +use warp_sessions::{CookieOptions, SameSiteCookieOption, SessionWithStore}; + +use crate::{Config, Database}; + +macro_rules! warp_try { + ($expr:expr) => { + match $expr { + Ok(o) => o, + Err(e) => { + warn!("Error in a request handler: {}", e); + return reply::with_status( + format!("Error: {}", e), + StatusCode::INTERNAL_SERVER_ERROR, + ) + .into_response(); + } + } + }; + + ($store:expr, $expr:expr) => { + match $expr { + Ok(o) => o, + Err(e) => { + warn!("Error in a request handler: {}", e); + return ( + reply::with_status(format!("Error: {}", e), StatusCode::INTERNAL_SERVER_ERROR) + .into_response(), + $store, + ); + } + } + }; +} + +macro_rules! warp_session { + ($filter:expr, $store:expr, $then:expr) => { + $filter + .and( + warp_sessions::request::with_session( + $store.clone(), + Some(CookieOptions { + cookie_name: "tmtd_sid", + cookie_value: None, + max_age: Some(86400*7), + domain: None, + path: None, + secure: true, + http_only: true, + same_site: Some(SameSiteCookieOption::Strict), + }), + ) + .map(|mut s: SessionWithStore| { + s.session.set_expiry(Utc::now() + Duration::days(7)); + s + }), + ) + .then($then) + .untuple_one() + .and_then(warp_sessions::reply::with_session) + }; +} pub struct App { config: Arc, db: Arc, - tera: Arc + session_store: PostgresSessionStore, + tera: Arc, } impl App { - pub fn new(config: Arc, db: Arc) -> Result { + pub fn new( + config: Arc, + db: Arc, + session_store: PostgresSessionStore, + ) -> Result { Ok(Self { config, db, - tera: Arc::new(Tera::new("templates/*.html")?) + session_store, + tera: Arc::new(Tera::new("templates/*.html")?), }) } pub async fn run(self, mut cancel: broadcast::Receiver<()>) { - let hello = warp::path!("hello" / String).map({ - let tera = self.tera.clone(); - move |name: String| { - let mut ctx = Context::new(); - ctx.insert("name", &name); - reply::html(tera.render("hello.html", &ctx).unwrap()) + let route = warp_session!( + warp::path::end(), + self.session_store, + move |mut s: SessionWithStore| async move { + s.session.insert_raw("test", "something".into()); + ( + warp::reply::html(format!("session id: {}", s.session.id())), + s, + ) } - }); + ); let (_, server) = - warp::serve(hello).bind_with_graceful_shutdown(self.config.listen_addr, async move { + warp::serve(route).bind_with_graceful_shutdown(self.config.listen_addr, async move { cancel.recv().await.unwrap(); }); server.await; - info!("Web app has been shut down") + info!("Web app has been shut down"); } }