diff --git a/.gitignore b/.gitignore index 42d0755dd..c786504ea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ Cargo.lock target/ guide/build/ /gh-pages +/.history *.so *.out diff --git a/CHANGES.md b/CHANGES.md index d8a1632f0..3e22d0019 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,18 @@ # Changes -## 0.6.0 (2019-05-18) + +## 0.6.0 (2019-07-08) * actix-web 1.0.0 compatibility +* Upgraded logic that evaluates session state, including new SessionStatus field, + and introduced ``session.renew()`` and ``session.purge()`` functionality. + Use ``renew()`` to cycle the session key at successful login. ``renew()`` keeps a + session's state while replacing the old cookie and session key with new ones. + Use ``purge()`` at logout to invalidate the session cookie and remove the + session's redis cache entry. + + ## 0.5.1 (2018-08-02) diff --git a/src/session.rs b/src/session.rs index 935a7ce22..315dc189e 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,10 +1,7 @@ -use std::collections::HashMap; -use std::iter; -use std::rc::Rc; - +use std::{collections::HashMap, iter, rc::Rc}; use actix::prelude::*; use actix_service::{Service, Transform}; -use actix_session::Session; +use actix_session::{Session, SessionStatus}; use actix_utils::cloneable::CloneableService; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; @@ -14,7 +11,7 @@ use futures::future::{err, ok, Either, Future, FutureResult}; use futures::Poll; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; -use time::Duration; +use time::{self, Duration}; use crate::redis::{Command, RedisActor}; @@ -147,10 +144,54 @@ where }; srv.call(req).and_then(move |mut res| { - if let Some(state) = Session::get_changes(&mut res) { - Either::A(inner.update(res, state, value)) - } else { - Either::B(ok(res)) + match Session::get_changes(&mut res) { + (SessionStatus::Unchanged, None) => + Either::A(Either::A(Either::A(ok(res)))), + (SessionStatus::Unchanged, Some(state)) => + Either::A(Either::A(Either::B( + if value.is_none(){ // implies the session is new + Either::A( + inner.update(res, state, value) + ) + } else { + Either::B( + ok(res) + ) + } + ))), + (SessionStatus::Changed, Some(state)) => + Either::A(Either::B(Either::A(inner.update(res, state, value)))), + (SessionStatus::Purged, Some(_)) => { + if let Some(val) = value { + Either::A(Either::B(Either::B(Either::A( + inner.clear_cache(val) + .and_then(move |_| + match inner.remove_cookie(&mut res){ + Ok(_) => Either::A(ok(res)), + Err(_err) => Either::B(err( + error::ErrorInternalServerError(_err))) + }) + )))) + } else { + Either::A(Either::B(Either::B(Either::B( + err(error::ErrorInternalServerError("unexpected")) + )))) + } + }, + (SessionStatus::Renewed, Some(state)) => { + if let Some(val) = value { + Either::B(Either::A( + inner.clear_cache(val) + .and_then(move |_| + inner.update(res, state, None)) + )) + } else { + Either::B(Either::B( + inner.update(res, state, None) + )) + } + }, + (_, None) => unreachable!() } }) })) @@ -266,7 +307,6 @@ impl Inner { }; let state: HashMap<_, _> = state.collect(); - match serde_json::to_string(&state) { Err(e) => Either::A(err(e.into())), Ok(body) => Either::B( @@ -289,4 +329,35 @@ impl Inner { ), } } + + /// removes cache entry + fn clear_cache(&self, key: String) + -> impl Future { + self.addr + .send(Command(resp_array!["DEL", key])) + .map_err(Error::from) + .and_then(|res| { + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache")) + } + }) + } + + /// invalidates session cookie + fn remove_cookie(&self, res: &mut ServiceResponse) + -> Result<(), Error> { + let mut cookie = Cookie::named(self.name.clone()); + cookie.set_value(""); + cookie.set_max_age(Duration::seconds(0)); + cookie.set_expires(time::now() - Duration::days(365)); + + let val = HeaderValue::from_str(&cookie.to_string()) + .map_err(|err| error::ErrorInternalServerError(err))?; + res.headers_mut().append(header::SET_COOKIE, val); + + Ok(()) + } }