2017-12-28 21:14:04 -08:00
|
|
|
use std::collections::HashMap;
|
2018-07-20 14:53:56 -07:00
|
|
|
use std::iter;
|
2018-05-08 10:12:57 -07:00
|
|
|
use std::rc::Rc;
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2018-05-08 10:12:57 -07:00
|
|
|
use actix::prelude::*;
|
2019-03-29 11:31:48 -07:00
|
|
|
use actix_service::{Service, Transform};
|
|
|
|
use actix_session::Session;
|
|
|
|
use actix_utils::cloneable::CloneableService;
|
|
|
|
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
|
|
|
use actix_web::http::header::{self, HeaderValue};
|
|
|
|
use actix_web::{error, Error, HttpMessage};
|
2018-06-23 12:17:37 +08:00
|
|
|
use cookie::{Cookie, CookieJar, Key, SameSite};
|
2019-03-29 11:31:48 -07:00
|
|
|
use futures::future::{err, ok, Either, Future, FutureResult};
|
|
|
|
use futures::Poll;
|
|
|
|
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
|
2018-05-08 10:12:57 -07:00
|
|
|
use redis_async::resp::RespValue;
|
2018-06-23 12:17:37 +08:00
|
|
|
use time::Duration;
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
use crate::redis::{Command, RedisActor};
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2017-12-29 01:10:27 -08:00
|
|
|
/// Use redis as session storage.
|
|
|
|
///
|
|
|
|
/// You need to pass an address of the redis server and random value to the
|
2018-05-08 10:12:57 -07:00
|
|
|
/// constructor of `RedisSessionBackend`. This is private key for cookie
|
|
|
|
/// session, When this value is changed, all session data is lost.
|
2017-12-29 01:10:27 -08:00
|
|
|
///
|
|
|
|
/// Constructor panics if key length is less than 32 bytes.
|
2019-03-29 11:31:48 -07:00
|
|
|
pub struct RedisSession(Rc<Inner>);
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
impl RedisSession {
|
2017-12-28 21:14:04 -08:00
|
|
|
/// Create new redis session backend
|
2017-12-29 01:10:27 -08:00
|
|
|
///
|
|
|
|
/// * `addr` - address of the redis server
|
2019-03-29 11:31:48 -07:00
|
|
|
pub fn new<S: Into<String>>(addr: S, key: &[u8]) -> RedisSession {
|
|
|
|
RedisSession(Rc::new(Inner {
|
2018-05-08 10:12:57 -07:00
|
|
|
key: Key::from_master(key),
|
|
|
|
ttl: "7200".to_owned(),
|
|
|
|
addr: RedisActor::start(addr),
|
|
|
|
name: "actix-session".to_owned(),
|
2018-06-23 12:17:37 +08:00
|
|
|
path: "/".to_owned(),
|
|
|
|
domain: None,
|
|
|
|
secure: false,
|
2018-06-23 16:31:00 +08:00
|
|
|
max_age: Some(Duration::days(7)),
|
2018-06-23 12:17:37 +08:00
|
|
|
same_site: None,
|
2018-05-08 10:12:57 -07:00
|
|
|
}))
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
2017-12-29 01:10:27 -08:00
|
|
|
|
|
|
|
/// Set time to live in seconds for session value
|
|
|
|
pub fn ttl(mut self, ttl: u16) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().ttl = format!("{}", ttl);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-01-22 10:50:24 -08:00
|
|
|
/// Set custom cookie name for session id
|
2017-12-29 01:10:27 -08:00
|
|
|
pub fn cookie_name(mut self, name: &str) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().name = name.to_owned();
|
|
|
|
self
|
|
|
|
}
|
2018-06-23 12:17:37 +08:00
|
|
|
|
|
|
|
/// Set custom cookie path
|
|
|
|
pub fn cookie_path(mut self, path: &str) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().path = path.to_owned();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set custom cookie domain
|
|
|
|
pub fn cookie_domain(mut self, domain: &str) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().domain = Some(domain.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set custom cookie secure
|
|
|
|
/// If the `secure` field is set, a cookie will only be transmitted when the
|
|
|
|
/// connection is secure - i.e. `https`
|
|
|
|
pub fn cookie_secure(mut self, secure: bool) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().secure = secure;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set custom cookie max-age
|
|
|
|
pub fn cookie_max_age(mut self, max_age: Duration) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().max_age = Some(max_age);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set custom cookie SameSite
|
|
|
|
pub fn cookie_same_site(mut self, same_site: SameSite) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
|
|
|
|
self
|
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
impl<S, P, B> Transform<S> for RedisSession
|
|
|
|
where
|
|
|
|
S: Service<
|
|
|
|
Request = ServiceRequest<P>,
|
|
|
|
Response = ServiceResponse<B>,
|
|
|
|
Error = Error,
|
|
|
|
> + 'static,
|
|
|
|
S::Future: 'static,
|
|
|
|
P: 'static,
|
|
|
|
B: 'static,
|
|
|
|
{
|
|
|
|
type Request = ServiceRequest<P>;
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
type Error = S::Error;
|
|
|
|
type InitError = ();
|
|
|
|
type Transform = RedisSessionMiddleware<S>;
|
|
|
|
type Future = FutureResult<Self::Transform, Self::InitError>;
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
|
|
|
ok(RedisSessionMiddleware {
|
|
|
|
service: CloneableService::new(service),
|
|
|
|
inner: self.0.clone(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
/// Cookie session middleware
|
|
|
|
pub struct RedisSessionMiddleware<S: 'static> {
|
|
|
|
service: CloneableService<S>,
|
|
|
|
inner: Rc<Inner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S, P, B> Service for RedisSessionMiddleware<S>
|
|
|
|
where
|
|
|
|
S: Service<
|
|
|
|
Request = ServiceRequest<P>,
|
|
|
|
Response = ServiceResponse<B>,
|
|
|
|
Error = Error,
|
|
|
|
> + 'static,
|
|
|
|
S::Future: 'static,
|
|
|
|
P: 'static,
|
|
|
|
B: 'static,
|
|
|
|
{
|
|
|
|
type Request = ServiceRequest<P>;
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
type Error = Error;
|
|
|
|
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
|
|
|
self.service.poll_ready()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
|
|
|
let mut srv = self.service.clone();
|
|
|
|
let inner = self.inner.clone();
|
|
|
|
|
|
|
|
Box::new(self.inner.load(&req).and_then(move |state| {
|
|
|
|
let value = if let Some((state, value)) = state {
|
|
|
|
Session::set_session(state.into_iter(), &mut req);
|
|
|
|
Some(value)
|
2017-12-28 21:14:04 -08:00
|
|
|
} else {
|
2019-03-29 11:31:48 -07:00
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
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))
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
2019-03-29 11:31:48 -07:00
|
|
|
})
|
2017-12-28 21:14:04 -08:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Inner {
|
2017-12-29 01:10:27 -08:00
|
|
|
key: Key,
|
|
|
|
ttl: String,
|
2018-07-20 14:53:56 -07:00
|
|
|
addr: Addr<RedisActor>,
|
2018-06-23 12:17:37 +08:00
|
|
|
name: String,
|
|
|
|
path: String,
|
|
|
|
domain: Option<String>,
|
|
|
|
secure: bool,
|
|
|
|
max_age: Option<Duration>,
|
|
|
|
same_site: Option<SameSite>,
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Inner {
|
2019-03-29 11:31:48 -07:00
|
|
|
fn load<P>(
|
|
|
|
&self,
|
|
|
|
req: &ServiceRequest<P>,
|
|
|
|
) -> impl Future<Item = Option<(HashMap<String, String>, String)>, Error = Error>
|
2018-01-22 10:50:24 -08:00
|
|
|
{
|
2017-12-28 21:14:04 -08:00
|
|
|
if let Ok(cookies) = req.cookies() {
|
2018-07-20 14:53:56 -07:00
|
|
|
for cookie in cookies.iter() {
|
2017-12-29 01:10:27 -08:00
|
|
|
if cookie.name() == self.name {
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.add_original(cookie.clone());
|
|
|
|
if let Some(cookie) = jar.signed(&self.key).get(&self.name) {
|
|
|
|
let value = cookie.value().to_owned();
|
2019-03-29 11:31:48 -07:00
|
|
|
return Either::A(
|
2018-05-08 10:12:57 -07:00
|
|
|
self.addr
|
|
|
|
.send(Command(resp_array!["GET", cookie.value()]))
|
2017-12-29 01:10:27 -08:00
|
|
|
.map_err(Error::from)
|
2018-01-22 10:50:24 -08:00
|
|
|
.and_then(move |res| match res {
|
|
|
|
Ok(val) => {
|
|
|
|
match val {
|
2018-05-08 10:12:57 -07:00
|
|
|
RespValue::Error(err) => {
|
2018-01-22 10:50:24 -08:00
|
|
|
return Err(
|
2018-07-20 14:53:56 -07:00
|
|
|
error::ErrorInternalServerError(err),
|
2019-03-29 11:31:48 -07:00
|
|
|
);
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
|
|
|
RespValue::SimpleString(s) => {
|
|
|
|
if let Ok(val) = serde_json::from_str(&s)
|
|
|
|
{
|
|
|
|
return Ok(Some((val, value)));
|
|
|
|
}
|
|
|
|
}
|
2018-01-22 10:50:24 -08:00
|
|
|
RespValue::BulkString(s) => {
|
2018-05-08 10:12:57 -07:00
|
|
|
if let Ok(val) =
|
|
|
|
serde_json::from_slice(&s)
|
|
|
|
{
|
|
|
|
return Ok(Some((val, value)));
|
2018-01-22 10:50:24 -08:00
|
|
|
}
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
2018-01-22 10:50:24 -08:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
Ok(None)
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2018-07-20 14:53:56 -07:00
|
|
|
Err(error::ErrorInternalServerError(err))
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
);
|
2017-12-29 01:10:27 -08:00
|
|
|
} else {
|
2019-03-29 11:31:48 -07:00
|
|
|
return Either::B(ok(None));
|
2017-12-29 01:10:27 -08:00
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-29 11:31:48 -07:00
|
|
|
Either::B(ok(None))
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
fn update<B>(
|
|
|
|
&self,
|
|
|
|
mut res: ServiceResponse<B>,
|
|
|
|
state: impl Iterator<Item = (String, String)>,
|
|
|
|
value: Option<String>,
|
|
|
|
) -> impl Future<Item = ServiceResponse<B>, Error = Error> {
|
2017-12-29 01:10:27 -08:00
|
|
|
let (value, jar) = if let Some(value) = value {
|
|
|
|
(value.clone(), None)
|
|
|
|
} else {
|
2019-03-29 11:31:48 -07:00
|
|
|
let mut rng = OsRng::new().unwrap();
|
2018-07-20 14:53:56 -07:00
|
|
|
let value: String = iter::repeat(())
|
|
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
|
|
.take(32)
|
|
|
|
.collect();
|
2017-12-29 01:10:27 -08:00
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
// prepare session id cookie
|
2017-12-29 01:10:27 -08:00
|
|
|
let mut cookie = Cookie::new(self.name.clone(), value.clone());
|
2018-06-23 12:17:37 +08:00
|
|
|
cookie.set_path(self.path.clone());
|
|
|
|
cookie.set_secure(self.secure);
|
2017-12-29 01:10:27 -08:00
|
|
|
cookie.set_http_only(true);
|
|
|
|
|
2018-06-23 12:17:37 +08:00
|
|
|
if let Some(ref domain) = self.domain {
|
|
|
|
cookie.set_domain(domain.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(max_age) = self.max_age {
|
|
|
|
cookie.set_max_age(max_age);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(same_site) = self.same_site {
|
|
|
|
cookie.set_same_site(same_site);
|
|
|
|
}
|
|
|
|
|
2017-12-29 01:10:27 -08:00
|
|
|
// set cookie
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.signed(&self.key).add(cookie);
|
|
|
|
|
|
|
|
(value, Some(jar))
|
|
|
|
};
|
|
|
|
|
2019-03-29 11:31:48 -07:00
|
|
|
let state: HashMap<_, _> = state.collect();
|
|
|
|
|
|
|
|
match serde_json::to_string(&state) {
|
|
|
|
Err(e) => Either::A(err(e.into())),
|
2018-05-08 10:12:57 -07:00
|
|
|
Ok(body) => Either::B(
|
|
|
|
self.addr
|
2018-07-20 14:53:56 -07:00
|
|
|
.send(Command(resp_array!["SET", value, body, "EX", &self.ttl]))
|
2018-05-08 10:12:57 -07:00
|
|
|
.map_err(Error::from)
|
2019-03-29 11:31:48 -07:00
|
|
|
.and_then(move |redis_result| match redis_result {
|
2018-05-08 10:12:57 -07:00
|
|
|
Ok(_) => {
|
|
|
|
if let Some(jar) = jar {
|
|
|
|
for cookie in jar.delta() {
|
|
|
|
let val =
|
|
|
|
HeaderValue::from_str(&cookie.to_string())?;
|
2019-03-29 11:31:48 -07:00
|
|
|
res.headers_mut().append(header::SET_COOKIE, val);
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
2019-03-29 11:31:48 -07:00
|
|
|
Ok(res)
|
2018-05-08 10:12:57 -07:00
|
|
|
}
|
2018-07-20 14:53:56 -07:00
|
|
|
Err(err) => Err(error::ErrorInternalServerError(err)),
|
2018-05-08 10:12:57 -07:00
|
|
|
}),
|
|
|
|
),
|
2019-03-29 11:31:48 -07:00
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
}
|