summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2017-06-17 22:23:39 -0400
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2017-06-17 22:23:59 -0400
commit1e5f237c20310cbf6ace17b6a7b05298429aca46 (patch)
tree61c67dc5846cec4a79e960eb4e28afa1699272de /src
parent41025a52fe3d2e4988296bfdc1ef549b60b8b667 (diff)
feature: Working login with authentication.
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.rs35
-rw-r--r--src/db.rs14
-rw-r--r--src/render/mod.rs8
-rw-r--r--src/server.rs53
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<T> = std::result::Result<T, String>;
fn is_initialized(conn: &mut Connection) -> Result<bool> {
+ // 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<Option<HashedPassword>> {
+ 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<RenderBox> = 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<Option<Login>> {
fn login(r: &mut Request) -> IronResult<Response> {
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<Response> {
- let login = {
+fn authenticate(r: &mut Request) -> IronResult<Response> {
+ let (user, password) = {
let map = r.get_ref::<Params>().unwrap();
let user = match map.get("username") {
Some(&Value::String(ref v)) => v,
@@ -136,14 +141,25 @@ fn login_submit(r: &mut Request) -> IronResult<Response> {
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::<Write<Context>>().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<Response> {
@@ -160,24 +176,21 @@ fn make_renderer(r: &mut Request) -> IronResult<render::Renderer> {
})
}
-pub fn serve(port: u16) -> HttpResult<Listening> {
+pub fn serve(context: Context, port: u16) -> HttpResult<Listening> {
// 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::<Context>::both(context));
+ chain.link(Write::<Context>::both(context));
let bind_address = format!("{}:{}", "::", port);
Iron::new(chain).http(bind_address.as_str())
}