use axum::http::{header::{HeaderValue},uri::Uri}; use html_minifier::HTMLMinifier; use std::{ error::Error, collections::HashMap, str, io::{ErrorKind, Result}, }; use tera::Context; use totp_rs::{Algorithm, TOTP, Secret}; use rand::Rng; use crate::{ defs::{ AppDBs, ReqHeaderMap, Local, TraceData, TraceContent, TraceLogContent, AuthState, Random, TOKEN_AUTH_VALUE, TOKEN_KEY_VALUE, }, users::{ UserRole, User, }, }; use super::{CLAIM_UID, CLAIM_AUTH, CLAIM_APP_KEY}; pub struct ReqHandler<'a> { pub req_header: ReqHeaderMap, pub app_dbs: &'a AppDBs, pub lang: Local, pub uri: &'a Uri, pub auth_state: AuthState, pub random: &'a Random, pub req_name: String, pub context: Context, } impl<'a> ReqHandler<'a> { pub fn new(req_header: ReqHeaderMap, app_dbs: &'a AppDBs, uri: &'a Uri, auth_state: &'a AuthState, random: &'a Random, req_name: &str) -> Self { let curr_lang = req_header.lang(&app_dbs.config); let lang = Local::get_lang(&app_dbs.config.locales,&curr_lang, &app_dbs.config.dflt_lang); let mut context = app_dbs.context.to_owned(); context.insert("site_name",&app_dbs.config.name); context.insert("site_org", &app_dbs.config.org); context.insert("lang", &lang.itms); context.insert("req_name", &req_name); context.insert("main_url", &format!("{}://{}",&app_dbs.config.protocol, &app_dbs.config.hostport)); context.insert("html_url", &app_dbs.config.html_url); context.insert("assets_url", &app_dbs.config.assets_url); context.insert("req_path", uri.path()); context.insert("css_link", &app_dbs.config.ui.css_link); context.insert("js_link", &app_dbs.config.ui.js_link); context.insert("other_css_link", &app_dbs.config.ui.other_css_link); context.insert("other_js_link", &app_dbs.config.ui.other_js_link); context.insert("main_js_link", &app_dbs.config.ui.main_js_link); context.insert("utils_js_link", &app_dbs.config.ui.utils_js_link); context.insert("web_menu_items", &app_dbs.config.ui.web_menu_items); context.insert("usr_roles", &auth_state.user_roles()); let user_items = User::hash_items(&auth_state.user_items()); context.insert("usr_items", &user_items); context.insert("usr_name", &auth_state.user_name()); if auth_state.is_admin() { context.insert("isadmin", "TRUE"); } context.insert("usr_email", &auth_state.user_email()); context.insert("session_expire", &app_dbs.config.session_expire); context.insert("update_session_item", &format!("{}://{}/update_item", &app_dbs.config.protocol, &app_dbs.config.hostport)); let user_id = auth_state.id(); let sid = if user_id.is_empty() || user_id == "No ID" { String::from("") } else { user_id.replace("-", "").to_owned() }; context.insert("session_id",&sid); context.insert("signup_mode",&app_dbs.config.signup_mode); ReqHandler { req_header, app_dbs, lang, uri, auth_state: auth_state.to_owned(), random, req_name: req_name.to_owned(), context, } } pub fn prepare_response(&mut self) { // self.req_header.header.contains_key(axum::http::header::CONTENT_TYPE) if self.req_header.is_browser() || self.req_header.is_wget() { self.req_header.header.append(axum::http::header::CONTENT_TYPE, HeaderValue::try_from("text/html; charset=utf-8") .expect("URI isn't a valid header value") ); } } pub fn render_template(&mut self,template_file: &str, dflt_content: &str) -> String { self.context.insert("page", &template_file.replace(".j2","").replace("html/", "").as_str()); if template_file.contains("md") || template_file.contains("sample") || template_file.contains("code") { self.context.insert("with_code", "true"); } match self.app_dbs.tera.render(&template_file, &self.context) { Ok(s) => { if self.req_header.is_browser() { let mut html_minifier = HTMLMinifier::new(); match html_minifier.digest(s.as_str()) { Ok(_) => { let Ok(result) = str::from_utf8(html_minifier.get_html()) else { return s }; // let result = str::from_utf8(html_minifier.get_html()).unwrap_or(s); result.to_owned() }, Err(e) => { println!("Error minifier: {}",e); s }, } } else { s } }, Err(e) => { println!("Error: {}", &e); let mut cause = e.source(); while let Some(e) = cause { println!("Reason: {}", e); cause = e.source(); } dflt_content.to_owned() } } } #[allow(dead_code)] pub fn bad_request(&mut self,msg: &str) -> String { if let Some(tpl) = self.app_dbs.config.tpls.get("notfound") { self.render_template(tpl, msg) } else { msg.to_owned() } } #[allow(dead_code)] pub fn auth_role(&self) -> UserRole { #[cfg(feature = "authstore")] if let Some(authz) = self.app_dbs.auth_store.authz.get(&self.notify_data.auth) { println!("aut: {}, name: {}, role: {}",&self.notify_data.auth, &authz.name,&authz.role); authz.role.to_owned() } else { UserRole::Anonymous } #[cfg(feature = "casbin")] UserRole::Anonymous } #[allow(dead_code)] pub fn get_lang(&self, key: &str) -> Local { Local::get_lang(&self.app_dbs.config.locales, key, &self.app_dbs.config.dflt_lang) } pub fn new_token(&self) -> String { if self.app_dbs.config.use_token { let data_claim = HashMap::from([ (String::from(CLAIM_UID), self.auth_state.user_id()), (String::from(CLAIM_AUTH), TOKEN_AUTH_VALUE.to_owned()), (String::from(CLAIM_APP_KEY), TOKEN_KEY_VALUE.to_owned()), ]); let expire = false; self.app_dbs.config.paseto.generate_token("", &data_claim, expire).unwrap_or_else(|e|{ eprintln!("Error generating token: {}", e); String::from("") }) } else { let mut u128_pool = [0u8; 16]; match self.random.lock() { Ok(mut r) => r.fill(&mut u128_pool), Err(e) => println!("Error random: {}",e), } u128::from_le_bytes(u128_pool).to_string() } } pub fn otp_make(&self, code: &str, defs: &str) -> Result { let secret = match Secret::Encoded(code.to_owned()).to_bytes() { Ok(scrt) => scrt, Err(e) => return Err(std::io::Error::new( ErrorKind::NotFound, format!("ERROR: Invalid Secret {}",&e) )) }; let (digits, str_algorithm) = if defs.is_empty() { ( self.app_dbs.config.totp_digits, format!("{}",self.app_dbs.config.totp_algorithm) ) } else { let arr_defs = defs.split(",").collect::>(); if arr_defs.len() > 1 { ( arr_defs[0].parse().unwrap_or(self.app_dbs.config.totp_digits), arr_defs[1].to_owned() ) } else { ( self.app_dbs.config.totp_digits, format!("{}",self.app_dbs.config.totp_algorithm) ) } }; let algorithm = match str_algorithm.to_uppercase().as_str() { "SHA1" => Algorithm::SHA1, "SHA256" => Algorithm::SHA256, "SHA512" => Algorithm::SHA512, _ => Algorithm::SHA1, }; let totp = match TOTP::new( algorithm, digits, 1, 30, secret, Some(format!("{}",self.app_dbs.config.name)), format!("{}",self.app_dbs.config.org) ) { Ok(totp) => totp, Err(e) => return Err(std::io::Error::new( ErrorKind::NotFound, format!("ERROR: Invalid Topt {}",&e) )) }; Ok(totp) } pub fn otp_generate(&self) -> Result { let mut rng = rand::thread_rng(); let data_byte: [u8; 21] = rng.gen(); let base32_string = base32::encode(base32::Alphabet::RFC4648 { padding: false }, &data_byte); match self.otp_make(&base32_string, "") { Ok(totp) => Ok(totp), Err(e) => Err(std::io::Error::new( ErrorKind::NotFound, format!("ERROR: Invalid TOTP {}",&e) )) } } pub fn otp_check(&self, code: &str, token: &str, defs: &str) -> Result { let totp = match self.otp_make(code, defs) { Ok(totp) => totp, Err(e) => return Err(std::io::Error::new( ErrorKind::NotFound, format!("ERROR: Invalid TOTP {}",&e) )) }; match totp.check_current(&token) { Ok(result) => Ok(result), Err(e) => return Err(std::io::Error::new( ErrorKind::NotFound, format!("ERROR: check {}",&e) )) } } pub fn trace_req(&self, info: String) -> Result<()> { let timestamp = chrono::Utc::now().timestamp().to_string(); let user_id = self.auth_state.user_id(); let trace_content = TraceContent{ when: timestamp.to_owned(), sid: self.auth_state.id(), origin: self.uri.path().to_owned(), trigger: String::from("req_handler"), id: user_id.to_owned(), info: info.to_owned(), context: self.req_name.to_owned(), role: self.auth_state.user_roles(), req: self.req_header.req_info(), }; let trace_data = TraceData{ user_id, timestamp, contents: vec![trace_content] }; trace_data.save(&self.app_dbs.config, false) } pub fn logs(&self, userid: &str, human: bool, reverse: bool) -> Vec { let timestamp = chrono::Utc::now().timestamp().to_string(); let trace_data = TraceData{ user_id: format!("{}",userid), timestamp, contents: Vec::new() }; trace_data.load(&self.app_dbs.config, human, reverse) } }