use tera::Context;
use std::{
path::PathBuf,
collections::HashMap,
sync::RwLock,
};
use hyper::{
client::HttpConnector,
header,
header::COOKIE,
service::{make_service_fn, service_fn},
Body,
Client,
Method,
Request,
Response,
Server,
StatusCode,
};
use lazy_static::*;
use regex::Regex;
use serde_derive::Serialize;
use url::form_urlencoded;
use ::log::*;
use crate::utils;
use crate::registration;
use crate::login;
type GenericError = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, GenericError>;
static NOTFOUND: &[u8] = b"Oops! Not Found";
#[derive(Debug, Serialize)]
pub struct User {
email: String,
logged_in: bool,
}
lazy_static!{
static ref SESSIONS: RwLock<HashMap<String, User>> = {
let map = HashMap::new();
RwLock::new(map)
};
}
fn add_user_into_session(email: &str, logged_in: bool) -> String {
let user = User { email: email.to_string(), logged_in: logged_in };
let id = utils::generate_id();
let mut sessions = SESSIONS.write().unwrap();
debug!("add user: {:?} with session id: {:?}", &user, &id);
sessions.insert(id.clone(), user);
id
}
fn get_user(id: &str) -> User {
let sessions = SESSIONS.read().unwrap();
match sessions.get(id) {
Some(user) => User{ email: user.email.clone(), logged_in: user.logged_in },
None => User{email: String::new(), logged_in: false}
}
}
fn update_user(id: String, logged_in: bool) {
let mut user = get_user(&id);
user.logged_in = logged_in;
let mut sessions = SESSIONS.write().unwrap();
sessions.remove(&id);
sessions.insert(id, user);
}
fn four_zero_four() -> Result<Response<Body>> {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(NOTFOUND.into())
.unwrap())
}
async fn index(req: Request<Body>) -> Result<Response<Body>> {
debug!("index( req )...............<<");
let sid = get_session_id(&req);
let user = get_user(&sid);
let mut ctx = Context::new();
ctx.insert("user", &user);
let body = Body::from(super::TERA.render("index.html", &ctx).unwrap().to_string());
Ok(Response::new(body))
}
async fn registration() -> Result<Response<Body>> {
debug!("registration()...............<<");
let ctx = Context::new();
let body = Body::from(super::TERA.render("registration.html", &ctx).unwrap().to_string());
Ok(Response::new(body))
}
async fn add_registration_await_confirmation(req: Request<Body>) -> Result<Response<Body>> {
debug!("add_registration_await_confirmation( req )............<<");
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let body_for_next_page = process_registration_data( &map );
Ok(Response::new(body_for_next_page))
}
fn process_registration_data(map: &HashMap<String, String>) -> Body {
debug!("process_registration_data( {:?} )............<<", map);
let result = registration::Registration::from_map(map);
let email = map.get("email").unwrap();
let mut ctx = Context::new();
let duration = super::app_config("token_validity_time");
let unit = super::app_config("token_validity_unit");
ctx.insert("duration", &duration);
ctx.insert("unit", &unit);
ctx.insert("email", &email);
match result {
Ok(_msg) => {
let delay = delay_into_millis(&duration, &unit);
registration::Registration::wait_to_remove_unconfirmed(delay, email.to_string());
Body::from(super::TERA.render("confirm-registration.html", &ctx).unwrap().to_string())
},
Err(msg) => {
let err_msg = format!("Registration failed: {:?}", msg);
Body::from(err_msg)
}
}
}
fn delay_into_millis(duration: &str, unit: &str) -> u64 {
let delay: u64 = duration.parse().unwrap();
match unit.as_ref() {
"minutes" => delay * 60 * 1000,
"seconds" => delay * 1000,
_ => delay,
}
}
async fn handle_registration_confirmation(req: Request<Body>) -> Result<Response<Body>> {
debug!("handle_registration_confirmation({:?})..............<<", &req);
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let body_for_next_page = process_confirmation_data( &map );
Ok(Response::new(body_for_next_page))
}
fn process_confirmation_data(map: &HashMap<String, String>) -> Body {
debug!("process_confirmation_data ( {:?} ) ..............<<", map);
let mut ctx = Context::new();
if map.contains_key("expired") {
let email = map.get("email").unwrap();
let _result = registration::Registration::delete_expired_registration(&email);
ctx.insert("status", "Registration expired! Sorry try to register again!");
ctx.insert("show_login_link", &false);
return Body::from(super::TERA.render("confirm-registration-status.html", &ctx).unwrap().to_string());
}
let token = map.get("token").unwrap();
let result = registration::Registration::confirm(&token);
match result {
Ok(_msg) => {
ctx.insert("status", "Registration confirmed!.");
ctx.insert("show_login_link", &true);
Body::from(super::TERA.render("confirm-registration-status.html", &ctx).unwrap().to_string())
},
Err(msg) => {
let err_msg = format!("Registration confirmation failed: {:?}", msg);
Body::from(err_msg)
}
}
}
async fn login() -> Result<Response<Body>> {
debug!("login().....................<<");
let mut ctx = Context::new();
ctx.insert("has_error", &false);
let body = Body::from(super::TERA.render("login.html", &ctx).unwrap().to_string());
Ok(Response::new(body))
}
async fn login_submitted(req: Request<Body>) -> Result<Response<Body>> {
debug!("login_submitted( {:?} )....................<<", &req);
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let response = process_login_data( &map );
Ok(response)
}
fn process_login_data(map: &HashMap<String, String>) -> Response<Body> {
debug!("process_login_data( {:?} )...................<<", map);
let email = map.get("email").unwrap();
debug!("email entered as: {:?}", &email);
let passwd = map.get("password").unwrap();
debug!("password entered as: {:?}", &passwd);
match login::Login::authenticate(&email, &passwd) {
Ok(msg) => {
debug!("authenciated successfully: {:?}", msg);
let logged_in = true;
let id = add_user_into_session(&email, logged_in);
login_success_page(&id)
},
Err(msg) => {
error!("authentication failed: {:?}", msg);
login_failed(&email)
},
}
}
fn login_success_page(id: &str) -> Response<Body> {
debug!("login_success_page( {:?} )....................<<", id);
let ctx = Context::new();
let body = Body::from(super::TERA.render("login-success.html", &ctx).unwrap().to_string());
let sid = "sessionId=".to_owned() + id + "; PATH=/";
Response::builder()
.status(StatusCode::OK)
.header(header::SET_COOKIE, sid)
.body(body)
.unwrap()
}
fn login_failed(email: &str) -> Response<Body> {
debug!("login_failed( {:?} ).................<<", email);
let mut ctx = Context::new();
ctx.insert("error", &"Invalid Credentials!");
ctx.insert("email", email);
ctx.insert("has_error", &true);
let body = Body::from(super::TERA.render("login.html", &ctx).unwrap().to_string());
Response::new(body)
}
async fn cancel_registration(req: Request<Body>) -> Result<Response<Body>> {
debug!("cancel_registration(req).........................<<");
let sid = get_session_id(&req);
let user = get_user(&sid);
let logged_in = false;
let _user = update_user(sid, logged_in);
let result = registration::Registration::cancel(&user.email);
let cancelled: bool = match result {
Ok(_msg) => true,
Err(msg) => {
debug!("cancel_registration has error: {:?}", msg);
false
},
};
let mut ctx = Context::new();
ctx.insert("cancelled", &cancelled);
let body = Body::from(super::TERA.render("cancellation-status.html", &ctx).unwrap().to_string());
let response = Response::builder()
.status(StatusCode::OK)
.body(body)
.unwrap();
Ok(response)
}
async fn forgot_password() -> Result<Response<Body>> {
debug!("forgot_password(req).................<<");
let mut ctx = Context::new();
ctx.insert("has_error", &false);
let body = Body::from(super::TERA.render("forgot-password.html", &ctx).unwrap().to_string());
Ok(Response::new(body))
}
async fn forgot_password_submitted(req: Request<Body>) -> Result<Response<Body>> {
debug!("forgot_password_submitted( req )...................<<");
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let body_for_next_page = process_forgot_password( &map );
Ok(Response::new(body_for_next_page))
}
fn process_forgot_password(map: &HashMap<String, String>) -> Body {
let email = map.get("email").unwrap();
let duration = super::app_config("token_validity_time");
let unit = super::app_config("token_validity_unit");
debug!("email entered as: {:?}", &email);
match login::Login::forgot_password(&email) {
Ok(msg) => {
debug!("Successfully processed forgot password: {:?}", msg);
let delay = delay_into_millis(&duration, &unit);
let mut ctx = Context::new();
ctx.insert("duration", &duration);
ctx.insert("unit", &unit);
ctx.insert("email", &email);
login::Login::wait_to_expire_forgot_password_token(delay, email.to_string());
Body::from(super::TERA.render("confirm-forgot-password.html", &ctx).unwrap().to_string())
},
Err(msg) => {
let error = format!("Email submitted failed due to: {:?}", &msg);
let mut ctx = Context::new();
ctx.insert("has_error", &true);
ctx.insert("email", &email);
ctx.insert("error", &error);
Body::from(super::TERA.render("forgot-password.html", &ctx).unwrap().to_string())
},
}
}
async fn handle_forgot_password_confirmation(req: Request<Body>) -> Result<Response<Body>> {
debug!("handle_forgot_password_confirmation(req)..............<<");
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let response = process_forgot_password_confirmation_data( &map );
Ok(response)
}
fn process_forgot_password_confirmation_data(map: &HashMap<String, String>) -> Response<Body> {
debug!("process_confirmation_data ( {:?} ) ..............<<", map);
let mut ctx = Context::new();
let email = map.get("email").unwrap();
if map.contains_key("expired") {
let _result = login::Login::forgot_password_expired(&email);
ctx.insert("status", "Forgot password token expired! Sorry try to submit it again!");
ctx.insert("show_login_link", &true);
let body = Body::from(super::TERA.render("confirm-forgot-password-failed.html", &ctx).unwrap().to_string());
Response::new(body)
} else {
let logged_in = false;
let id = add_user_into_session(&email, logged_in);
debug!("Session ID generated for forgot password: {:?} ***********************", &id);
let body = Body::from(super::TERA.render("confirm-forgot-password-success.html", &ctx).unwrap().to_string());
let sid = "sessionId=".to_owned() + &id + "; PATH=/";
Response::builder()
.status(StatusCode::OK)
.header(header::SET_COOKIE, sid)
.body(body)
.unwrap()
}
}
async fn reset_password() -> Result<Response<Body>> {
debug!("reset_password().................<<");
let mut ctx = Context::new();
ctx.insert("has_error", &false);
let body = Body::from(super::TERA.render("reset-password.html", &ctx).unwrap().to_string());
Ok(Response::new(body))
}
async fn reset_password_submitted(req: Request<Body>) -> Result<Response<Body>> {
debug!("reset_password_submitted(req)..............<<");
let sid = get_session_id(&req);
debug!("Session ID received at reset password: {:?} ~~~~~~~~~~~~~~~~~~~~~", &sid);
let user = get_user(&sid);
let whole_body = hyper::body::to_bytes(req).await?;
let map = form_urlencoded::parse(whole_body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
debug!("json map: {:?}", &map);
let body_for_next_page = process_reset_password( &map, user.email );
Ok(Response::new(body_for_next_page))
}
fn process_reset_password(map: &HashMap<String, String>, email: String) -> Body {
debug!("before calling Login::reset_password(map, email)+++++++++++++++++++++++++++");
let result = login::Login::reset_password(map, email.clone());
debug!("after calling Login::reset_password(map, email) with result: {:?} -------------------", &result);
let mut ctx = Context::new();
match result {
Ok(_msg) => {
ctx.insert("status", "Reset Password Completed!");
ctx.insert("show_login_link", &true);
Body::from(super::TERA.render("reset-password-status.html", &ctx).unwrap().to_string())
},
Err(messages) => {
error!("{:?} occurred in process_reset_password(map, email)----------------------", &messages);
ctx.insert("has_error", &true);
let err_msg = format!("Error(s): {:?}", messages);
ctx.insert("error", &err_msg);
Body::from(super::TERA.render("reset-password.html", &ctx).unwrap().to_string())
}
}
}
async fn logout(req: Request<Body>) -> Result<Response<Body>> {
debug!("logout(req).........................<<");
let sid = get_session_id(&req);
let logged_in = false;
let _user = update_user(sid, logged_in);
let ctx = Context::new();
let body = Body::from(super::TERA.render("logout-success.html", &ctx).unwrap().to_string());
let response = Response::builder()
.status(StatusCode::OK)
.body(body)
.unwrap();
Ok(response)
}
fn stylesheet(css: &'static str) -> Result<Response<Body>> {
let body = Body::from(css);
Ok(
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/css")
.body(body)
.unwrap(),
)
}
fn javascript(js: &'static str) -> Result<Response<Body>> {
let body = Body::from(js);
Ok(
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/javascript")
.body(body)
.unwrap(),
)
}
fn image(path_str: &str) -> Result<Response<Body>> {
let path_buf = PathBuf::from(path_str);
let file_name = path_buf.file_name().unwrap().to_str().unwrap();
let ext = path_buf.extension().unwrap().to_str().unwrap();
match ext {
"svg" => {
let body = {
let xml = match file_name {
"search.svg" => include_str!("resource/search.svg"),
_ => "",
};
Body::from(xml)
};
Ok(
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "image/svg+xml")
.body(body)
.unwrap(),
)
}
_ => four_zero_four(),
}
}
async fn router(req: Request<Body>, _client: Client<HttpConnector>) -> Result<Response<Body>> {
debug!("router( {:?} )......................<<", req.headers().get("host"));
let host: &str = req.headers().get("host").unwrap().to_str().unwrap();
let host_parts: Vec<&str> = host.split(|c| c == '.').collect();
debug!("host parts: {:?}", host_parts);
let sid = get_session_id(&req);
debug!("Extracted SESSION ID: {:?}", &sid);
debug!("Req uri path: {:?}", req.uri().path());
debug!("Req method: {:?}", req.method());
let uri_query = req.uri().query();
debug!("Req URI Query {:?}", &uri_query);
match(req.method(), req.uri().path()) {
(&Method::GET, "/") | (&Method::GET, "/index.html")
=> index(req).await,
(&Method::GET, "/registration")
=> registration().await,
(&Method::POST, "/registration")
=> add_registration_await_confirmation(req).await,
(&Method::POST, "/registration-confirm-process")
=> handle_registration_confirmation(req).await,
(&Method::GET, "/static/registration.css")
=> stylesheet(include_str!("resource/registration.css")),
(&Method::GET, "/static/registration_script.js")
=> javascript(include_str!("resource/registration_script.js")),
(&Method::GET, "/static/confirmation_script.js")
=> javascript(include_str!("resource/confirmation_script.js")),
(&Method::GET, "/login")
=> login().await,
(&Method::POST, "/login")
=> login_submitted(req).await,
(&Method::GET, "/cancel-my-registration")
=> cancel_registration(req).await,
(&Method::GET, "/forgot-password")
=> forgot_password().await,
(&Method::POST, "/forgot-password")
=> forgot_password_submitted(req).await,
(&Method::POST, "/forgot-password-confirm-process")
=> handle_forgot_password_confirmation(req).await,
(&Method::GET, "/reset-password")
=> reset_password().await,
(&Method::POST, "/reset-password")
=> reset_password_submitted(req).await,
(&Method::GET, "/static/login.css")
=> stylesheet(include_str!("resource/login.css")),
(&Method::GET, "/static/login_script.js")
=> javascript(include_str!("resource/login_script.js")),
(&Method::GET, "/logout")
=> logout(req).await,
(&Method::GET, "/static/logout.css")
=> stylesheet(include_str!("resource/logout.css")),
(&Method::GET, path_str)
=> image(path_str),
_ => four_zero_four(),
}
}
pub fn get_session_id(req: &Request<Body>) -> String {
debug!("get_session_id(req) .............................<<");
if !req.headers().contains_key(COOKIE) {
error!("No cookies in the header found!");
return String::new();
}
debug!("Req cookie: {:?}", req.headers().get("cookie"));
let cookies: &str = req.headers().get("cookie").unwrap().to_str().unwrap();
let re = Regex::new(r"(^|;)sessionId=(.*)($|;)");
let session_cookie = match re {
Ok(expression) => expression.find(cookies),
Err(error) => {
error!("error occured in finding session id from cookie string: {:?}", error);
None
}
};
debug!("session cookie: {:?}", &session_cookie);
match session_cookie {
Some(cookie) => {
let ids: Vec<&str> = cookie.as_str().split(|c| c == '=').collect();
ids[1].to_string()
},
None => String::new(),
}
}
#[tokio::main]
pub async fn start() -> Result<()> {
let client = Client::new();
let new_service = make_service_fn(move |_| {
let client = client.clone();
async {
Ok::<_, GenericError>(service_fn(move |req| {
router(req, client.to_owned())
}))
}
});
let ip_address = super::app_config("ip_address");
let socket_address = ip_address.as_str().parse().unwrap();
let server = Server::bind(&socket_address).serve(new_service);
println!("Listening on http://{}", socket_address);
server.await?;
Ok(())
}