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_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",

View file

@ -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"] }

View file

@ -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:?}");

View file

@ -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