From 1e5f237c20310cbf6ace17b6a7b05298429aca46 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sat, 17 Jun 2017 22:23:39 -0400 Subject: feature: Working login with authentication. --- src/bin/main.rs | 35 ++++++++++++++++++++--------------- src/db.rs | 14 +++++++++++++- src/render/mod.rs | 8 +++++++- src/server.rs | 53 +++++++++++++++++++++++++++++++++-------------------- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index b07a524..ed9e179 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -31,6 +31,21 @@ fn create_user_prompt() -> Option<(String, String)> { Some((user.trim().to_string(), password)) } +fn create_admin_user(conn: &rusqlite::Connection) { + info!("Create admin user"); + if let Some((user, password)) = create_user_prompt() { + let enc = auth::encode("test_salt", password.as_str()); + systemhttp::db::insert_user( + &conn, user.as_str(), + &enc).expect("create user"); + } +} + +fn serve(context: systemhttp::server::Context, port: u16) { + let _server = systemhttp::server::serve(context, port).unwrap(); + println!("Serving on {}", port); +} + fn main() { let matches = App::new("systemhttpd") .version("0.1") @@ -62,24 +77,14 @@ fn main() { .expect(format!("opening sqlite database at {}", db_file).as_str()); systemhttp::db::init(&mut conn); - let mut serve = || { - let _server = systemhttp::server::serve(port).unwrap(); - println!("Serving on {}", port); - }; - - let mut create_admin_user = || { - info!("Create admin user"); - if let Some((user, password)) = create_user_prompt() { - let enc = auth::encode("test_salt", password.as_str()); - systemhttp::db::insert_user( - &mut conn, user.as_str(), - &enc).expect("create user"); - } + let mut context = systemhttp::server::Context { + base_url: "http://localhost:8080".to_string(), + conn: conn }; match matches.subcommand_name() { - Some("serve") => serve(), - Some("create_admin_user") => create_admin_user(), + Some("serve") => serve(context, port), + Some("create_admin_user") => create_admin_user(&context.conn), x => panic!("Don't know about subcommand: {:?}", x) } } diff --git a/src/db.rs b/src/db.rs index e9a5d1f..ba4025f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,9 +2,12 @@ use rusqlite::{Connection}; use std; use auth::HashedPassword; +// TODO Replace the unwraps in this file with a custom error type. + type Result = std::result::Result; fn is_initialized(conn: &mut Connection) -> Result { + // We just initialize every time for now. Ok(false) } @@ -21,7 +24,7 @@ pub fn init(conn: &mut Connection) -> Result<()> { Ok(()) } -pub fn insert_user(conn: &mut Connection, +pub fn insert_user(conn: &Connection, username: &str, password: &HashedPassword) -> Result<()> { conn.execute("INSERT INTO users (username, salt, passwd) @@ -29,3 +32,12 @@ pub fn insert_user(conn: &mut Connection, &[&username, &password.salt, &password.enc]).unwrap(); Ok(()) } + +pub fn lookup_user(conn: &Connection, + username: &str) -> Result> { + let mut stmt = conn.prepare("SELECT salt, passwd FROM users WHERE username = ?").unwrap(); + let result = stmt.query_map(&[&username], |row| { + HashedPassword {salt: row.get(0), enc: row.get(1)} + }).unwrap().map(|v| v.unwrap()).next(); + Ok(result) +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 8575e9b..fe0053a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -13,6 +13,7 @@ impl Renderer { info!("Rendering page with context: {:?}", self); let login_box: Box = match self.user { Some(ref user) => box_html!{ + : "Logged in as "; : user; : " ("; a(href="logout") { // TODO get base url from context @@ -57,9 +58,14 @@ impl Renderer { }).into_string().unwrap() } - pub fn login_page(&self) -> String { + pub fn login_page(&self, is_retry: bool) -> String { self.render_in_page(box_html! { h1 { : "Log in" } + @ if is_retry { + p { + : "Incorrect username or password. Try again." + } + } form(method="post") { p { : "Username" } input(type="text", name="username") {} diff --git a/src/server.rs b/src/server.rs index 2ab4f75..3a03ea0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,11 +15,15 @@ use router::Router; use staticfile::Static; use systemd::journal; use systemd::unit; -use persistent::Read; - -#[derive(Clone, Default, Debug)] -struct Context { - base_url: String +use persistent::Write; +use rusqlite::Connection; +use db; +use auth; + +#[derive(Debug)] +pub struct Context { + pub base_url: String, + pub conn: Connection } impl iron::typemap::Key for Context { type Value = Context; @@ -120,13 +124,14 @@ fn get_logged_in_user(r: &mut Request) -> IronResult> { fn login(r: &mut Request) -> IronResult { let renderer = make_renderer(r)?; + let is_retry = r.method == iron::method::Method::Post; Ok(Response::with((status::Ok, Header(ContentType::html()), - renderer.login_page()))) + renderer.login_page(is_retry)))) } -fn login_submit(r: &mut Request) -> IronResult { - let login = { +fn authenticate(r: &mut Request) -> IronResult { + let (user, password) = { let map = r.get_ref::().unwrap(); let user = match map.get("username") { Some(&Value::String(ref v)) => v, @@ -136,14 +141,25 @@ fn login_submit(r: &mut Request) -> IronResult { Some(&Value::String(ref v)) => v, _ => panic!("no password in params: {:?}", map) }; - Login { user: user.clone() } + (user.to_string(), password.to_string()) }; - info!("User logged in: {:?}", login); - r.session().set(login)?; - let url = Url::parse("http://localhost:8080/").unwrap(); - Ok(Response::with((status::Found, - Redirect(url)))) + let hash = { + let mutex = r.get::>().unwrap(); + let context = mutex.lock().unwrap(); + db::lookup_user(&context.conn, &user).unwrap() + }; + + if let Some(true) = hash.map(|h| auth::validate(&password, &h)) { + let login = Login{ user: user.to_string() }; // TODO Make a validated login type + info!("User logged in: {:?}", login); + r.session().set(login)?; + let url = Url::parse("http://localhost:8080/").unwrap(); + Ok(Response::with((status::Found, + Redirect(url)))) + } else { + login(r) + } } fn logout(r: &mut Request) -> IronResult { @@ -160,24 +176,21 @@ fn make_renderer(r: &mut Request) -> IronResult { }) } -pub fn serve(port: u16) -> HttpResult { +pub fn serve(context: Context, port: u16) -> HttpResult { // TODO: Use a real secret. let secret = b"secret2".to_vec(); let router = router!( root: get "/" => overview, login: get "/login" => login, - login_submit: post "/login" => login_submit, + authenticate: post "/login" => authenticate, logout: get "/logout" => logout, details: get "/status/:unit" => unit_status, journal: get "/journal/:unit" => journal, css: get "/static/main.css" => Static::new(""), ); let mut chain = Chain::new(router); - let context = Context { - base_url: String::from("http://localhost:8080/"), - }; chain.link_around(SessionStorage::new(SignedCookieBackend::new(secret))); - chain.link(Read::::both(context)); + chain.link(Write::::both(context)); let bind_address = format!("{}:{}", "::", port); Iron::new(chain).http(bind_address.as_str()) } -- cgit v1.2.3