Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tower-sessions-core crate rework #218

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5df956b
progress
carloskiki Sep 20, 2024
b51d557
mvp
carloskiki Sep 20, 2024
1a2e893
purge unused deps
carloskiki Sep 20, 2024
afd2de2
strongly typed Session API
carloskiki Sep 23, 2024
97aed45
work on middleware
carloskiki Sep 26, 2024
a8aa9bb
progress on middleware
carloskiki Sep 27, 2024
80e89d2
more progress
carloskiki Sep 27, 2024
e92fcf6
more progress
carloskiki Sep 27, 2024
bc9f013
list todos in readme
carloskiki Sep 27, 2024
f8ef37e
Finished middleware
carloskiki Sep 29, 2024
a41b933
complete memory-store impl
carloskiki Sep 29, 2024
8623a3d
working memory store impl
carloskiki Sep 29, 2024
2f39520
cargo.toml update
carloskiki Sep 29, 2024
0d40791
add support for randomly generating Ids
carloskiki Oct 1, 2024
c335490
minimal docs
carloskiki Oct 1, 2024
08ec50e
remove comment
carloskiki Oct 3, 2024
401b83b
very nice API
carloskiki Oct 3, 2024
c64ea98
purge unused deps
carloskiki Oct 3, 2024
1a873e1
Full documentation of core
carloskiki Oct 4, 2024
11b8092
doc fixes
carloskiki Oct 4, 2024
e1346eb
changes
carloskiki Oct 5, 2024
494dd51
add note on expiration handling in store
carloskiki Oct 5, 2024
72e4ab9
fix
carloskiki Oct 5, 2024
941d63c
remove useless examples, and unused deps
carloskiki Oct 5, 2024
de5e013
stop the flex in the readme
carloskiki Oct 7, 2024
b23fb18
misc
carloskiki Oct 9, 2024
b4514c8
remove useless constructor
carloskiki Oct 9, 2024
3b6f028
add documentation
carloskiki Oct 9, 2024
66de666
fix tests
carloskiki Oct 9, 2024
7b776aa
add tracing
carloskiki Oct 9, 2024
d7649c4
remove extra bounds on Service
carloskiki Oct 10, 2024
f82d9ce
better tracing
carloskiki Oct 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 20 additions & 47 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [".", "memory-store", "tower-sessions-core"]
members = [".", "tower-sessions-core", "memory-store"]
resolver = "2"

[workspace.package]
Expand Down Expand Up @@ -30,75 +30,48 @@ readme.workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["axum-core", "memory-store"]
axum-core = ["tower-sessions-core/axum-core"]
memory-store = ["tower-sessions-memory-store"]
signed = ["tower-cookies/signed"]
private = ["tower-cookies/private"]
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

[workspace.dependencies]
tower-sessions = { version = "=0.13.0", path = ".", default-features = false }

tower-sessions-core = { version = "=0.13.0", path = "tower-sessions-core", default-features = false }
tower-sessions = { version = "=0.13.0", path = ".", features = ["memory-store", "extractor"] }
tower-sessions-core = { version = "=0.13.0", path = "tower-sessions-core" }
tower-sessions-memory-store = { version = "=0.13.0", path = "memory-store" }

async-trait = "0.1.74"
parking_lot = { version = "0.12.1", features = ["serde"] }
rmp-serde = { version = "1.1.2" }
serde = "1.0.192"
thiserror = "1.0.50"
time = "0.3.30"
tokio = { version = "1.32.0", default-features = false, features = ["sync"] }
tokio = { version = "1.32.0", default-features = false }

[features]
memory-store = ["tower-sessions-memory-store"]
extractor = ["dep:axum-core", "dep:async-trait"]

[dependencies]
async-trait = "0.1.73"
async-trait = { version = "0.1.74", optional = true }
axum-core = { version = "0.4", optional = true }
cookie = "0.18.1"
http = "1.0"
tokio = { version = "1.32.0", features = ["sync"] }
pin-project-lite = "0.2.14"
time = { workspace = true, features = ["serde"] }
tower-layer = "0.3.2"
tower-service = "0.3.2"
tower-sessions-core = { workspace = true }
tower-sessions-memory-store = { workspace = true, optional = true }
tracing = { version = "0.1.40", features = ["log"] }
tower-cookies = "0.10.0"
time = { version = "0.3.29", features = ["serde"] }

