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:
commit
14cc5a5d6b
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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))
|
||||||
|
| (SessionStatus::Renewed, Some(state)) =>
|
||||||
|
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 {
|
} else {
|
||||||
res
|
res
|
||||||
|
},
|
||||||
|
(SessionStatus::Purged, _) => {
|
||||||
|
inner.remove_cookie(&mut res);
|
||||||
|
res
|
||||||
|
},
|
||||||
|
_ => res
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -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,26 +130,47 @@ 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.status = SessionStatus::Changed;
|
||||||
inner
|
inner
|
||||||
.state
|
.state
|
||||||
.insert(key.to_owned(), serde_json::to_string(&value)?);
|
.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.status = SessionStatus::Changed;
|
||||||
inner.state.remove(key);
|
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.status = SessionStatus::Changed;
|
||||||
inner.state.clear()
|
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(
|
||||||
data: impl Iterator<Item = (String, String)>,
|
data: impl Iterator<Item = (String, String)>,
|
||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user