diff --git a/Cargo.toml b/Cargo.toml index 2d6f849..8f098ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,21 @@ authors = ["Tom Houlé "] edition = "2018" [dependencies] -tide = { path = "../tide-aturon" } -futures-preview = "0.3.0-alpha.13" -http-service = "0.1.4" -http = "0.1.16" +tide = "0.2.0" +tide-core = { git = "https://github.com/rustasync/tide", branch = "master" } +tide-cookies = { git = "https://github.com/rustasync/tide", branch = "master" } +cookie = { version = "0.12", features = ["percent-encode"] } +futures-preview = "0.3.0-alpha.16" +#http-service = "0.2.0" +http = "0.1" regex = "1.1.2" uuid = { version = "0.7.2", features = ["v4"] } failure = "0.1.5" -http-service-mock = "0.1.0" log = "0.4.6" pretty_env_logger = "0.3.0" + +[patch.crates-io] +http-service = { git = "https://github.com/rustasync/http-service", branch = "master" } +http-service-hyper = { git = "https://github.com/rustasync/http-service", branch = "master" } +tide = { git = "https://github.com/rustasync/tide", branch = "master" } +tide-core = { git = "https://github.com/rustasync/tide", branch = "master" } diff --git a/README.md b/README.md index dfd52f7..61dd96d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ store (implementing the `SessionStorage` trait) ## Examples +### Setting the middleware + +```rust + let state: InMemorySession<()> = InMemorySession::new(); + app.middleware(CookieSessionMiddleware::new("MySession".to_string(), state)); +``` + ### Reading the contents of the session ```rust diff --git a/src/lib.rs b/src/lib.rs index ef8ec8f..7027b05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,43 +2,47 @@ #![feature(associated_type_defaults)] #![feature(async_await)] -#![feature(await_macro)] #![feature(futures_api)] pub mod storage; use storage::*; - +use tide_core::{error::StringError, Context, box_async}; use futures::channel::oneshot; -use futures::future::{FutureExt, FutureObj}; +use futures::future::BoxFuture; +use tide_cookies::ContextExt as _; +use cookie::{Cookie,SameSite}; +const MIDDLEWARE_MISSING_MSG: &str = + "SessionMiddleware must be used to populate request and response cookies"; -pub trait WithSession { - fn set_session(&mut self, new_session: T); - fn take_session(&mut self) -> T; +pub trait ContextExt { + fn set_session(&mut self, new_session: T) -> Result<(), StringError>; + fn take_session(&mut self) -> Result; // see rails security guide, reset_session // could be implemented with a channel signaling the reset to the middleware // fn reset(&self); } -impl WithSession for tide::Context { - fn set_session(&mut self, new_session: T) { - match self.remove_extension::>() { - Some(session) => { - session - .sender - .send(new_session) - .map_err(|_| ()) - .expect("TODO: error handling"); - } - None => (), - } +impl ContextExt for tide::Context { + fn set_session(&mut self, new_session: T) -> Result<(), StringError> { + let session = self + .extensions_mut() + .remove::>() + .ok_or_else(|| StringError(MIDDLEWARE_MISSING_MSG.to_owned()))?; + session + .sender + .send(new_session) + .map_err(|_| StringError("Unable to handle session".to_owned())) } - fn take_session(&mut self) -> T { - self.remove_extension::>() + fn take_session(&mut self) -> Result { + let session = self + .extensions_mut() + .remove::>() + .ok_or_else(|| StringError(MIDDLEWARE_MISSING_MSG.to_owned())); + session .map(|s| s.data) - .unwrap_or_else(Default::default) } } @@ -62,10 +66,10 @@ where type SessionId = String; /// The cookie session middleware. -pub struct CookieSessionMiddleware { +pub struct CookieSessionMiddleware { /// The name of the cookie used to store the session id. cookie_name: String, - storage: Store, + storage: Storage, } /// The `Shape` parameter is the user-defined shape of the sessions managed by the @@ -73,76 +77,65 @@ pub struct CookieSessionMiddleware { impl CookieSessionMiddleware where Storage: SessionStorage, - Shape: Send + Sync + 'static + Clone + Default, + //Shape: Send + Sync + 'static + Clone + Default, + Shape: 'static, { /// `cookie_name` will be the name of the cookie used to store the session id. - pub fn new(cookie_name: String, storage: Storage) -> Result { - Ok(CookieSessionMiddleware { + pub fn new(cookie_name: String, storage: Storage) -> Self { + CookieSessionMiddleware { cookie_name, storage, - }) + } } /// Attempt to read the session id from the cookies on a request. fn extract_session_id(&self, ctx: &mut tide::Context) -> Option { - use tide::cookies::Cookies as _; - - ctx.cookie(&self.cookie_name).map(|c| c.value().to_owned()) + ctx.get_cookie(&self.cookie_name).expect("can't read cookies").map(|c| c.value().to_owned()) } - async fn middleware_impl<'a, AppData>( - &'a self, - mut ctx: tide::Context, - next: tide::middleware::Next<'a, AppData>, - ) -> tide::Response - where - AppData: Send + 'static + Clone, - { - let session_id = self - .extract_session_id(&mut ctx) - .unwrap_or_else(new_session_id); - - let set_cookie_header = - http::header::HeaderValue::from_str(&format!("{}={}", &self.cookie_name, &session_id)) - .expect("TODO: error handling"); - - let session_shape = await!(self.storage.get(&session_id)) - .ok() - .and_then(|a| a) - .unwrap_or_default(); - - let (session, mut receiver) = Session::new(session_shape); - - ctx.insert_extension::>(session); - - let mut res = await! { next.run(ctx) }; - - let received = receiver.try_recv().ok().and_then(|a| a); - - if let Some(received) = received { - await!(self.storage.set(&session_id, received)).expect("TODO: error handling"); - } - - res.headers_mut().insert("Set-Cookie", set_cookie_header); - - res - } } impl tide::middleware::Middleware for CookieSessionMiddleware where - AppData: Clone + Sync + Send + 'static, + AppData: Send + Sync + 'static, Storage: SessionStorage + Sync + Send + 'static, Shape: Clone + Send + Sync + 'static + Default, { fn handle<'a>( &'a self, - ctx: tide::Context, + mut ctx: tide::Context, next: tide::middleware::Next<'a, AppData>, - ) -> FutureObj<'a, tide::Response> { - let fut = self.middleware_impl(ctx, next).boxed(); - FutureObj::new(fut) + ) -> BoxFuture<'a, tide::Response> { + box_async! { + let session_id = self + .extract_session_id(&mut ctx) + .unwrap_or_else(new_session_id); + + let session_shape = self.storage.get(&session_id).await + .ok() + .and_then(|a| a) + .unwrap_or_default(); + + let (session, mut receiver) = Session::new(session_shape); + ctx.extensions_mut().insert::>(session); + + let mut session_cookie = Cookie::new(self.cookie_name.clone(), session_id.clone()); + session_cookie.set_path("/"); + session_cookie.set_http_only(true); + session_cookie.set_same_site(SameSite::Strict); + ctx.set_cookie(session_cookie); + + let res = next.run(ctx).await; + + let received = receiver.try_recv().ok().and_then(|a| a); + + if let Some(received) = received { + self.storage.set(&session_id, received).await.expect("TODO: error handling"); + } + + res + } } } diff --git a/tests/.cookies.rs.swp b/tests/.cookies.rs.swp new file mode 100644 index 0000000..6fa0fdd Binary files /dev/null and b/tests/.cookies.rs.swp differ