summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYves Fischer <yvesf-git@xapek.org>2018-11-26 12:33:37 +0100
committerYves Fischer <yvesf-git@xapek.org>2018-11-26 12:36:07 +0100
commit63636d00ca56ee37f9cb9db3fece81d615e21a1a (patch)
tree970f86c8ed2abd32d6af964b0f7cfb34f9bd8d45
parent20242a8d3cc2e9a70812f34fcc50c170a654f6c6 (diff)
downloadnginx-auth-totp-63636d00ca56ee37f9cb9db3fece81d615e21a1a.tar.gz
nginx-auth-totp-63636d00ca56ee37f9cb9db3fece81d615e21a1a.zip
Refactor html views
-rw-r--r--src/auth_handler/handler_info.rs30
-rw-r--r--src/auth_handler/handler_login.rs142
-rw-r--r--src/auth_handler/mod.rs170
-rw-r--r--src/auth_handler/urls.rs18
-rw-r--r--src/cookie_store.rs2
-rw-r--r--src/main.rs15
-rw-r--r--src/request_handler/handler_login.rs71
-rw-r--r--src/request_handler/mod.rs170
-rw-r--r--src/request_handler/views.rs138
9 files changed, 391 insertions, 365 deletions
diff --git a/src/auth_handler/handler_info.rs b/src/auth_handler/handler_info.rs
deleted file mode 100644
index 0aeaa09..0000000
--- a/src/auth_handler/handler_info.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use std::io;
-
-use tokio::prelude::*;
-
-use http::{Request, Response, StatusCode};
-use bytes::Bytes;
-
-use ::ApplicationState;
-use super::AuthHandler;
-
-pub(in super) fn respond<'a>(auth_handler: &AuthHandler, state: &ApplicationState, req: &Request<Bytes>,
- path_rest: &'a str) -> Response<String> {
- let body = html! {
- : horrorshow::helper::doctype::HTML;
- html {
- head {
- title: "Hello world!";
- }
- body {
- h1(id = "heading") {
- : "Hello! This is <html />";
- : "And path rest is: ";
- : path_rest;
- : "... ok :)";
- }
- }
- }
- };
- Response::builder().body(body.to_string()).unwrap()
-} \ No newline at end of file
diff --git a/src/auth_handler/handler_login.rs b/src/auth_handler/handler_login.rs
deleted file mode 100644
index 83d5214..0000000
--- a/src/auth_handler/handler_login.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-use std::io;
-use std::borrow::Cow;
-
-use tokio::prelude::*;
-
-use http::{Request, Response, StatusCode, Method};
-use http::header::{SET_COOKIE, COOKIE};
-use url::form_urlencoded;
-
-use ::ApplicationState;
-use ::totp;
-use super::*;
-
-
-pub(in super) fn GET<'a>(header_infos: &HeaderExtract, state: &ApplicationState, path_rest: &'a str)
- -> Response<String> {
- let body = if is_logged_in(&header_infos.cookies, &state.cookie_store) {
- format!("{}", html! {
- : horrorshow::helper::doctype::HTML;
- html {
- head {
- title: "TOTP Login";
- }
- body {
- h1(id = "heading") {
- : "Currently logged in"
- }
- }
- }
- })
- } else {
- format!("{}", html! {
- : horrorshow::helper::doctype::HTML;
- html {
- head {
- title: "TOTP Login";
- }
- body {
- h1(id = "heading") {
- : "Login"
- }
- form(method="POST") {
- label(for="token") {
- : "Enter TOTP token"
- }
- input(name="token",id="token",type="text");
- input(name="redirect", type="hidden", value=path_rest);
- input(name="send",type="submit",value="Submit");
- }
- }
- }
- })
- };
- make_response(StatusCode::OK, format!("{}", body))
-}
-
-fn test_secrets(secrets: &Vec<&str>, token: &String) -> bool {
- secrets.iter()
- .any(|secret| {
- match totp::verify(secret, token) {
- Ok(true) => true,
- Ok(false) => false,
- Err(e) => {
- error!("Error from totp::verify: {}", e);
- false
- }
- }
- })
-}
-
-pub(in super) fn POST<'a>(header_infos: &HeaderExtract, state: &ApplicationState, req: &Request<Bytes>)
- -> Response<String> {
- let mut token = None;
- let mut redirect = None;
- for (key, val) in form_urlencoded::parse(req.body()) {
- if key == "token" {
- token = Some(val.into_owned())
- } else if key == "redirect" {
- redirect = Some(val.into_owned())
- }
- }
- if token.is_none() {
- return error_handler_internal("missing argument 'token'".to_string());
- }
- let redirect = redirect.unwrap_or(Default::default());
-
- if header_infos.totp_secrets.is_empty() {
- return error_handler_internal("no secrets configured".to_string())
- }
-
- let mut ret = Response::builder();
- let body = if test_secrets(&header_infos.totp_secrets, &token.unwrap()) {
- let cookie_value = state.cookie_store.create_authenticated_cookie();
- let cookie = CookieBuilder::new(COOKIE_NAME, cookie_value.to_string())
- .http_only(true)
- .path("/")
- .max_age(state.cookie_max_age)
- .finish();
- ret.header(SET_COOKIE, cookie.to_string());
- warn!("Authenticated user with cookie {}", cookie);
- format!("{}", html! {
- : horrorshow::helper::doctype::HTML;
- html {
- head {
- title: "TOTP Successful";
- meta(http-equiv="refresh", content=format!("3; URL={}", redirect))
- }
- body {
- h1(id = "heading") {
- : "Login succesful"
- }
- a(href="login") {
- : "Try again... redirecting to ";
- }
- span {
- : format!("{}", redirect)
- }
- }
- }
- })
- } else {
- format!("{}", html! {
- : horrorshow::helper::doctype::HTML;
- html {
- head {
- title: "TOTP Login failed";
- meta(http-equiv="refresh", content="1")
- }
- body {
- h1(id = "heading") {
- : "Login failed"
- }
- a(href="login") {
- : "Try again... "
- }
- }
- }
- })
- };
-
- ret.body(body).unwrap()
-} \ No newline at end of file
diff --git a/src/auth_handler/mod.rs b/src/auth_handler/mod.rs
deleted file mode 100644
index b4e02d1..0000000
--- a/src/auth_handler/mod.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-#![allow(warnings)]
-
-use std::cell::Cell;
-use std::collections::HashMap;
-use std::io;
-use std::marker::Sync;
-use std::str;
-use std::str::FromStr;
-use std::sync::{Arc, RwLock, Mutex, MutexGuard};
-use std::cell::RefCell;
-
-use time::{Tm, Duration};
-use http::{Request, Response, StatusCode, Method};
-use tokio::prelude::*;
-use horrorshow;
-use cookie::{Cookie,CookieBuilder};
-use bytes::Bytes;
-
-use router;
-use cookie_store::CookieStore;
-use cookie_store::to_cookie;
-use http_server::HttpHandler;
-
-mod urls;
-mod handler_login;
-mod handler_info;
-
-pub(in auth_handler) struct HeaderExtract<'a> {
- totp_secrets: Vec<&'a str>,
- cookies: Vec<Cookie<'a>>,
-}
-
-pub struct HeaderMissing {
- name: &'static str,
-}
-
-static HTTP_HEADER_AUTHORIZATION: &'static str = r"Authorization";
-static HTTP_HEADER_X_ORIGINAL_URL: &'static str = r"X-Original-Url";
-static HTTP_HEADER_WWW_AUTHENTICATE: &'static str = r"WWW-Authenticate";
-static HTTP_HEADER_X_TOTP_SECRET: &'static str = r"X-Totp-Secret";
-static COOKIE_NAME: &'static str = r"totp_cookie";
-
-#[derive(Clone)]
-pub struct AuthHandler {
- routing_table: router::RoutingTable<urls::Route>,
-}
-
-pub(in auth_handler) fn make_response(code: StatusCode, body: String) -> Response<String> {
- Response::builder().status(code).body(body).unwrap()
-}
-
-pub(in auth_handler) fn error_handler_internal(body: String) -> Response<String> {
- Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(body).unwrap()
-}
-
-impl HttpHandler<super::ApplicationState> for AuthHandler {
- fn respond(&self, state: &super::ApplicationState, req: Request<Bytes>) -> Response<String> {
- match self.routing_table.match_path(req.uri().path()) {
- Ok((urls::Route::Info, rest)) => handler_info::respond(self, state, &req, rest),
- Ok((urls::Route::Login, rest)) => self.login(state, &req, rest),
- Ok((urls::Route::Logout, rest)) => self.logout(state, &req, rest),
- Ok((urls::Route::Check, rest)) => self.check(state, &req, rest),
- Err(error) => match error {
- router::NoMatchingRoute =>
- make_response(StatusCode::NOT_FOUND, "Resource not found".to_string()),
- }
- }
- }
-}
-
-pub fn is_logged_in(cookies: &Vec<Cookie>, cookie_store: &CookieStore) -> bool {
- for cookie in cookies {
- if cookie.name() == COOKIE_NAME {
- let cookie_value = to_cookie(cookie.value());
- if cookie_value.is_some() && cookie_store.is_cookie_authenticated(&cookie_value.unwrap()) {
- return true;
- }
- }
- }
- false
-}
-
-
-impl AuthHandler {
- pub fn make() -> AuthHandler {
- AuthHandler { routing_table: urls::create_routing_table() }
- }
-
- fn login<'a>(&self, state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str,
- ) -> Response<String> {
- let header_infos = match Self::parse_header_infos(req) {
- Ok(infos) => infos,
- Err(message) => return error_handler_internal(message),
- };
- match *req.method() {
- Method::GET => handler_login::GET(&header_infos, state, path_rest),
- Method::POST => handler_login::POST(&header_infos, state, req),
- _ => error_handler_internal("Wrong method".to_string()),
- }
- }
-
- fn logout<'a>(&self, state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str,
- ) -> Response<String> {
- let header_infos = match Self::parse_header_infos(req) {
- Ok(infos) => infos,
- Err(message) => return error_handler_internal(message),
- };
-
- let body = format!("Rest: {}", path_rest);
- Response::builder().body(body.to_string()).unwrap()
- }
-
-
- fn check<'a>(&self, state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str) -> Response<String> {
- let header_infos = match Self::parse_header_infos(req) {
- Ok(infos) => infos,
- Err(message) => return error_handler_internal(message),
- };
- if is_logged_in(&header_infos.cookies, &state.cookie_store) {
- make_response(StatusCode::OK, "".to_string())
- } else {
- make_response(StatusCode::UNAUTHORIZED, "Cookie expired".to_string())
- }
- }
-
-
- fn parse_header_infos(req: &Request<Bytes>) -> Result<HeaderExtract, String> {
- let mut totp_secrets = Vec::new();
- for header_value in req.headers().get_all(HTTP_HEADER_X_TOTP_SECRET) {
- let value = header_value.to_str().or(Err("Failed to read totp-secret header value"))?;
- totp_secrets.push(value);
- }
-
- let mut cookies = Vec::new();
- for header_value in req.headers().get_all(::http::header::COOKIE) {
- let value = header_value.to_str().or(Err("Failed to read cookie value"))?;
- let cookie = Cookie::parse(value).or(Err("Failed to parse cookie value"))?;
- cookies.push(cookie);
- }
-
- Ok(HeaderExtract { totp_secrets, cookies })
- }
-}
-
-#[cfg(test)]
-mod test1 {
- // use super::*;
- use test::Bencher;
- // use horrorshow::prelude::*;
- use horrorshow::helper::doctype;
-
- #[bench]
- fn bench_1(_: &mut Bencher) {
- let _ = format!("{}", html! {
-: doctype::HTML;
-html {
-head {
-title: "Hello world!";
-}
-body {
-// attributes
-h1(id = "heading") {
-// Insert escaped text
-: "Hello! This is <html />"
-}
-}
-}
-});
- }
-}
diff --git a/src/auth_handler/urls.rs b/src/auth_handler/urls.rs
deleted file mode 100644
index fa7c76f..0000000
--- a/src/auth_handler/urls.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use ::router;
-
-#[derive(Clone, Copy)]
-pub(in auth_handler) enum Route {
- Login,
- Logout,
- Info,
- Check
-}
-
-pub(in auth_handler) fn create_routing_table() -> router::RoutingTable<Route> {
- let mut r = router::RoutingTable::new();
- r.insert("/info", Route::Info);
- r.insert("/login", Route::Login);
- r.insert("/logout", Route::Logout);
- r.insert("/check", Route::Check);
- r
-} \ No newline at end of file
diff --git a/src/cookie_store.rs b/src/cookie_store.rs
index a77bb14..66586b6 100644
--- a/src/cookie_store.rs
+++ b/src/cookie_store.rs
@@ -112,7 +112,7 @@ impl CookieStore {
let reader = &self.reader;
let value = reader.get_and(key, |v| v[0]);
- warn!("Reading {} -> {:?}", key.to_string(), value);
+ debug!("Reading {} -> {:?}", key.to_string(), value);
if value.is_none() {
false
} else if value.unwrap() < Self::now_unix_epoch() {
diff --git a/src/main.rs b/src/main.rs
index 2d5218f..a025e69 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,7 +36,7 @@ use futures::{Future, Stream};
use tokio_threadpool::Builder;
use tokio_executor::enter;
-mod auth_handler;
+mod request_handler;
mod cookie_store;
mod http_server;
mod router;
@@ -49,6 +49,7 @@ use cookie_store::CookieStore;
pub struct ApplicationState {
cookie_store: CookieStore,
cookie_max_age: Duration,
+ debug: bool,
}
#[derive(Debug, StructOpt)]
@@ -64,8 +65,14 @@ fn main() {
let opt = Opt::from_args();
simple_logger::init_with_level(if opt.debug { Debug } else { Warn })
.unwrap_or_else(|_| panic!("Failed to initialize logger"));
+ debug!("If you read this message then we're running debug (-d) mode.");
+ debug!("Debug mode is not safe for public accesible instances");
- let state = ApplicationState { cookie_store: CookieStore::new(), cookie_max_age: Duration::days(1) };
+ let state = ApplicationState {
+ cookie_store: CookieStore::new(),
+ cookie_max_age: Duration::days(1),
+ debug: opt.debug,
+ };
let server_shutdown_condvar = Arc::new(atomic::AtomicBool::new(false));
@@ -82,7 +89,7 @@ fn main() {
})
};
- let auth_handler = auth_handler::AuthHandler::make();
+ let request_handler = request_handler::RequestHandler::make();
let runtime = Builder::new()
.name_prefix("httpd-")
.after_start(|| {
@@ -91,7 +98,7 @@ fn main() {
})
.build();
- let program = http_server::serve(opt.addr, state, auth_handler);
+ let program = http_server::serve(opt.addr, state, request_handler);
runtime.spawn(program);
let ctrl_c_block = tokio_signal::ctrl_c()
diff --git a/src/request_handler/handler_login.rs b/src/request_handler/handler_login.rs
new file mode 100644
index 0000000..bfa7016
--- /dev/null
+++ b/src/request_handler/handler_login.rs
@@ -0,0 +1,71 @@
+use std::io;
+use std::borrow::Cow;
+
+use tokio::prelude::*;
+
+use http::{Request, Response, StatusCode, Method};
+use http::header::{SET_COOKIE, COOKIE};
+use url::form_urlencoded;
+
+use ::ApplicationState;
+use ::totp;
+use super::*;
+
+pub(in super) fn GET<'a>(header_infos: &HeaderExtract, state: &ApplicationState, path_rest: &'a str)
+ -> Response<String> {
+ if is_logged_in(&header_infos.cookies, &state.cookie_store) {
+ make_response(StatusCode::OK, views::login_is_logged_in())
+ } else {
+ make_response(StatusCode::OK, views::login_login_form(path_rest))
+ }
+}
+
+fn test_secrets(secrets: &Vec<&str>, token: &String) -> bool {
+ secrets.iter()
+ .any(|secret| {
+ match totp::verify(secret, token) {
+ Ok(true) => true,
+ Ok(false) => false,
+ Err(e) => {
+ error!("Error from totp::verify: {}", e);
+ false
+ }
+ }
+ })
+}
+
+pub(in super) fn POST<'a>(header_infos: &HeaderExtract, state: &ApplicationState, req: &Request<Bytes>)
+ -> Response<String> {
+ let mut token = None;
+ let mut redirect = None;
+ for (key, val) in form_urlencoded::parse(req.body()) {
+ if key == "token" {
+ token = Some(val.into_owned())
+ } else if key == "redirect" {
+ redirect = Some(val.into_owned())
+ }
+ }
+ if token.is_none() {
+ return error_handler_internal("missing argument 'token'".to_string());
+ }
+ let redirect = redirect.unwrap_or(Default::default());
+
+ if header_infos.totp_secrets.is_empty() {
+ return error_handler_internal("no secrets configured".to_string())
+ }
+
+ let mut ret = Response::builder();
+ if test_secrets(&header_infos.totp_secrets, &token.unwrap()) {
+ let cookie_value = state.cookie_store.create_authenticated_cookie();
+ let cookie = CookieBuilder::new(COOKIE_NAME, cookie_value.to_string())
+ .http_only(true)
+ .path("/")
+ .max_age(state.cookie_max_age)
+ .finish();
+ ret.header(SET_COOKIE, cookie.to_string());
+ warn!("Authenticated user with cookie {}", cookie);
+ ret.body(views::login_auth_success(&redirect)).unwrap()
+ } else {
+ ret.body(views::login_auth_fail()).unwrap()
+ }
+} \ No newline at end of file
diff --git a/src/request_handler/mod.rs b/src/request_handler/mod.rs
new file mode 100644
index 0000000..4a01af6
--- /dev/null
+++ b/src/request_handler/mod.rs
@@ -0,0 +1,170 @@
+#![allow(warnings)]
+
+use std::cell::Cell;
+use std::collections::HashMap;
+use std::io;
+use std::marker::Sync;
+use std::str;
+use std::str::FromStr;
+use std::sync::{Arc, RwLock, Mutex, MutexGuard};
+use std::cell::RefCell;
+
+
+use time;
+use http::{Request, Response, StatusCode, Method};
+use tokio::prelude::*;
+use horrorshow;
+use cookie::{Cookie, CookieBuilder};
+use bytes::Bytes;
+
+use router;
+use cookie_store::CookieStore;
+use cookie_store::to_cookie;
+use http_server::HttpHandler;
+
+mod handler_login;
+mod views;
+
+#[derive(Clone, Copy)]
+enum Route {
+ Login,
+ Logout,
+ Info,
+ Check,
+}
+
+fn create_routing_table() -> router::RoutingTable<Route> {
+ let mut r = router::RoutingTable::new();
+ r.insert("/info", Route::Info);
+ r.insert("/login", Route::Login);
+ r.insert("/logout", Route::Logout);
+ r.insert("/check", Route::Check);
+ r
+}
+
+struct HeaderExtract<'a> {
+ totp_secrets: Vec<&'a str>,
+ cookies: Vec<Cookie<'a>>,
+}
+
+static HTTP_HEADER_X_TOTP_SECRET: &'static str = r"X-Totp-Secret";
+static COOKIE_NAME: &'static str = r"totp_cookie";
+
+#[derive(Clone)]
+pub struct RequestHandler {
+ routing_table: router::RoutingTable<Route>,
+}
+
+pub(in request_handler) fn make_response(code: StatusCode, body: String) -> Response<String> {
+ Response::builder().status(code).body(body).unwrap()
+}
+
+pub(in request_handler) fn error_handler_internal(body: String) -> Response<String> {
+ Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(body).unwrap()
+}
+
+impl HttpHandler<super::ApplicationState> for RequestHandler {
+ fn respond(&self, state: &super::ApplicationState, req: Request<Bytes>) -> Response<String> {
+ match self.routing_table.match_path(req.uri().path()) {
+ Ok((Route::Info, rest)) => info(self, state, &req, rest),
+ Ok((Route::Login, rest)) => login(state, &req, rest),
+ Ok((Route::Logout, rest)) => logout(state, &req, rest),
+ Ok((Route::Check, rest)) => check(state, &req, rest),
+ Err(error) => match error {
+ router::NoMatchingRoute =>
+ make_response(StatusCode::NOT_FOUND, "Resource not found".to_string()),
+ }
+ }
+ }
+}
+
+impl RequestHandler {
+ pub fn make() -> RequestHandler {
+ RequestHandler { routing_table: create_routing_table() }
+ }
+}
+
+pub(in request_handler) fn is_logged_in(cookies: &Vec<Cookie>, cookie_store: &CookieStore) -> bool {
+ for cookie in cookies {
+ if cookie.name() == COOKIE_NAME {
+ let cookie_value = to_cookie(cookie.value());
+ if cookie_value.is_some() && cookie_store.is_cookie_authenticated(&cookie_value.unwrap()) {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn info<'a>(request_handler: &RequestHandler, state: &super::ApplicationState,
+ req: &Request<Bytes>, path_rest: &'a str) -> Response<String> {
+ let ftime = |ts| -> String {
+ let ts = time::Timespec::new(ts, 0);
+ let tm = time::at_utc(ts);
+ time::strftime("%c", &tm).unwrap_or("</>".to_string())
+ };
+ let view = if state.debug {
+ let valid_cookies: Vec<(String, String)> = state.cookie_store.reader
+ .map_into(|k, v|
+ (k.to_string(), ftime(v[0] as i64)));
+ views::info_debug(path_rest, valid_cookies)
+ } else {
+ views::info(path_rest)
+ };
+ Response::builder().body(view).unwrap()
+}
+
+fn login<'a>(state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str,
+) -> Response<String> {
+ let header_infos = match parse_header_infos(req) {
+ Ok(infos) => infos,
+ Err(message) => return error_handler_internal(message),
+ };
+ match *req.method() {
+ Method::GET => handler_login::GET(&header_infos, state, path_rest),
+ Method::POST => handler_login::POST(&header_infos, state, req),
+ _ => error_handler_internal("Wrong method".to_string()),
+ }
+}
+
+fn logout<'a>(state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str,
+) -> Response<String> {
+ let header_infos = match parse_header_infos(req) {
+ Ok(infos) => infos,
+ Err(message) => return error_handler_internal(message),
+ };
+
+ let body = format!("Rest: {}", path_rest);
+ Response::builder().body(body.to_string()).unwrap()
+}
+
+
+fn check<'a>(state: &super::ApplicationState, req: &Request<Bytes>, path_rest: &'a str) -> Response<String> {
+ let header_infos = match parse_header_infos(req) {
+ Ok(infos) => infos,
+ Err(message) => return error_handler_internal(message),
+ };
+ if is_logged_in(&header_infos.cookies, &state.cookie_store) {
+ make_response(StatusCode::OK, "".to_string())
+ } else {
+ make_response(StatusCode::UNAUTHORIZED, "Cookie expired".to_string())
+ }
+}
+
+
+fn parse_header_infos(req: &Request<Bytes>) -> Result<HeaderExtract, String> {
+ let mut totp_secrets = Vec::new();
+ for header_value in req.headers().get_all(HTTP_HEADER_X_TOTP_SECRET) {
+ let value = header_value.to_str().or(Err("Failed to read totp-secret header value"))?;
+ totp_secrets.push(value);
+ }
+
+ let mut cookies = Vec::new();
+ for header_value in req.headers().get_all(::http::header::COOKIE) {
+ let value = header_value.to_str().or(Err("Failed to read cookie value"))?;
+ let cookie = Cookie::parse(value).or(Err("Failed to parse cookie value"))?;
+ cookies.push(cookie);
+ }
+
+ Ok(HeaderExtract { totp_secrets, cookies })
+}
diff --git a/src/request_handler/views.rs b/src/request_handler/views.rs
new file mode 100644
index 0000000..ee020eb
--- /dev/null
+++ b/src/request_handler/views.rs
@@ -0,0 +1,138 @@
+use horrorshow::Template;
+
+pub(in super) fn info_debug<'a>(path_rest: &'a str, cookies: Vec<(String, String)>) -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "Hello world!";
+ }
+ body {
+ h1(id = "heading") {
+ : "Hello! This is <html />";
+ : "And path rest is: ";
+ : path_rest;
+ : "... ok :)";
+ }
+ h2: "Valid cookies are:";
+ table(border="1") {
+ thead {
+ th: "Cookie value";
+ th: "Valid until";
+ }
+ tbody {
+ @ for (name, valid_until) in cookies {
+ tr {
+ td: name;
+ td: valid_until;
+ }
+ }
+ }
+ }
+ }
+ }
+ }).into_string().unwrap()
+}
+
+pub(in super) fn info<'a>(path_rest: &'a str) -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "Hello world!";
+ }
+ body {
+ h1(id = "heading") {
+ : "Hello! This is <html />";
+ : "And path rest is: ";
+ : path_rest;
+ : "... ok :)";
+ }
+ }
+ }
+ }).into_string().unwrap()
+}
+
+pub(in super) fn login_is_logged_in() -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "TOTP Login";
+ }
+ body {
+ h1(id = "heading") {
+ : "Currently logged in"
+ }
+ }
+ }
+ }).into_string().unwrap()
+}
+
+pub(in super) fn login_login_form<'a>(redirect: &'a str) -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "TOTP Login";
+ }
+ body {
+ h1(id = "heading") {
+ : "Login"
+ }
+ form(method="POST") {
+ label(for="token") {
+ : "Enter TOTP token"
+ }
+ input(name="token",id="token",type="text");
+ input(name="redirect", type="hidden", value=redirect);
+ input(name="send",type="submit",value="Submit");
+ }
+ }
+ }
+ }).into_string().unwrap()
+}
+
+pub(in super) fn login_auth_success(redirect: &String) -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "TOTP Successful";
+ meta(http-equiv="refresh", content=format!("3; URL={}", redirect))
+ }
+ body {
+ h1(id = "heading") {
+ : "Login succesful"
+ }
+ a(href=redirect) {
+ : "Try again... redirecting to ";
+ }
+ span {
+ : format!("{}", redirect)
+ }
+ }
+ }
+ }).into_string().unwrap()
+}
+
+
+pub(in super) fn login_auth_fail() -> String {
+ (html! {
+ : horrorshow::helper::doctype::HTML;
+ html {
+ head {
+ title: "TOTP Login failed";
+ meta(http-equiv="refresh", content="1")
+ }
+ body {
+ h1(id = "heading") {
+ : "Login failed"
+ }
+ a(href="login") {
+ : "Try again... "
+ }
+ }
+ }
+ }).into_string().unwrap()
+} \ No newline at end of file