Skip to content

Commit

Permalink
provide session expiration a la django
Browse files Browse the repository at this point in the history
Here we rework how session expiration works, implementing a new type,
`SessionExpiry` (later to be renamed `Expiry`). This enum defines three
possible types of expiry,

1. Activity durations, i.e. durations for which the session will be
   renewed until inactivity for that duration has elapsed,
2. Absolute expirations, i.e. timestamps at which the session will
   expire regardless of activity,
3. And browser sessions, which are sessions that are valid for so long
   as the client remains open or the default threshold (two weeks) is
   reached.

Several breaking changes are required to make this change and
importantly, the session itself is now serialized to the session store.
This also means we no longer need `SessionRecord` and it has been
removed entirely.

Please note that fields and methods related to "expiration" have been
renamed or removed and with this change all use cases for the expiration
are provided for by the expiry type.

Closes #57.
  • Loading branch information
maxcountryman committed Oct 26, 2023
1 parent c2b2b8f commit 73ebd97
Show file tree
Hide file tree
Showing 28 changed files with 403 additions and 484 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ tokio-time = ["tokio/time"]
continuously-delete-expired = ["tokio-rt", "tokio-time"]
memory-store = []
redis-store = ["fred", "rmp-serde"]
mongodb-store = ["mongodb", "bson"]
mongodb-store = ["mongodb", "bson", "rmp-serde"]
sqlx-store = ["sqlx", "rmp-serde"]
sqlite-store = ["sqlx/sqlite", "sqlx-store"]
postgres-store = ["sqlx/postgres", "sqlx-store"]
Expand All @@ -36,7 +36,7 @@ futures = { version = "0.3.28", default-features = false, features = [
"async-await",
] }
parking_lot = { version = "0.12.1", features = ["serde"] }
serde = { version = "1.0.188", features = ["derive"] }
serde = { version = "1.0.189", features = ["derive", "rc"] }
serde_json = "1.0.107"
thiserror = "1.0.49"
time = { version = "0.3.29", features = ["serde"] }
Expand All @@ -45,8 +45,8 @@ tower-layer = "0.3.2"
tower-service = "0.3.2"
uuid = { version = "1.4.1", features = ["v4", "serde"] }

axum-core = { optional = true, version = "0.3.4" }
fred = { optional = true, version = "7.0.0" }
axum-core = { optional = true, version = "0.3.4" }
rmp-serde = { optional = true, version = "1.1.2" }
mongodb = { optional = true, version = "2.7.0" }
bson = { optional = true, version = "2.7.0", features = ["time-0_3", "uuid-1"] }
Expand Down
4 changes: 2 additions & 2 deletions examples/counter-concurrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use http::StatusCode;
use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
use tower_sessions::{MemoryStore, Session, SessionExpiry, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

Expand All @@ -24,7 +24,7 @@ async fn main() {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::days(1)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::days(1))),
);

