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