1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! Async HTTP sessions.
//!
//! This crate provides a generic interface between cookies and storage
//! backends to create a concept of sessions. It provides an interface that
//! can be used to encode and store sessions, and decode and load sessions
//! generating cookies in the process.
//!
//! # Security
//!
//! This module has not been vetted for security purposes, and in particular
//! the in-memory storage backend is wildly insecure. Please thoroughly
//! validate whether this crate is a match for your intended use case before
//! relying on it in any sensitive context.
//!
//! # Examples
//!
//! ```
//! use async_session::mem::MemoryStore;
//! use async_session::{Session, SessionStore};
//! use cookie::CookieJar;
//!
//! # fn main() -> std::io::Result<()> {
//! # async_std::task::block_on(async {
//! #
//! // Init a new session store we can persist sessions to.
//! let mut store = MemoryStore::new();
//!
//! // Create a new session.
//! let sess = store.create_session();
//!
//! // Persist the session to our backend, and store a cookie
//! // to later access the session.
//! let mut jar = CookieJar::new();
//! let sess = store.store_session(sess, &mut jar).await?;
//!
//! // Retrieve the session using the cookie.
//! let sess = store.load_session(&jar).await?;
//! println!("session: {:?}", sess);
//! #
//! # Ok(()) }) }
//! ```

#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
#![deny(missing_debug_implementations, nonstandard_style)]
#![warn(missing_docs, missing_doc_code_examples, unreachable_pub)]

use async_trait::async_trait;
use std::collections::HashMap;

/// An async session backend.
#[async_trait]
pub trait SessionStore: Send + Sync + 'static + Clone {
    /// The type of error that can occur when storing and loading errors.
    type Error;

    /// Get a session from the storage backend.
    ///
    /// The input should usually be the content of a cookie. This will then be
    /// parsed by the session middleware into a valid session.
    async fn load_session(&self, jar: &cookie::CookieJar) -> Result<Session, Self::Error>;

    /// Store a session on the storage backend.
    ///
    /// This method should return a stringified representation of the session so
    /// that it can be sent back to the client through a cookie.
    async fn store_session(
        &mut self,
        session: Session,
        jar: &mut cookie::CookieJar,
    ) -> Result<(), Self::Error>;
}

/// The main session type.
#[derive(Clone, Debug)]
pub struct Session {
    inner: HashMap<String, String>,
}

impl Session {
    /// Create a new session.
    pub fn new() -> Self {
        Self {
            inner: HashMap::new(),
        }
    }

    /// Insert a new value into the Session.
    pub fn insert(&mut self, k: String, v: String) -> Option<String> {
        self.inner.insert(k, v)
    }

    /// Get a value from the session.
    pub fn get(&self, k: &str) -> Option<&String> {
        self.inner.get(k)
    }
}

/// In-memory session store.
pub mod mem {
    use async_std::io::{Error, ErrorKind};
    use async_std::sync::{Arc, RwLock};
    use cookie::Cookie;
    use std::collections::HashMap;

    use async_trait::async_trait;
    use uuid::Uuid;

    use crate::{Session, SessionStore};

    /// An in-memory session store.
    ///
    /// # Security
    ///
    /// This store *does not* generate secure sessions, and should under no
    /// circumstance be used in production. It's meant only to quickly create
    /// sessions.
    #[derive(Debug)]
    pub struct MemoryStore {
        inner: Arc<RwLock<HashMap<String, Session>>>,
    }

    impl MemoryStore {
        /// Create a new instance of MemoryStore.
        pub fn new() -> Self {
            Self {
                inner: Arc::new(RwLock::new(HashMap::new())),
            }
        }

        /// Generates a new session by generating a new uuid.
        ///
        /// This is *not* a secure way of generating sessions, and is intended for debug purposes only.
        pub fn create_session(&self) -> Session {
            let mut sess = Session::new();
            sess.insert("id".to_string(), uuid::Uuid::new_v4().to_string());
            sess
        }
    }

    impl Clone for MemoryStore {
        fn clone(&self) -> Self {
            Self {
                inner: self.inner.clone(),
            }
        }
    }

    #[async_trait]
    impl SessionStore for MemoryStore {
        /// The type of error that can occur when storing and loading errors.
        type Error = std::io::Error;

        /// Get a session from the storage backend.
        async fn load_session(&self, jar: &cookie::CookieJar) -> Result<Session, Self::Error> {
            let id = match jar.get("session") {
                Some(cookie) => Uuid::parse_str(cookie.value()),
                None => return Err(Error::new(ErrorKind::Other, "No session cookie found")),
            };

            let id = id
                .map_err(|_| Error::new(ErrorKind::Other, "Cookie content was not a valid uuid"))?
                .to_string();

            let inner = self.inner.read().await;
            let sess = inner.get(&id).ok_or(Error::from(ErrorKind::Other))?;
            Ok(sess.clone())
        }

        /// Store a session on the storage backend.
        ///
        /// The data inside the session will be url-encoded so it can be stored
        /// inside a cookie.
        async fn store_session(
            &mut self,
            sess: Session,
            jar: &mut cookie::CookieJar,
        ) -> Result<(), Self::Error> {
            let mut inner = self.inner.write().await;
            let id = sess.get("id").unwrap().to_string();
            inner.insert(id.clone(), sess);
            jar.add(Cookie::new("session", id));
            Ok(())
        }
    }
}