[dev-dependencies]
async-trait = "0.1.74"
anyhow = "1"
axum = "0.7.1"
axum-core = "0.4.0"
futures = { version = "0.3.28", default-features = false, features = [
"async-await",
] }
http = "1.0"
http-body-util = "0.1"
hyper = "1.0"
reqwest = { version = "0.12.3", default-features = false, features = [
"rustls-tls",
] }
serde = "1.0.192"
time = "0.3.30"
time = { workspace = true }
tokio = { version = "1.32.0", features = ["full"] }
tokio-test = "0.4.3"
tower = { version = "0.5.0", features = ["util"] }
tower-cookies = "0.10.0"
tower-sessions-core = { workspace = true, features = ["deletion-task"] }
tower-sessions-core = { workspace = true }
tower-sessions-memory-store = { workspace = true }
tower-sessions = { workspace = true }

[[example]]
name = "counter"
required-features = ["axum-core", "memory-store"]

[[example]]
name = "counter-extractor"
required-features = ["axum-core", "memory-store"]

[[example]]
name = "strongly-typed"
required-features = ["axum-core"]

[[example]]
name = "signed"
required-features = ["signed", "memory-store"]
required-features = ["memory-store"]
94 changes: 7 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</h1>

<p align="center">
🥠 Sessions as a `tower` and `axum` middleware.
User sessions as a `tower` middleware.
</p>

<div align="center">
Expand All @@ -21,33 +21,12 @@
</a>
</div>

## 🎨 Overview
## TODOs
- [ ] tracing.
- [ ] TEST

This crate provides sessions, key-value pairs associated with a site
visitor, as a `tower` middleware.

It offers:

- **Pluggable Storage Backends:** Bring your own backend simply by
implementing the `SessionStore` trait, fully decoupling sessions from their
storage.
- **Minimal Overhead**: Sessions are only loaded from their backing stores
when they're actually used and only in e.g. the handler they're used in.
That means this middleware can be installed anywhere in your route
graph with minimal overhead.
- **An `axum` Extractor for `Session`:** Applications built with `axum`
can use `Session` as an extractor directly in their handlers. This makes
using sessions as easy as including `Session` in your handler.
- **Simple Key-Value Interface:** Sessions offer a key-value interface that
supports native Rust types. So long as these types are `Serialize` and can
be converted to JSON, it's straightforward to insert, get, and remove any
value.
- **Strongly-Typed Sessions:** Strong typing guarantees are easy to layer on
top of this foundational key-value interface.

This crate's session implementation is inspired by the [Django sessions middleware](https://docs.djangoproject.com/en/4.2/topics/http/sessions) and it provides a transliteration of those semantics.

### Session stores
## Session stores

Session data persistence is managed by user-provided types that implement
`SessionStore`. What this means is that applications can and should
Expand All @@ -58,83 +37,24 @@ useful starting points.

