diff --git a/src/handlers.rs b/src/handlers.rs index bba8cf0..ba65aa7 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -2,10 +2,18 @@ mod users_handlers; mod other_handlers; mod pages_handlers; mod admin_handlers; +mod users; pub(crate) use users_handlers::users_router_handlers; pub(crate) use admin_handlers::admin_router_handlers; pub(crate) use pages_handlers::pages_router_handlers; +pub(crate) use users::{ + login_user, + users_login_router_handlers, + users_password_router_handlers, + users_settings_router_handlers, + users_invite_router_handlers, +}; pub(crate) use other_handlers::{ rewrite_request_uri, diff --git a/src/handlers/admin_handlers.rs b/src/handlers/admin_handlers.rs index 4191210..913643c 100644 --- a/src/handlers/admin_handlers.rs +++ b/src/handlers/admin_handlers.rs @@ -27,12 +27,15 @@ use crate::{ User, UserItem, UserStatus, + AllUserData, }, handlers::{ add_session_cookie, get_auth_state, }, }; +use crate::users::OpenidUser; + pub fn admin_router_handlers() -> Router { async fn users_handler( header: HeaderMap, @@ -55,7 +58,7 @@ pub fn admin_router_handlers() -> Router { let _ = req_handler.trace_req(format!("User: {} is not admin",auth_state.user_id())); return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); } - let usrs = match User::list(&app_dbs.user_store, true, false, "").await { + let users = match User::list(&app_dbs.user_store, true, false, "").await { Ok(data) => data, Err(e) => { let _ = req_handler.trace_req(format!("Error list users: {}",e)); @@ -63,8 +66,19 @@ pub fn admin_router_handlers() -> Router { Vec::new() }, }; - req_handler.context.insert("usrs", &usrs); - req_handler.context.insert("total_usrs", &usrs.len()); + req_handler.context.insert("users", &users); + let mut users_openids = Vec::new(); + for usr in users.iter() { + let openid_sel = OpenidUser::list_selection( + "userid", &usr.id.to_string(), &app_dbs.user_store, true,false, "|" + ).await.unwrap_or_else(|e| { + println!("Error list selection {}: {}", &usr.name, e); + Vec::new() + }); + users_openids.push(openid_sel.iter().map(|id| id.appkey.to_string()).collect::>().join(",")); + } + req_handler.context.insert("users_openids", &users_openids); + req_handler.context.insert("total_usrs", &users.len()); req_handler.context.insert("with_menu", "1"); let mut res_headers = HeaderMap::new(); if req_handler.req_header.is_browser() { @@ -138,13 +152,21 @@ pub fn admin_router_handlers() -> Router { } user_sel.password = String::from(""); user_sel.otp_base32 = String::from(""); - let result = serde_json::to_string(&user_sel).unwrap_or_else(|e|{ - let msg = format!("Error to convert user items to json: {}",e); - println!("{}", &msg); - let _ = req_handler.trace_req(msg); - String::from("") + let openid_sel = OpenidUser::list_selection( + "userid", &user_sel.id.to_string(), &app_dbs.user_store, true,false, "|" + ).await.unwrap_or_else(|e| { + println!("Error list selection {}: {}", &user_sel.name, e); + Vec::new() + }); + let user_openids = openid_sel.iter().map(|id| id.appkey.to_string()).collect::>().join(","); + let str_user = serde_json::to_string(&user_sel).unwrap_or_else(|e|{ + let msg = format!("Error to convert user data items to json: {}",e); + println!("{}", &msg); + let _ = req_handler.trace_req(msg); + String::from("") }); let _ = req_handler.trace_req(format!("User: {} get data", &user_sel.id)); + let result = format!("{{\"user\": {}, \"openids\": \"{}\"}}",str_user, user_openids); ( res_headers, result.to_owned() @@ -157,7 +179,7 @@ pub fn admin_router_handlers() -> Router { Extension(cookies): Extension, Extension(random): Extension, ConnectInfo(app_connect_info): ConnectInfo, - Json(new_user): Json, + Json(new_user): Json, ) -> Response { let auth_state = get_auth_state(true, &cookies, &app_dbs).await; let req_handler = ReqHandler::new( @@ -194,7 +216,7 @@ pub fn admin_router_handlers() -> Router { "Error" ).into_response(); } - let mut user_sel = User::select("id", &format!("{}",&new_user.id), false,&app_dbs.user_store).await.unwrap_or_default(); + let mut user_sel = User::select("id", &format!("{}",&new_user.user.id), false,&app_dbs.user_store).await.unwrap_or_default(); if user_sel.name.is_empty() { // User not exists return ( @@ -203,7 +225,8 @@ pub fn admin_router_handlers() -> Router { "Error" ).into_response(); } - user_sel.from_user(new_user); + let new_id = new_user.user.id; + user_sel.from_user(new_user.user); let user_data = user_sel.session_data(); if user_sel.update(&app_dbs.user_store).await.is_err() { return ( @@ -215,6 +238,7 @@ pub fn admin_router_handlers() -> Router { let session_token = req_handler.new_token(); let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + OpenidUser::sync_ids(&new_id.to_string(), &new_user.openids, &app_dbs.user_store).await; let result = String::from("Ok"); ( res_headers, @@ -277,9 +301,11 @@ pub fn admin_router_handlers() -> Router { "Error" ).into_response(); } + let result = match User::delete(user_sel.id,&app_dbs.user_store).await { Ok(val) => if val { let _ = req_handler.trace_req(format!("User '{}' delete",&user_id)); + OpenidUser::sync_ids(&user_sel.id.to_string(), "", &app_dbs.user_store).await; format!("Ok") } else { let _ = req_handler.trace_req(format!("Error delete user '{}'" ,&user_id)); diff --git a/src/handlers/login_user.rs b/src/handlers/login_user.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/handlers/users.rs b/src/handlers/users.rs new file mode 100644 index 0000000..1e16a04 --- /dev/null +++ b/src/handlers/users.rs @@ -0,0 +1,11 @@ +mod login_handlers; +mod password_handlers; +mod settings_handlers; +mod invite_handlers; +mod login; + +pub(crate) use login::login_user; +pub(crate) use login_handlers::users_login_router_handlers; +pub(crate) use password_handlers::users_password_router_handlers; +pub(crate) use invite_handlers::users_invite_router_handlers; +pub(crate) use settings_handlers::users_settings_router_handlers; diff --git a/src/handlers/users/invite_handlers.rs b/src/handlers/users/invite_handlers.rs new file mode 100644 index 0000000..4ecbd6f --- /dev/null +++ b/src/handlers/users/invite_handlers.rs @@ -0,0 +1,345 @@ +use std::sync::Arc; +use urlencoding::{encode,decode}; +use axum::{ + http::{ + StatusCode, + Uri, + header::HeaderMap, + }, + Json, + routing::{get,post}, + Extension, + extract::ConnectInfo, + response::{IntoResponse,Response,Redirect}, + Router, +}; +use tower_cookies::Cookies; + +use crate::{ + route, + defs::{ + AppDBs, + AuthState, + ReqHandler, + ReqHeaderMap, + MailMessage, + Random, + AppConnectInfo, + }, + users::{ + User, + UserStatus, + UserInvitation, + }, + handlers::{ + add_session_cookie, + get_auth_state, + }, +}; +pub fn users_invite_router_handlers() -> Router { + async fn invite_signup_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + axum::extract::Path(data): axum::extract::Path, + // auth_state: AuthState, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "invite_signup_handler" + ); + if ! app_dbs.config.signup_mode.contains("invitation") { + let msg = format!("Config signup mode not invitation: {}", + app_dbs.config.signup_mode + ); + println!("{}", &msg); + let _ = req_handler.trace_req(msg); + return Redirect::temporary( &format!("/")).into_response(); + } + // println!("root_handler: {}",&session_cookie); + let session_cookie = decode(&data).unwrap_or_default().to_string(); + let auth_state = AuthState::from_cookie(session_cookie.to_owned(), &app_dbs).await; + if auth_state.session.is_none() { + // TODO make it prettier + let _ = req_handler.trace_req(format!("No session found")); + return ( + StatusCode::NOT_FOUND, + req_handler.req_header.header, + "No valid invitation found" + ).into_response(); + } + //let user_data = auth_state.user_data(); + let mut usr = User::default(); + usr.roles = auth_state.user_roles(); + usr.email = auth_state.user_email(); + let usr_id = auth_state.user_id(); + let invite_id = if usr_id == "0" { + usr_id + } else { + usr.email.to_owned() + }; + // let _uri_path = format!("{}",&uri.path().to_string()); + // let file = "hello.html"; + // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ + // println!("Error render {}: {}",&file,e); + // String::from("") + // }); + + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + req_handler.context.insert("user", &usr); + req_handler.context.insert("isadmin", ""); + req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); + req_handler.context.insert("invite_key", &data); + req_handler.context.insert("invite_id", &invite_id); + req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); + let result = if let Some(tpl) = app_dbs.config.tpls.get("signup") { + req_handler.render_template(&tpl,"signup") + } else { + String::from("signup") + }; + let _ = req_handler.trace_req(format!("Invite to '{}' data: {}",&invite_id, &data)); + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn post_invite_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_invite): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_invite_handler" + ); + if ! app_dbs.config.signup_mode.contains("invitation") { + let msg = format!("Config signup mode not invitation: {}", + app_dbs.config.signup_mode + ); + println!("{}", &msg); + let _ = req_handler.trace_req(msg); + return Redirect::temporary( &format!("/")).into_response(); + } + if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { + let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + // return ( + // StatusCode::UNAUTHORIZED, + // header, + // "Error authorization" + // ).into_response(); + } + if ! user_invite.email.contains("@") { + let _ = req_handler.trace_req(format!("Invite email '{}' not contains '@'",&user_invite.email)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error invitation data" + ).into_response(); + } + let session_token = req_handler.new_token(); + let mut usr = User::default(); + usr.email = user_invite.email.to_owned(); + usr.roles = user_invite.roles.to_owned(); + usr.isadmin = user_invite.isadmin; + usr.status = UserStatus::Pending; + let user_data = usr.session_data(); + let session_cookie = add_session_cookie(false,&cookies, &session_token, &user_data, user_invite.expire, &app_dbs, "invitation").await; + let session_encoded_key = encode(session_cookie.as_str()); + let body=format!("This is an invitation to docserver service"); + let subject = format!("DocServer Invitation"); + let signup_url= format!( + "{}://{}/signup", + &app_dbs.config.protocol, + &app_dbs.config.hostport, + ); + let invite_expiration = format!("{} minutes", + (&app_dbs.config.session_expire/60) + ); + req_handler.context.insert("signup_url", &signup_url); + req_handler.context.insert("invite_expiration", &invite_expiration); + req_handler.context.insert("email_body",&body); + req_handler.context.insert("invite_key",&session_encoded_key); + req_handler.context.insert("email_subject",&subject); + let (status, result) = if app_dbs.config.use_mail && user_invite.send_email { + let mail_content= if let Some(tpl) = app_dbs.config.tpls.get("invite_mail_txt") { + req_handler.render_template(tpl, "invite") + } else { + format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) + }; + let mail_html_content = if let Some(tpl) = app_dbs.config.tpls.get("invite_mail_html") { + req_handler.render_template(tpl, "invite") + } else { + format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) + }; + let mail_check = MailMessage::check(&app_dbs); + if ! mail_check.is_empty() { + ( + StatusCode::BAD_REQUEST, + mail_check + ) + } else { + //"jesus.perezlorenzo@gmail.com", + // "jesus@librecloud.online", + match MailMessage::new( + &app_dbs.config.mail_from, + &user_invite.email, + &app_dbs.config.mail_reply_to, + ) { + Ok(mail_message) => { + //match mail_message.send_message( + match mail_message.send_html_message( + &subject, + &mail_content, + &mail_html_content, + &app_dbs + ).await { + Ok(_) => { + let _ = req_handler.trace_req(format!( + "Invitation mail sent to: '{}' reset url: {} roles: {}, isadmin: {}, expiration: {} cookie: {}", + &user_invite.email, &invite_expiration, user_invite.roles, user_invite.isadmin, &signup_url,&session_cookie + )); + (StatusCode::OK, format!("Mail sent to {}",&user_invite.email)) + }, + Err(e) => { + println!("Invitation to: {} Error mail message send: {}",&user_invite.email, e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + String::from("Error") + ) + } + } + }, + Err(e) => { + println!("Invitation to: {} Error mail message creation: {}",&user_invite.email, e); + ( + StatusCode::BAD_REQUEST, + String::from("Error") + ) + } + } + } + } else { + let _ = req_handler.trace_req(format!( + "Created invitation: '{}' reset url: {} roles: {}, isadmin: {}, expiration: {} cookie: {}", + &user_invite.email, &invite_expiration, user_invite.roles, user_invite.isadmin, &signup_url,&session_cookie + )); + (StatusCode::OK, format!("No mail sent to {}",&user_invite.email)) + }; + req_handler.prepare_response(); + req_handler.context.insert("email_result",&result); + let response = if let Some(tpl) = app_dbs.config.tpls.get("invite_output") { + req_handler.render_template(tpl, "invite") + } else { + format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) + }; + ( + status, + req_handler.req_header.header, + response, + ).into_response() + } + async fn invite_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "invite_handler" + ); + if ! app_dbs.config.signup_mode.contains("invitation") { + let msg = format!("Config signup mode not invitation: {}", + app_dbs.config.signup_mode + ); + println!("{}",&msg); + let _ = req_handler.trace_req(msg); + return Redirect::temporary( &format!("/")).into_response(); + } + if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { + let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + // return ( + // StatusCode::UNAUTHORIZED, + // header, + // "Error authorization" + // ).into_response(); + } + + let title = format!("DocServer Invitation"); + let invite_url= format!( + "{}://{}/invite", + &app_dbs.config.protocol, + &app_dbs.config.hostport, + ); + let invite_expire = format!("{} minutes", + (&app_dbs.config.invite_expire/60) + ); + req_handler.context.insert("target_url", &invite_url); + req_handler.context.insert("invite_expire", &invite_expire); + if app_dbs.config.use_mail { + req_handler.context.insert("use_mail", &app_dbs.config.use_mail); + } + req_handler.prepare_response(); + req_handler.context.insert("with_menu", "1"); + let response = if let Some(tpl) = app_dbs.config.tpls.get("invite_create") { + req_handler.render_template(tpl, "invite create") + } else { + format!("{} invite",&title) + }; + let _ = req_handler.trace_req(format!( + "Invitation: url: {}, expiration: {}", + &invite_url, &invite_expire, + )); + ( + req_handler.req_header.header, + response, + ).into_response() + // let uri_path = format!("{}",&uri.path().to_string()); + // let file = "hello.html"; + // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ + // println!("Error render {}: {}",&file,e); + // String::from("") + // }); + // let mut new_header = header.to_owned(); + //new_header.append("Set-Cookie", "session_token=_; Max-Age=0".parse().unwrap()); + // cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); + // ( + // header, + // result.to_owned() + // ).into_response() + // "Hello, World!" + } + route( + "/signup/:data", get(invite_signup_handler)) + .route("/invite", get(invite_handler)) + .route("/invite", post(post_invite_handler)) +} diff --git a/src/handlers/users/login.rs b/src/handlers/users/login.rs new file mode 100644 index 0000000..d924b8c --- /dev/null +++ b/src/handlers/users/login.rs @@ -0,0 +1,281 @@ +use std::sync::Arc; +use axum::{ + http::{ + StatusCode, + Uri, + header::HeaderMap, + }, + response::{IntoResponse,Response,Redirect}, +}; +use tower_cookies::Cookies; + +use crate::{ + defs::{ + AppDBs, + AuthState, + ReqHandler, + ReqHeaderMap, + TotpMode, + Random, + AppConnectInfo, + }, + users::{ + User, + UserLogin, + UserStatus, + }, + login_password::verify_password, + handlers::{ + add_session_cookie, + get_auth_state, + }, +}; +use crate::users::OpenidUser; + +pub async fn login_user ( + header: HeaderMap, + uri: Uri, + app_dbs: Arc, + cookies: Cookies, + random: Random, + app_connect_info: AppConnectInfo, + user_login: UserLogin, + source: &str, +) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_login_handler" + ); + if user_login.name.is_empty() || source != "from_login_openid" && user_login.password.is_empty() { + let _ = req_handler.trace_req(String::from("Empty name or password")); + // return Err(error_page(&SignupError::MissingDetails)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let field = if user_login.name.contains("@") { + "email" + } else { + "name" + }; + let (mut user_sel, mut openid_user) = if source == "from_login_openid" { + let user_data = auth_state.user_data(); + dbg!(&user_data); + let openid_conf = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) { + openid_conf + } else { + let _ = req_handler.trace_req(format!("No name openid conf for '{}' found",&user_login.name)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error openid auth" + ).into_response(); + }; + let openid_user = OpenidUser::app_select(&openid_conf.signup_application, &field, &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else(|e| { + println!("Error openid {}/{} select: {}", &openid_conf.signup_application, &user_login.name, e); + OpenidUser::default() + }); + dbg!(&openid_user); + if openid_user.name.is_empty() { + let str_user_data = format!("openid|{}|{}|{}|{}|{}|", &user_data[0], &user_data[1], &user_data[2], &openid_conf.signup_application, &user_login.name); + let session_token = req_handler.new_token(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + let _ = req_handler.trace_req(format!("No openid name '{}' found",&openid_user.name)); + // User not exists + return Redirect::temporary( &format!("/login")).into_response(); + } + ( + User::select("id",&openid_user.userid.to_string(), false, &app_dbs.user_store).await.unwrap_or_else(|e|{ + println!("Error select: {}", e); + User::default() + }), + openid_user + ) + } else { + ( + User::select( &field, &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else( | e |{ + println ! ("Error select: {}", e); + User::default() + }), + OpenidUser::default() + ) + }; + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("No name '{}' found",&user_login.name)); + // User not exists + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error data" + ).into_response(); + } + if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created { + let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_login.name, &user_sel.status)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error status" + ).into_response(); + } + let result = if source == "from_login_openid" { + format!("{}:true", "OK") + } else { + if verify_password(&user_login.password, &user_sel.password).is_err() { + let _ = req_handler.trace_req(format!("user '{}' not valid password: {}", &user_login.name, &user_sel.password)); + println!("password NOT valid"); + // TODO + //return Err(error_page(&SignupError::PasswordsDoNotMatch)) + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error data" + ).into_response(); + } + let result = format!("{}:{}", "OK", &user_sel.otp_verified); + if app_dbs.config.totp_mode != TotpMode::No { + if user_login.otp_auth.is_empty() + && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) + { + let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}", &user_login.name, &user_sel.otp_enabled)); + return ( + req_handler.req_header.header, + result + ).into_response(); + } else if user_sel.otp_enabled && user_sel.otp_verified + && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() + { + match req_handler.otp_check(&user_sel.otp_base32, &user_login.otp_auth, &user_sel.otp_defs) { + Ok(val) => { + if !val { + let _ = req_handler.trace_req(format!("user '{}' not valid TOTP code", &user_login.name)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }, + Err(e) => { + let _ = req_handler.trace_req(format!("user '{}' TOTP check error: {}", &user_login.name, e)); + println!("TOTP check: {}", e); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + } + } + } + result + }; + let session_user_data = auth_state.user_data(); + user_sel.lastaccess = chrono::Utc::now().timestamp().to_string(); + if user_sel.status != UserStatus::Active { user_sel.status = UserStatus::Active } + let user_data = user_sel.session_data(); + let curr_id = user_sel.id; + match user_sel.update(&app_dbs.user_store).await { + Ok(_) => { + let session_token = req_handler.new_token(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + if openid_user.userid == curr_id { + openid_user.lastaccess = chrono::Utc::now().timestamp().to_string(); + let info_update = format!("User '{}/{} ({})' -> {} update", + &openid_user.application, + &openid_user.appkey, + &openid_user.name, + &openid_user.userid, + ); + match openid_user.update(&app_dbs.user_store).await { + Ok(_) => println!("{}d !", &info_update), + Err(e) => println!("{} error: {}", &info_update, e) + } + return Redirect::temporary( &format!("/")).into_response(); + } else if session_user_data.len() > 4 && session_user_data[0] == "openid" && !session_user_data[4].is_empty() && !session_user_data[5].is_empty() { + let openid_user = OpenidUser { + id: 0, + name: session_user_data[5].to_owned(), + application: session_user_data[4].to_owned(), + appkey: session_user_data[1].to_owned(), + userid: curr_id, + description: String::from("created from user"), + created: chrono::Utc::now().timestamp().to_string(), + lastaccess: String::from(""), + }; + let info_add = format!("user {}/{} ({}) -> {} created -> {}", + &openid_user.application, + &openid_user.appkey, + &openid_user.name, + &openid_user.userid, + &curr_id + ); + let data_add = format!("{}|{}|{}|{}|{}", + &curr_id, + &openid_user.application, + &openid_user.appkey, + &openid_user.name, + &openid_user.userid, + ); + let str_openid_user_data = match openid_user.add(&app_dbs.user_store).await { + Ok(id) => { + println!("{} - {}",&info_add,id); + data_add + }, + Err(e) => { + let _ = req_handler.trace_req(format!("{} error: {}", &info_add, e)); + println!("{} error -> {:#}",&info_add,e); + return ( + StatusCode::NOT_FOUND, + req_handler.req_header.header, + "Error create openid" + ).into_response(); + } + }; + let _ = req_handler.trace_req(format!("user '{}', openid link {} ", + &user_login.name, + str_openid_user_data + )); + } + req_handler = ReqHandler::new( + req_handler.req_header, + &app_dbs, + &uri, + &new_auth_state, + &random, + "post_login_handler" + ); + let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie)); + // TODO Is this a bug ?? + // http -> tokio-runtime-worker' panicked at 'payload claims content-length of 10, custom content-length header claims 34 + if app_dbs.config.protocol == "https" { + ( + req_handler.req_header.header, + result + ).into_response() + } else { + ( + result + ).into_response() + } + }, + Err(e) => { + let _ = req_handler.trace_req(format!("user '{}' update error: {}",&user_login.name,e)); + ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response() + } + } +} \ No newline at end of file diff --git a/src/handlers/users/login_handlers.rs b/src/handlers/users/login_handlers.rs new file mode 100644 index 0000000..b6c4c88 --- /dev/null +++ b/src/handlers/users/login_handlers.rs @@ -0,0 +1,601 @@ +use std::collections::HashMap; +use std::sync::Arc; +use axum::{ + http::{ + StatusCode, + Uri, + header::HeaderMap, + }, + Json, + routing::{get,post}, + Extension, + extract::ConnectInfo, + response::{IntoResponse,Response,Redirect}, + Router, +}; +use tower_cookies::{Cookie, Cookies}; + +use crate::{ + SESSION_COOKIE_NAME, + DEFAULT_ROLES, + route, + defs::{ + AppDBs, + AuthState, + SessionStoreDB, + ReqHandler, + ReqHeaderMap, + TotpMode, + Random, + AppConnectInfo, + OpenidData, + OpenidCli, + }, + users::{ + User, + UserData, + UserLogin, + UserItem, + UserStatus, + }, + login_password::generate_hash, + handlers::{ + add_session_cookie, + get_auth_state, + login_user, + }, +}; +pub fn users_login_router_handlers() -> Router { + async fn signup_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "signup_handler" + ); + if !auth_state.is_admin() { + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + println!("Count error: {}",e); + 0 + }); + if ! app_dbs.config.signup_mode.contains("open") && total_users != 0 { + let msg = format!("Config signup mode not open: {}", app_dbs.config.signup_mode); + println!("{}",&msg); + let _ = req_handler.trace_req(msg); + return Redirect::temporary( &format!("/")).into_response(); + } + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + println!("Count error: {}",e); + -1 + }); + // Fake to insert firt time admin user ... + if total_users < 1 { + let isadmin= String::from("TRUE"); + req_handler.context.insert("isadmin", &isadmin); + } + } + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + req_handler.context.insert("password_score", &app_dbs.config.password_score); + req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); + // req_handler.context.insert("with_menu", "1"); + if app_dbs.config.totp_mode != TotpMode::No { + match req_handler.otp_generate() { + Ok(totp) => { + req_handler.context.insert("otp_code", &totp.get_secret_base32()); + req_handler.context.insert("otp_url", &totp.get_url()); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); + req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); + req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm)); + }, + Err(e) => { + println!("Error TOTP generartor: {}",e); + } + } + } + req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); + let result = if let Some(tpl) = app_dbs.config.tpls.get("signup") { + req_handler.render_template(&tpl,"signup") + } else { + String::from("signup") + }; + let _ = req_handler.trace_req(format!("Signup request")); + ( + res_headers, + result.to_owned() + ).into_response() + } + + async fn post_signup_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(random): Extension, + Extension(cookies): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_data): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_signup_handler" + ); + if user_data.name.is_empty() { + let _ = req_handler.trace_req(format!("No name found")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + if user_data.password.is_empty() { + let _ = req_handler.trace_req(format!("user '{}' no password found",&user_data.name)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let passwd_score = User::password_score(&user_data.password); + if passwd_score < app_dbs.config.password_score { + let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" + ,&user_data.name,&user_data.password, passwd_score,app_dbs.config.password_score) + ); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let new_hash = generate_hash(&user_data.password); + // println!("password: {}", &new_hash); + // // if user_data.password.is_empty() { + // // return Err(error_page(&SignupError::MissingDetails)); + // // } + // match verify_password(&user_data.password, &new_hash) { + // Ok(_) => { + // println!("Is valid!"); + // }, + // Err(e) => { + // println!("NOT valid {}",e); + // //return Err(error_page(&SignupError::PasswordsDoNotMatch)) + // } + // } + let otp_enabled: bool; + let otp_verified: bool; + let otp_base32: String; + let otp_auth_url: String; + let otp_defs: String; + if app_dbs.config.totp_mode != TotpMode::No && !user_data.otp_auth.is_empty() { + match req_handler.otp_check(&user_data.otp_code,&user_data.otp_auth, "") { + Ok(val) => { + if val { + otp_enabled = true; + } else { + let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_data.name)); + // otp_enabled = false; + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }, + Err(e) => { + println!("TOTP check: {}", e); + let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_data.name,e)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + } + } else { + otp_enabled = false; + } + if otp_enabled { + otp_verified= true; + otp_base32 = user_data.otp_code.to_owned(); + otp_auth_url = user_data.otp_url.to_owned(); + otp_defs = format!("{},{}", + &app_dbs.config.totp_digits, + format!("{}",&app_dbs.config.totp_algorithm), + ); + } else { + otp_verified = false; + otp_base32 = String::from(""); + otp_auth_url = String::from(""); + otp_defs = String::from(""); + } + let roles = if ! user_data.roles.is_empty() { + user_data.roles.to_owned() + } else { + DEFAULT_ROLES.to_owned() + }; + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let _ = req_handler.trace_req( format!("Users count error: {}",e)); + println!("Count error: {}",e); + -1 + }); + let isadmin = if user_data.id == "A" || total_users < 1 { + true + } else { + false + }; + let mut new_items = user_data.items.to_owned(); + new_items.remove("invite_key"); + new_items.remove("invite_id"); + let user = User{ + id: 0, + name: user_data.name.to_owned(), + fullname: user_data.name.to_owned(), + email: user_data.email.to_owned(), + description: user_data.description.to_owned(), + password: new_hash, + otp_enabled, + otp_verified, + otp_base32, + otp_auth_url, + otp_defs, + created: chrono::Utc::now().timestamp().to_string(), + lastaccess: String::from(""), + status: UserStatus::Created, + items: User::json_items(new_items), + isadmin, + roles, + }; + let usr_sel = User::select("name", &user_data.name, false, &app_dbs.user_store).await.unwrap_or_default(); + if usr_sel.name == user_data.name { + // User already exists + let _ = req_handler.trace_req(format!("User 'name' = '{}' already exists",&user_data.name)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let str_user_data = match user.add(&app_dbs.user_store).await { + Ok(id) => { + println!("user {} created -> {}", &user_data.name, id); + format!("{}|{}|{}",id, &user_data.name, &user_data.roles) + }, + Err(e) => { + let _ = req_handler.trace_req(format!("User '{}' create error: {}",&user_data.name,e)); + println!("user {} error -> {:#}", &user_data.name, e); + return ( + StatusCode::NOT_FOUND, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }; + let session_token = req_handler.new_token(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie); } + ( + req_handler.req_header.header, + "Ok" + ).into_response() + } + async fn login_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + ) -> Response { + SessionStoreDB::cleanup_data(&app_dbs).await; + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + dbg!(&header); + dbg!(&auth_state); + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "login_handler" + ); + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let _ = req_handler.trace_req( format!("Users count error: {}",e)); + println!("Count error: {}",e); + -1 + }); + if total_users < 1 { + let _ = req_handler.trace_req(String::from("No users found")); + return Redirect::temporary( &format!("/signup")).into_response(); + } + let user_data = auth_state.user_data(); + if user_data.len() > 4 && user_data[0] == "openid" { + req_handler.context.insert("login_text", &format!("Login {} / {}", &user_data[1], &user_data[5])); + req_handler.context.insert("signin_target", &user_data[1]); + } + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::>()); + // req_handler.context.insert("with_menu", "1"); + req_handler.context.insert("use_mail", &app_dbs.config.use_mail); + req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); + if app_dbs.config.totp_mode != TotpMode::No { + req_handler.context.insert("with_totp", "1"); + req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); + } + let _ = req_handler.trace_req(String::from("login request")); + let result = if let Some(tpl) = app_dbs.config.tpls.get("login") { + req_handler.render_template(&tpl,"login") + } else { + String::from("login") + }; + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn openid_login_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + Extension(openid_clients): Extension>>, + ConnectInfo(app_connect_info): ConnectInfo, + axum::extract::Path(data): axum::extract::Path, + ) -> Response { + SessionStoreDB::cleanup_data(&app_dbs).await; + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "login_handler" + ); + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let _ = req_handler.trace_req( format!("Users count error: {}",e)); + println!("Count error: {}",e); + -1 + }); + if total_users < 1 { + let _ = req_handler.trace_req(String::from("No users found")); + return Redirect::temporary( &format!("/signup")).into_response(); + } + if data.is_empty() { + return Redirect::temporary( &format!("/login")).into_response(); + } + let (str_user_data,url) = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&data) { + if let Some(openid_cli) = openid_clients.get(&data) { + match openid_conf.get_auth(openid_cli).await { + Ok(openid) => { + //let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); + let str_user_data = format!("{}|{}|{}", &data, openid.token.secret(), openid.nonce.secret()); + //let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + //if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + //let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + /*req_handler = ReqHandler::new( + req_handler.req_header, + &app_dbs, + &uri, + &new_auth_state, + &random, + "post_login_handler" + );*/ + //let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie)); + (str_user_data, format!("{}", openid.url.to_string())) + }, + Err(e) => { + println!("Error openid login handler: {}", e); + (String::from(""), String::from("/login")) + } + } + } else { + (String::from(""), String::from("/login")) + } + } else { + (String::from(""), String::from("/login")) + }; + let session_token = req_handler.new_token(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + return Redirect::temporary(&format!("{}",url)).into_response() + } + async fn post_login_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_login): Json, + ) -> Response { + login_user ( header, uri, + app_dbs, cookies, random, + app_connect_info, + user_login, + "post_login" + ).await + } + async fn openid_from_login_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Extension(openid_clients): Extension>>, + //axum::extract::Path(data): axum::extract::Path, + data: axum::extract::Query, + // Json(user_login): Json, + ) -> Response { + dbg!(&header); + dbg!(&uri); + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let user_data = auth_state.user_data(); + //let _req_handler = ReqHandler::new( + // ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + // &app_dbs, + // &uri, + // &auth_state, + // &random, + // "post_login_handler" + //); + if user_data.len() < 1 || user_data[1] != data.state { + println!("State {} not found=", data.state); + return Redirect::temporary(&format!("/login")).into_response(); + } + println!("OK State {} == {}", user_data[1], data.state); + let mut user_login = UserLogin::default(); + if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) { + if let Some(openid_cli) = openid_clients.get(&user_data[0]) { + match openid_conf.get_token(openid_cli, data.code.to_owned(), user_data[2].to_owned()).await { + Ok(data) => { + user_login = UserLogin { + name: data.name.to_owned(), + password: String::from(""), + otp_auth: String::from(""), + email: data.email.to_owned(), + }; + }, + Err(e) => { + println!("Error openid login handler: {}", e); + } + } + } + } + if ! user_login.name.is_empty() { + let res = login_user(header, uri, + app_dbs, cookies, random, + app_connect_info, + user_login, + "from_login_openid" + ).await; + res + } else { + let url="/login"; // https://tiicl.tls13.io:8800"; + Redirect::temporary(&format!("{}",url)).into_response() + } + } + async fn logout_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "logout_handler" + ); + let id = auth_state.id(); + let res = auth_state.destroy(); + cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); + if app_dbs.config.verbose > 1 { println!("Session: {} destroyed: {}",&id, &res); } + let _ = req_handler.trace_req(format!("Session '{}' logout",&id)); + let auth_state = AuthState::default(); + req_handler = ReqHandler::new( + req_handler.req_header, + &app_dbs, + &uri, + &auth_state, + &random, + "logout_handler" + ); + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + // these is to use user_items like color_theme + // let user_items = User::hash_items(&auth_state.user_items()); + // req_handler.context.insert("usr_items", &user_items); + req_handler.context.insert("with_menu", "1"); + let result = if let Some(tpl) = app_dbs.config.tpls.get("logout") { + req_handler.render_template(&tpl,"logout") + } else { + String::from("logout") + }; + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn check_item_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_item): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "logout_handler" + ); + let result = if user_item.name == "password" { + let _ = req_handler.trace_req(format!("Password estimate")); + User::estimate_password(&user_item.value) + } else { + let user_sel = User::select(&user_item.name, &user_item.value, false,&app_dbs.user_store).await.unwrap_or_default(); + if !user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&user_item.name,&user_item.value)); + // User not exists + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + String::from("OK") + }; + ( + req_handler.req_header.header, + result.to_owned() + ).into_response() + } + route( + "/login", get(login_handler)) + .route("/login", post(post_login_handler)) + + .route("/openid/:data", get(openid_login_handler)) + .route("/from_openid", get(openid_from_login_handler)) + + .route("/signup", get(signup_handler)) + .route("/signup", post(post_signup_handler)) + + .route("/logout", get(logout_handler)) + + .route("/check", post(check_item_handler)) +} diff --git a/src/handlers/users/password_handlers.rs b/src/handlers/users/password_handlers.rs new file mode 100644 index 0000000..21a60a5 --- /dev/null +++ b/src/handlers/users/password_handlers.rs @@ -0,0 +1,395 @@ +use std::sync::Arc; +use urlencoding::{encode,decode}; +use axum::{ + http::{ + StatusCode, + Uri, + header::HeaderMap, + }, + Json, + routing::{get,post}, + Extension, + extract::ConnectInfo, + response::{IntoResponse,Response,Redirect}, + Router, +}; +use tower_cookies::Cookies; + +use crate::{ + route, + defs::{ + AppDBs, + AuthState, + ReqHandler, + ReqHeaderMap, + MailMessage, + TotpMode, + Random, + AppConnectInfo, + }, + users::{ + User, + UserData, + UserItem, + UserStatus, + }, + login_password::generate_hash, + handlers::{ + add_session_cookie, + get_auth_state, + }, +}; +pub fn users_password_router_handlers() -> Router { + async fn post_reset_password_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_item): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_reset_password_handler" + ); + if ! app_dbs.config.use_mail { + let _ = req_handler.trace_req(format!("Mail disabled in config, user '{}' password can not be reset",&user_item.name)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error no service" + ).into_response(); + } + if user_item.name.is_empty() { + let _ = req_handler.trace_req(format!("No user name")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let field = if user_item.name.contains("@") { + "email" + } else { + "name" + }; + let mut user_sel = User::select(&field, &user_item.name, false, &app_dbs.user_store).await.unwrap_or_else(|e|{ + println!("Error select: {}", e); + User::default() + }); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&field,&user_item.name)); + // User not exists + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error data" + ).into_response(); + } + if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created { + let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_item.name, &user_sel.status)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error status" + ).into_response(); + } + if app_dbs.config.totp_mode != TotpMode::No { + if user_sel.otp_base32.is_empty() + && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) + { + let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}",&user_sel.name, &user_sel.otp_enabled)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error data" + ).into_response(); + } else if user_sel.otp_enabled && user_sel.otp_verified + && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() + { + match req_handler.otp_check(&user_sel.otp_base32,&user_item.value, &user_sel.otp_defs) { + Ok(val) => { + if !val { + let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_item.name)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }, + Err(e) => { + println!("TOTP check: {}", e); + let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_item.name,e)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + } + } + } + let session_token = req_handler.new_token(); + user_sel.status = UserStatus::Pending; + let user_data = user_sel.session_data(); + let session_cookie = add_session_cookie(false,&cookies, &session_token, &user_data, app_dbs.config.session_expire, &app_dbs, "invitation").await; + let session_encoded_key = encode(session_cookie.as_str()); + let body=format!("This is a user password reset request for docserver service"); + let subject = format!("DocServer password reset"); + let reset_url= format!( + "{}://{}/reset", + &app_dbs.config.protocol, + &app_dbs.config.hostport, + ); + let reset_expiration = format!("{} minutes",(&app_dbs.config.session_expire/60)); + req_handler.context.insert("reset_url", &reset_url); + req_handler.context.insert("reset_expiration", &reset_expiration); + req_handler.context.insert("email_body",&body); + req_handler.context.insert("reset_key",&session_encoded_key); + req_handler.context.insert("email_subject",&subject); + let mail_content= if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_txt") { + req_handler.render_template(tpl, "reset password") + } else { + format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie) + }; + let mail_html_content = if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_html") { + req_handler.render_template(tpl, "invite") + } else { + format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie) + }; + let mail_check = MailMessage::check(&app_dbs); + if ! mail_check.is_empty() { + let _ = req_handler.trace_req(format!("Mail service check error: {}",&mail_check)); + ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error service" + ).into_response() + } else { + match MailMessage::new( + &app_dbs.config.mail_from, + &user_sel.email, + &app_dbs.config.mail_reply_to, + ) { + Ok(mail_message) => { + match mail_message.send_html_message( + &subject, + &mail_content, + &mail_html_content, + &app_dbs + ).await { + Ok(_) => { + let _ = req_handler.trace_req(format!("Mail sent to: '{}' reset url: {} cookie: {}", + &user_sel.name, &reset_url,&session_cookie + )); + ( + StatusCode::OK, + format!("Mail sent to {}",&user_sel.name) + ).into_response() + }, + Err(e) => { + let _ = req_handler.trace_req(format!("Mail message send to: '{}' Error: {} ",&user_sel.name,e)); + println!("Error mail message send: {}",e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + req_handler.req_header.header, + "Error service" + ).into_response() + } + } + }, + Err(e) => { + let _ = req_handler.trace_req(format!("Mail message send to: '{}' Creation error: {} ",&user_sel.name,e)); + println!("Error mail message creation: {}",e); + ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error service" + ).into_response() + } + } + } + //let result=format!("{}","OK"); + // if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { + // return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + // // return ( + // // StatusCode::UNAUTHORIZED, + // // header, + // // "Error authorization" + // // ).into_response(); + // } + // ( + // //status, + // req_handler.req_header.header, + // result, + // ).into_response() + } + async fn reset_password_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + // Query(req_params): Query, + axum::extract::Path(data): axum::extract::Path, + ) -> Response { + let session_cookie = decode(&data).unwrap_or_default().to_string(); + let auth_state = AuthState::from_cookie(session_cookie.to_owned(), &app_dbs).await; + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "reset_password_handler" + ); + let user_id = auth_state.user_id(); + if user_id.is_empty() { + let _ = req_handler.trace_req(format!("No user found")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let user_sel = User::select("id", &user_id, false, &app_dbs.user_store).await.unwrap_or_else(|e|{ + println!("Error select: {}", e); + User::default() + }); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("User 'id' = '{}' not found",&user_id)); + // User not exists + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error data" + ).into_response(); + } + if user_sel.status != UserStatus::Active { + let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_id, &user_sel.status)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error status" + ).into_response(); + } + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + req_handler.context.insert("with_menu", "1"); + req_handler.context.insert("user", &user_sel); + req_handler.context.insert("edit_target", "password"); + req_handler.context.insert("edit_reset", "password"); + req_handler.context.remove("web_menu_items"); + let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { + req_handler.render_template(&tpl,"user setting") + } else { + String::from("user settings") + }; + let user_data = user_sel.session_data(); + let session_token = req_handler.new_token(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _ = req_handler.trace_req(format!("user '{}' reset password",&user_id)); + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn post_user_password_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_data): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_reset_password_handler" + ); + if user_data.password.is_empty() { + let _ = req_handler.trace_req(format!("No passwordfound")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + if auth_state.session.is_none() { + let _ = req_handler.trace_req(format!("No session found for user '{}'",&user_data.id)); + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + } + let user_id = auth_state.user_id(); + if user_id.is_empty() { + let _ = req_handler.trace_req(format!("No user found")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let mut user_sel = User::select("id", &user_id, false,&app_dbs.user_store).await.unwrap_or_default(); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("User '{}' not found",&user_id)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let passwd_score = User::password_score(&user_data.password); + if passwd_score < app_dbs.config.password_score { + let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" + ,&user_id,&user_data.password, passwd_score,app_dbs.config.password_score) + ); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + user_sel.password = generate_hash(&user_data.password); + let user_data = user_sel.session_data(); + match user_sel.update(&app_dbs.user_store).await { + Ok(_) => { + let session_token = auth_state.id(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _ = req_handler.trace_req(format!("user '{}' reset password OK",&user_id)); + let result =String::from("OK"); + ( + req_handler.req_header.header, + result.to_owned() + ).into_response() + }, + Err(e) => { + let _ = req_handler.trace_req(format!("Error user '{}' reset password: {}",&user_id,e)); + ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response() + }, + } + } + route( + "/reset/:data", get(reset_password_handler)) + .route("/reset", post(post_reset_password_handler)) + .route("/resetup", post(post_user_password_handler)) +} diff --git a/src/handlers/users/settings_handlers.rs b/src/handlers/users/settings_handlers.rs new file mode 100644 index 0000000..94a5030 --- /dev/null +++ b/src/handlers/users/settings_handlers.rs @@ -0,0 +1,400 @@ +use std::sync::Arc; +use axum::{ + http::{ + StatusCode, + Uri, + header::HeaderMap, + }, + Json, + routing::{get,post}, + Extension, + extract::ConnectInfo, + response::{IntoResponse,Response,Redirect}, + Router, +}; +use tower_cookies::Cookies; + +use crate::{ + route, + defs::{ + AppDBs, + ReqHandler, + ReqHeaderMap, + TotpMode, + Random, + AppConnectInfo, + }, + users::{ + User, + UserData, + UserItem, + OpenidUser, + }, + login_password::generate_hash, + handlers::{ + add_session_cookie, + get_auth_state, + }, +}; + +pub fn users_settings_router_handlers() -> Router { + // async fn edit_user_handler( + // header: HeaderMap, + // uri: Uri, + // Extension(app_dbs): Extension>, + // Extension(cookies): Extension, + // //_auth_state: AuthState, + // ) -> Response { + // let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + // let mut req_handler = ReqHandler::new( + // ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string())), + // &app_dbs, + // &uri, + // &auth_state, + // "root_handler" + // ); + // let uri_path = format!("{}",&uri.path().to_string()); + // let file = "hello.html"; + // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ + // println!("Error render {}: {}",&file,e); + // String::from("") + // }); + // req_handler.context.insert("with_menu", "1"); + // // let mut new_header = header.to_owned(); + // //new_header.append("Set-Cookie", "session_token=_; Max-Age=0".parse().unwrap()); + // // cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); + // ( + // req_handler.req_header.header, + // result.to_owned() + // ).into_response() + // // "Hello, World!" + // } + async fn user_settings_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + if auth_state.session.is_none() { + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + } + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "user_settings_handler" + ); + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + let user_id = auth_state.user_id(); + if user_id.is_empty() { + let _ = req_handler.trace_req(format!("user not id found")); + // User not exists + return Redirect::temporary( &format!("/")).into_response(); + } + let user_sel = User::select("id", &user_id, true, &app_dbs.user_store).await.unwrap_or_default(); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("Edit user id '{}' not found ",&user_id)); + // User not exists + return Redirect::temporary( &format!("/")).into_response(); + } + let openid_sel = OpenidUser::list_selection("userid", &user_sel.id.to_string(), &app_dbs.user_store, true,false, "|").await.unwrap_or_else(|e| { + println!("Error list selection {}: {}", &user_sel.name, e); + Vec::new() + }); + req_handler.context.insert("openid_sel", &openid_sel); + let openid_sel_appkeys = openid_sel.iter().map(|id| id.appkey.to_string()).collect::>().join(","); + req_handler.context.insert("openid_sel_appkeys", &openid_sel_appkeys); + req_handler.context.insert("with_menu", "1"); + req_handler.context.insert("user", &user_sel); + req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); + req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); + // let user_items = User::hash_items(&user_sel.items); + // req_handler.context.insert("usr_items", &user_items); + req_handler.context.insert("no_edit", "true"); + req_handler.context.insert("edit_target", "main"); + let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { + req_handler.render_template(&tpl,"user setting") + } else { + String::from("user settings") + }; + let _ = req_handler.trace_req(format!("User '{}' settings",&user_sel.id)); + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn user_settings_edit_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + axum::extract::Path(data): axum::extract::Path, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + if auth_state.session.is_none() { + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + } + let mut req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "user_settings_edit_handler" + ); + let mut res_headers = HeaderMap::new(); + if req_handler.req_header.is_browser() { + res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); + } + if data != "main" && data != "password" && data != "totp" { + let _ = req_handler.trace_req(format!("Edit user not data section '{}' ",&data)); + return Redirect::temporary( &format!("/")).into_response(); + } + let user_id = auth_state.user_id(); + if user_id.is_empty() { + // User not exists + let _ = req_handler.trace_req(format!("Edit user not id ")); + return Redirect::temporary( &format!("/")).into_response(); + } + let user_sel = User::select("id", &user_id, true, &app_dbs.user_store).await.unwrap_or_default(); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("Edit user id '{}' not found ",&user_id)); + // User not exists + return Redirect::temporary( &format!("/")).into_response(); + } + let openid_sel = OpenidUser::list_selection("userid", &user_sel.id.to_string(), &app_dbs.user_store, true,false, "|").await.unwrap_or_else(|e| { + println!("Error list selection {}: {}", &user_sel.name, e); + Vec::new() + }); + req_handler.context.insert("openid_sel", &openid_sel); + let openid_sel_appkeys = openid_sel.iter().map(|id| id.appkey.to_string()).collect::>().join(","); + req_handler.context.insert("openid_sel_appkeys", &openid_sel_appkeys); + req_handler.context.insert("with_menu", "1"); + req_handler.context.insert("user", &user_sel); + req_handler.context.insert("edit_target", &data); + req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); + req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); + if data == "totp" && app_dbs.config.totp_mode != TotpMode::No { + if !user_sel.otp_base32.is_empty() { + match req_handler.otp_make(&user_sel.otp_base32, &user_sel.otp_defs) { + Ok(totp) => { + req_handler.context.insert("otp_code", &user_sel.otp_base32); + req_handler.context.insert("otp_url", &user_sel.otp_auth_url); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); + }, + Err(e) => { + println!("User settings error totp: {}",e); + } + } + } else { + match req_handler.otp_generate() { + Ok(totp) => { + req_handler.context.insert("otp_code", &totp.get_secret_base32()); + req_handler.context.insert("otp_url", &totp.get_url()); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); + }, + Err(e) => { + println!("Error TOTP generartor: {}",e); + } + } + } + req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); + req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm)); + } + let _ = req_handler.trace_req(format!("Edit user '{}' settings",&user_id)); + // req_handler.context.insert("no_edit", "true"); + let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { + req_handler.render_template(&tpl,"user setting") + } else { + String::from("user settings") + }; + ( + res_headers, + result.to_owned() + ).into_response() + } + async fn post_user_settings_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_data): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "post_user_settings_handler" + ); + if auth_state.session.is_none() { + let _ = req_handler.trace_req(format!("No session found for user '{}'",&user_data.id)); + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + } + let user_id = auth_state.user_id(); + if user_id.is_empty() { + let _ = req_handler.trace_req(format!("No user found")); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + let mut user_sel = User::select("id", &user_id, false,&app_dbs.user_store).await.unwrap_or_default(); + if user_sel.name.is_empty() { + let _ = req_handler.trace_req(format!("User '{}' not found",&user_id)); + return ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response(); + } + if !user_data.password.is_empty() { + let passwd_score = User::password_score(&user_data.password); + if passwd_score < app_dbs.config.password_score { + let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" + ,&user_id,&user_data.password, passwd_score,app_dbs.config.password_score + )); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + user_sel.password = generate_hash(&user_data.password); + } + //let openid_sel = OpenidUser::list_selection("userid", &user_sel.id.to_string(), &app_dbs.user_store, true,false, "|").await.unwrap_or_else(|e| { + // println!("Error list selection {}: {}", &user_sel.name, e); + // Vec::new() + //}); + //let openid_sel_appkeys = openid_sel.iter().map(|id| id.appkey.to_string()).collect::>().join(","); + let otp_enabled: bool; + if app_dbs.config.totp_mode != TotpMode::No && !user_data.otp_auth.is_empty() { + match req_handler.otp_check(&user_data.otp_code,&user_data.otp_auth, "") { + Ok(val) => { + if val { + otp_enabled = true; + } else { + // otp_enabled = fasle; + let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_id)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }, + Err(e) => { + println!("TOTP check: {}", e); + let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_id,e)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + } + } else { + otp_enabled = false; + } + if otp_enabled { + user_sel.otp_enabled= true; + user_sel.otp_verified= true; + user_sel.otp_base32 = user_data.otp_code.to_owned(); + user_sel.otp_auth_url = user_data.otp_url.to_owned(); + user_sel.otp_defs = format!("{},{}", + &app_dbs.config.totp_digits, + format!("{}",&app_dbs.config.totp_algorithm), + ); + } else { + user_sel.otp_enabled= false; + user_sel.otp_verified = false; + user_sel.otp_base32 = String::from(""); + user_sel.otp_auth_url = String::from(""); + user_sel.otp_defs = String::from(""); + } + let user_openids = user_data.opendis.to_owned(); + user_sel.from_data(user_data); + let new_id= user_sel.id.to_string(); + let user_data = user_sel.session_data(); + match user_sel.update(&app_dbs.user_store).await { + Ok(_) => { + let session_token = auth_state.id(); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _ = req_handler.trace_req(format!("User '{}' updated",&user_id)); + OpenidUser::sync_ids(&new_id, &user_openids, &app_dbs.user_store).await; + let result =String::from("OK"); + ( + req_handler.req_header.header, + result.to_owned() + ).into_response() + }, + Err(e) => { + let _ = req_handler.trace_req(format!("User '{}' update error: {}",&user_id,e)); + ( + StatusCode::BAD_REQUEST, + req_handler.req_header.header, + "Error" + ).into_response() + } + } + } + async fn update_user_item_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_item): Json, + //_auth_state: AuthState, + //axum::extract::Path(data): axum::extract::Path, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "update_user_item_handler" + ); + if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { + let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); + return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); + // return ( + // StatusCode::UNAUTHORIZED, + // header, + // "Error authorization" + // ).into_response(); + } + dbg!("{}",&user_item); + let result=""; + ( + //status, + req_handler.req_header.header, + result, + ).into_response() + } + route( + "/settings", get(user_settings_handler)) + .route("/settings/:item", get(user_settings_edit_handler)) +// .route("/update", post(update_user_handler)) + .route("/settings", post(post_user_settings_handler)) + .route("/update_item", post(update_user_item_handler)) +} diff --git a/src/handlers/users_handlers.rs b/src/handlers/users_handlers.rs index bcfe110..8e25507 100644 --- a/src/handlers/users_handlers.rs +++ b/src/handlers/users_handlers.rs @@ -1,60 +1,32 @@ -use std::collections::HashMap; use std::sync::Arc; -use urlencoding::{encode,decode}; -// use tokio::sync::RwLock; use axum::{ - //extract::{self,Request,Query}, http::{ - StatusCode, Uri, - header::HeaderMap,// HeaderValue}, + header::HeaderMap, }, - Json, - routing::{get,post}, + routing::get, Extension, extract::ConnectInfo, - response::{IntoResponse,Response,Redirect}, -// http::Request, handler::HandlerWithoutStateExt, http::StatusCode, routing::get, Router, + response::{IntoResponse,Response}, Router, }; -use tower_cookies::{Cookie, Cookies}; +use tower_cookies::Cookies; use crate::{ - SESSION_COOKIE_NAME, - DEFAULT_ROLES, route, defs::{ AppDBs, - AuthState, - SessionStoreDB, ReqHandler, ReqHeaderMap, - MailMessage, -// TotpAlgorithm, - TotpMode, Random, AppConnectInfo, - OpenidData, - OpenidCli, -// OpenidClaims, - // UserNotifyData, - // TOKEN_AUTH_VALUE, - // TOKEN_KEY_VALUE, }, - users::{ - User, - // UserStore, -// UserId, - UserData, - UserLogin, - UserItem, - UserStatus, - UserInvitation, - }, - login_password::{generate_hash,verify_password}, handlers::{ - add_session_cookie, get_auth_state, + users_login_router_handlers, + users_password_router_handlers, + users_invite_router_handlers, + users_settings_router_handlers, }, }; pub fn users_router_handlers() -> Router { @@ -186,1692 +158,12 @@ pub fn users_router_handlers() -> Router { // result.to_owned() // ).into_response() // // "Hello, World!" - // } - - async fn signup_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "signup_handler" - ); - if !auth_state.is_admin() { - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ - println!("Count error: {}",e); - 0 - }); - if ! app_dbs.config.signup_mode.contains("open") && total_users != 0 { - let msg = format!("Config signup mode not open: {}", app_dbs.config.signup_mode); - println!("{}",&msg); - let _ = req_handler.trace_req(msg); - return Redirect::temporary( &format!("/")).into_response(); - } - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ - println!("Count error: {}",e); - -1 - }); - // Fake to insert firt time admin user ... - if total_users < 1 { - let isadmin=true; - req_handler.context.insert("isadmin", &isadmin); - } - } - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - req_handler.context.insert("password_score", &app_dbs.config.password_score); - req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); - // req_handler.context.insert("with_menu", "1"); - if app_dbs.config.totp_mode != TotpMode::No { - match req_handler.otp_generate() { - Ok(totp) => { - req_handler.context.insert("otp_code", &totp.get_secret_base32()); - req_handler.context.insert("otp_url", &totp.get_url()); - req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); - req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); - req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm)); - }, - Err(e) => { - println!("Error TOTP generartor: {}",e); - } - } - } - req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); - let result = if let Some(tpl) = app_dbs.config.tpls.get("signup") { - req_handler.render_template(&tpl,"signup") - } else { - String::from("signup") - }; - let _ = req_handler.trace_req(format!("Signup request")); - ( - res_headers, - result.to_owned() - ).into_response() - } + // }:1 - async fn post_signup_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(random): Extension, - Extension(cookies): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_data): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_signup_handler" - ); - if user_data.name.is_empty() { - let _ = req_handler.trace_req(format!("No name found")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - if user_data.password.is_empty() { - let _ = req_handler.trace_req(format!("user '{}' no password found",&user_data.name)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let passwd_score = User::password_score(&user_data.password); - if passwd_score < app_dbs.config.password_score { - let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" - ,&user_data.name,&user_data.password, passwd_score,app_dbs.config.password_score) - ); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let new_hash = generate_hash(&user_data.password); - // println!("password: {}", &new_hash); - // // if user_data.password.is_empty() { - // // return Err(error_page(&SignupError::MissingDetails)); - // // } - // match verify_password(&user_data.password, &new_hash) { - // Ok(_) => { - // println!("Is valid!"); - // }, - // Err(e) => { - // println!("NOT valid {}",e); - // //return Err(error_page(&SignupError::PasswordsDoNotMatch)) - // } - // } - let otp_enabled: bool; - let otp_verified: bool; - let otp_base32: String; - let otp_auth_url: String; - let otp_defs: String; - if app_dbs.config.totp_mode != TotpMode::No && !user_data.otp_auth.is_empty() { - match req_handler.otp_check(&user_data.otp_code,&user_data.otp_auth, "") { - Ok(val) => { - if val { - otp_enabled = true; - } else { - let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_data.name)); - // otp_enabled = false; - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - }, - Err(e) => { - println!("TOTP check: {}", e); - let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_data.name,e)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - } - } else { - otp_enabled = false; - } - if otp_enabled { - otp_verified= true; - otp_base32 = user_data.otp_code.to_owned(); - otp_auth_url = user_data.otp_url.to_owned(); - otp_defs = format!("{},{}", - &app_dbs.config.totp_digits, - format!("{}",&app_dbs.config.totp_algorithm), - ); - } else { - otp_verified = false; - otp_base32 = String::from(""); - otp_auth_url = String::from(""); - otp_defs = String::from(""); - } - let roles = if ! user_data.roles.is_empty() { - user_data.roles.to_owned() - } else { - DEFAULT_ROLES.to_owned() - }; - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ - let _ = req_handler.trace_req( format!("Users count error: {}",e)); - println!("Count error: {}",e); - -1 - }); - let isadmin = if user_data.id == "A" || total_users < 1 { - true - } else { - false - }; - let mut new_items = user_data.items.to_owned(); - new_items.remove("invite_key"); - new_items.remove("invite_id"); - let user = User{ - id: 0, - name: user_data.name.to_owned(), - fullname: user_data.name.to_owned(), - email: user_data.email.to_owned(), - description: user_data.description.to_owned(), - password: new_hash, - otp_enabled, - otp_verified, - otp_base32, - otp_auth_url, - otp_defs, - created: chrono::Utc::now().timestamp().to_string(), - lastaccess: String::from(""), - status: UserStatus::Created, - items: User::json_items(new_items), - isadmin, - roles, - }; - let usr_sel = User::select("name", &user_data.name, false, &app_dbs.user_store).await.unwrap_or_default(); - if usr_sel.name == user_data.name { - // User already exists - let _ = req_handler.trace_req(format!("User 'name' = '{}' already exists",&user_data.name)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let str_user_data = match user.add(&app_dbs.user_store).await { - Ok(id) => { - println!("user {} created -> {}", &user_data.name, id); - format!("{}|{}|{}",id, &user_data.name, &user_data.roles) - }, - Err(e) => { - let _ = req_handler.trace_req(format!("User '{}' create error: {}",&user_data.name,e)); - println!("user {} error -> {:#}", &user_data.name, e); - return ( - StatusCode::NOT_FOUND, - req_handler.req_header.header, - "Error" - ).into_response(); - } - }; - let session_token = req_handler.new_token(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; - let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie); } - ( - req_handler.req_header.header, - "Ok" - ).into_response() - } - async fn login_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - ) -> Response { - SessionStoreDB::cleanup_data(&app_dbs).await; - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "login_handler" - ); - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ - let _ = req_handler.trace_req( format!("Users count error: {}",e)); - println!("Count error: {}",e); - -1 - }); - if total_users < 1 { - let _ = req_handler.trace_req(String::from("No users found")); - return Redirect::temporary( &format!("/signup")).into_response(); - } - - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::>()); - // req_handler.context.insert("with_menu", "1"); - req_handler.context.insert("use_mail", &app_dbs.config.use_mail); - req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); - if app_dbs.config.totp_mode != TotpMode::No { - req_handler.context.insert("with_totp", "1"); - req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); - } - let _ = req_handler.trace_req(String::from("login request")); - let result = if let Some(tpl) = app_dbs.config.tpls.get("login") { - req_handler.render_template(&tpl,"login") - } else { - String::from("login") - }; - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn login_openid_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - Extension(openid_clients): Extension>>, - ConnectInfo(app_connect_info): ConnectInfo, - axum::extract::Path(data): axum::extract::Path, - ) -> Response { - SessionStoreDB::cleanup_data(&app_dbs).await; - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "login_handler" - ); - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ - let _ = req_handler.trace_req( format!("Users count error: {}",e)); - println!("Count error: {}",e); - -1 - }); - if total_users < 1 { - let _ = req_handler.trace_req(String::from("No users found")); - return Redirect::temporary( &format!("/signup")).into_response(); - } - if data.is_empty() { - return Redirect::temporary( &format!("/login")).into_response(); - } - let (str_user_data,url) = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&data) { - if let Some(openid_cli) = openid_clients.get(&data) { - match openid_conf.get_auth(openid_cli).await { - Ok(openid) => { - //let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); - let str_user_data = format!("{}|{}|{}", &data, openid.token.secret(), openid.nonce.secret()); - //let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; - //if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - //let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; - /*req_handler = ReqHandler::new( - req_handler.req_header, - &app_dbs, - &uri, - &new_auth_state, - &random, - "post_login_handler" - );*/ - //let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie)); - (str_user_data, format!("{}", openid.url.to_string())) - }, - Err(e) => { - println!("Error openid login handler: {}", e); - (String::from(""), String::from("/login")) - } - } - } else { - (String::from(""), String::from("/login")) - } - } else { - (String::from(""), String::from("/login")) - }; - let session_token = req_handler.new_token(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; - return Redirect::temporary(&format!("{}",url)).into_response() - } - - async fn login_user ( - header: HeaderMap, - uri: Uri, - app_dbs: Arc, - cookies: Cookies, - random: Random, - app_connect_info: AppConnectInfo, - user_login: UserLogin, - source: &str, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_login_handler" - ); - if user_login.name.is_empty() || source != "from_login_openid" && user_login.password.is_empty() { - let _ = req_handler.trace_req(String::from("Empty name or password")); - // return Err(error_page(&SignupError::MissingDetails)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let field = if user_login.name.contains("@") { - "email" - } else { - "name" - }; - let mut user_sel = User::select(&field, &user_login.name, false, &app_dbs.user_store).await.unwrap_or_else(|e|{ - println!("Error select: {}", e); - User::default() - }); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("No name '{}' found",&user_login.name)); - // User not exists - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error data" - ).into_response(); - } - if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created { - let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_login.name, &user_sel.status)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error status" - ).into_response(); - } - let result = if source == "from_login_openid" { - format!("{}:true", "OK") - } else { - if verify_password(&user_login.password, &user_sel.password).is_err() { - let _ = req_handler.trace_req(format!("user '{}' not valid password: {}", &user_login.name, &user_sel.password)); - println!("password NOT valid"); - // TODO - //return Err(error_page(&SignupError::PasswordsDoNotMatch)) - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error data" - ).into_response(); - } - let result = format!("{}:{}", "OK", &user_sel.otp_verified); - if app_dbs.config.totp_mode != TotpMode::No { - if user_login.otp_auth.is_empty() - && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) - { - let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}", &user_login.name, &user_sel.otp_enabled)); - return ( - req_handler.req_header.header, - result - ).into_response(); - } else if user_sel.otp_enabled && user_sel.otp_verified - && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() - { - match req_handler.otp_check(&user_sel.otp_base32, &user_login.otp_auth, &user_sel.otp_defs) { - Ok(val) => { - if !val { - let _ = req_handler.trace_req(format!("user '{}' not valid TOTP code", &user_login.name)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - }, - Err(e) => { - let _ = req_handler.trace_req(format!("user '{}' TOTP check error: {}", &user_login.name, e)); - println!("TOTP check: {}", e); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - } - } - } - result - }; - user_sel.lastaccess = chrono::Utc::now().timestamp().to_string(); - if user_sel.status != UserStatus::Active { user_sel.status = UserStatus::Active } - let user_data = user_sel.session_data(); - match user_sel.update(&app_dbs.user_store).await { - Ok(_) => { - let session_token = req_handler.new_token(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - let new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; - req_handler = ReqHandler::new( - req_handler.req_header, - &app_dbs, - &uri, - &new_auth_state, - &random, - "post_login_handler" - ); - let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie)); - // TODO Is this a bug ?? - // http -> tokio-runtime-worker' panicked at 'payload claims content-length of 10, custom content-length header claims 34 - if app_dbs.config.protocol == "https" { - ( - req_handler.req_header.header, - result - ).into_response() - } else { - ( - result - ).into_response() - } - }, - Err(e) => { - let _ = req_handler.trace_req(format!("user '{}' update error: {}",&user_login.name,e)); - ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response() - } - } - } - async fn post_login_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_login): Json, - ) -> Response { - login_user ( header, uri, - app_dbs, cookies, random, - app_connect_info, - user_login, - "post_login" - ).await - } - async fn from_login_openid_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Extension(openid_clients): Extension>>, - //axum::extract::Path(data): axum::extract::Path, - data: axum::extract::Query, - // Json(user_login): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let user_data = auth_state.user_data(); - //let _req_handler = ReqHandler::new( - // ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - // &app_dbs, - // &uri, - // &auth_state, - // &random, - // "post_login_handler" - //); - if user_data.len() > 1 && user_data[1] == data.state { - println!("OK State {} == {}", user_data[1], data.state); - } - let mut user_login = UserLogin::default(); - if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) { - if let Some(openid_cli) = openid_clients.get(&user_data[0]) { - match openid_conf.get_token(openid_cli, data.code.to_owned(), user_data[2].to_owned()).await { - Ok(data) => { - user_login = UserLogin { - name: data.name.to_owned(), - password: String::from(""), - otp_auth: String::from(""), - email: data.email.to_owned(), - }; - }, - Err(e) => { - println!("Error openid login handler: {}", e); - } - } - } - } - if ! user_login.name.is_empty() { - let _res = login_user(header, uri, - app_dbs, cookies, random, - app_connect_info, - user_login, - "from_login_openid" - ).await; - } - let url="https://tiicl.tls13.io:8800"; - Redirect::temporary(&format!("{}",url)).into_response() - } - async fn logout_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "logout_handler" - ); - let id = auth_state.id(); - let res = auth_state.destroy(); - cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); - if app_dbs.config.verbose > 1 { println!("Session: {} destroyed: {}",&id, &res); } - let _ = req_handler.trace_req(format!("Session '{}' logout",&id)); - let auth_state = AuthState::default(); - req_handler = ReqHandler::new( - req_handler.req_header, - &app_dbs, - &uri, - &auth_state, - &random, - "logout_handler" - ); - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - // these is to use user_items like color_theme - // let user_items = User::hash_items(&auth_state.user_items()); - // req_handler.context.insert("usr_items", &user_items); - req_handler.context.insert("with_menu", "1"); - let result = if let Some(tpl) = app_dbs.config.tpls.get("logout") { - req_handler.render_template(&tpl,"logout") - } else { - String::from("logout") - }; - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn check_item_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_item): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "logout_handler" - ); - let result = if user_item.name == "password" { - let _ = req_handler.trace_req(format!("Password estimate")); - User::estimate_password(&user_item.value) - } else { - let user_sel = User::select(&user_item.name, &user_item.value, false,&app_dbs.user_store).await.unwrap_or_default(); - if !user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&user_item.name,&user_item.value)); - // User not exists - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - String::from("OK") - }; - ( - req_handler.req_header.header, - result.to_owned() - ).into_response() - } - // async fn edit_user_handler( - // header: HeaderMap, - // uri: Uri, - // Extension(app_dbs): Extension>, - // Extension(cookies): Extension, - // //_auth_state: AuthState, - // ) -> Response { - // let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - // let mut req_handler = ReqHandler::new( - // ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string())), - // &app_dbs, - // &uri, - // &auth_state, - // "root_handler" - // ); - // let uri_path = format!("{}",&uri.path().to_string()); - // let file = "hello.html"; - // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ - // println!("Error render {}: {}",&file,e); - // String::from("") - // }); - // req_handler.context.insert("with_menu", "1"); - // // let mut new_header = header.to_owned(); - // //new_header.append("Set-Cookie", "session_token=_; Max-Age=0".parse().unwrap()); - // // cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); - // ( - // req_handler.req_header.header, - // result.to_owned() - // ).into_response() - // // "Hello, World!" - // } - async fn user_settings_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - if auth_state.session.is_none() { - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - } - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "user_settings_handler" - ); - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - let user_id = auth_state.user_id(); - if user_id.is_empty() { - let _ = req_handler.trace_req(format!("user not id found")); - // User not exists - return Redirect::temporary( &format!("/")).into_response(); - } - let user_sel = User::select("id", &user_id, true, &app_dbs.user_store).await.unwrap_or_default(); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("Edit user id '{}' not found ",&user_id)); - // User not exists - return Redirect::temporary( &format!("/")).into_response(); - } - req_handler.context.insert("with_menu", "1"); - req_handler.context.insert("user", &user_sel); - req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); - req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); - // let user_items = User::hash_items(&user_sel.items); - // req_handler.context.insert("usr_items", &user_items); - req_handler.context.insert("no_edit", "true"); - req_handler.context.insert("edit_target", "main"); - let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { - req_handler.render_template(&tpl,"user setting") - } else { - String::from("user settings") - }; - let _ = req_handler.trace_req(format!("User '{}' settings",&user_sel.id)); - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn user_settings_edit_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - axum::extract::Path(data): axum::extract::Path, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - if auth_state.session.is_none() { - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - } - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "user_settings_edit_handler" - ); - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - if data != "main" && data != "password" && data != "totp" { - let _ = req_handler.trace_req(format!("Edit user not data section '{}' ",&data)); - return Redirect::temporary( &format!("/")).into_response(); - } - let user_id = auth_state.user_id(); - if user_id.is_empty() { - // User not exists - let _ = req_handler.trace_req(format!("Edit user not id ")); - return Redirect::temporary( &format!("/")).into_response(); - } - let user_sel = User::select("id", &user_id, true, &app_dbs.user_store).await.unwrap_or_default(); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("Edit user id '{}' not found ",&user_id)); - // User not exists - return Redirect::temporary( &format!("/")).into_response(); - } - req_handler.context.insert("with_menu", "1"); - req_handler.context.insert("user", &user_sel); - req_handler.context.insert("edit_target", &data); - req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); - req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); - if data == "totp" && app_dbs.config.totp_mode != TotpMode::No { - if !user_sel.otp_base32.is_empty() { - match req_handler.otp_make(&user_sel.otp_base32, &user_sel.otp_defs) { - Ok(totp) => { - req_handler.context.insert("otp_code", &user_sel.otp_base32); - req_handler.context.insert("otp_url", &user_sel.otp_auth_url); - req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); - }, - Err(e) => { - println!("User settings error totp: {}",e); - } - } - } else { - match req_handler.otp_generate() { - Ok(totp) => { - req_handler.context.insert("otp_code", &totp.get_secret_base32()); - req_handler.context.insert("otp_url", &totp.get_url()); - req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); - }, - Err(e) => { - println!("Error TOTP generartor: {}",e); - } - } - } - req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); - req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm)); - } - let _ = req_handler.trace_req(format!("Edit user '{}' settings",&user_id)); - // req_handler.context.insert("no_edit", "true"); - let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { - req_handler.render_template(&tpl,"user setting") - } else { - String::from("user settings") - }; - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn post_user_settings_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_data): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_user_settings_handler" - ); - if auth_state.session.is_none() { - let _ = req_handler.trace_req(format!("No session found for user '{}'",&user_data.id)); - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - } - let user_id = auth_state.user_id(); - if user_id.is_empty() { - let _ = req_handler.trace_req(format!("No user found")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let mut user_sel = User::select("id", &user_id, false,&app_dbs.user_store).await.unwrap_or_default(); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("User '{}' not found",&user_id)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - if !user_data.password.is_empty() { - let passwd_score = User::password_score(&user_data.password); - if passwd_score < app_dbs.config.password_score { - let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" - ,&user_id,&user_data.password, passwd_score,app_dbs.config.password_score - )); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - user_sel.password = generate_hash(&user_data.password); - } - - let otp_enabled: bool; - if app_dbs.config.totp_mode != TotpMode::No && !user_data.otp_auth.is_empty() { - match req_handler.otp_check(&user_data.otp_code,&user_data.otp_auth, "") { - Ok(val) => { - if val { - otp_enabled = true; - } else { - // otp_enabled = fasle; - let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_id)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - }, - Err(e) => { - println!("TOTP check: {}", e); - let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_id,e)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - } - } else { - otp_enabled = false; - } - if otp_enabled { - user_sel.otp_enabled= true; - user_sel.otp_verified= true; - user_sel.otp_base32 = user_data.otp_code.to_owned(); - user_sel.otp_auth_url = user_data.otp_url.to_owned(); - user_sel.otp_defs = format!("{},{}", - &app_dbs.config.totp_digits, - format!("{}",&app_dbs.config.totp_algorithm), - ); - } else { - user_sel.otp_enabled= false; - user_sel.otp_verified = false; - user_sel.otp_base32 = String::from(""); - user_sel.otp_auth_url = String::from(""); - user_sel.otp_defs = String::from(""); - } - user_sel.from_data(user_data); - let user_data = user_sel.session_data(); - match user_sel.update(&app_dbs.user_store).await { - Ok(_) => { - let session_token = auth_state.id(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - let _ = req_handler.trace_req(format!("User '{}' updated",&user_id)); - let result =String::from("OK"); - ( - req_handler.req_header.header, - result.to_owned() - ).into_response() - }, - Err(e) => { - let _ = req_handler.trace_req(format!("User '{}' update error: {}",&user_id,e)); - ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response() - } - } - } - async fn post_reset_password_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_item): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_reset_password_handler" - ); - if ! app_dbs.config.use_mail { - let _ = req_handler.trace_req(format!("Mail disabled in config, user '{}' password can not be reset",&user_item.name)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error no service" - ).into_response(); - } - if user_item.name.is_empty() { - let _ = req_handler.trace_req(format!("No user name")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let field = if user_item.name.contains("@") { - "email" - } else { - "name" - }; - let mut user_sel = User::select(&field, &user_item.name, false, &app_dbs.user_store).await.unwrap_or_else(|e|{ - println!("Error select: {}", e); - User::default() - }); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("User '{}' = '{}' not found",&field,&user_item.name)); - // User not exists - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error data" - ).into_response(); - } - if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created { - let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_item.name, &user_sel.status)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error status" - ).into_response(); - } - if app_dbs.config.totp_mode != TotpMode::No { - if user_sel.otp_base32.is_empty() - && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) - { - let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}",&user_sel.name, &user_sel.otp_enabled)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error data" - ).into_response(); - } else if user_sel.otp_enabled && user_sel.otp_verified - && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() - { - match req_handler.otp_check(&user_sel.otp_base32,&user_item.value, &user_sel.otp_defs) { - Ok(val) => { - if !val { - let _ = req_handler.trace_req(format!("User '{}' not valid TOTP code",&user_item.name)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - }, - Err(e) => { - println!("TOTP check: {}", e); - let _ = req_handler.trace_req(format!("User '{}' TOTP check error: {}",&user_item.name,e)); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - } - } - } - let session_token = req_handler.new_token(); - user_sel.status = UserStatus::Pending; - let user_data = user_sel.session_data(); - let session_cookie = add_session_cookie(false,&cookies, &session_token, &user_data, app_dbs.config.session_expire, &app_dbs, "invitation").await; - let session_encoded_key = encode(session_cookie.as_str()); - let body=format!("This is a user password reset request for docserver service"); - let subject = format!("DocServer password reset"); - let reset_url= format!( - "{}://{}/reset", - &app_dbs.config.protocol, - &app_dbs.config.hostport, - ); - let reset_expiration = format!("{} minutes",(&app_dbs.config.session_expire/60)); - req_handler.context.insert("reset_url", &reset_url); - req_handler.context.insert("reset_expiration", &reset_expiration); - req_handler.context.insert("email_body",&body); - req_handler.context.insert("reset_key",&session_encoded_key); - req_handler.context.insert("email_subject",&subject); - let mail_content= if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_txt") { - req_handler.render_template(tpl, "reset password") - } else { - format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie) - }; - let mail_html_content = if let Some(tpl) = app_dbs.config.tpls.get("reset_password_mail_html") { - req_handler.render_template(tpl, "invite") - } else { - format!("{}\n{}\n{}/{}\n",&subject,&body,&reset_url,&session_cookie) - }; - let mail_check = MailMessage::check(&app_dbs); - if ! mail_check.is_empty() { - let _ = req_handler.trace_req(format!("Mail service check error: {}",&mail_check)); - ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error service" - ).into_response() - } else { - match MailMessage::new( - &app_dbs.config.mail_from, - &user_sel.email, - &app_dbs.config.mail_reply_to, - ) { - Ok(mail_message) => { - match mail_message.send_html_message( - &subject, - &mail_content, - &mail_html_content, - &app_dbs - ).await { - Ok(_) => { - let _ = req_handler.trace_req(format!("Mail sent to: '{}' reset url: {} cookie: {}", - &user_sel.name, &reset_url,&session_cookie - )); - ( - StatusCode::OK, - format!("Mail sent to {}",&user_sel.name) - ).into_response() - }, - Err(e) => { - let _ = req_handler.trace_req(format!("Mail message send to: '{}' Error: {} ",&user_sel.name,e)); - println!("Error mail message send: {}",e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - req_handler.req_header.header, - "Error service" - ).into_response() - } - } - }, - Err(e) => { - let _ = req_handler.trace_req(format!("Mail message send to: '{}' Creation error: {} ",&user_sel.name,e)); - println!("Error mail message creation: {}",e); - ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error service" - ).into_response() - } - } - } - //let result=format!("{}","OK"); - // if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { - // return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - // // return ( - // // StatusCode::UNAUTHORIZED, - // // header, - // // "Error authorization" - // // ).into_response(); - // } - // ( - // //status, - // req_handler.req_header.header, - // result, - // ).into_response() - } - async fn reset_password_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - // Query(req_params): Query, - axum::extract::Path(data): axum::extract::Path, - ) -> Response { - let session_cookie = decode(&data).unwrap_or_default().to_string(); - let auth_state = AuthState::from_cookie(session_cookie.to_owned(), &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "reset_password_handler" - ); - let user_id = auth_state.user_id(); - if user_id.is_empty() { - let _ = req_handler.trace_req(format!("No user found")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let user_sel = User::select("id", &user_id, false, &app_dbs.user_store).await.unwrap_or_else(|e|{ - println!("Error select: {}", e); - User::default() - }); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("User 'id' = '{}' not found",&user_id)); - // User not exists - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error data" - ).into_response(); - } - if user_sel.status != UserStatus::Active { - let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_id, &user_sel.status)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error status" - ).into_response(); - } - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - req_handler.context.insert("with_menu", "1"); - req_handler.context.insert("user", &user_sel); - req_handler.context.insert("edit_target", "password"); - req_handler.context.insert("edit_reset", "password"); - req_handler.context.remove("web_menu_items"); - let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") { - req_handler.render_template(&tpl,"user setting") - } else { - String::from("user settings") - }; - let user_data = user_sel.session_data(); - let session_token = req_handler.new_token(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - let _ = req_handler.trace_req(format!("user '{}' reset password",&user_id)); - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn post_user_password_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_data): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_reset_password_handler" - ); - if user_data.password.is_empty() { - let _ = req_handler.trace_req(format!("No passwordfound")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - if auth_state.session.is_none() { - let _ = req_handler.trace_req(format!("No session found for user '{}'",&user_data.id)); - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - } - let user_id = auth_state.user_id(); - if user_id.is_empty() { - let _ = req_handler.trace_req(format!("No user found")); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let mut user_sel = User::select("id", &user_id, false,&app_dbs.user_store).await.unwrap_or_default(); - if user_sel.name.is_empty() { - let _ = req_handler.trace_req(format!("User '{}' not found",&user_id)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response(); - } - let passwd_score = User::password_score(&user_data.password); - if passwd_score < app_dbs.config.password_score { - let _ = req_handler.trace_req(format!("User '{}' password '{}' score: {} under {}" - ,&user_id,&user_data.password, passwd_score,app_dbs.config.password_score) - ); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); - } - user_sel.password = generate_hash(&user_data.password); - let user_data = user_sel.session_data(); - match user_sel.update(&app_dbs.user_store).await { - Ok(_) => { - let session_token = auth_state.id(); - let session_cookie = add_session_cookie(true,&cookies, &session_token, &user_data, 0, &app_dbs, "/").await; - if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; - let _ = req_handler.trace_req(format!("user '{}' reset password OK",&user_id)); - let result =String::from("OK"); - ( - req_handler.req_header.header, - result.to_owned() - ).into_response() - }, - Err(e) => { - let _ = req_handler.trace_req(format!("Error user '{}' reset password: {}",&user_id,e)); - ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error" - ).into_response() - }, - } - } - async fn invite_signup_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - axum::extract::Path(data): axum::extract::Path, - // auth_state: AuthState, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "invite_signup_handler" - ); - if ! app_dbs.config.signup_mode.contains("invitation") { - let msg = format!("Config signup mode not invitation: {}", - app_dbs.config.signup_mode - ); - println!("{}", &msg); - let _ = req_handler.trace_req(msg); - return Redirect::temporary( &format!("/")).into_response(); - } - // println!("root_handler: {}",&session_cookie); - let session_cookie = decode(&data).unwrap_or_default().to_string(); - let auth_state = AuthState::from_cookie(session_cookie.to_owned(), &app_dbs).await; - if auth_state.session.is_none() { - // TODO make it prettier - let _ = req_handler.trace_req(format!("No session found")); - return ( - StatusCode::NOT_FOUND, - req_handler.req_header.header, - "No valid invitation found" - ).into_response(); - } - //let user_data = auth_state.user_data(); - let mut usr = User::default(); - usr.roles = auth_state.user_roles(); - usr.email = auth_state.user_email(); - let usr_id = auth_state.user_id(); - let invite_id = if usr_id == "0" { - usr_id - } else { - usr.email.to_owned() - }; - // let _uri_path = format!("{}",&uri.path().to_string()); - // let file = "hello.html"; - // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ - // println!("Error render {}: {}",&file,e); - // String::from("") - // }); - - let mut res_headers = HeaderMap::new(); - if req_handler.req_header.is_browser() { - res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); - } - req_handler.context.insert("user", &usr); - req_handler.context.insert("isadmin", ""); - req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); - req_handler.context.insert("invite_key", &data); - req_handler.context.insert("invite_id", &invite_id); - req_handler.context.insert("admin_fields", &app_dbs.config.admin_fields); - let result = if let Some(tpl) = app_dbs.config.tpls.get("signup") { - req_handler.render_template(&tpl,"signup") - } else { - String::from("signup") - }; - let _ = req_handler.trace_req(format!("Invite to '{}' data: {}",&invite_id, &data)); - ( - res_headers, - result.to_owned() - ).into_response() - } - async fn post_invite_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_invite): Json, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "post_invite_handler" - ); - if ! app_dbs.config.signup_mode.contains("invitation") { - let msg = format!("Config signup mode not invitation: {}", - app_dbs.config.signup_mode - ); - println!("{}", &msg); - let _ = req_handler.trace_req(msg); - return Redirect::temporary( &format!("/")).into_response(); - } - if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { - let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - // return ( - // StatusCode::UNAUTHORIZED, - // header, - // "Error authorization" - // ).into_response(); - } - if ! user_invite.email.contains("@") { - let _ = req_handler.trace_req(format!("Invite email '{}' not contains '@'",&user_invite.email)); - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error invitation data" - ).into_response(); - } - let session_token = req_handler.new_token(); - let mut usr = User::default(); - usr.email = user_invite.email.to_owned(); - usr.roles = user_invite.roles.to_owned(); - usr.isadmin = user_invite.isadmin; - usr.status = UserStatus::Pending; - let user_data = usr.session_data(); - let session_cookie = add_session_cookie(false,&cookies, &session_token, &user_data, user_invite.expire, &app_dbs, "invitation").await; - let session_encoded_key = encode(session_cookie.as_str()); - let body=format!("This is an invitation to docserver service"); - let subject = format!("DocServer Invitation"); - let signup_url= format!( - "{}://{}/signup", - &app_dbs.config.protocol, - &app_dbs.config.hostport, - ); - let invite_expiration = format!("{} minutes", - (&app_dbs.config.session_expire/60) - ); - req_handler.context.insert("signup_url", &signup_url); - req_handler.context.insert("invite_expiration", &invite_expiration); - req_handler.context.insert("email_body",&body); - req_handler.context.insert("invite_key",&session_encoded_key); - req_handler.context.insert("email_subject",&subject); - let (status, result) = if app_dbs.config.use_mail && user_invite.send_email { - let mail_content= if let Some(tpl) = app_dbs.config.tpls.get("invite_mail_txt") { - req_handler.render_template(tpl, "invite") - } else { - format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) - }; - let mail_html_content = if let Some(tpl) = app_dbs.config.tpls.get("invite_mail_html") { - req_handler.render_template(tpl, "invite") - } else { - format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) - }; - let mail_check = MailMessage::check(&app_dbs); - if ! mail_check.is_empty() { - ( - StatusCode::BAD_REQUEST, - mail_check - ) - } else { - //"jesus.perezlorenzo@gmail.com", - // "jesus@librecloud.online", - match MailMessage::new( - &app_dbs.config.mail_from, - &user_invite.email, - &app_dbs.config.mail_reply_to, - ) { - Ok(mail_message) => { - //match mail_message.send_message( - match mail_message.send_html_message( - &subject, - &mail_content, - &mail_html_content, - &app_dbs - ).await { - Ok(_) => { - let _ = req_handler.trace_req(format!( - "Invitation mail sent to: '{}' reset url: {} roles: {}, isadmin: {}, expiration: {} cookie: {}", - &user_invite.email, &invite_expiration, user_invite.roles, user_invite.isadmin, &signup_url,&session_cookie - )); - (StatusCode::OK, format!("Mail sent to {}",&user_invite.email)) - }, - Err(e) => { - println!("Invitation to: {} Error mail message send: {}",&user_invite.email, e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - String::from("Error") - ) - } - } - }, - Err(e) => { - println!("Invitation to: {} Error mail message creation: {}",&user_invite.email, e); - ( - StatusCode::BAD_REQUEST, - String::from("Error") - ) - } - } - } - } else { - let _ = req_handler.trace_req(format!( - "Created invitation: '{}' reset url: {} roles: {}, isadmin: {}, expiration: {} cookie: {}", - &user_invite.email, &invite_expiration, user_invite.roles, user_invite.isadmin, &signup_url,&session_cookie - )); - (StatusCode::OK, format!("No mail sent to {}",&user_invite.email)) - }; - req_handler.prepare_response(); - req_handler.context.insert("email_result",&result); - let response = if let Some(tpl) = app_dbs.config.tpls.get("invite_output") { - req_handler.render_template(tpl, "invite") - } else { - format!("{}\n{}\n{}/signup/{}\n",&subject,&body,&signup_url,&session_cookie) - }; - ( - status, - req_handler.req_header.header, - response, - ).into_response() - } - async fn invite_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let mut req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "invite_handler" - ); - if ! app_dbs.config.signup_mode.contains("invitation") { - let msg = format!("Config signup mode not invitation: {}", - app_dbs.config.signup_mode - ); - println!("{}",&msg); - let _ = req_handler.trace_req(msg); - return Redirect::temporary( &format!("/")).into_response(); - } - if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { - let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - // return ( - // StatusCode::UNAUTHORIZED, - // header, - // "Error authorization" - // ).into_response(); - } - - let title = format!("DocServer Invitation"); - let invite_url= format!( - "{}://{}/invite", - &app_dbs.config.protocol, - &app_dbs.config.hostport, - ); - let invite_expire = format!("{} minutes", - (&app_dbs.config.invite_expire/60) - ); - req_handler.context.insert("target_url", &invite_url); - req_handler.context.insert("invite_expire", &invite_expire); - if app_dbs.config.use_mail { - req_handler.context.insert("use_mail", &app_dbs.config.use_mail); - } - req_handler.prepare_response(); - req_handler.context.insert("with_menu", "1"); - let response = if let Some(tpl) = app_dbs.config.tpls.get("invite_create") { - req_handler.render_template(tpl, "invite create") - } else { - format!("{} invite",&title) - }; - let _ = req_handler.trace_req(format!( - "Invitation: url: {}, expiration: {}", - &invite_url, &invite_expire, - )); - ( - req_handler.req_header.header, - response, - ).into_response() - // let uri_path = format!("{}",&uri.path().to_string()); - // let file = "hello.html"; - // let result = app_dbs.tera.render(&file, &app_dbs.context).unwrap_or_else(|e|{ - // println!("Error render {}: {}",&file,e); - // String::from("") - // }); - // let mut new_header = header.to_owned(); - //new_header.append("Set-Cookie", "session_token=_; Max-Age=0".parse().unwrap()); - // cookies.remove(Cookie::new(SESSION_COOKIE_NAME, "")); - // ( - // header, - // result.to_owned() - // ).into_response() - // "Hello, World!" - } - async fn update_user_item_handler( - header: HeaderMap, - uri: Uri, - Extension(app_dbs): Extension>, - Extension(cookies): Extension, - Extension(random): Extension, - ConnectInfo(app_connect_info): ConnectInfo, - Json(user_item): Json, - //_auth_state: AuthState, - //axum::extract::Path(data): axum::extract::Path, - ) -> Response { - let auth_state = get_auth_state(true, &cookies, &app_dbs).await; - let req_handler = ReqHandler::new( - ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), - &app_dbs, - &uri, - &auth_state, - &random, - "update_user_item_handler" - ); - if ! auth_state.has_auth_role(&app_dbs.config.auth_roles) { - let _ = req_handler.trace_req(format!("User '{}' not have role 'dev'",&auth_state.user_id())); - return Redirect::temporary( &format!("/login?o={}",uri.path().to_string())).into_response(); - // return ( - // StatusCode::UNAUTHORIZED, - // header, - // "Error authorization" - // ).into_response(); - } - dbg!("{}",&user_item); - let result=""; - ( - //status, - req_handler.req_header.header, - result, - ).into_response() - } route("/", get(main_handler)) // .route("/auto", get(auto_handler)) - - .route("/login", get(login_handler)) - .route("/login", post(post_login_handler)) - - .route("/openid/:data", get(login_openid_handler)) - //.route("/from_openid/:data", get(from_login_openid_handler)) - .route("/from_openid", get(from_login_openid_handler)) - - .route("/signup", get(signup_handler)) - .route("/signup", post(post_signup_handler)) - .route("/signup/:data", get(invite_signup_handler)) - - .route("/logout", get(logout_handler)) - - .route("/check", post(check_item_handler)) - - .route("/invite", get(invite_handler)) - .route("/invite", post(post_invite_handler)) - - .route("/reset/:data", get(reset_password_handler)) - .route("/reset", post(post_reset_password_handler)) - .route("/resetup", post(post_user_password_handler)) - - .route("/settings", get(user_settings_handler)) - .route("/settings/:item", get(user_settings_edit_handler)) -// .route("/update", post(update_user_handler)) - .route("/settings", post(post_user_settings_handler)) - .route("/update_item", post(update_user_item_handler)) + .merge(users_login_router_handlers()) + .merge(users_password_router_handlers()) + .merge(users_invite_router_handlers()) + .merge(users_settings_router_handlers()) } diff --git a/src/main.rs b/src/main.rs index 4c4bcad..884b9ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,9 @@ static PKG_FULLNAME: Lazy = Lazy::new(|| { }); pub const USERS_TABLENAME: &str = "users"; +pub const OPENID_USERS_TABLENAME: &str = "openid_users"; pub const USERS_FILESTORE: &str = "users"; +pub const OPENID_USERS_FILESTORE: &str = "openid_users"; pub const DEFAULT_ROLES: &str = "user"; diff --git a/src/users/user.rs b/src/users/user.rs index 776c5b9..7aa5aef 100644 --- a/src/users/user.rs +++ b/src/users/user.rs @@ -480,11 +480,11 @@ impl User { ) } pub fn hash_items(items: &str) -> HashMap { - if items.is_empty() { + if items.is_empty() || ! items.starts_with("{") { HashMap::new() } else { serde_json::from_str(items).unwrap_or_else(|e|{ - println!("Error to convert user items to json: {}",e); + println!("Error to pars string to user items: {}",e); HashMap::new() }) } diff --git a/src/users/userdata.rs b/src/users/userdata.rs index 3506f6a..77da15c 100644 --- a/src/users/userdata.rs +++ b/src/users/userdata.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; -use serde::{Deserialize,Serialize}; +use serde::{Deserialize,Serialize, Deserializer}; // use crate::defs::AppDBs; +use crate::users::User; fn default_empty() -> String { "".to_string() @@ -21,9 +22,19 @@ fn default_isadmin() -> bool { fn default_otp_empty() -> String { String::from("") } +fn deserialize_id<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + Ok(String::deserialize(deserializer)?) +} + +#[derive(Default,Deserialize,Serialize,Debug,Clone)] +pub struct AllUserData { + pub user: User, + pub openids: String, +} #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct UserData { - #[serde(default = "default_empty")] + #[serde(default = "default_empty",deserialize_with = "deserialize_id")] pub id: String, #[serde(default = "default_empty")] pub name: String, @@ -45,6 +56,8 @@ pub struct UserData { pub roles: String, #[serde(default = "default_items")] pub items: HashMap, + #[serde(default = "default_empty")] + pub opendis: String, } // impl UserData { // pub fn from_id(id: String, _app_dbs: &AppDBs) -> Self {