-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
523c466
commit 28b49ec
Showing
15 changed files
with
3,820 additions
and
12 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,54 @@ | ||
[package] | ||
name = "tower-sessions" | ||
description = "Session manager middleware for tower." | ||
description = "🥠 Cookie-based sessions as a `tower` middleware." | ||
version = "0.0.0" | ||
edition = "2021" | ||
license = "MIT" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
[features] | ||
default = ["axum-core", "memory-store"] | ||
memory-store = [] | ||
redis-store = ["fred"] | ||
sqlite-store = ["sqlx", "sqlx/sqlite"] | ||
|
||
[dependencies] | ||
async-trait = "0.1" | ||
http = "0.2" | ||
parking_lot = { version = "0.12", features = ["nightly", "serde"] } | ||
serde = { version = "1.0.188", features = ["derive"] } | ||
serde_json = "1.0.107" | ||
thiserror = "1.0.48" | ||
time = { version = "0.3", features = ["serde"] } | ||
tower-cookies = "0.9" | ||
tower-layer = "0.3" | ||
tower-service = "0.3" | ||
uuid = { version = "1.4.1", features = ["v4", "serde"] } | ||
axum-core = { optional = true, version = "0.3" } | ||
fred = { optional = true, version = "6", features = ["serde-json"] } | ||
sqlx = { optional = true, version = "0.7.1", features = [ | ||
"time", | ||
"uuid", | ||
"runtime-tokio", | ||
] } | ||
|
||
[dev-dependencies] | ||
axum = "0.6" | ||
hyper = "0.14" | ||
tokio = { version = "1", features = ["full"] } | ||
tower = "0.4" | ||
|
||
[package.metadata.docs.rs] | ||
all-features = true | ||
rustdoc-args = ["--cfg", "docsrs"] | ||
|
||
[[example]] | ||
name = "counter" | ||
required-features = ["axum-core", "memory-store"] | ||
|
||
[[example]] | ||
name = "redis-store" | ||
required-features = ["axum-core", "redis-store"] | ||
|
||
[[example]] | ||
name = "sqlite-store" | ||
required-features = ["axum-core", "sqlite-store"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<h1 align="center"> | ||
tower-sessions | ||
</h1> | ||
|
||
<p align="center"> | ||
🥠 Cookie-based sessions as a `tower` middleware. | ||
</p> | ||
|
||
<div align="center"> | ||
<a href="https://crates.io/crates/tower-sessions"> | ||
<img src="https://img.shields.io/crates/v/tower-sessions.svg" /> | ||
</a> | ||
<a href="https://docs.rs/tower-sessions"> | ||
<img src="https://docs.rs/tower-sessions/badge.svg" /> | ||
</a> | ||
<a href="https://github.com/maxcountryman/tower-sessions/actions/workflows/rust.yml"> | ||
<img src="https://github.com/maxcountryman/tower-sessions/actions/workflows/rust.yml/badge.svg" /> | ||
</a> | ||
</div> | ||
|
||
## 🎨 Overview | ||
|
||
This crate provides cookie-based sessions as a `tower` middleware. | ||
|
||
- Wraps `tower-cookies` for cookie management | ||
- Decouples sessions from their storage (`SessionStore`) | ||
- `Session` works as an extractor when using `axum` | ||
- Redis and SQLx stores provided via feature flags | ||
- Works directly with types that implement `Serialize` and `Deserialize` | ||
|
||
## 📦 Install | ||
|
||
To use the crate in your project, add the following to your `Cargo.toml` file: | ||
|
||
```toml | ||
[dependencies] | ||
tower-sessions = "0.0.0" | ||
``` | ||
|
||
## 🤸 Usage | ||
|
||
### `axum` Example | ||
|
||
```rust | ||
use std::net::SocketAddr; | ||
|
||
use axum::{ | ||
error_handling::HandleErrorLayer, response::IntoResponse, routing::get, BoxError, Router, | ||
}; | ||
use http::StatusCode; | ||
use serde::{Deserialize, Serialize}; | ||
use tower::ServiceBuilder; | ||
use tower_sessions::{time::Duration, MemoryStore, Session, SessionManagerLayer}; | ||
|
||
#[derive(Default, Deserialize, Serialize)] | ||
struct Counter(usize); | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let session_store = MemoryStore::default(); | ||
let session_service = ServiceBuilder::new() | ||
.layer(HandleErrorLayer::new(|_: BoxError| async { | ||
StatusCode::BAD_REQUEST | ||
})) | ||
.layer( | ||
SessionManagerLayer::new(session_store) | ||
.with_secure(false) | ||
.with_max_age(Duration::seconds(10)), | ||
); | ||
|
||
let app = Router::new() | ||
.route("/", get(handler)) | ||
.layer(session_service); | ||
|
||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||
axum::Server::bind(&addr) | ||
.serve(app.into_make_service()) | ||
.await | ||
.unwrap(); | ||
} | ||
|
||
async fn handler(session: Session) -> impl IntoResponse { | ||
let counter: Counter = session | ||
.get("counter") | ||
.expect("Could not deserialize.") | ||
.unwrap_or_default(); | ||
|
||
session | ||
.insert("counter", counter.0 + 1) | ||
.expect("Could not serialize."); | ||
|
||
format!("Current count: {}", counter.0) | ||
} | ||
``` | ||
|
||
You can find this [example][counter-example] as well as other example projects in the [example directory][examples]. | ||
|
||
See the [crate documentation][docs] for more usage information. | ||
|
||
[counter-example]: https://github.com/maxcountryman/tower-sessions/tree/main/examples/counter.rs | ||
[examples]: https://github.com/maxcountryman/tower-sessions/tree/main/examples | ||
[docs]: https://docs.rs/tower-sessions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use std::net::SocketAddr; | ||
|
||
use axum::{ | ||
error_handling::HandleErrorLayer, response::IntoResponse, routing::get, BoxError, Router, | ||
}; | ||
use http::StatusCode; | ||
use serde::{Deserialize, Serialize}; | ||
use tower::ServiceBuilder; | ||
use tower_sessions::{time::Duration, MemoryStore, Session, SessionManagerLayer}; | ||
|
||
#[derive(Default, Deserialize, Serialize)] | ||
struct Counter(usize); | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let session_store = MemoryStore::default(); | ||
let session_service = ServiceBuilder::new() | ||
.layer(HandleErrorLayer::new(|_: BoxError| async { | ||
StatusCode::BAD_REQUEST | ||
})) | ||
.layer( | ||
SessionManagerLayer::new(session_store) | ||
.with_secure(false) | ||
.with_max_age(Duration::seconds(10)), | ||
); | ||
|
||
let app = Router::new() | ||
.route("/", get(handler)) | ||
.layer(session_service); | ||
|
||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||
axum::Server::bind(&addr) | ||
.serve(app.into_make_service()) | ||
.await | ||
.unwrap(); | ||
} | ||
|
||
async fn handler(session: Session) -> impl IntoResponse { | ||
let counter: Counter = session | ||
.get("counter") | ||
.expect("Could not deserialize.") | ||
.unwrap_or_default(); | ||
|
||
session | ||
.insert("counter", counter.0 + 1) | ||
.expect("Could not serialize."); | ||
|
||
format!("Current count: {}", counter.0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
use std::net::SocketAddr; | ||
|
||
use axum::{ | ||
error_handling::HandleErrorLayer, response::IntoResponse, routing::get, BoxError, Router, | ||
}; | ||
use http::StatusCode; | ||
use serde::{Deserialize, Serialize}; | ||
use tower::ServiceBuilder; | ||
use tower_sessions::{fred::prelude::*, time::Duration, RedisStore, Session, SessionManagerLayer}; | ||
|
||
#[derive(Serialize, Deserialize, Default)] | ||
struct Counter(usize); | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let config = RedisConfig::from_url("redis://127.0.0.1:6379/1").unwrap(); | ||
let client = RedisClient::new(config, None, None); | ||
|
||
let _ = client.connect(); | ||
let _ = client.wait_for_connect().await.unwrap(); | ||
|
||
let session_store = RedisStore::new(client); | ||
let session_service = ServiceBuilder::new() | ||
.layer(HandleErrorLayer::new(|_: BoxError| async { | ||
StatusCode::BAD_REQUEST | ||
})) | ||
.layer( | ||
SessionManagerLayer::new(session_store) | ||
.with_secure(false) | ||
.with_max_age(Duration::seconds(10)), | ||
); | ||
|
||
let app = Router::new() | ||
.route("/", get(handler)) | ||
.layer(session_service); | ||
|
||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||
axum::Server::bind(&addr) | ||
.serve(app.into_make_service()) | ||
.await | ||
.unwrap(); | ||
} | ||
|
||
async fn handler(session: Session) -> impl IntoResponse { | ||
let counter: Counter = session | ||
.get("counter") | ||
.expect("Could not deserialize.") | ||
.unwrap_or_default(); | ||
|
||
session | ||
.insert("counter", counter.0 + 1) | ||
.expect("Could not serialize."); | ||
|
||
format!("Current count: {}", counter.0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
use std::net::SocketAddr; | ||
|
||
use axum::{ | ||
error_handling::HandleErrorLayer, response::IntoResponse, routing::get, BoxError, Router, | ||
}; | ||
use http::StatusCode; | ||
use serde::{Deserialize, Serialize}; | ||
use tower::ServiceBuilder; | ||
use tower_sessions::{sqlx::SqlitePool, time::Duration, Session, SessionManagerLayer, SqliteStore}; | ||
|
||
#[derive(Serialize, Deserialize, Default)] | ||
struct Counter(usize); | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); | ||
let session_store = SqliteStore::new(pool); | ||
session_store.migrate().await.unwrap(); | ||
let session_service = ServiceBuilder::new() | ||
.layer(HandleErrorLayer::new(|_: BoxError| async { | ||
StatusCode::BAD_REQUEST | ||
})) | ||
.layer( | ||
SessionManagerLayer::new(session_store) | ||
.with_secure(false) | ||
.with_max_age(Duration::seconds(10)), | ||
); | ||
|
||
let app = Router::new() | ||
.route("/", get(handler)) | ||
.layer(session_service); | ||
|
||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||
axum::Server::bind(&addr) | ||
.serve(app.into_make_service()) | ||
.await | ||
.unwrap(); | ||
} | ||
|
||
async fn handler(session: Session) -> impl IntoResponse { | ||
let counter: Counter = session | ||
.get("counter") | ||
.expect("Could not deserialize.") | ||
.unwrap_or_default(); | ||
|
||
session | ||
.insert("counter", counter.0 + 1) | ||
.expect("Could not serialize."); | ||
|
||
format!("Current count: {}", counter.0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
format_code_in_doc_comments = true | ||
format_strings = true | ||
imports_granularity = "Crate" | ||
group_imports = "StdExternalCrate" | ||
wrap_comments = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use async_trait::async_trait; | ||
use axum_core::extract::FromRequestParts; | ||
use http::{request::Parts, StatusCode}; | ||
|
||
use crate::session::Session; | ||
|
||
#[async_trait] | ||
impl<S> FromRequestParts<S> for Session | ||
where | ||
S: Sync + Send, | ||
{ | ||
type Rejection = (http::StatusCode, &'static str); | ||
|
||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { | ||
parts.extensions.get::<Session>().cloned().ok_or(( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
"Can't extract session. Is `SessionManagerLayer` enabled?", | ||
)) | ||
} | ||
} |
Oops, something went wrong.