|
1 | 1 | //! Async HTTP sessions. |
2 | 2 | //! |
3 | | -//! This crate provides a generic interface between cookies and storage |
4 | | -//! backends to create a concept of sessions. It provides an interface that |
5 | | -//! can be used to encode and store sessions, and decode and load sessions |
6 | | -//! generating cookies in the process. |
| 3 | +//! This crate provides a generic interface between cookie values and |
| 4 | +//! storage backends to create a concept of sessions. It provides an |
| 5 | +//! interface that can be used to encode and store sessions, and |
| 6 | +//! decode and load sessions generating cookies in the process. |
7 | 7 | //! |
8 | | -//! # Security |
9 | | -//! |
10 | | -//! This module has not been vetted for security purposes, and in particular |
11 | | -//! the in-memory storage backend is wildly insecure. Please thoroughly |
12 | | -//! validate whether this crate is a match for your intended use case before |
13 | | -//! relying on it in any sensitive context. |
14 | | -//! |
15 | | -//! # Examples |
| 8 | +//! # Example |
16 | 9 | //! |
17 | 10 | //! ``` |
18 | | -//! use async_session::mem::MemoryStore; |
19 | | -//! use async_session::{Session, SessionStore}; |
20 | | -//! use cookie::CookieJar; |
| 11 | +//! use async_session::{Session, SessionStore, MemoryStore}; |
21 | 12 | //! |
22 | | -//! # fn main() -> std::io::Result<()> { |
| 13 | +//! # fn main() -> async_session::Result { |
23 | 14 | //! # async_std::task::block_on(async { |
24 | 15 | //! # |
25 | 16 | //! // Init a new session store we can persist sessions to. |
26 | 17 | //! let mut store = MemoryStore::new(); |
27 | 18 | //! |
28 | 19 | //! // Create a new session. |
29 | | -//! let sess = store.create_session(); |
| 20 | +//! let mut session = Session::new(); |
| 21 | +//! session.insert("user_id", 1)?; |
| 22 | +//! assert!(session.data_changed()); |
30 | 23 | //! |
31 | | -//! // Persist the session to our backend, and store a cookie |
32 | | -//! // to later access the session. |
33 | | -//! let mut jar = CookieJar::new(); |
34 | | -//! let sess = store.store_session(sess, &mut jar).await?; |
| 24 | +//! // retrieve the cookie value to store in a session cookie |
| 25 | +//! let cookie_value = store.store_session(session).await?.unwrap(); |
35 | 26 | //! |
36 | 27 | //! // Retrieve the session using the cookie. |
37 | | -//! let sess = store.load_session(&jar).await?; |
38 | | -//! println!("session: {:?}", sess); |
| 28 | +//! let session = store.load_session(cookie_value).await?.unwrap(); |
| 29 | +//! assert_eq!(session.get::<usize>("user_id").unwrap(), 1); |
| 30 | +//! assert!(!session.data_changed()); |
39 | 31 | //! # |
40 | 32 | //! # Ok(()) }) } |
41 | 33 | //! ``` |
42 | 34 |
|
43 | | -#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)] |
44 | | -#![deny(missing_debug_implementations, nonstandard_style)] |
45 | | -#![warn(missing_docs, missing_doc_code_examples, unreachable_pub)] |
46 | | - |
47 | | -use async_trait::async_trait; |
48 | | -use std::collections::HashMap; |
49 | | - |
50 | | -/// An async session backend. |
51 | | -#[async_trait] |
52 | | -pub trait SessionStore: Send + Sync + 'static + Clone { |
53 | | - /// The type of error that can occur when storing and loading errors. |
54 | | - type Error; |
55 | | - |
56 | | - /// Get a session from the storage backend. |
57 | | - /// |
58 | | - /// The input should usually be the content of a cookie. This will then be |
59 | | - /// parsed by the session middleware into a valid session. |
60 | | - async fn load_session(&self, jar: &cookie::CookieJar) -> Result<Session, Self::Error>; |
61 | | - |
62 | | - /// Store a session on the storage backend. |
63 | | - /// |
64 | | - /// This method should return a stringified representation of the session so |
65 | | - /// that it can be sent back to the client through a cookie. |
66 | | - async fn store_session( |
67 | | - &mut self, |
68 | | - session: Session, |
69 | | - jar: &mut cookie::CookieJar, |
70 | | - ) -> Result<(), Self::Error>; |
71 | | -} |
72 | | - |
73 | | -/// The main session type. |
74 | | -#[derive(Clone, Debug)] |
75 | | -pub struct Session { |
76 | | - inner: HashMap<String, String>, |
77 | | -} |
78 | | - |
79 | | -impl Session { |
80 | | - /// Create a new session. |
81 | | - pub fn new() -> Self { |
82 | | - Self { |
83 | | - inner: HashMap::new(), |
84 | | - } |
85 | | - } |
86 | | - |
87 | | - /// Insert a new value into the Session. |
88 | | - pub fn insert(&mut self, k: String, v: String) -> Option<String> { |
89 | | - self.inner.insert(k, v) |
90 | | - } |
91 | | - |
92 | | - /// Get a value from the session. |
93 | | - pub fn get(&self, k: &str) -> Option<&String> { |
94 | | - self.inner.get(k) |
95 | | - } |
96 | | -} |
97 | | - |
98 | | -/// In-memory session store. |
99 | | -pub mod mem { |
100 | | - use async_std::io::{Error, ErrorKind}; |
101 | | - use async_std::sync::{Arc, RwLock}; |
102 | | - use cookie::Cookie; |
103 | | - use std::collections::HashMap; |
104 | | - |
105 | | - use async_trait::async_trait; |
106 | | - use uuid::Uuid; |
107 | | - |
108 | | - use crate::{Session, SessionStore}; |
109 | | - |
110 | | - /// An in-memory session store. |
111 | | - /// |
112 | | - /// # Security |
113 | | - /// |
114 | | - /// This store *does not* generate secure sessions, and should under no |
115 | | - /// circumstance be used in production. It's meant only to quickly create |
116 | | - /// sessions. |
117 | | - #[derive(Debug)] |
118 | | - pub struct MemoryStore { |
119 | | - inner: Arc<RwLock<HashMap<String, Session>>>, |
120 | | - } |
121 | | - |
122 | | - impl MemoryStore { |
123 | | - /// Create a new instance of MemoryStore. |
124 | | - pub fn new() -> Self { |
125 | | - Self { |
126 | | - inner: Arc::new(RwLock::new(HashMap::new())), |
127 | | - } |
128 | | - } |
129 | | - |
130 | | - /// Generates a new session by generating a new uuid. |
131 | | - /// |
132 | | - /// This is *not* a secure way of generating sessions, and is intended for debug purposes only. |
133 | | - pub fn create_session(&self) -> Session { |
134 | | - let mut sess = Session::new(); |
135 | | - sess.insert("id".to_string(), uuid::Uuid::new_v4().to_string()); |
136 | | - sess |
137 | | - } |
138 | | - } |
139 | | - |
140 | | - impl Clone for MemoryStore { |
141 | | - fn clone(&self) -> Self { |
142 | | - Self { |
143 | | - inner: self.inner.clone(), |
144 | | - } |
145 | | - } |
146 | | - } |
147 | | - |
148 | | - #[async_trait] |
149 | | - impl SessionStore for MemoryStore { |
150 | | - /// The type of error that can occur when storing and loading errors. |
151 | | - type Error = std::io::Error; |
152 | | - |
153 | | - /// Get a session from the storage backend. |
154 | | - async fn load_session(&self, jar: &cookie::CookieJar) -> Result<Session, Self::Error> { |
155 | | - let id = match jar.get("session") { |
156 | | - Some(cookie) => Uuid::parse_str(cookie.value()), |
157 | | - None => return Err(Error::new(ErrorKind::Other, "No session cookie found")), |
158 | | - }; |
159 | | - |
160 | | - let id = id |
161 | | - .map_err(|_| Error::new(ErrorKind::Other, "Cookie content was not a valid uuid"))? |
162 | | - .to_string(); |
163 | | - |
164 | | - let inner = self.inner.read().await; |
165 | | - let sess = inner.get(&id).ok_or(Error::from(ErrorKind::Other))?; |
166 | | - Ok(sess.clone()) |
167 | | - } |
168 | | - |
169 | | - /// Store a session on the storage backend. |
170 | | - /// |
171 | | - /// The data inside the session will be url-encoded so it can be stored |
172 | | - /// inside a cookie. |
173 | | - async fn store_session( |
174 | | - &mut self, |
175 | | - sess: Session, |
176 | | - jar: &mut cookie::CookieJar, |
177 | | - ) -> Result<(), Self::Error> { |
178 | | - let mut inner = self.inner.write().await; |
179 | | - let id = sess.get("id").unwrap().to_string(); |
180 | | - inner.insert(id.clone(), sess); |
181 | | - jar.add(Cookie::new("session", id)); |
182 | | - Ok(()) |
183 | | - } |
184 | | - } |
185 | | -} |
| 35 | +// #![forbid(unsafe_code, future_incompatible)] |
| 36 | +// #![deny(missing_debug_implementations, nonstandard_style)] |
| 37 | +// #![warn(missing_docs, missing_doc_code_examples, unreachable_pub)] |
| 38 | +#![forbid(unsafe_code, future_incompatible)] |
| 39 | +#![deny( |
| 40 | + missing_debug_implementations, |
| 41 | + nonstandard_style, |
| 42 | + missing_docs, |
| 43 | + unreachable_pub, |
| 44 | + missing_copy_implementations, |
| 45 | + unused_qualifications |
| 46 | +)] |
| 47 | + |
| 48 | +pub use anyhow::Error; |
| 49 | +/// An anyhow::Result with default return type of () |
| 50 | +pub type Result<T = ()> = std::result::Result<T, Error>; |
| 51 | + |
| 52 | +mod cookie_store; |
| 53 | +mod memory_store; |
| 54 | +mod session; |
| 55 | +mod session_store; |
| 56 | + |
| 57 | +pub use cookie_store::CookieStore; |
| 58 | +pub use memory_store::MemoryStore; |
| 59 | +pub use session::Session; |
| 60 | +pub use session_store::SessionStore; |
| 61 | + |
| 62 | +pub use async_trait::async_trait; |
| 63 | +pub use base64; |
| 64 | +pub use blake3; |
| 65 | +pub use chrono; |
| 66 | +pub use hmac; |
| 67 | +pub use kv_log_macro as log; |
| 68 | +pub use serde; |
| 69 | +pub use serde_json; |
| 70 | +pub use sha2; |
0 commit comments