persist sessions to db

Also switches from `chrono` to `time` because `tower-sessions-sqlx-store`
forces it.
This commit is contained in:
azdle 2025-07-24 10:42:04 -05:00
parent 233968fa97
commit 3e15b7b0c9
4 changed files with 97 additions and 24 deletions

89
Cargo.lock generated
View file

@ -417,7 +417,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_urlencoded", "serde_urlencoded",
"thiserror", "thiserror 2.0.12",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tower-service", "tower-service",
@ -1961,6 +1961,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "pathdiff" name = "pathdiff"
version = "0.2.3" version = "0.2.3"
@ -1999,7 +2005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror", "thiserror 2.0.12",
"ucd-trie", "ucd-trie",
] ]
@ -2450,6 +2456,28 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "ron" name = "ron"
version = "0.8.1" version = "0.8.1"
@ -2848,7 +2876,6 @@ checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"chrono",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
@ -2868,7 +2895,8 @@ dependencies = [
"serde_json", "serde_json",
"sha2", "sha2",
"smallvec", "smallvec",
"thiserror", "thiserror 2.0.12",
"time",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
@ -2926,7 +2954,6 @@ dependencies = [
"bitflags", "bitflags",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"digest", "digest",
"dotenvy", "dotenvy",
@ -2953,7 +2980,8 @@ dependencies = [
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror", "thiserror 2.0.12",
"time",
"tracing", "tracing",
"uuid", "uuid",
"whoami", "whoami",
@ -2969,7 +2997,6 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bitflags", "bitflags",
"byteorder", "byteorder",
"chrono",
"crc", "crc",
"dotenvy", "dotenvy",
"etcetera", "etcetera",
@ -2992,7 +3019,8 @@ dependencies = [
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror", "thiserror 2.0.12",
"time",
"tracing", "tracing",
"uuid", "uuid",
"whoami", "whoami",
@ -3005,7 +3033,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc"
dependencies = [ dependencies = [
"atoi", "atoi",
"chrono",
"flume", "flume",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -3018,7 +3045,8 @@ dependencies = [
"serde", "serde",
"serde_urlencoded", "serde_urlencoded",
"sqlx-core", "sqlx-core",
"thiserror", "thiserror 2.0.12",
"time",
"tracing", "tracing",
"url", "url",
"uuid", "uuid",
@ -3204,13 +3232,33 @@ dependencies = [
"syn 2.0.101", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.12" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [ 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]] [[package]]
@ -3499,7 +3547,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror 2.0.12",
"time", "time",
"tokio", "tokio",
"tracing", "tracing",
@ -3517,6 +3565,20 @@ dependencies = [
"tower-sessions-core", "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]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.41" version = "0.1.41"
@ -4288,13 +4350,14 @@ dependencies = [
"sqlx", "sqlx",
"tar", "tar",
"test-log", "test-log",
"thiserror", "thiserror 2.0.12",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
"tower-sessions", "tower-sessions",
"tower-sessions-sqlx-store",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"uuid", "uuid",

View file

@ -24,7 +24,7 @@ pin-project = "1.1.0"
rand = "0.9.1" rand = "0.9.1"
serde = { version = "1.0.164", features = ["derive"] } serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.99" 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" tar = "0.4.44"
thiserror = "2.0" thiserror = "2.0"
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
@ -33,6 +33,7 @@ tokio-util = "0.7.15"
tower = "0.5.2" tower = "0.5.2"
tower-http = { version = "0.6.6", features = ["trace"] } tower-http = { version = "0.6.6", features = ["trace"] }
tower-sessions = "0.14.0" tower-sessions = "0.14.0"
tower-sessions-sqlx-store = { version = "0.15.0", features = ["postgres"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
uuid = { version = "1.16.0", features = ["serde", "v4"] } uuid = { version = "1.16.0", features = ["serde", "v4"] }

View file

@ -4,6 +4,7 @@ pub mod session;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use axum::extract::FromRef; use axum::extract::FromRef;
use axum_extra::extract::cookie::Key; use axum_extra::extract::cookie::Key;
use futures_util::FutureExt as _;
use pin_project::pin_project; use pin_project::pin_project;
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool; use sqlx::PgPool;
@ -15,7 +16,8 @@ use std::sync::Arc;
use tokio::signal; use tokio::signal;
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use tower_sessions::cookie::time::Duration; 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 tracing::info;
use crate::email_client::EmailClient; use crate::email_client::EmailClient;
@ -53,6 +55,17 @@ impl ZeroToAxum {
let email_client = let email_client =
email_client::EmailClient::new(conf.email.clone()).context("build 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 { let app_state = AppState {
conf: Arc::new(conf.clone()), conf: Arc::new(conf.clone()),
key: Key::derive_from(conf.app.key.as_bytes()), key: Key::derive_from(conf.app.key.as_bytes()),
@ -60,12 +73,6 @@ impl ZeroToAxum {
email_client, 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() let app = routes::build()
.with_state(app_state) .with_state(app_state)
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
@ -75,7 +82,9 @@ impl ZeroToAxum {
.await .await
.unwrap(); .unwrap();
let bound_addr = listener.local_addr().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:?}"); info!("server started, listening on {bound_addr:?}");

View file

@ -6,9 +6,9 @@ use axum::{
routing::{get, post}, routing::{get, post},
Form, Router, Form, Router,
}; };
use cookie::time::OffsetDateTime;
use rand::{distr::Alphanumeric, rng, Rng as _}; use rand::{distr::Alphanumeric, rng, Rng as _};
use serde::Deserialize; use serde::Deserialize;
use sqlx::types::chrono::Utc;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use uuid::Uuid; use uuid::Uuid;
@ -53,7 +53,7 @@ pub async fn subscribe(
subscriber_id, subscriber_id,
form.email, form.email,
form.name, form.name,
Utc::now() OffsetDateTime::now_utc()
) )
.execute(&mut *txn) .execute(&mut *txn)
.await .await