| Crate | Persistent | Description |
| ---------------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------- |
| [`tower-sessions-dynamodb-store`](https://github.com/necrobious/tower-sessions-dynamodb-store) | Yes | DynamoDB session store |
| [`tower-sessions-firestore-store`](https://github.com/AtTheTavern/tower-sessions-firestore-store) | Yes | Firestore session store |
| [`tower-sessions-libsql-store`](https://github.com/daybowbow-dev/tower-sessions-libsql-store) | Yes | libSQL session store |
| [`tower-sessions-mongodb-store`](https://github.com/maxcountryman/tower-sessions-stores/tree/main/mongodb-store) | Yes | MongoDB session store |
| [`tower-sessions-moka-store`](https://github.com/maxcountryman/tower-sessions-stores/tree/main/moka-store) | No | Moka session store |
| [`tower-sessions-redis-store`](https://github.com/maxcountryman/tower-sessions-stores/tree/main/redis-store) | Yes | Redis via `fred` session store |
| [`tower-sessions-rorm-store`](https://github.com/rorm-orm/tower-sessions-rorm-store) | Yes | SQLite, Postgres and Mysql session store provided by `rorm` |
| [`tower-sessions-rusqlite-store`](https://github.com/patte/tower-sessions-rusqlite-store) | Yes | Rusqlite session store |
| [`tower-sessions-sled-store`](https://github.com/Zatzou/tower-sessions-sled-store) | Yes | Sled session store |
| [`tower-sessions-sqlx-store`](https://github.com/maxcountryman/tower-sessions-stores/tree/main/sqlx-store) | Yes | SQLite, Postgres, and MySQL session stores |
| [`tower-sessions-surrealdb-store`](https://github.com/rynoV/tower-sessions-surrealdb-store) | Yes | SurrealDB session store |

Have a store to add? Please open a PR adding it.

### User session management

To facilitate authentication and authorization, we've built [`axum-login`](https://github.com/maxcountryman/axum-login) on top of this crate. Please check it out if you're looking for a generalized auth solution.

## 📦 Install
## Usage

To use the crate in your project, add the following to your `Cargo.toml` file:

```toml
[dependencies]
tower-sessions = "0.13.0"
```

## 🤸 Usage

### `axum` Example

```rust
use std::net::SocketAddr;

use axum::{response::IntoResponse, routing::get, Router};
use serde::{Deserialize, Serialize};
use time::Duration;
use tower_sessions::{Expiry, MemoryStore, Session, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

#[derive(Default, Deserialize, Serialize)]
struct Counter(usize);

async fn handler(session: Session) -> impl IntoResponse {
let counter: Counter = session.get(COUNTER_KEY).await.unwrap().unwrap_or_default();
session.insert(COUNTER_KEY, counter.0 + 1).await.unwrap();
format!("Current count: {}", counter.0)
}

#[tokio::main]
async fn main() {
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false)
.with_expiry(Expiry::OnInactivity(Duration::seconds(10)));

let app = Router::new().route("/", get(handler)).layer(session_layer);

let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
```

You can find this [example][counter-example] as well as other example projects in the [example directory][examples].

> [!NOTE]
> See the [crate documentation][docs] for more usage information.

## 🦺 Safety

This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust.

## 🛟 Getting Help
## Getting Help

We've put together a number of [examples][examples] to help get you started. You're also welcome to [open a discussion](https://github.com/maxcountryman/tower-sessions/discussions/new?category=q-a) and ask additional questions you might have.

Expand Down
48 changes: 0 additions & 48 deletions examples/counter-extractor.rs

This file was deleted.

48 changes: 35 additions & 13 deletions examples/counter.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
use std::net::SocketAddr;

use axum::{response::IntoResponse, routing::get, Router};
use serde::{Deserialize, Serialize};
use time::Duration;
use tower_sessions::{Expiry, MemoryStore, Session, SessionManagerLayer};
use tower_sessions::{Expires, Expiry, MemoryStore, Session, SessionManagerLayer};

const COUNTER_KEY: &str = "counter";

#[derive(Default, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug)]
struct Counter(usize);

async fn handler(session: Session) -> impl IntoResponse {
let counter: Counter = session.get(COUNTER_KEY).await.unwrap().unwrap_or_default();
session.insert(COUNTER_KEY, counter.0 + 1).await.unwrap();
format!("Current count: {}", counter.0)
impl Expires for Counter {
fn expires(&self) -> Expiry {
Expiry::OnInactivity(Duration::seconds(10))
}
}

async fn handler(session: Session<MemoryStore<Counter>>) -> impl IntoResponse {
let value = if let Some(counter_state) = session.clone().load::<Counter>().await.unwrap() {
// We loaded the session, let's update the counter.
match counter_state
.update(|counter| counter.0 += 1)
.await
.unwrap()
{
Some(new_state) => new_state.data().0,
None => {
// The session has expired while we were updating it, let's create a new one.
session.create(Counter(0)).await.unwrap();
0
}
}
} else {
// No session found, let's create a new one.
session.create(Counter(0)).await.unwrap();
0
};

format!("Current count: {}", value)
}

#[tokio::main]
async fn main() {
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false)
.with_expiry(Expiry::OnInactivity(Duration::seconds(10)));
let session_store: MemoryStore<Counter> = MemoryStore::default();
let session_layer = SessionManagerLayer {
store: session_store,
config: Default::default(),
};

let app = Router::new().route("/", get(handler)).layer(session_layer);

Expand Down
36 changes: 0 additions & 36 deletions examples/signed.rs

This file was deleted.

Loading