From 41025a52fe3d2e4988296bfdc1ef549b60b8b667 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sat, 17 Jun 2017 20:08:31 -0400 Subject: Add login / logout functionality. Adds the ability to log in, but doesn't yet authenticate against the database. --- src/lib.rs | 2 + src/render/mod.rs | 183 +++++++++++++++++++++++++++++++----------------------- src/server.rs | 106 ++++++++++++++++++++++++------- 3 files changed, 189 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index e165a1e..3fd5b67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ extern crate env_logger; extern crate iron_sessionstorage; extern crate staticfile; extern crate regex; +extern crate params; +extern crate persistent; pub mod systemd; pub mod render; diff --git a/src/render/mod.rs b/src/render/mod.rs index a824bd6..8575e9b 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -3,103 +3,128 @@ use systemd::unit; use horrorshow::prelude::*; use horrorshow::Raw; -fn render_in_page<'a>(content: Box) -> String { - (html!{ - : Raw(""); - html { - head { - title: "Systemhttpd"; - link(rel="stylesheet", type="text/css", href="/static/main.css"); +#[derive(Debug)] +pub struct Renderer { + pub user: Option +} + +impl Renderer { + fn render_in_page<'a>(&self, content: Box) -> String { + info!("Rendering page with context: {:?}", self); + let login_box: Box = match self.user { + Some(ref user) => box_html!{ + : user; + : " ("; + a(href="logout") { // TODO get base url from context + : "log out" + } + : ")"; + }, + None => box_html! { + a(href="login") { // TODO Get base url from context + : "Login" + } } - body { - nav { - ul { - li { - p { : "SystemHttpd" } + }; + + (html!{ + : Raw(""); + html { + head { + title: "Systemhttpd"; + link(rel="stylesheet", type="text/css", href="/static/main.css"); + } + body { + nav { + ul { + li { + p { : "SystemHttpd" } + } } - } - ul(class="right") { - li { - p { : "Login" } + ul(class="right") { + li { + p { + : login_box + } + } } } - } - main { - : content + main { + : content + } } } - } - }).into_string().unwrap() -} + }).into_string().unwrap() + } -pub fn login_page() -> String { - render_in_page(box_html! { - h1 { : "Log in" } - form(method="post") { - p { : "Username" } - input(type="text", name="username") {} - p { : "Password" } - input(type="text", name="password") {} - p {} - input(type="submit", value="Log in") {} - } - }) -} + pub fn login_page(&self) -> String { + self.render_in_page(box_html! { + h1 { : "Log in" } + form(method="post") { + p { : "Username" } + input(type="text", name="username") {} + p { : "Password" } + input(type="text", name="password") {} + p {} + input(type="submit", value="Log in") {} + } + }) + } -fn unit_table<'a>(units: &'a [&unit::Unit]) -> Box { - fn render_unit<'a>(unit: &'a unit::Unit) -> Box { - box_html! { - tr { - td { - a(href=format_args!("/status/{}", &unit.name)) { - : &unit.name + fn unit_table<'a>(&self, units: &'a [&unit::Unit]) -> Box { + fn render_unit<'a>(unit: &'a unit::Unit) -> Box { + box_html! { + tr { + td { + a(href=format_args!("/status/{}", &unit.name)) { + : &unit.name + } + } + td { + : format_args!("{} ({})", + &unit.active_state, + &unit.sub_state) } - } - td { - : format_args!("{} ({})", - &unit.active_state, - &unit.sub_state) } } } - } - box_html! { - table { - tr { - th { - : "Unit" + box_html! { + table { + tr { + th { + : "Unit" + } + th { + : "Active" + } } - th { - : "Active" + @ for unit in units { + : render_unit(unit) } } - @ for unit in units { - : render_unit(unit) - } } } -} -pub fn system_status(sections: &[(String, Vec<&unit::Unit>)]) -> String { - let b = - box_html! { - @ for &(ref type_, ref units) in sections { - h1 { - : type_ - } - : unit_table(&units) - } - }; - render_in_page(b) -} + pub fn system_status(&self, sections: &[(String, Vec<&unit::Unit>)]) + -> String { + self.render_in_page(box_html! { + @ for &(ref type_, ref units) in sections { + h1 { + : type_ + } + : self.unit_table(&units) + } + }) + } -pub fn unit_status(unit: &unit::Unit, log: &str) -> String { - render_in_page(box_html! { - h1 { :&unit.name } - p { : format_args!("{} ({})", &unit.active_state, &unit.sub_state) } - pre { - : log - } - }) + pub fn unit_status(&self, unit: &unit::Unit, log: &str) -> String { + self.render_in_page(box_html! { + h1 { :&unit.name } + p { : format_args!("{} ({})", &unit.active_state, &unit.sub_state) } + pre { + : log + } + }) + } } diff --git a/src/server.rs b/src/server.rs index f34489f..2ab4f75 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,19 +1,31 @@ -extern crate iron_sessionstorage; -use iron_sessionstorage::traits::*; +use iron; +use iron::error::{HttpResult}; +use iron::headers::ContentType; +use iron::modifiers::{Header, Redirect}; +use iron::status; +use iron::{Iron, IronError, Request, IronResult, Response, Chain, Listening, Plugin, Url}; use iron_sessionstorage::SessionStorage; use iron_sessionstorage::backends::SignedCookieBackend; -use iron::status; -use iron::modifiers::Header; -use iron::headers::ContentType; -use iron::{Iron, IronError, Request, IronResult, Response, Chain, Listening}; -use iron::error::{HttpResult}; -use router::Router; -use systemd::unit; -use systemd::journal; +use iron_sessionstorage::traits::*; +use iron_sessionstorage; +use params::{Params, Value}; +use regex::Regex; use render; +use router::Router; use staticfile::Static; -use regex::Regex; +use systemd::journal; +use systemd::unit; +use persistent::Read; +#[derive(Clone, Default, Debug)] +struct Context { + base_url: String +} +impl iron::typemap::Key for Context { + type Value = Context; +} + +#[derive(Default, Debug, Clone)] struct Login { user: String, } @@ -52,9 +64,10 @@ fn overview(r: &mut Request) -> IronResult { // info!("Updating session value. Current value: {}", session_value.0); // session_value.0.push('a'); // try!(r.session().set(session_value)); + let renderer = make_renderer(r)?; Ok(Response::with((status::Ok, Header(ContentType::html()), - render::system_status(&units_by_section)))) + renderer.system_status(&units_by_section)))) } fn journal(r: &mut Request) -> IronResult { @@ -77,47 +90,94 @@ fn unit_status(r: &mut Request) -> IronResult { let unit_name = iexpect!(r.extensions .get::() .unwrap() - .find("unit"), status::BadRequest); + .find("unit"), status::BadRequest).to_string(); let re = Regex::new(r"[-_\w\d]*").unwrap(); - if !re.is_match(unit_name) { + if !re.is_match(&unit_name) { return Ok(Response::with( (status::BadRequest, format!("Unit ({}) does not match {}", unit_name, re)))); } - let ref unit = itry!(unit::get_units(unit_name))[0]; - let log = itry!(journal::get_log(unit_name, 15)); + let ref unit = itry!(unit::get_units(&unit_name))[0]; + let log = itry!(journal::get_log(&unit_name, 15)); + let renderer = make_renderer(r)?; Ok(Response::with((status::Ok, Header(ContentType::html()), - render::unit_status(&unit, &log)))) + renderer.unit_status(&unit, &log)))) +} + +fn get_logged_in_user(r: &mut Request) -> IronResult> { + let login = r.session().get::()?; + // Session storage doesn't have a way to delete its cookie, + // so we set the username to empty on logout. + if let &Some(Login { ref user }) = &login { + if user.is_empty() { + return Ok(None) + } + } + Ok(login) } fn login(r: &mut Request) -> IronResult { + let renderer = make_renderer(r)?; Ok(Response::with((status::Ok, Header(ContentType::html()), - render::login_page()))) + renderer.login_page()))) } fn login_submit(r: &mut Request) -> IronResult { - Ok(Response::with((status::Ok, - Header(ContentType::plaintext()), - "login"))) - // TODO: Need this to get params: - // https://github.com/iron/params + let login = { + let map = r.get_ref::().unwrap(); + let user = match map.get("username") { + Some(&Value::String(ref v)) => v, + _ => panic!("no username in params: {:?}", map) + }; + let password = match map.get("password") { + Some(&Value::String(ref v)) => v, + _ => panic!("no password in params: {:?}", map) + }; + Login { user: user.clone() } + }; + + info!("User logged in: {:?}", login); + r.session().set(login)?; + let url = Url::parse("http://localhost:8080/").unwrap(); + Ok(Response::with((status::Found, + Redirect(url)))) +} + +fn logout(r: &mut Request) -> IronResult { + r.session().set::(Default::default()); + let url = Url::parse("http://localhost:8080/").unwrap(); + Ok(Response::with((status::Found, + Redirect(url)))) +} + +fn make_renderer(r: &mut Request) -> IronResult { + let user = get_logged_in_user(r)?.map(|u| u.user); + Ok(render::Renderer { + user: user + }) } pub fn serve(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, + 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)); let bind_address = format!("{}:{}", "::", port); Iron::new(chain).http(bind_address.as_str()) } -- cgit v1.2.3