Skip to content

Commit 84c4500

Browse files
committed
first version done
1 parent 82a1afd commit 84c4500

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@ authors = [
1616
[features]
1717

1818
[dependencies]
19+
async-trait = "0.1.24"
20+
async-std = "1.5.0"
21+
serde_urlencoded = "0.6.1"
22+
cookie = "0.13.3"
23+
uuid = {version = "0.8.1", features = ["v4"] }
1924

2025
[dev-dependencies]
26+
tide = "0.6.0"

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ look at some of these issues:
6161
[good-first-issue]: https://github.com/http-rs/async-session/labels/good%20first%20issue
6262
[help-wanted]: https://github.com/http-rs/async-session/labels/help%20wanted
6363

64+
## Acknowledgements
65+
66+
This work is based on the work initiated by
67+
[@chrisdickinson](https://github.com/chrisdickinson) in
68+
[tide#266](https://github.com/http-rs/tide/pull/266).
69+
6470
## License
6571

6672
<sup>

src/lib.rs

+176-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,185 @@
1-
//! Async session support with pluggable middleware
1+
//! Async HTTP sessions.
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.
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.
214
//!
315
//! # Examples
416
//!
517
//! ```
6-
//! // tbi
18+
//! use async_session::mem::MemoryStore;
19+
//! use async_session::{Session, SessionStore};
20+
//! use cookie::CookieJar;
21+
//!
22+
//! # fn main() -> std::io::Result<()> {
23+
//! # async_std::task::block_on(async {
24+
//! #
25+
//! // Init a new session store we can persist sessions to.
26+
//! let mut store = MemoryStore::new();
27+
//!
28+
//! // Create a new session.
29+
//! let sess = store.create_session();
30+
//!
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?;
35+
//!
36+
//! // Retrieve the session using the cookie.
37+
//! let sess = store.load_session(&jar).await?;
38+
//! println!("session: {:?}", sess);
39+
//! #
40+
//! # Ok(()) }) }
741
//! ```
842
943
#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
1044
#![deny(missing_debug_implementations, nonstandard_style)]
1145
#![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 dbg!(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+
}

tests/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)