auth routes

This commit is contained in:
ntr 2019-07-11 22:00:36 +10:00
parent 7b50bfaffb
commit 6419fe55a0
3 changed files with 81 additions and 54 deletions

View File

@ -136,10 +136,6 @@ pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
let row = result.iter().next() let row = result.iter().next()
.ok_or(format_err!("account not updated {:?}", id))?; .ok_or(format_err!("account not updated {:?}", id))?;
let name: String = row.get(1);
info!("login account={:?}", name);
Ok(token) Ok(token)
} }

View File

@ -1,16 +1,13 @@
use iron::status;
use iron::headers::{SetCookie};
use iron::prelude::*;
use persistent::Read;
use iron::typemap::Key;
use router::Router;
use iron::{AfterMiddleware};
use cookie::{Cookie, SameSite};
use chrono::Duration; use chrono::Duration;
use cookie::{Cookie, SameSite};
use failure::Fail; use failure::Fail;
use iron::headers::{Cookie as CookieHdr, SetCookie};
use iron::prelude::*;
use iron::status;
use iron::typemap::Key;
use iron::{typemap, BeforeMiddleware,AfterMiddleware};
use persistent::Read;
use router::Router;
// use warden::{warden}; // use warden::{warden};
// use pubsub::{pg_listen}; // use pubsub::{pg_listen};
@ -56,6 +53,38 @@ impl From<MnmlHttpError> for IronError {
} }
} }
impl typemap::Key for account::Account { type Value = account::Account; }
struct AuthMiddleware;
impl BeforeMiddleware for AuthMiddleware {
fn before(&self, req: &mut Request) -> IronResult<()> {
let state = req.get::<Read<State>>().unwrap();
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
match req.headers.get::<CookieHdr>() {
Some(cookies) => {
for c in cookies.iter() {
let cookie = Cookie::parse(c)
.or(Err(MnmlHttpError::BadRequest))?;
// got auth token
if cookie.name() == TOKEN_HEADER {
match account::from_token(&db, cookie.value().to_string()) {
Ok(a) => req.extensions.insert::<account::Account>(a),
Err(_) => return Err(IronError::from(MnmlHttpError::Unauthorized)),
};
}
}
}
None => (),
};
Ok(())
}
}
const AUTH_CLEAR: &str =
"x-auth-token=; HttpOnly; SameSite=Strict; Max-Age=-1;";
struct ErrorHandler; struct ErrorHandler;
impl AfterMiddleware for ErrorHandler { impl AfterMiddleware for ErrorHandler {
fn catch(&self, _: &mut Request, mut err: IronError) -> IronResult<Response> { fn catch(&self, _: &mut Request, mut err: IronError) -> IronResult<Response> {
@ -63,12 +92,7 @@ impl AfterMiddleware for ErrorHandler {
// on unauthorized we clear the auth token // on unauthorized we clear the auth token
match err.response.status { match err.response.status {
Some(status::Unauthorized) => { Some(status::Unauthorized) => {
let v = Cookie::build(TOKEN_HEADER, "") err.response.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()]));
.http_only(true)
.same_site(SameSite::Strict)
.max_age(Duration::seconds(-1)) // 1 week aligns with db set
.finish();
err.response.headers.set(SetCookie(vec![v.to_string()]));
}, },
_ => (), _ => (),
}; };
@ -79,23 +103,18 @@ impl AfterMiddleware for ErrorHandler {
} }
} }
// fn logout(r: HttpRequest, state: web::Data<State>) -> Result<HttpResponse, MnmlHttpError> { fn token_res(token: String) -> Response {
// match r.cookie("x-auth-token") { let v = Cookie::build(TOKEN_HEADER, token)
// Some(t) => { .http_only(true)
// let db = state.pool.get().or(Err(MnmlHttpError::ServerError))?; .same_site(SameSite::Strict)
// let mut tx = db.transaction().or(Err(MnmlHttpError::ServerError))?; .max_age(Duration::weeks(1)) // 1 week aligns with db set
// match account::from_token(&mut tx, t.value().to_string()) { .finish();
// Ok(a) => {
// account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?; let mut res = Response::with(status::Ok);
// tx.commit().or(Err(MnmlHttpError::ServerError))?; res.headers.set(SetCookie(vec![v.to_string()]));
// return Ok(logout_res());
// }, return res;
// Err(_) => Err(MnmlHttpError::Unauthorized), }
// }
// },
// None => Err(MnmlHttpError::Unauthorized),
// }
// }
#[derive(Debug,Clone,Deserialize)] #[derive(Debug,Clone,Deserialize)]
@ -118,7 +137,7 @@ fn register(req: &mut Request) -> IronResult<Response> {
match account::create(&params.name, &params.password, &params.code, &mut tx) { match account::create(&params.name, &params.password, &params.code, &mut tx) {
Ok(token) => { Ok(token) => {
tx.commit().or(Err(MnmlHttpError::ServerError))?; tx.commit().or(Err(MnmlHttpError::ServerError))?;
Ok(login_res(token)) Ok(token_res(token))
}, },
Err(e) => { Err(e) => {
warn!("{:?}", e); warn!("{:?}", e);
@ -133,19 +152,6 @@ struct LoginBody {
password: String, password: String,
} }
fn login_res(token: String) -> Response {
let v = Cookie::build(TOKEN_HEADER, token)
.http_only(true)
.same_site(SameSite::Strict)
.max_age(Duration::weeks(1)) // 1 week aligns with db set
.finish();
let mut res = Response::with(status::Ok);
res.headers.set(SetCookie(vec![v.to_string()]));
return res;
}
fn login(req: &mut Request) -> IronResult<Response> { fn login(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap(); let state = req.get::<Read<State>>().unwrap();
let params = match req.get::<bodyparser::Struct<LoginBody>>() { let params = match req.get::<bodyparser::Struct<LoginBody>>() {
@ -160,7 +166,7 @@ fn login(req: &mut Request) -> IronResult<Response> {
Ok(a) => { Ok(a) => {
let token = account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?; let token = account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?;
tx.commit().or(Err(MnmlHttpError::ServerError))?; tx.commit().or(Err(MnmlHttpError::ServerError))?;
Ok(login_res(token)) Ok(token_res(token))
}, },
Err(e) => { Err(e) => {
warn!("{:?}", e); warn!("{:?}", e);
@ -169,6 +175,27 @@ fn login(req: &mut Request) -> IronResult<Response> {
} }
} }
fn logout(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
match req.extensions.get::<account::Account>() {
Some(a) => {
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?;
tx.commit().or(Err(MnmlHttpError::ServerError))?;
let mut res = Response::with(status::Ok);
res.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()]));
Ok(res)
},
None => Err(IronError::from(MnmlHttpError::Unauthorized)),
}
}
const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10; const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10;
pub struct State { pub struct State {
@ -181,10 +208,13 @@ impl Key for State { type Value = State; }
pub fn start(pool: PgPool) { pub fn start(pool: PgPool) {
let mut router = Router::new(); let mut router = Router::new();
router.post("/api/login", login, "login"); router.post("/api/login", login, "login");
router.post("/api/logout", logout, "logout");
router.post("/api/register", register, "register");
let mut chain = Chain::new(router); let mut chain = Chain::new(router);
chain.link(Read::<State>::both(State { pool })); chain.link(Read::<State>::both(State { pool }));
chain.link_before(Read::<bodyparser::MaxBodyLength>::one(MAX_BODY_LENGTH)); chain.link_before(Read::<bodyparser::MaxBodyLength>::one(MAX_BODY_LENGTH));
chain.link_before(AuthMiddleware);
chain.link_after(ErrorHandler); chain.link_after(ErrorHandler);
Iron::new(chain).http("127.0.0.1:40000").unwrap(); Iron::new(chain).http("127.0.0.1:40000").unwrap();
} }

View File

@ -35,6 +35,7 @@ pub fn start(pool: PgPool) {
let (acc_s, acc_r) = unbounded(); let (acc_s, acc_r) = unbounded();
// search through the ws request for the auth cookie
let cb = |req: &Request| { let cb = |req: &Request| {
let err = || ErrorResponse { let err = || ErrorResponse {
error_code: StatusCode::FORBIDDEN, error_code: StatusCode::FORBIDDEN,
@ -50,7 +51,6 @@ pub fn start(pool: PgPool) {
// got auth token // got auth token
if cookie.name() == TOKEN_HEADER { if cookie.name() == TOKEN_HEADER {
info!("{:?}", cookie.value().to_string());
acc_s.send(Some(cookie.value().to_string())).or(Err(err()))?; acc_s.send(Some(cookie.value().to_string())).or(Err(err()))?;
} }
}; };
@ -61,6 +61,7 @@ pub fn start(pool: PgPool) {
let mut websocket = accept_hdr(stream.unwrap(), cb).unwrap(); let mut websocket = accept_hdr(stream.unwrap(), cb).unwrap();
// get a copy of the account
let account = match acc_r.recv().unwrap() { let account = match acc_r.recv().unwrap() {
Some(t) => { Some(t) => {
let db = ws_pool.get() let db = ws_pool.get()