1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-12-02 19:12:24 +01:00

Merge pull request #912 from Dowwie/master

updated actix-session to support login and logout functionality
This commit is contained in:
Darin 2019-07-03 21:07:07 -04:00 committed by GitHub
commit 14cc5a5d6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 116 additions and 23 deletions

View File

@ -1,5 +1,12 @@
# Changes # Changes
## [0.2.0] - 2019-07-03
* Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()``
at successful login to cycle a session (new key/cookie but keeps state).
Use ``Session.purge()`` at logout to invalid a session cookie (and remove
from redis cache, if applicable).
## [0.1.1] - 2019-06-03 ## [0.1.1] - 2019-06-03
* Fix optional cookie session support * Fix optional cookie session support

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-session" name = "actix-session"
version = "0.1.1" version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework." description = "Session for actix web framework."
readme = "README.md" readme = "README.md"

View File

@ -28,7 +28,7 @@ use futures::future::{ok, Future, FutureResult};
use futures::Poll; use futures::Poll;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use crate::Session; use crate::{Session, SessionStatus};
/// Errors that can occur during handling cookie session /// Errors that can occur during handling cookie session
#[derive(Debug, From, Display)] #[derive(Debug, From, Display)]
@ -119,7 +119,21 @@ impl CookieSessionInner {
Ok(()) Ok(())
} }
fn load(&self, req: &ServiceRequest) -> HashMap<String, String> { /// invalidates session cookie
fn remove_cookie<B>(&self, res: &mut ServiceResponse<B>)
-> Result<(), Error> {
let mut cookie = Cookie::named(self.name.clone());
cookie.set_value("");
cookie.set_max_age(time::Duration::seconds(0));
cookie.set_expires(time::now() - time::Duration::days(365));
let val = HeaderValue::from_str(&cookie.to_string())?;
res.headers_mut().append(SET_COOKIE, val);
Ok(())
}
fn load(&self, req: &ServiceRequest) -> (bool, HashMap<String, String>) {
if let Ok(cookies) = req.cookies() { if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() { for cookie in cookies.iter() {
if cookie.name() == self.name { if cookie.name() == self.name {
@ -134,13 +148,13 @@ impl CookieSessionInner {
}; };
if let Some(cookie) = cookie_opt { if let Some(cookie) = cookie_opt {
if let Ok(val) = serde_json::from_str(cookie.value()) { if let Ok(val) = serde_json::from_str(cookie.value()) {
return val; return (false, val);
} }
} }
} }
} }
} }
HashMap::new() (true, HashMap::new())
} }
} }
@ -302,16 +316,34 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
/// On first request, a new session cookie is returned in response, regardless
/// of whether any session state is set. With subsequent requests, if the
/// session state changes, then set-cookie is returned in response. As
/// a user logs out, call session.purge() to set SessionStatus accordingly
/// and this will trigger removal of the session cookie in the response.
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
let state = self.inner.load(&req); let (is_new, state) = self.inner.load(&req);
Session::set_session(state.into_iter(), &mut req); Session::set_session(state.into_iter(), &mut req);
Box::new(self.service.call(req).map(move |mut res| { Box::new(self.service.call(req).map(move |mut res| {
if let Some(state) = Session::get_changes(&mut res) { match Session::get_changes(&mut res) {
res.checked_expr(|res| inner.set_cookie(res, state)) (SessionStatus::Changed, Some(state))
} else { | (SessionStatus::Renewed, Some(state)) =>
res res.checked_expr(|res| inner.set_cookie(res, state)),
(SessionStatus::Unchanged, _) =>
// set a new session cookie upon first request (new client)
if is_new {
let state: HashMap<String, String> = HashMap::new();
res.checked_expr(|res| inner.set_cookie(res, state.into_iter()))
} else {
res
},
(SessionStatus::Purged, _) => {
inner.remove_cookie(&mut res);
res
},
_ => res
} }
})) }))
} }

View File

