extern crate actix;
extern crate actix_web;
extern crate base64;
extern crate failure;
extern crate futures;
extern crate rand;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate failure_derive;
mod sql;
use std::rc::Rc;
use failure::Error;
use actix::prelude::Syn;
use actix::Addr;
use actix_web::error::Error as ActixWebError;
use actix_web::middleware::identity::{Identity, IdentityPolicy};
use actix_web::middleware::Response as MiddlewareResponse;
use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use futures::future::ok as FutOk;
use futures::Future;
use sql::{DeleteIdentity, FindIdentity, SqlActor, SqlIdentityModel, UpdateIdentity};
use rand::Rng;
const DEFAULT_RESPONSE_HDR: &'static str= "actix-auth";
const DEFAULT_POOL_SIZE: usize = 3;
#[derive(Debug, Fail)]
enum SqlIdentityError {
#[allow(dead_code)]
#[fail(display = "sql variant not supported")]
SqlVariantNotSupported,
#[fail(display = "token not found")]
SqlTokenNotFound,
}
enum SqlIdentityState {
Saved,
Deleted,
Unchanged,
}
pub struct SqlIdentity {
state: SqlIdentityState,
identity: Option<String>,
token: Option<String>,
inner: Rc<SqlIdentityInner>,
}
impl Identity for SqlIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.identity = Some(value);
let mut arr = [0u8; 24];
rand::thread_rng().fill(&mut arr[..]);
self.token = Some(base64::encode(&arr));
self.state = SqlIdentityState::Saved;
}
fn forget(&mut self) {
self.identity = None;
self.state = SqlIdentityState::Deleted;
}
fn write(&mut self, resp: HttpResponse) -> Result<MiddlewareResponse, ActixWebError> {
match self.state {
SqlIdentityState::Saved if self.token.is_some() && self.identity.is_some() => {
let token = self.token.as_ref().expect("[SIS::Saved] Token is None!");
let identity = self.identity.as_ref().expect("[SIS::Saved] Identity is None!");
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Future(
self.inner.save(token, identity, resp),
))
}
SqlIdentityState::Deleted if self.token.is_some() => {
let token = self.token.as_ref().expect("[SIS::Deleted] Token is None!");
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Future(self.inner.remove(token, resp)))
}
SqlIdentityState::Deleted | SqlIdentityState::Saved => {
Ok(MiddlewareResponse::Done(
HttpResponse::BadRequest().finish(),
))
}
_ => {
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Done(resp))
}
}
}
}
struct SqlIdentityInner {
addr: Addr<Syn, SqlActor>,
hdr: &'static str,
}
impl SqlIdentityInner {
fn new(addr: Addr<Syn, SqlActor>, hdr: &'static str) -> SqlIdentityInner {
SqlIdentityInner { addr, hdr }
}
fn save(
&self,
token: &str,
userid: &str,
mut resp: HttpResponse,
) -> Box<Future<Item = HttpResponse, Error = ActixWebError>> {
{
let headers = resp.headers_mut();
headers.append(self.hdr, token.parse().expect("[SII::save] token failed to parse"));
}
Box::new(
self.addr
.send(UpdateIdentity {
token: token.to_string(),
userid: userid.to_string(),
})
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(_) => Ok(resp),
Err(e) => {
println!("[SII::save] ERROR: {:?}", e);
Ok(HttpResponse::InternalServerError().finish())
}
}),
)
}
fn remove(
&self,
token: &str,
resp: HttpResponse,
) -> Box<Future<Item = HttpResponse, Error = ActixWebError>> {
Box::new(
self.addr
.send(DeleteIdentity {
token: token.to_string(),
})
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(_) => Ok(resp),
Err(_) => Ok(HttpResponse::InternalServerError().finish()),
}),
)
}
fn load<S>(
&self,
req: &HttpRequest<S>,
) -> Box<Future<Item = Option<SqlIdentityModel>, Error = ActixWebError>> {
let headers = req.headers();
let auth_header = headers.get("Authorization");
if let Some(auth_header) = auth_header {
if let Ok(auth_header) = auth_header.to_str() {
let mut iter = auth_header.split(' ');
let scheme = iter.next();
let token = iter.next();
if scheme.is_some() && token.is_some() {
let _scheme = scheme.expect("[SII::load] Scheme is None!");
let token = token.expect("[SII::load] Token is None!");
return Box::new(
self.addr
.send(FindIdentity {
token: token.to_string(),
})
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(val) => Ok(Some(val)),
Err(_) => Ok(None),
}),
);
}
}
}
Box::new(FutOk(None))
}
}
pub struct SqlIdentityPolicy(Rc<SqlIdentityInner>);
pub struct SqlIdentityBuilder {
pool: usize,
uri: String,
hdr: &'static str,
}
impl SqlIdentityBuilder {
pub fn new<T: Into<String>>(uri: T) -> SqlIdentityBuilder {
SqlIdentityBuilder {
pool: DEFAULT_POOL_SIZE,
uri: uri.into(),
hdr: DEFAULT_RESPONSE_HDR,
}
}
pub fn response_header(mut self, hdr: &'static str) -> SqlIdentityBuilder {
self.hdr = hdr;
self
}
pub fn pool_size(mut self, count: usize) -> SqlIdentityBuilder {
self.pool = count;
self
}
pub fn sqlite(self) -> Result<SqlIdentityPolicy, Error> {
Ok(SqlIdentityPolicy(Rc::new(SqlIdentityInner::new(
SqlActor::sqlite(self.pool, &self.uri)?,
self.hdr,
))))
}
pub fn mysql(self) -> Result<SqlIdentityPolicy, Error> {
Ok(SqlIdentityPolicy(Rc::new(SqlIdentityInner::new(
SqlActor::mysql(self.pool, &self.uri)?,
self.hdr,
))))
}
pub fn postgresql(self) -> Result<SqlIdentityPolicy, Error> {
Ok(SqlIdentityPolicy(Rc::new(SqlIdentityInner::new(
SqlActor::pg(self.pool, &self.uri)?,
self.hdr,
))))
}
}
impl<S> IdentityPolicy<S> for SqlIdentityPolicy {
type Identity = SqlIdentity;
type Future = Box<Future<Item = SqlIdentity, Error = ActixWebError>>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
let inner = Rc::clone(&self.0);
Box::new(self.0.load(req).map(move |ident| {
if let Some(id) = ident {
SqlIdentity {
identity: Some(id.userid),
token: Some(id.token),
state: SqlIdentityState::Unchanged,
inner: inner,
}
} else {
SqlIdentity {
identity: None,
token: None,
state: SqlIdentityState::Unchanged,
inner: inner,
}
}
}))
}
}