let app = Router::new()
Expand Down
4 changes: 2 additions & 2 deletions examples/counter-extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use http::{request::Parts, StatusCode};
use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
use tower_sessions::{MemoryStore, Session, SessionExpiry, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

Expand Down Expand Up @@ -49,7 +49,7 @@ async fn main() {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
4 changes: 2 additions & 2 deletions examples/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use http::StatusCode;
use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
use tower_sessions::{MemoryStore, Session, SessionExpiry, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

Expand All @@ -24,7 +24,7 @@ async fn main() {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
24 changes: 12 additions & 12 deletions examples/diesel-store-with-custom-table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use tower::ServiceBuilder;
use tower_sessions::{
diesel_store::{DieselStore, DieselStoreError, SessionTable},
session_store::ExpiredDeletion,
Session, SessionManagerLayer,
Session, SessionExpiry, SessionManagerLayer,
};

const COUNTER_KEY: &str = "counter";
Expand All @@ -28,27 +28,27 @@ diesel::table! {
my_sessions {
/// `id` column, contains a session id
id -> Text,
/// `expiration_time` column, contains an optional expiration timestamp
expiration_time -> Nullable<Timestamp>,
/// `expiry_date` column, contains a required expiry timestamp
expiry_date -> Timestamp,
/// `data` column, contains serialized session data
data -> Binary,
}
}

impl SessionTable<SqliteConnection> for self::my_sessions::table {
type ExpirationTime = self::my_sessions::expiration_time;
type ExpiryDate = self::my_sessions::expiry_date;

fn insert(
conn: &mut SqliteConnection,
session_record: &tower_sessions::session::SessionRecord,
session_record: &tower_sessions::session::Session,
) -> Result<(), DieselStoreError> {
let t = session_record.expiry_date();
let t = time::PrimitiveDateTime::new(t.date(), t.time());
diesel::insert_into(my_sessions::table)
.values((
my_sessions::id.eq(session_record.id().to_string()),
my_sessions::expiration_time.eq(session_record
.expiration_time()
.map(|t| time::PrimitiveDateTime::new(t.date(), t.time()))),
my_sessions::data.eq(rmp_serde::to_vec(&session_record.data())?),
my_sessions::expiry_date.eq(t),
my_sessions::data.eq(rmp_serde::to_vec(session_record)?),
))
.execute(conn)?;
Ok(())
Expand All @@ -58,8 +58,8 @@ impl SessionTable<SqliteConnection> for self::my_sessions::table {
// or create the table via normal diesel migrations on startup and leave that
// function empty
conn.batch_execute(
"CREATE TABLE `my_sessions` (`id` TEXT PRIMARY KEY NOT NULL, `expiration_time` TEXT \
NULL, `data` BLOB NOT NULL);",
"CREATE TABLE `my_sessions` (`id` TEXT PRIMARY KEY NOT NULL, `expiry_date` TEXT \
NOT NULL, `data` BLOB NOT NULL);",
)?;
Ok(())
}
Expand Down Expand Up @@ -87,7 +87,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
5 changes: 3 additions & 2 deletions examples/diesel-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{
diesel_store::DieselStore, session_store::ExpiredDeletion, Session, SessionManagerLayer,
diesel_store::DieselStore, session_store::ExpiredDeletion, Session, SessionExpiry,
SessionManagerLayer,
};

const COUNTER_KEY: &str = "counter";
Expand Down Expand Up @@ -42,7 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
5 changes: 3 additions & 2 deletions examples/moka-postgres-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{
sqlx::PgPool, CachingSessionStore, MokaStore, PostgresStore, Session, SessionManagerLayer,
sqlx::PgPool, CachingSessionStore, MokaStore, PostgresStore, Session, SessionExpiry,
SessionManagerLayer,
};

const COUNTER_KEY: &str = "counter";
Expand All @@ -34,7 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(caching_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
6 changes: 3 additions & 3 deletions examples/mongodb-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use http::StatusCode;
use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{mongodb::Client, MongoDBStore, Session, SessionManagerLayer};
use tower_sessions::{mongodb::Client, MongoDBStore, Session, SessionExpiry, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

Expand All @@ -17,7 +17,7 @@ struct Counter(usize);
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let database_url = std::option_env!("DATABASE_URL").expect("Missing DATABASE_URL.");
let client = Client::with_uri_str(database_url).await.unwrap();
let client = Client::with_uri_str(database_url).await?;
let session_store = MongoDBStore::new(client, "tower-sessions".to_string());
let session_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_: BoxError| async {
Expand All @@ -26,7 +26,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
5 changes: 3 additions & 2 deletions examples/mysql-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{
session_store::ExpiredDeletion, sqlx::MySqlPool, MySqlStore, Session, SessionManagerLayer,
session_store::ExpiredDeletion, sqlx::MySqlPool, MySqlStore, Session, SessionExpiry,
SessionManagerLayer,
};

const COUNTER_KEY: &str = "counter";
Expand Down Expand Up @@ -36,7 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
5 changes: 3 additions & 2 deletions examples/postgres-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{
session_store::ExpiredDeletion, sqlx::PgPool, PostgresStore, Session, SessionManagerLayer,
session_store::ExpiredDeletion, sqlx::PgPool, PostgresStore, Session, SessionExpiry,
SessionManagerLayer,
};

const COUNTER_KEY: &str = "counter";
Expand Down Expand Up @@ -36,7 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
4 changes: 2 additions & 2 deletions examples/redis-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use http::StatusCode;
use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{fred::prelude::*, RedisStore, Session, SessionManagerLayer};
use tower_sessions::{fred::prelude::*, RedisStore, Session, SessionExpiry, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

Expand All @@ -29,7 +29,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
5 changes: 3 additions & 2 deletions examples/sqlite-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize};
use time::Duration;
use tower::ServiceBuilder;
use tower_sessions::{
session_store::ExpiredDeletion, sqlx::SqlitePool, Session, SessionManagerLayer, SqliteStore,
session_store::ExpiredDeletion, sqlx::SqlitePool, Session, SessionExpiry, SessionManagerLayer,
SqliteStore,
};

const COUNTER_KEY: &str = "counter";
Expand All @@ -35,7 +36,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
4 changes: 2 additions & 2 deletions examples/strongly-typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use http::{request::Parts, StatusCode};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};
use tower::ServiceBuilder;
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
use tower_sessions::{MemoryStore, Session, SessionExpiry, SessionManagerLayer};
use uuid::Uuid;

#[derive(Clone, Deserialize, Serialize)]
Expand Down Expand Up @@ -117,7 +117,7 @@ async fn main() {
.layer(
SessionManagerLayer::new(session_store)
.with_secure(false)
.with_max_age(Duration::seconds(10)),
.with_expiry(SessionExpiry::InactivityDuration(Duration::seconds(10))),
);

let app = Router::new()
Expand Down
21 changes: 5 additions & 16 deletions src/cookie_config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! Defines the configuration for the cookie belonging to the session.
use time::{Duration, OffsetDateTime};
use tower_cookies::{cookie::SameSite, Cookie};

use crate::Session;
use crate::{session::SessionExpiry, Session};

/// Defines the configuration for the cookie belonging to the session.
#[derive(Debug, Clone)]
Expand All @@ -26,13 +25,8 @@ pub struct CookieConfig {
/// origin.
pub same_site: SameSite,

/// Specifies the maximum age of the cookie.
///
/// This field represents the duration for which the cookie is considered
/// valid before it expires. If set to `None`, the cookie will be
/// treated as a session cookie and will expire when the browser is
/// closed.
pub max_age: Option<Duration>,
/// Specifies the maximum age of the session.
pub expiry: Option<SessionExpiry>,

/// Indicates whether the cookie should only be transmitted over secure
/// (HTTPS) connections.
Expand Down Expand Up @@ -75,12 +69,7 @@ impl CookieConfig {
.secure(self.secure)
.path(self.path.clone());

if let Some(max_age) = session
.expiration_time()
.map(|et| et - OffsetDateTime::now_utc())
{
cookie_builder = cookie_builder.max_age(max_age);
}
cookie_builder = cookie_builder.max_age(session.expiry_age());

if let Some(domain) = &self.domain {
cookie_builder = cookie_builder.domain(domain.clone());
Expand All @@ -95,7 +84,7 @@ impl Default for CookieConfig {
Self {
name: String::from("tower.sid"),
same_site: SameSite::Strict,
max_age: None, // TODO: Is `Max-Age: "Session"` the right default?
expiry: None, // TODO: Is `Max-Age: "Session"` the right default?
secure: false,
path: String::from("/"),
domain: None,
Expand Down
Loading

0 comments on commit 73ebd97

Please sign in to comment.