Skip to main content

trillium_sessions/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(
3    clippy::dbg_macro,
4    missing_copy_implementations,
5    rustdoc::missing_crate_level_docs,
6    missing_debug_implementations,
7    missing_docs,
8    nonstandard_style,
9    unused_qualifications
10)]
11
12//! # Trillium sessions
13//!
14//! Trillium sessions is built on top of
15//! [`async-session`](https://github.com/http-rs/async-session).
16//!
17//! Sessions allows trillium to securely attach data to a browser session
18//! allowing for retrieval and modification of this data within trillium
19//! on subsequent visits. Session data is generally only retained for the
20//! duration of a browser session.
21//!
22//! Trillium's session implementation provides guest sessions by default,
23//! meaning that all web requests to a session-enabled trillium host will
24//! have a cookie attached, whether or not there is anything stored in
25//! that client's session yet.
26//!
27//! ## Stores
28//!
29//! Although this crate provides two bundled session stores, it is highly
30//! recommended that trillium applications use an
31//! external-datastore-backed session storage. For a list of currently
32//! available session stores, see [the documentation for
33//! async-session](https://github.com/http-rs/async-session).
34//!
35//! ## Security
36//!
37//! Although each session store may have different security implications,
38//! the general approach of trillium's session system is as follows: On
39//! each request, trillium checks the cookie configurable as `cookie_name`
40//! on the handler.
41//!
42//! ### If no cookie is found:
43//!
44//! A cryptographically random cookie value is generated. A cookie is set
45//! on the outbound response and signed with an HKDF key derived from the
46//! `secret` provided on creation of the SessionHandler.  The configurable
47//! session store uses a SHA256 digest of the cookie value and stores the
48//! session along with a potential expiry.
49//!
50//! ### If a cookie is found:
51//!
52//! The hkdf derived signing key is used to verify the cookie value's
53//! signature. If it verifies, it is then passed to the session store to
54//! retrieve a Session. For most session stores, this will involve taking
55//! a SHA256 digest of the cookie value and retrieving a serialized
56//! Session from an external datastore based on that digest.
57//!
58//! ### Expiry
59//!
60//! In addition to setting an expiry on the session cookie, trillium
61//! sessions include the same expiry in their serialization format. If an
62//! adversary were able to tamper with the expiry of a cookie, trillium
63//! sessions would still check the expiry on the contained session before
64//! using it
65//!
66//! ### If anything goes wrong with the above process
67//!
68//! If there are any failures in the above session retrieval process, a
69//! new empty session is generated for the request, which proceeds through
70//! the application as normal.
71//!
72//! ## Stale/expired session cleanup
73//!
74//! Any session store other than the cookie store will accumulate stale
75//! sessions.  Although the trillium session handler ensures that they
76//! will not be used as valid sessions, For most session stores, it is the
77//! trillium application's responsibility to call cleanup on the session
78//! store if it requires it
79//!
80//! ```
81//! use trillium::Conn;
82//! use trillium_cookies::{CookiesHandler, cookie::Cookie};
83//! use trillium_sessions::{MemoryStore, SessionConnExt, SessionHandler};
84//! use trillium_testing::TestServer;
85//!
86//! # trillium_testing::block_on(async {
87//! # unsafe { std::env::set_var(
88//! #    "TRILLIUM_SESSION_SECRET",
89//! #     "this is just for testing and you should not do this",
90//! # ); }
91//! let session_secret = std::env::var("TRILLIUM_SESSION_SECRET").unwrap();
92//!
93//! let handler = (
94//!     CookiesHandler::new(),
95//!     SessionHandler::new(MemoryStore::new(), session_secret.as_bytes()),
96//!     |conn: Conn| async move {
97//!         let count: usize = conn.session().get("count").unwrap_or_default();
98//!         conn.with_session("count", count + 1)
99//!             .ok(format!("count: {}", count))
100//!     },
101//! );
102//!
103//! let app = TestServer::new(handler).await;
104//!
105//! let response = app.get("/").await;
106//! response.assert_ok().assert_body("count: 0");
107//! let set_cookie_header = response.header("set-cookie").unwrap();
108//! let cookie = Cookie::parse_encoded(set_cookie_header).unwrap();
109//! let cookie_header = format!("{}={}", cookie.name(), cookie.value());
110//!
111//! app.get("/")
112//!     .with_request_header("cookie", cookie_header.clone())
113//!     .await
114//!     .assert_ok()
115//!     .assert_body("count: 1");
116//!
117//! app.get("/")
118//!     .with_request_header("cookie", cookie_header.clone())
119//!     .await
120//!     .assert_ok()
121//!     .assert_body("count: 2");
122//!
123//! app.get("/")
124//!     .with_request_header("cookie", cookie_header.clone())
125//!     .await
126//!     .assert_ok()
127//!     .assert_body("count: 3");
128//!
129//! app.get("/")
130//!     .with_request_header("cookie", cookie_header.clone())
131//!     .await
132//!     .assert_ok()
133//!     .assert_body("count: 4");
134//! # });
135//! ```
136
137#[cfg(test)]
138#[doc = include_str!("../README.md")]
139mod readme {}
140
141mod session_conn_ext;
142pub use session_conn_ext::SessionConnExt;
143
144mod session_handler;
145pub use async_session::{CookieStore, MemoryStore, Session};
146pub use session_handler::{SessionHandler, sessions};