diff --git a/Cargo.lock b/Cargo.lock index 46c2198..d13960a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,7 +417,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-util", "tower-service", @@ -1961,6 +1961,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1999,7 +2005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] @@ -2450,6 +2456,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "ron" version = "0.8.1" @@ -2848,7 +2876,6 @@ checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" dependencies = [ "base64 0.22.1", "bytes", - "chrono", "crc", "crossbeam-queue", "either", @@ -2868,7 +2895,8 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.12", + "time", "tokio", "tokio-stream", "tracing", @@ -2926,7 +2954,6 @@ dependencies = [ "bitflags", "byteorder", "bytes", - "chrono", "crc", "digest", "dotenvy", @@ -2953,7 +2980,8 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", + "time", "tracing", "uuid", "whoami", @@ -2969,7 +2997,6 @@ dependencies = [ "base64 0.22.1", "bitflags", "byteorder", - "chrono", "crc", "dotenvy", "etcetera", @@ -2992,7 +3019,8 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", + "time", "tracing", "uuid", "whoami", @@ -3005,7 +3033,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" dependencies = [ "atoi", - "chrono", "flume", "futures-channel", "futures-core", @@ -3018,7 +3045,8 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.12", + "time", "tracing", "url", "uuid", @@ -3204,13 +3232,33 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -3499,7 +3547,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", "time", "tokio", "tracing", @@ -3517,6 +3565,20 @@ dependencies = [ "tower-sessions-core", ] +[[package]] +name = "tower-sessions-sqlx-store" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e054622079f57fc1a7d6a6089c9334f963d62028fe21dc9eddd58af9a78480b3" +dependencies = [ + "async-trait", + "rmp-serde", + "sqlx", + "thiserror 1.0.69", + "time", + "tower-sessions-core", +] + [[package]] name = "tracing" version = "0.1.41" @@ -4288,13 +4350,14 @@ dependencies = [ "sqlx", "tar", "test-log", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", "tower", "tower-http", "tower-sessions", + "tower-sessions-sqlx-store", "tracing", "tracing-subscriber", "uuid", diff --git a/Cargo.toml b/Cargo.toml index ae93159..70a49e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ pin-project = "1.1.0" rand = "0.9.1" serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0.99" -sqlx = { version = "0.8.5", features = ["runtime-tokio", "macros", "postgres", "uuid", "chrono", "migrate"] } +sqlx = { version = "0.8.5", features = ["runtime-tokio", "macros", "postgres", "uuid", "migrate"] } tar = "0.4.44" thiserror = "2.0" tokio = { version = "1.28.2", features = ["full"] } @@ -33,6 +33,7 @@ tokio-util = "0.7.15" tower = "0.5.2" tower-http = { version = "0.6.6", features = ["trace"] } tower-sessions = "0.14.0" +tower-sessions-sqlx-store = { version = "0.15.0", features = ["postgres"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } uuid = { version = "1.16.0", features = ["serde", "v4"] } diff --git a/src/server/mod.rs b/src/server/mod.rs index 021b6f7..b7c0df2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,6 +4,7 @@ pub mod session; use anyhow::{Context as _, Result}; use axum::extract::FromRef; use axum_extra::extract::cookie::Key; +use futures_util::FutureExt as _; use pin_project::pin_project; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; @@ -15,7 +16,8 @@ use std::sync::Arc; use tokio::signal; use tower_http::trace::TraceLayer; use tower_sessions::cookie::time::Duration; -use tower_sessions::{Expiry, MemoryStore, SessionManagerLayer}; +use tower_sessions::{session_store::ExpiredDeletion, Expiry, SessionManagerLayer}; +use tower_sessions_sqlx_store::PostgresStore; use tracing::info; use crate::email_client::EmailClient; @@ -53,6 +55,17 @@ impl ZeroToAxum { let email_client = email_client::EmailClient::new(conf.email.clone()).context("build email client")?; + let session_store = PostgresStore::new(db.clone()); + session_store.migrate().await?; + let session_cleanup_task = tokio::task::spawn( + session_store + .clone() + .continuously_delete_expired(std::time::Duration::from_secs(60)), + ); + let session_layer = SessionManagerLayer::new(session_store) + .with_secure(false) + .with_expiry(Expiry::OnInactivity(Duration::weeks(1))); + let app_state = AppState { conf: Arc::new(conf.clone()), key: Key::derive_from(conf.app.key.as_bytes()), @@ -60,12 +73,6 @@ impl ZeroToAxum { email_client, }; - // Just store locally for now. Supports database connections. - let session_store = MemoryStore::default(); - let session_layer = SessionManagerLayer::new(session_store) - .with_secure(false) - .with_expiry(Expiry::OnInactivity(Duration::weeks(1))); - let app = routes::build() .with_state(app_state) .layer(TraceLayer::new_for_http()) @@ -75,7 +82,9 @@ impl ZeroToAxum { .await .unwrap(); let bound_addr = listener.local_addr().unwrap(); - let server = axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()); + let server = axum::serve(listener, app).with_graceful_shutdown( + shutdown_signal().then(async move |()| session_cleanup_task.abort_handle().abort()), + ); info!("server started, listening on {bound_addr:?}"); diff --git a/src/server/routes/subscriptions/mod.rs b/src/server/routes/subscriptions/mod.rs index c23a02e..00576d3 100644 --- a/src/server/routes/subscriptions/mod.rs +++ b/src/server/routes/subscriptions/mod.rs @@ -6,9 +6,9 @@ use axum::{ routing::{get, post}, Form, Router, }; +use cookie::time::OffsetDateTime; use rand::{distr::Alphanumeric, rng, Rng as _}; use serde::Deserialize; -use sqlx::types::chrono::Utc; use tracing::{debug, error, info}; use uuid::Uuid; @@ -53,7 +53,7 @@ pub async fn subscribe( subscriber_id, form.email, form.name, - Utc::now() + OffsetDateTime::now_utc() ) .execute(&mut *txn) .await