extern crate nickel;
extern crate plugin;
extern crate typemap;
extern crate jwt;
extern crate crypto;
extern crate cookie;
extern crate hyper;
#[macro_use] extern crate log;
use cookie::Cookie as CookiePair;
use crypto::sha2::Sha256;
use hyper::header::SetCookie;
use hyper::header;
use jwt::{Header, Registered, Token};
use nickel::{Continue, Middleware, MiddlewareResult, Request, Response};
use plugin::Extensible;
use std::default::Default;
use typemap::Key;
#[derive(Clone)]
pub struct SessionMiddleware {
server_key: String,
issuer: Option<String>,
expiration_time: u64,
}
impl SessionMiddleware {
pub fn new(server_key: &str) -> SessionMiddleware {
SessionMiddleware {
server_key: server_key.to_owned(),
issuer: None,
expiration_time: 24 * 60 * 60,
}
}
pub fn issuer(mut self, issuer: &str) -> Self {
self.issuer = Some(issuer.to_owned());
self
}
pub fn expiration_time(mut self, expiration_time: u64) -> Self {
self.expiration_time = expiration_time;
self
}
fn make_token(&self, user: &str) -> Option<String> {
let header: Header = Default::default();
let now = current_numeric_date();
let claims = Registered {
iss: self.issuer.clone(),
sub: Some(user.into()),
exp: Some(now + self.expiration_time),
nbf: Some(now),
..Default::default()
};
let token = Token::new(header, claims);
token.signed(self.server_key.as_ref(),
Sha256::new()).ok()
}
}
#[derive(Debug)]
struct Session {
authorized_user: String,
}
impl Key for SessionMiddleware {
type Value = SessionMiddleware;
}
impl Key for Session {
type Value = Session;
}
fn get_cookie<'mw, 'conn, D>(req: &Request<'mw, 'conn, D>, name: &str)
-> Option<String> {
if let Some(cookies) = req.origin.headers.get::<header::Cookie>() {
for cookie in cookies.iter() {
if cookie.name == name {
return Some(cookie.value.to_string());
}
}
}
None
}
impl<D> Middleware<D> for SessionMiddleware {
fn invoke<'mw, 'conn>(&self,
req: &mut Request<'mw, 'conn, D>,
mut res: Response<'mw, D>)
-> MiddlewareResult<'mw, D> {
res.extensions_mut().insert::<SessionMiddleware>((*self).clone());
if let Some(jwtstr) = get_cookie(req, "jwt") {
match Token::<Header, Registered>::parse(&jwtstr) {
Ok(token) => {
if token.verify(self.server_key.as_ref(), Sha256::new()) {
debug!("Verified token for: {:?}", token.claims);
let now = current_numeric_date();
if let Some(nbf) = token.claims.nbf {
if now < nbf {
warn!("Got a not-yet valid token: {:?}",
token.claims);
return Ok(Continue(res));
}
}
if let Some(exp) = token.claims.exp {
if now > exp {
warn!("Got an expired token: {:?}",
token.claims);
return Ok(Continue(res));
}
}
if let Some(user) = token.claims.sub {
info!("User {:?} is authorized for {} on {}",
user, req.origin.remote_addr, req.origin.uri);
req.extensions_mut()
.insert::<Session>(Session { authorized_user: user });
}
} else {
info!("Invalid token {:?}", token);
}
}
Err(err) => {
info!("Bad jwt token: {:?}", err);
}
}
}
Ok(Continue(res))
}
}
pub trait SessionRequestExtensions {
fn authorized_user(&self) -> Option<String>;
}
pub trait SessionResponseExtensions {
fn set_jwt_user(&mut self, user: &str);
fn clear_jwt_user(&mut self);
}
impl<'a, 'b, D> SessionRequestExtensions for Request<'a, 'b, D> {
fn authorized_user(&self) -> Option<String> {
if let Some(session) = self.extensions().get::<Session>() {
debug!("Got a session: {:?}", session);
return Some(session.authorized_user.clone());
}
debug!("authorized_user returning None");
None
}
}
impl<'a, 'b, D> SessionResponseExtensions for Response<'a, D> {
fn set_jwt_user(&mut self, user: &str) {
debug!("Should set a user jwt for {}", user);
let cookie = {
if let Some(sm) = self.extensions().get::<SessionMiddleware>() {
sm.make_token(user).map(|data| {
let mut cookie = CookiePair::new("jwt".to_owned(), data);
cookie.max_age = Some(sm.expiration_time);
cookie
})
} else {
warn!("No SessionMiddleware on response. :-(");
None
}
};
if let Some(cookie) = cookie {
debug!("Setting new token {}", cookie);
self.set(SetCookie(vec![cookie]));
}
}
fn clear_jwt_user(&mut self) {
let mut gone = CookiePair::new("jwt".to_owned(), "".to_owned());
gone.max_age = Some(0);
self.set(SetCookie(vec![gone]));
}
}
fn current_numeric_date() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH).ok().unwrap().as_secs()
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}