@ -98,10 +98,23 @@ impl UserSession for ServiceRequest {
} }
} }
#[derive(PartialEq, Clone, Debug)]
pub enum SessionStatus {
Changed,
Purged,
Renewed,
Unchanged
}
impl Default for SessionStatus {
fn default() -> SessionStatus {
SessionStatus::Unchanged
}
}
#[derive(Default)] #[derive(Default)]
struct SessionInner { struct SessionInner {
state: HashMap<String, String>, state: HashMap<String, String>,
changed: bool, pub status: SessionStatus,
} }
impl Session { impl Session {
@ -117,25 +130,46 @@ impl Session {
/// Set a `value` from the session. /// Set a `value` from the session.
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> { pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
inner.changed = true; if inner.status != SessionStatus::Purged {
inner inner.status = SessionStatus::Changed;
.state inner
.insert(key.to_owned(), serde_json::to_string(&value)?); .state
.insert(key.to_owned(), serde_json::to_string(&value)?);
}
Ok(()) Ok(())
} }
/// Remove value from the session. /// Remove value from the session.
pub fn remove(&self, key: &str) { pub fn remove(&self, key: &str) {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
inner.changed = true; if inner.status != SessionStatus::Purged {
inner.state.remove(key); inner.status = SessionStatus::Changed;
inner.state.remove(key);
}
} }
/// Clear the session. /// Clear the session.
pub fn clear(&self) { pub fn clear(&self) {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
inner.changed = true; if inner.status != SessionStatus::Purged {
inner.state.clear() inner.status = SessionStatus::Changed;
inner.state.clear()
}
}
/// Removes session, both client and server side.
pub fn purge(&self) {
let mut inner = self.0.borrow_mut();
inner.status = SessionStatus::Purged;
inner.state.clear();
}
/// Renews the session key, assigning existing session state to new key.
pub fn renew(&self) {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Renewed;
}
} }
pub fn set_session( pub fn set_session(
@ -149,7 +183,7 @@ impl Session {
pub fn get_changes<B>( pub fn get_changes<B>(
res: &mut ServiceResponse<B>, res: &mut ServiceResponse<B>,
) -> Option<impl Iterator<Item = (String, String)>> { ) -> (SessionStatus, Option<impl Iterator<Item = (String, String)>>) {
if let Some(s_impl) = res if let Some(s_impl) = res
.request() .request()
.extensions() .extensions()
@ -157,9 +191,9 @@ impl Session {
{ {
let state = let state =
std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new()); std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new());
Some(state.into_iter()) (s_impl.borrow().status.clone(), Some(state.into_iter()))
} else { } else {
None (SessionStatus::Unchanged, None)
} }
} }
@ -224,7 +258,8 @@ mod tests {
session.remove("key"); session.remove("key");
let mut res = req.into_response(HttpResponse::Ok().finish()); let mut res = req.into_response(HttpResponse::Ok().finish());
let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect(); let (_status, state) = Session::get_changes(&mut res);
let changes: Vec<_> = state.unwrap().collect();
assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]);
} }
@ -241,4 +276,23 @@ mod tests {
let res = session.get::<String>("key").unwrap(); let res = session.get::<String>("key").unwrap();
assert_eq!(res, Some("value".to_string())); assert_eq!(res, Some("value".to_string()));
} }
#[test]
fn purge_session() {
let mut req = test::TestRequest::default().to_srv_request();
let session = Session::get_session(&mut *req.extensions_mut());
assert_eq!(session.0.borrow().status, SessionStatus::Unchanged);
session.purge();
assert_eq!(session.0.borrow().status, SessionStatus::Purged);
}
#[test]
fn renew_session() {
let mut req = test::TestRequest::default().to_srv_request();
let session = Session::get_session(&mut *req.extensions_mut());
assert_eq!(session.0.borrow().status, SessionStatus::Unchanged);
session.renew();
assert_eq!(session.0.borrow().status, SessionStatus::Renewed);
}
} }