mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
29 Commits
web-v1.0.3
...
session-v0
Author | SHA1 | Date | |
---|---|---|---|
f410f3330f | |||
e1fcd203f8 | |||
0d8a4304a9 | |||
14cc5a5d6b | |||
287c2b1d18 | |||
7596ab69e0 | |||
1fdd77bffa | |||
2d424957fb | |||
dabc4fe00b | |||
5bf5b0acd2 | |||
099a8ff7d8 | |||
a28b7139e6 | |||
a0a469fe85 | |||
dbab55dd6b | |||
d2eb1edac3 | |||
5901dfee1a | |||
0e05b37082 | |||
37f4ce8604 | |||
12b5174850 | |||
50a9d9e2c5 | |||
93855b889a | |||
fa7e0fe6df | |||
382d4ca216 | |||
32a66a99bf | |||
73ae801a13 | |||
ca4ed0932e | |||
9fc7c8b1af | |||
65732197b8 | |||
959eef05ae |
@ -3,7 +3,7 @@ sudo: required
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
cargo: true
|
||||
# cargo: true
|
||||
apt: true
|
||||
|
||||
matrix:
|
||||
|
@ -1,5 +1,11 @@
|
||||
# Changes
|
||||
|
||||
## [1.0.4] - TBD
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade `rand` dependency version to 0.7
|
||||
|
||||
## [1.0.3] - 2019-06-28
|
||||
|
||||
### Added
|
||||
|
@ -105,7 +105,7 @@ rustls = { version = "0.15", optional = true }
|
||||
actix = { version = "0.8.3" }
|
||||
actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
actix-http-test = { version = "0.2.2", features=["ssl"] }
|
||||
rand = "0.6"
|
||||
rand = "0.7"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
tokio-timer = "0.2.8"
|
||||
|
@ -3,7 +3,7 @@
|
||||
## Documentation & community resources
|
||||
|
||||
* [User Guide](https://actix.rs/docs/)
|
||||
* [API Documentation](https://docs.rs/actix-identity/)
|
||||
* [API Documentation](https://docs.rs/actix-cors/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-session](https://crates.io/crates/actix-identity)
|
||||
* Cargo package: [actix-cors](https://crates.io/crates/actix-cors)
|
||||
* Minimum supported Rust version: 1.34 or later
|
||||
|
@ -1,5 +1,11 @@
|
||||
# Changes
|
||||
|
||||
## [0.2.6] - TBD
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade `rand` dependency version to 0.7
|
||||
|
||||
## [0.2.5] - 2019-06-28
|
||||
|
||||
### Added
|
||||
|
@ -55,7 +55,7 @@ base64 = "0.10"
|
||||
bitflags = "1.0"
|
||||
bytes = "0.4"
|
||||
byteorder = "1.2"
|
||||
copyless = "0.1.2"
|
||||
copyless = "0.1.4"
|
||||
derive_more = "0.15.0"
|
||||
either = "1.5.2"
|
||||
encoding_rs = "0.8"
|
||||
@ -70,7 +70,7 @@ language-tags = "0.2"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.6"
|
||||
rand = "0.7"
|
||||
regex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
@ -1,5 +1,12 @@
|
||||
# Changes
|
||||
|
||||
## [0.2.0] - 2019-07-08
|
||||
|
||||
* 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
|
||||
|
||||
* Fix optional cookie session support
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-session"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Session for actix web framework."
|
||||
readme = "README.md"
|
||||
|
@ -6,4 +6,4 @@
|
||||
* [API Documentation](https://docs.rs/actix-session/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-session](https://crates.io/crates/actix-session)
|
||||
* Minimum supported Rust version: 1.33 or later
|
||||
* Minimum supported Rust version: 1.34 or later
|
||||
|
@ -28,7 +28,7 @@ use futures::future::{ok, Future, FutureResult};
|
||||
use futures::Poll;
|
||||
use serde_json::error::Error as JsonError;
|
||||
|
||||
use crate::Session;
|
||||
use crate::{Session, SessionStatus};
|
||||
|
||||
/// Errors that can occur during handling cookie session
|
||||
#[derive(Debug, From, Display)]
|
||||
@ -119,7 +119,20 @@ impl CookieSessionInner {
|
||||
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() {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == self.name {
|
||||
@ -134,13 +147,13 @@ impl CookieSessionInner {
|
||||
};
|
||||
if let Some(cookie) = cookie_opt {
|
||||
if let Ok(val) = serde_json::from_str(cookie.value()) {
|
||||
return val;
|
||||
return (false, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
HashMap::new()
|
||||
(true, HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,16 +315,37 @@ where
|
||||
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 {
|
||||
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);
|
||||
|
||||
Box::new(self.service.call(req).map(move |mut res| {
|
||||
if let Some(state) = Session::get_changes(&mut res) {
|
||||
res.checked_expr(|res| inner.set_cookie(res, state))
|
||||
} else {
|
||||
res
|
||||
match Session::get_changes(&mut res) {
|
||||
(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 {
|
||||
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)]
|
||||
struct SessionInner {
|
||||
state: HashMap<String, String>,
|
||||
changed: bool,
|
||||
pub status: SessionStatus,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
@ -117,25 +130,46 @@ impl Session {
|
||||
/// Set a `value` from the session.
|
||||
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
inner.changed = true;
|
||||
inner
|
||||
.state
|
||||
.insert(key.to_owned(), serde_json::to_string(&value)?);
|
||||
if inner.status != SessionStatus::Purged {
|
||||
inner.status = SessionStatus::Changed;
|
||||
inner
|
||||
.state
|
||||
.insert(key.to_owned(), serde_json::to_string(&value)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove value from the session.
|
||||
pub fn remove(&self, key: &str) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
inner.changed = true;
|
||||
inner.state.remove(key);
|
||||
if inner.status != SessionStatus::Purged {
|
||||
inner.status = SessionStatus::Changed;
|
||||
inner.state.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the session.
|
||||
pub fn clear(&self) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
inner.changed = true;
|
||||
inner.state.clear()
|
||||
if inner.status != SessionStatus::Purged {
|
||||
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(
|
||||
@ -149,7 +183,10 @@ impl Session {
|
||||
|
||||
pub fn get_changes<B>(
|
||||
res: &mut ServiceResponse<B>,
|
||||
) -> Option<impl Iterator<Item = (String, String)>> {
|
||||
) -> (
|
||||
SessionStatus,
|
||||
Option<impl Iterator<Item = (String, String)>>,
|
||||
) {
|
||||
if let Some(s_impl) = res
|
||||
.request()
|
||||
.extensions()
|
||||
@ -157,9 +194,9 @@ impl Session {
|
||||
{
|
||||
let state =
|
||||
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 {
|
||||
None
|
||||
(SessionStatus::Unchanged, None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +261,8 @@ mod tests {
|
||||
session.remove("key");
|
||||
|
||||
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())]);
|
||||
}
|
||||
|
||||
@ -241,4 +279,22 @@ mod tests {
|
||||
let res = session.get::<String>("key").unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.8.3"
|
||||
actix-web = "1.0.2"
|
||||
actix-http = "0.2.4"
|
||||
actix-web = "1.0.3"
|
||||
actix-http = "0.2.5"
|
||||
actix-codec = "0.1.2"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
|
@ -1,5 +1,14 @@
|
||||
# Changes
|
||||
|
||||
## [0.2.2] - 2019-07-01
|
||||
|
||||
### Changed
|
||||
|
||||
* Always append a colon after username in basic auth
|
||||
|
||||
* Upgrade `rand` dependency version to 0.7
|
||||
|
||||
|
||||
## [0.2.1] - 2019-06-05
|
||||
|
||||
### Added
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http client."
|
||||
readme = "README.md"
|
||||
@ -49,7 +49,7 @@ futures = "0.1.25"
|
||||
log =" 0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.6"
|
||||
rand = "0.7"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.5.3"
|
||||
@ -66,5 +66,5 @@ actix-server = { version = "0.5.1", features=["ssl"] }
|
||||
brotli2 = { version="0.3.2" }
|
||||
flate2 = { version="1.0.2" }
|
||||
env_logger = "0.6"
|
||||
rand = "0.6"
|
||||
tokio-tcp = "0.1"
|
||||
rand = "0.7"
|
||||
tokio-tcp = "0.1"
|
||||
|
@ -115,7 +115,7 @@ impl ClientBuilder {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}", username),
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
self.header(
|
||||
header::AUTHORIZATION,
|
||||
@ -164,7 +164,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU="
|
||||
"Basic dXNlcm5hbWU6"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -280,7 +280,7 @@ impl ClientRequest {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}", username),
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
self.header(
|
||||
header::AUTHORIZATION,
|
||||
@ -664,7 +664,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU="
|
||||
"Basic dXNlcm5hbWU6"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ impl WebsocketsRequest {
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}", username),
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth)))
|
||||
}
|
||||
@ -443,7 +443,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU="
|
||||
"Basic dXNlcm5hbWU6"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ where
|
||||
RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future()))
|
||||
}
|
||||
|
||||
/// Runs the provided function, blocking the current thread until the resul
|
||||
/// Runs the provided function, blocking the current thread until the result
|
||||
/// future completes.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
|
@ -13,7 +13,7 @@ use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from from the request's query.
|
||||
/// Extract typed information from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@ -90,7 +90,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from from the request's query.
|
||||
/// Extract typed information from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
Reference in New Issue
Block a user