Skip to content

Commit

Permalink
overhaul async-session
Browse files Browse the repository at this point in the history
* Remove interior mutability inside of Session
* store values in memory as serde_json::Values instead of strings
* add Session::take_value
* use dashmap in MemoryStore
* add an Error associated type instead of using anyhow
* remove reexported but unused libraries
* make memory-store and cookie-store cargo features (currently enabled by default)
* updates base64
* adds Session::from_parts and Session::data
  • Loading branch information
jbr committed Feb 17, 2023
1 parent 1c686d8 commit 3636ef2
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 196 deletions.
27 changes: 15 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,28 @@ authors = [
"Jacob Rothstein <hi@jbr.me>"
]

[features]
default = ["memory-store", "cookie-store"]
memory-store = ["dashmap", "thiserror", "log"]
cookie-store = ["bincode-json", "thiserror"]

[dependencies]
async-trait = "0.1.59"
async-trait = "0.1.64"
rand = "0.8.5"
base64 = "0.20.0"
sha2 = "0.10.6"
hmac = "0.12.1"
serde_json = "1.0.89"
bincode = "1.3.3"
anyhow = "1.0.66"
base64 = "0.21.0"
serde_json = "1.0.93"
blake3 = "1.3.3"
async-lock = "2.6.0"
log = "0.4.17"
log = { version = "0.4.17", optional = true }
dashmap = { version = "5.4.0", optional = true }
bincode-json = { version = "0.1.5", features = ["json"], optional = true }
thiserror = { version = "1.0.38", optional = true }

[dependencies.serde]
version = "1.0.150"
features = ["rc", "derive"]
version = "1.0.152"
features = ["derive"]

[dependencies.time]
version = "0.3.17"
version = "0.3.18"
features = ["serde"]

[dev-dependencies.async-std]
Expand Down
58 changes: 39 additions & 19 deletions src/cookie_store.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::{async_trait, Result, Session, SessionStore};
use crate::{async_trait, Session, SessionStore};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};

/// A session store that serializes the entire session into a Cookie.
///
/// # ***This is not recommended for most production deployments.***
///
/// This implementation uses [`bincode`](::bincode) to serialize the
/// Session to decrease the size of the cookie. Note: There is a
/// maximum of 4093 cookie bytes allowed _per domain_, so the cookie
/// store is limited in capacity.
/// This implementation uses [`bincode_json`](::bincode_json) to
/// serialize the Session to decrease the size of the cookie. Note:
/// There is a maximum of 4093 cookie bytes allowed _per domain_, so
/// the cookie store is limited in capacity.
///
/// **Note:** Currently, the data in the cookie is only signed, but *not
/// encrypted*. If the contained session data is sensitive and
Expand All @@ -29,24 +30,43 @@ impl CookieStore {
}
}

#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
/// All errors that can occur in the [`CookieStore`]
pub enum CookieStoreError {
/// A bincode_json error
#[error(transparent)]
Bincode(#[from] bincode_json::Error),

/// A base64 error
#[error(transparent)]
Base64(#[from] base64::DecodeError),

/// A json error
#[error(transparent)]
Json(#[from] serde_json::Error),
}

#[async_trait]
impl SessionStore for CookieStore {
async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
let serialized = base64::decode(cookie_value)?;
let session: Session = bincode::deserialize(&serialized)?;
type Error = CookieStoreError;

async fn load_session(&self, cookie_value: String) -> Result<Option<Session>, Self::Error> {
let serialized = BASE64.decode(cookie_value)?;
let session: Session = bincode_json::from_slice(&serialized)?;
Ok(session.validate())
}

async fn store_session(&self, session: Session) -> Result<Option<String>> {
let serialized = bincode::serialize(&session)?;
Ok(Some(base64::encode(serialized)))
async fn store_session(&self, session: Session) -> Result<Option<String>, Self::Error> {
let serialized = bincode_json::to_vec(&session)?;
Ok(Some(BASE64.encode(serialized)))
}

async fn destroy_session(&self, _session: Session) -> Result {
async fn destroy_session(&self, _session: Session) -> Result<(), Self::Error> {
Ok(())
}

async fn clear_store(&self) -> Result {
async fn clear_store(&self) -> Result<(), Self::Error> {
Ok(())
}
}
Expand All @@ -57,7 +77,7 @@ mod tests {
use async_std::task;
use std::time::Duration;
#[async_std::test]
async fn creating_a_new_session_with_no_expiry() -> Result {
async fn creating_a_new_session_with_no_expiry() -> Result<(), CookieStoreError> {
let store = CookieStore::new();
let mut session = Session::new();
session.insert("key", "Hello")?;
Expand All @@ -72,7 +92,7 @@ mod tests {
}

#[async_std::test]
async fn updating_a_session() -> Result {
async fn updating_a_session() -> Result<(), CookieStoreError> {
let store = CookieStore::new();
let mut session = Session::new();

Expand All @@ -90,18 +110,18 @@ mod tests {
}

#[async_std::test]
async fn updating_a_session_extending_expiry() -> Result {
async fn updating_a_session_extending_expiry() -> Result<(), CookieStoreError> {
let store = CookieStore::new();
let mut session = Session::new();
session.expire_in(Duration::from_secs(1));
let original_expires = session.expiry().unwrap().clone();
let original_expires = *session.expiry().unwrap();
let cookie_value = store.store_session(session).await?.unwrap();

let mut session = store.load_session(cookie_value.clone()).await?.unwrap();

assert_eq!(session.expiry().unwrap(), &original_expires);
session.expire_in(Duration::from_secs(3));
let new_expires = session.expiry().unwrap().clone();
let new_expires = *session.expiry().unwrap();
let cookie_value = store.store_session(session).await?.unwrap();

let session = store.load_session(cookie_value.clone()).await?.unwrap();
Expand All @@ -114,7 +134,7 @@ mod tests {
}

#[async_std::test]
async fn creating_a_new_session_with_expiry() -> Result {
async fn creating_a_new_session_with_expiry() -> Result<(), CookieStoreError> {
let store = CookieStore::new();
let mut session = Session::new();
session.expire_in(Duration::from_secs(3));
Expand Down
22 changes: 7 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! ```
//! use async_session::{Session, SessionStore, MemoryStore};
//!
//! # fn main() -> async_session::Result {
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # async_std::task::block_on(async {
//! #
//! // Init a new session store we can persist sessions to.
Expand Down Expand Up @@ -46,26 +46,18 @@
unused_qualifications
)]

pub use anyhow::Error;
/// An anyhow::Result with default return type of ()
pub type Result<T = ()> = std::result::Result<T, Error>;

#[cfg(feature = "cookie-store")]
mod cookie_store;
#[cfg(feature = "memory-store")]
mod memory_store;
mod session;
mod session_store;

pub use cookie_store::CookieStore;
pub use memory_store::MemoryStore;
#[cfg(feature = "cookie-store")]
pub use cookie_store::{CookieStore, CookieStoreError};
#[cfg(feature = "memory-store")]
pub use memory_store::{MemoryStore, MemoryStoreError};
pub use session::Session;
pub use session_store::SessionStore;

pub use async_trait::async_trait;
pub use base64;
pub use blake3;
pub use hmac;
pub use log;
pub use serde;
pub use serde_json;
pub use sha2;
pub use time;
Loading

0 comments on commit 3636ef2

Please sign in to comment.