chore: update to new crates versions and fix sources for it
Some checks are pending
Install / Cargo install on ubuntu-latest (push) Waiting to run

This commit is contained in:
Jesús Pérez 2024-08-14 17:34:50 +01:00
parent 7fe3d25788
commit 8ba9afbce3
No known key found for this signature in database
20 changed files with 628 additions and 130 deletions

View File

@ -14,65 +14,67 @@ default = "inrepo"
#rustdoc-args = ["--html-in-header", "rusdoc/header.html"]
[dependencies]
log = { version = "0.4.20", features = ["max_level_trace","release_max_level_trace"], package = "log" }
axum = { git = "https://github.com/tokio-rs/axum.git", branch = "main" }
#axum = { version = "0.6.20" } # use wrks axum_6
axum-server = { version = "0.5.1", features = ["tls-rustls"] }
tokio = { version = "1.32.0", features = ["full"] }
tower = { version = "0.4.13", features = ["util", "filter"] }
tower-http = { version = "0.4.4", features = ["fs", "cors", "trace", "add-extension", "auth", "compression-full"] }
tower-cookies = "0.9"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
log = { version = "0.4.22", features = ["max_level_trace","release_max_level_trace"], package = "log" }
#axum = { git = "https://github.com/tokio-rs/axum.git", branch = "main" }
axum = { version = "0.7.5" } # use wrks axum_6
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
tokio = { version = "1.39.2", features = ["full"] }
tower = { version = "0.5.0", features = ["util", "filter"] }
tower-http = { version = "0.5.2", features = ["fs", "cors", "trace", "add-extension", "auth", "compression-full"] }
tower-cookies = "0.10.0"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
casbin = { version = "2.0.9", features = ["cached","explain","logging"], optional = true}
casbin = { version = "2.2.0", features = ["cached","explain","logging"], optional = true}
pasetoken-lib= {path = "./pasetoken", package = "pasetoken-lib" }
pasetors = { version = "0.6.7" }
pasetors = { version = "0.6.8" }
serde = { version = "1.0.188", features = ["derive"] }
serde_derive = "1.0.188"
serde_json = "1.0.107"
toml = "0.8.0"
serde = { version = "1.0.207", features = ["derive"] }
serde_derive = "1.0.207"
serde_json = "1.0.124"
toml = "0.8.19"
clap = { version = "4.4.4", features = ["derive"] }
git-version = "0.3.5"
once_cell = "1.18.0"
clap = { version = "4.5.15", features = ["derive"] }
git-version = "0.3.9"
once_cell = "1.19.0"
hyper = { version = "0.14.27", features = ["full"] }
tera = "1.19.1"
html-minifier = "4.0.0"
hyper = { version = "1.4.1", features = ["full"] }
tera = "1.20.0"
html-minifier = "5.0.0"
urlencoding = "2.1.3"
password-hash = { version = "0.5", features = ["alloc", "rand_core"] }
rand_core = { version = "0.6", features = ["getrandom"] }
rand_core = { version = "0.6.4", features = ["getrandom"] }
argon2 = { version = "0.5", default-features = false, features = ["alloc", "simple"]}
rand_chacha = "0.3.1"
async-trait = "0.1.73"
async-trait = "0.1.81"
async-session = "3.0.0"
async-sqlx-session = { version = "0.4.0", features = ["sqlite"] }
forwarded-header-value = "0.1.1"
lettre = { version = "0.10.4", features = ["smtp-transport", "tokio1", "tokio1-native-tls", "builder"] }
lettre = { version = "0.11.7", features = ["smtp-transport", "tokio1", "tokio1-native-tls", "builder"] }
chrono = { version = "0.4.*" }
encoding = "0.*"
chrono = { version = "0.4.38" }
encoding = "0.2.33"
uuid = { version = "1.4.1", features = ["v4", "serde"] }
rand = "0.8.5"
walkdir = "2.4.0"
walkdir = "2.5.0"
binascii = "0.1.4"
anyhow = "1.0.75"
anyhow = "1.0.86"
totp-rs = { version = "5.0.2", features = ["qr","otpauth"] }
base32 = "0.4.0"
zxcvbn = "2.2.2"
totp-rs = { version = "5.6.0", features = ["qr","otpauth"] }
base32 = "0.5.1"
zxcvbn = "3.1.0"
futures= "0.3.28"
futures= "0.3.30"
#sqlx = { version = "0.8.0", features = ["sqlite", "runtime-tokio-native-tls"] }
#sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] }
#sqlx = { version = "0.8.0", features = ["all-types", "all-databases","runtime-tokio-native-tls"] }
# async-sqlx-session 0.4.0 still running in 0.6.2
sqlx = { version = "0.6.3", features = ["all-types", "all-databases","runtime-tokio-native-tls"] }
#sqlx = { version = "0.7.1", features = ["all-databases","runtime-tokio-native-tls"] }
sqlx = { version = "0.6.3", features = ["all-databases","runtime-tokio-native-tls"] }
openidconnect = { version = "3.3.1", default-features = true, features = [] }
url = { version = "2.4", features = [] }
jsonwebtoken = "8"
jsonwebtoken = "9.3.0"
[dev-dependencies]
async-std = { version = "1.12.0", features = ["default","attributes"] }

View File

@ -5,6 +5,7 @@ mod filestore;
mod session;
#[cfg(feature = "authstore")]
mod authz;
mod form_defs;
mod appdbs;
mod req_headermap;
mod req_handler;
@ -15,6 +16,7 @@ mod totp_mode;
mod totp_algorithm;
mod app_connect_info;
mod openid;
mod req_settings;
pub(crate) use appdbs::AppDBs;
pub(crate) use session::{
@ -32,6 +34,7 @@ pub(crate) use filestore::FileStore;
#[cfg(feature = "authstore")]
pub(crate) use authz::AuthStore;
pub(crate) use config::{ServPath,Config};
pub(crate) use form_defs::FormDefs;
pub(crate) use cli::parse_args;
pub(crate) use req_handler::ReqHandler;
pub(crate) use req_headermap::ReqHeaderMap;
@ -46,6 +49,8 @@ pub(crate) use app_connect_info::AppConnectInfo;
pub(crate) use openid::{OpenidConf,OpenidData,collect_openid_clients,OpenidCli};
pub(crate) use req_settings::ReqSettings;
pub const TOKEN_KEY_VALUE: &str = "tii-cl";
pub const TOKEN_AUTH_VALUE: &str = "tii-cl-token";
pub const CLAIM_UID: &str = "uid";

View File

@ -1,26 +1,28 @@
use std::net::SocketAddr;
use hyper::server::conn::AddrStream;
//use hyper::server::conn::AddrStream;
use axum::extract::connect_info::Connected;
use axum::serve::IncomingStream;
#[derive(Clone, Debug)]
pub struct AppConnectInfo {
pub remote_addr: SocketAddr,
pub local_addr: SocketAddr,
pub remote_addr: SocketAddr,
// pub local_addr: SocketAddr,
}
impl Connected<IncomingStream<'_>> for AppConnectInfo {
fn connect_info(target: IncomingStream<'_>) -> Self {
AppConnectInfo {
remote_addr: target.remote_addr(),
local_addr: target.local_addr().unwrap_or(target.remote_addr()),
// local_addr: target.local_addr().unwrap_or(target.remote_addr()),
}
}
}
impl Connected<&AddrStream> for AppConnectInfo {
fn connect_info(target: &AddrStream) -> Self {
// impl Connected<&AddrStream> for AppConnectInfo {
// fn connect_info(target: &AddrStream) -> Self {
impl Connected<SocketAddr> for AppConnectInfo {
fn connect_info(target: SocketAddr) -> Self {
AppConnectInfo {
remote_addr: target.remote_addr(),
local_addr: target.local_addr(),
remote_addr: target.to_owned(),
// local_addr: target.to_owned(),
}
}
}

View File

@ -14,6 +14,7 @@ use crate::{
FromFile,
load_from_file,
load_dict_from_file,
ReqSettings,
},
};
@ -70,6 +71,9 @@ fn default_web_menu_item_roles() -> Vec<String> {
fn default_auth_roles() -> Vec<String> {
Vec::new()
}
fn default_roles() -> Vec<String> {
Vec::new()
}
fn default_sub_menu_items() -> Vec<SubMenuItem> {
Vec::new()
}
@ -85,6 +89,9 @@ fn default_config_totp_mode() -> TotpMode {
fn default_config_password_score() -> u8 {
0
}
fn default_config_req_settings() -> ReqSettings {
ReqSettings::default()
}
fn default_config_trace_level() -> u8 { 1 }
fn default_config_openid_auths() -> HashMap<String,OpenidConf> { HashMap::new() }
#[derive(Debug, Clone, Serialize, Deserialize,Default)]
@ -229,6 +236,8 @@ pub struct Config {
pub user_store_access: String,
#[serde(default = "default_auth_roles")]
pub auth_roles: Vec<String>,
#[serde(default = "default_roles")]
pub default_roles: Vec<String>,
#[serde(default = "default_config_empty")]
pub trace_store_uri: String,
#[serde(default = "default_config_trace_level")]
@ -294,6 +303,12 @@ pub struct Config {
pub openid_access_token_url: String,
#[serde(default = "default_config_openid_auths")]
pub openid_auths: HashMap<String,OpenidConf>,
#[serde(default = "default_config_resource")]
pub forms_path: String,
#[serde(default = "default_config_resource")]
pub forms_responses_path: String,
#[serde(default = "default_config_req_settings")]
pub req_settings: ReqSettings,
}
impl FromFile for Config {
@ -372,6 +387,8 @@ impl Config {
self.paseto.footer = ConfigPaSeToken::make_footer(
self.paseto.map_footer.to_owned()
).unwrap_or(Footer::new());
self.forms_path = self.fix_item_path(self.forms_path.to_owned());
self.forms_responses_path = self.fix_item_path(self.forms_responses_path.to_owned());
}
#[allow(dead_code)]
pub fn to_json(&self) -> String {

247
src/defs/form_defs.rs Normal file
View File

@ -0,0 +1,247 @@
// use std::collections::HashMap;
use std::{
io::Write,
// sync::Arc,
fmt::Debug,
fs,
path::{Path, PathBuf},
io::{Error, ErrorKind},
};
use serde::{Serialize, Deserialize};
//use serde_json::value::{to_value, Value};
// use log::error;
use crate::{
defs::{
FromFile,
load_from_file,
},
};
// use std::path::Path;
// use crate::tools::generate_uuid;
// fn default_server_uid() -> String {
// generate_uuid(String::from("abcdef0123456789"))
// }
fn default_form_defs_empty() -> String {
"".to_string()
}
fn default_form_selections() -> Vec<FormSelectItem> {
Vec::new()
}
fn default_form_border() -> bool {
false
}
fn default_form_required() -> bool {
false
}
fn default_form_items_empty() -> Vec<FormField> {
Vec::new()
}
fn default_form_overwrite_response() -> bool {
false
}
#[derive(Debug, Clone, Serialize, Deserialize,Default)]
pub struct FormSelectItem {
#[serde(default = "default_form_defs_empty")]
pub title: String,
#[serde(default = "default_form_defs_empty")]
pub defs: String,
#[serde(default = "default_form_defs_empty")]
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize,Default)]
pub struct FormField {
#[serde(default = "default_form_defs_empty")]
pub name: String,
#[serde(default = "default_form_defs_empty")]
pub id: String,
#[serde(default = "default_form_defs_empty")]
pub placeholder: String,
#[serde(default = "default_form_defs_empty")]
pub typ: String,
#[serde(default = "default_form_defs_empty")]
pub css: String,
#[serde(default = "default_form_border")]
pub border: bool,
#[serde(default = "default_form_defs_empty")]
pub label_top_text: String,
#[serde(default = "default_form_defs_empty")]
pub label_css: String,
#[serde(default = "default_form_selections")]
pub selections: Vec<FormSelectItem>,
#[serde(default = "default_form_required")]
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize,Default)]
pub struct FormDefs {
#[serde(default = "default_form_defs_empty")]
pub title: String,
#[serde(default = "default_form_defs_empty")]
pub name: String,
#[serde(default = "default_form_defs_empty")]
pub id: String,
#[serde(default = "default_form_defs_empty")]
pub css: String,
#[serde(default = "default_form_defs_empty")]
pub action: String,
#[serde(default = "default_form_items_empty")]
pub fields: Vec<FormField>,
#[serde(default = "default_form_overwrite_response")]
pub overwrite_response: bool,
}
impl FromFile for FormDefs {
fn fix_root_path<Config>(&mut self, _root_path: String ) {
// if root_path != self.root_path {
// self.root_path = root_path.to_owned();
// }
// if self.root_path.is_empty() || ! Path::new(&self.root_path).exists() {
// return;
// }
}
}
impl FormDefs {
pub fn load_from(path: &str, name: &str) -> Self {
load_from_file(path,name).unwrap_or_else(|e|{
println!("FormDefs error: {}",e);
FormDefs::default()
})
}
pub fn write_data(&self, file_path: &str, data: &str, overwrite: bool) -> std::io::Result<()> {
let check_path = |path: &Path| -> std::io::Result<()> {
if ! Path::new(&path).exists() {
if let Err(e) = std::fs::create_dir(&path) {
return Err(Error::new( ErrorKind::InvalidInput,
format!("Error create path {}: {}",&path.display(), e)
));
// std::process::exit(2)
}
}
Ok(())
};
if file_path.is_empty() || data.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Error save {}",&file_path)
));
}
if ! Path::new(&file_path).exists() {
let path = PathBuf::from(&file_path);
if let Some(dir_path) = path.parent() {
if ! Path::new(&dir_path).exists() {
if let Some(parent_dir_path) = dir_path.parent() {
if ! Path::new(&parent_dir_path).exists() {
let res = check_path(&parent_dir_path);
if res.is_err() { return res; }
}
}
let res = check_path(&dir_path);
if res.is_err() { return res; }
}
}
}
if overwrite || ! Path::new(&file_path).exists() {
fs::write(&file_path, data)?;
println!("Overwrite: {}",&file_path);
} else {
let sid_settings_file = fs::OpenOptions::new()
.write(true)
.append(true) // This is needed to append to file
.open(&file_path);
if let Ok(mut file) = sid_settings_file {
file.write_all(data.as_bytes())?;
}
println!("write: {}",&file_path);
}
Ok(())
}
/*
fn fix_item_path(&mut self, item: String ) -> String {
if !item.is_empty() && ! Path::new(&item).exists() {
format!("{}/{}",&self.root_path,&item)
} else {
item
}
}
pub fn load_items(&mut self) {
self.fix_root_path::<Config>(self.root_path.to_owned());
if !self.path_menu_items.is_empty() {
self.path_menu_items = self.fix_item_path(self.path_menu_items.to_owned());
let data_menu_items = load_from_file(&self.path_menu_items.to_owned(), "menu_items").unwrap_or_else(|e|{
error!("Error loading menu_items from {}: {}",&self.path_menu_items,e);
DataMenuItems::default()
});
self.ui.web_menu_items = data_menu_items.web_menu_items;
}
if !self.path_serv_paths.is_empty() {
self.path_serv_paths = self.fix_item_path(self.path_serv_paths.to_owned());
let data_serv_paths = load_from_file(&self.path_serv_paths.to_owned(), "serv_paths").unwrap_or_else(|e|{
error!("Error loading serv_paths from {}: {}",&self.path_serv_paths,e);
DataServPath::default()
});
self.serv_paths = data_serv_paths.serv_paths;
}
if !self.path_locales_config.is_empty() {
self.path_locales_config = self.fix_item_path(self.path_locales_config.to_owned());
self.locales = load_dict_from_file(&self.path_locales_config.to_owned(), "locales").unwrap_or_else(|e|{
error!("Error loading locales from {}: {}",&self.path_locales_config,e);
HashMap::new()
});
}
if self.users_store_uri.starts_with(FILE_SCHEME) {
self.users_store_uri = format!("{}{}",
FILE_SCHEME, self.fix_item_path(self.users_store_uri.replace(FILE_SCHEME,"")));
}
if self.session_store_uri.starts_with(FILE_SCHEME) {
self.session_store_uri = format!("{}{}",
FILE_SCHEME, self.fix_item_path(self.session_store_uri.replace(FILE_SCHEME,"")));
}
if self.trace_store_uri.starts_with(FILE_SCHEME) {
self.trace_store_uri = format!("{}{}",
FILE_SCHEME, self.fix_item_path(self.trace_store_uri.replace(FILE_SCHEME,"")));
}
self.cert_file = self.fix_item_path(self.cert_file.to_owned());
self.key_file = self.fix_item_path(self.key_file.to_owned());
self.templates_path = self.fix_item_path(self.templates_path.to_owned());
self.defaults_path = self.fix_item_path(self.defaults_path.to_owned());
#[cfg(feature = "authstore")]
if self.authz_store_uri.starts_with(FILE_SCHEME) {
self.authz_store_uri = format!("{}{}",
FILE_SCHEME, self.fix_item_path(self.authz_store_uri.replace(FILE_SCHEME,"")));
}
#[cfg(feature = "casbin")]
{
self.authz_model_path = self.fix_item_path(self.authz_model_path.to_owned());
self.authz_policy_path = self.fix_item_path(self.authz_policy_path.to_owned());
}
self.paseto.public_path = self.fix_item_path(self.paseto.public_path.to_owned());
self.paseto.secret_path = self.fix_item_path(self.paseto.secret_path.to_owned());
(self.paseto.public_data, self.paseto.secret_data) = self.paseto.load_data();
self.paseto.footer = ConfigPaSeToken::make_footer(
self.paseto.map_footer.to_owned()
).unwrap_or(Footer::new());
}
#[allow(dead_code)]
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|e|{
println!("Error to convert Config to json: {}",e);
String::from("")
})
}
// #[allow(dead_code)]
// pub fn st_html_path(&self) -> &'static str {
// Box::leak(self.html_path.to_owned().into_boxed_str())
// }
#[allow(dead_code)]
pub fn full_html_url(&self, url: &str) -> String {
format!("{}://{}:{}/{}",
&self.protocol,&self.bind,&self.port,
url
)
}
*/
}

View File

@ -12,13 +12,18 @@ use openidconnect::{
CoreProviderMetadata,
CoreResponseType,
},
http::{
HeaderMap,
header::CONTENT_TYPE,
Method,
},
reqwest::async_http_client, HttpRequest,
AuthenticationFlow,
ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce,
RedirectUrl, Scope, PkceCodeChallenge, PkceCodeVerifier,
};
use axum::{http::method::Method, http::HeaderMap, http::header::CONTENT_TYPE};
// use axum::{http::method::Method, http::HeaderMap, http::header::CONTENT_TYPE};
use crate::defs::Config;

View File

@ -1,4 +1,4 @@
use axum::http::{header::{HeaderValue},uri::Uri};
use axum::http::{header::HeaderValue,uri::Uri};
use html_minifier::HTMLMinifier;
use std::{
error::Error,
@ -65,6 +65,7 @@ impl<'a> ReqHandler<'a> {
context.insert("main_name", &app_dbs.config.ui.main_name);
context.insert("title", &app_dbs.config.ui.title);
context.insert("subtitle", &app_dbs.config.ui.subtitle);
context.insert("req_settings", &app_dbs.config.req_settings);
context.insert("usr_roles", &auth_state.user_roles());
let user_items = User::hash_items(&auth_state.user_items());
context.insert("usr_items", &user_items);
@ -98,16 +99,16 @@ impl<'a> ReqHandler<'a> {
pub fn prepare_response(&mut self) {
// self.req_header.header.contains_key(axum::http::header::CONTENT_TYPE)
if self.req_header.is_browser() || self.req_header.is_wget() {
self.req_header.header.append(axum::http::header::CONTENT_TYPE,
self.req_header.header.append(axum::http::header::CONTENT_TYPE,
HeaderValue::try_from("text/html; charset=utf-8")
.expect("URI isn't a valid header value")
);
}
}
pub fn render_template(&mut self,template_file: &str, dflt_content: &str) -> String {
pub fn render_template(&mut self,template_file: &str, dflt_content: &str) -> String {
self.context.insert("page", &template_file.replace(".j2","").replace("html/", "").as_str());
if template_file.contains("md") || template_file.contains("sample") || template_file.contains("code") {
self.context.insert("with_code", "true");
self.context.insert("with_code", "true");
}
match self.app_dbs.tera.render(&template_file, &self.context) {
Ok(s) => {
@ -125,7 +126,7 @@ impl<'a> ReqHandler<'a> {
},
}
} else {
s
s
}
},
Err(e) => {
@ -161,7 +162,7 @@ impl<'a> ReqHandler<'a> {
}
#[allow(dead_code)]
pub fn get_lang(&self, key: &str) -> Local {
Local::get_lang(&self.app_dbs.config.locales, key, &self.app_dbs.config.dflt_lang)
Local::get_lang(&self.app_dbs.config.locales, key, &self.app_dbs.config.dflt_lang)
}
pub fn new_token(&self) -> String {
if self.app_dbs.config.use_token {
@ -236,7 +237,7 @@ impl<'a> ReqHandler<'a> {
pub fn otp_generate(&self) -> Result<TOTP> {
let mut rng = rand::thread_rng();
let data_byte: [u8; 21] = rng.gen();
let base32_string = base32::encode(base32::Alphabet::RFC4648 { padding: false }, &data_byte);
let base32_string = base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &data_byte);
match self.otp_make(&base32_string, "") {
Ok(totp) => Ok(totp),
Err(e) =>

View File

@ -1,5 +1,4 @@
use serde::{Serialize, Deserialize, Deserializer};
use std::collections::HashMap;
use serde::{Serialize, Deserialize };
fn default_config_resource() -> String {
String::from("")
@ -7,9 +6,6 @@ fn default_config_resource() -> String {
fn default_sitewith() -> Vec<String> {
Vec::new()
}
fn default_config_tpls() -> HashMap<String,String> {
HashMap::new()
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReqSettings {
pub name: String,
@ -19,6 +15,8 @@ pub struct ReqSettings {
#[serde(default = "default_config_resource")]
pub url: String,
#[serde(default = "default_config_resource")]
pub author_url: String,
#[serde(default = "default_config_resource")]
pub trace_url: String,
#[serde(default = "default_config_resource")]
pub title: String,
@ -26,8 +24,6 @@ pub struct ReqSettings {
pub subtitle: String,
#[serde(default = "default_config_resource")]
pub pagetitle: String,
#[serde(default = "default_config_tpls")]
pub tpls: HashMap<String,String>,
#[serde(default = "default_config_resource")]
pub sid: String,
#[serde(default = "default_sitewith")]

View File

@ -13,6 +13,7 @@ pub(crate) use users::{
users_password_router_handlers,
users_settings_router_handlers,
users_invite_router_handlers,
forms_router_handlers,
};
pub(crate) use other_handlers::{

View File

@ -51,12 +51,12 @@ pub async fn add_session_cookie(make: bool, cookies: &Cookies, session_token: &s
if result_store.is_empty() {
eprintln!("Unable to store session {}", &app_dbs.config.session_store_uri);
} else {
let cookie = Cookie::build(SESSION_COOKIE_NAME, result_store.to_owned())
let cookie = Cookie::build((SESSION_COOKIE_NAME, result_store.to_owned()))
// .domain(domain)
.path(format!("{}",cookie_path))
.secure(true)
.http_only(true)
.finish();
.build();
if make {
cookies.add(cookie);
}
@ -171,7 +171,7 @@ pub async fn rewrite_request_uri(
// try with email
let mut lock = enforcer.write().await;
let result = lock.enforce_mut(
vec![
vec![
name,
target_path.to_owned(),
req_method.to_owned()
@ -184,7 +184,7 @@ pub async fn rewrite_request_uri(
if result { return Ok(next.run(req).await); }
let new_uri = format!("{}",serv_path.not_auth);
let agent = if let Some(user_agent) = req.headers().get(USER_AGENT) {
user_agent.to_str().unwrap_or("").to_owned()
user_agent.to_str().unwrap_or("").to_owned()
} else {
String::from("")
};

View File

@ -2,6 +2,7 @@ mod login_handlers;
mod password_handlers;
mod settings_handlers;
mod invite_handlers;
mod forms_handlers;
mod login;
pub(crate) use login::login_user;
@ -9,3 +10,4 @@ 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;
pub(crate) use forms_handlers::forms_router_handlers;

View File

@ -0,0 +1,207 @@
use std::sync::Arc;
use axum::{
http::{
StatusCode,
Uri,
header::HeaderMap,
},
Json,
routing::{get,post},
Extension,
extract::ConnectInfo,
response::{IntoResponse,Response,Redirect},
Router,
};
use serde_json::{json,Value};
use tower_cookies::Cookies;
//use log::{trace,error};
use log::error;
use crate::{
route,
defs::{
AppDBs,
ReqHandler,
ReqHeaderMap,
Random,
AppConnectInfo,
FormDefs,
},
users::{
User,
// UserData,
// UserItem,
OpenidUser,
},
handlers::{
// add_session_cookie,
get_auth_state,
// is_allowed,
},
};
pub fn forms_router_handlers() -> Router {
async fn form_handler(
header: HeaderMap,
uri: Uri,
Extension(app_dbs): Extension<Arc<AppDBs>>,
Extension(cookies): Extension<Cookies>,
Extension(random): Extension<Random>,
ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
axum::extract::Path(data): axum::extract::Path<String>,
) -> 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| {
error!("Error list selection {}: {}", &user_sel.name, e);
Vec::new()
});
let form_defs = FormDefs::load_from(&format!("{}/{}.toml",&app_dbs.config.forms_path, &data), &data);
req_handler.context.insert("openid_sel", &openid_sel);
let openid_sel_appkeys = openid_sel.iter().map(|id| id.appkey.to_string()).collect::<Vec<String>>().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");
req_handler.context.insert("form_defs", &form_defs);
let result = if let Some(tpl) = app_dbs.config.tpls.get("form") {
req_handler.render_template(&tpl,"form")
} else {
String::from("user settings")
};
let _ = req_handler.trace_req(format!("User '{}' form",&user_sel.id));
(
res_headers,
result.to_owned()
).into_response()
}
async fn post_form_handler(
header: HeaderMap,
uri: Uri,
Extension(app_dbs): Extension<Arc<AppDBs>>,
Extension(cookies): Extension<Cookies>,
Extension(random): Extension<Random>,
ConnectInfo(app_connect_info): ConnectInfo<AppConnectInfo>,
axum::extract::Path(form): axum::extract::Path<String>,
Json(form_data): Json<Value>,
) -> Response {
dbg!(&form_data);
let str_data=serde_json::to_string(&form_data).unwrap_or_else(|e|{
println!("from_data error: {}",e);
String::from("")
});
dbg!(&str_data);
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 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 response_path = format!("{}/{}/{}.json",&app_dbs.config.forms_responses_path,&user_id,&form);
let form_defs = FormDefs::load_from(&format!("{}/{}.toml",&app_dbs.config.forms_path, &form), &form);
let write_res = match form_defs.write_data(&response_path, &str_data, form_defs.overwrite_response) {
Ok(_) => "done",
Err(e) => {
println!("Error write {}: {}",&response_path, e);
"error"
}
};
let json_result = json!(
{
"form": form,
"status": write_res,
}
);
let result=serde_json::to_string(&json_result).unwrap_or_default();
(
StatusCode::OK,
req_handler.req_header.header,
result
).into_response()
// Redirect::temporary( &format!("/")).into_response()
/*
//let form_defs = FormDefs::load_from(&format!("{}/{}.toml",&app_dbs.config.forms_path, &data), &data);
//req_handler.context.insert("openid_sel", &openid_sel);
//let openid_sel_appkeys = openid_sel.iter().map(|id| id.appkey.to_string()).collect::<Vec<String>>().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");
req_handler.context.insert("form_defs", &form_defs);
let result = if let Some(tpl) = app_dbs.config.tpls.get("form") {
req_handler.render_template(&tpl,"form")
} else {
String::from("user settings")
};
let _ = req_handler.trace_req(format!("User '{}' form",&user_sel.id));
(
res_headers,
result.to_owned()
).into_response()
*/
}
route(
"/form/:item", get(form_handler))
.route("/form/:item", post(post_form_handler))
}

View File

@ -18,7 +18,6 @@ use log::{trace,error};
use crate::{
SESSION_COOKIE_NAME,
DEFAULT_ROLES,
route,
defs::{
AppDBs,
@ -90,8 +89,10 @@ pub fn users_login_router_handlers() -> Router {
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("default_roles", &app_dbs.config.default_roles.join(","));
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("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::<Vec<String>>());
// req_handler.context.insert("with_menu", "1");
if app_dbs.config.totp_mode != TotpMode::No {
match req_handler.otp_generate() {
@ -229,7 +230,7 @@ pub fn users_login_router_handlers() -> Router {
let roles = if ! user_data.roles.is_empty() {
user_data.roles.to_owned()
} else {
DEFAULT_ROLES.to_owned()
app_dbs.config.default_roles.clone().join(",")
};
let is_admin = if user_data.id == "A" || get_total_users(&app_dbs.user_store).await < 1 {
true
@ -256,6 +257,7 @@ pub fn users_login_router_handlers() -> Router {
status: UserStatus::Created,
items: User::json_items(new_items),
isadmin: is_admin,
openids: user_data.openids.to_owned(),
roles,
};
let usr_sel = User::select("name", &user_data.name, false, &app_dbs.user_store).await.unwrap_or_default();
@ -381,7 +383,8 @@ pub fn users_login_router_handlers() -> Router {
match openid_conf.client().await {
Ok(openid_cli) => openid_cli.get_data(&data),
Err(e) => {
error!("Error openid client data found for {} handler: {}", &data, e);
dbg!("Error openid client data found for {} handler: {}", &data, e);
// error!("Error openid client data found for {} handler: {}", &data, e);
(String::from(""), String::from("/login"))
}
}
@ -390,6 +393,7 @@ pub fn users_login_router_handlers() -> Router {
error!("Error no openid client found for: {}", &data);
(String::from(""), String::from("/login"))
};
dbg!(&str_user_data, &url);
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) };

View File

@ -122,6 +122,7 @@ pub fn users_settings_router_handlers() -> Router {
// req_handler.context.insert("usr_items", &user_items);
req_handler.context.insert("no_edit", "true");
req_handler.context.insert("edit_target", "main");
req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::<Vec<String>>());
let result = if let Some(tpl) = app_dbs.config.tpls.get("user_settings") {
req_handler.render_template(&tpl,"user setting")
} else {
@ -186,6 +187,7 @@ pub fn users_settings_router_handlers() -> Router {
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));
req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::<Vec<String>>());
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) {
@ -329,7 +331,7 @@ pub fn users_settings_router_handlers() -> Router {
user_sel.otp_auth_url = String::from("");
user_sel.otp_defs = String::from("");
}
let user_openids = user_data.opendis.to_owned();
let user_openid = user_data.openids.to_owned();
user_sel.from_data(user_data);
let new_id= user_sel.id.to_string();
let user_data = user_sel.session_data();
@ -339,7 +341,7 @@ pub fn users_settings_router_handlers() -> Router {
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;
OpenidUser::sync_ids(&new_id, &user_openid, &app_dbs.user_store).await;
let result =String::from("OK");
(
req_handler.req_header.header,

View File

@ -17,8 +17,8 @@
// )]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
//#[cfg(feature = "std")]
//extern crate std;
use alloc::string::{String, ToString};
use core::fmt;

View File

@ -74,6 +74,7 @@ use handlers::{
admin_router_handlers,
users_router_handlers,
pages_router_handlers,
forms_router_handlers,
};
use crate::tools::get_socket_addr;
@ -99,7 +100,6 @@ 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";
pub fn route(path: &str, method_router: MethodRouter) -> Router {
Router::new().route(path, method_router)
@ -288,6 +288,7 @@ async fn main() {
.merge(users_router_handlers())
.merge(admin_router_handlers())
.merge(pages_router_handlers())
.merge(forms_router_handlers())
.layer(ServiceBuilder::new().layer(middleware))
.layer(CookieManagerLayer::new())
.layer(Extension(app_dbs))
@ -297,7 +298,7 @@ async fn main() {
;
if config.verbose > 2 { dbg!("{:?}",&origins); }
if config.allow_origin.len() > 0 {
web_router = web_router.layer(CorsLayer::new()
web_router = web_router.layer(CorsLayer::new()
.allow_origin(origins)
.allow_methods(vec![Method::GET, Method::POST])
.allow_headers(tower_http::cors::Any)
@ -313,10 +314,10 @@ async fn main() {
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
// web_router.into_make_service_with_connect_info::<SocketAddr>()
axum::serve(listener, web_router
.into_make_service_with_connect_info::<AppConnectInfo>()
)
.await
.unwrap();
.into_make_service_with_connect_info::<AppConnectInfo>()
)
.await
.unwrap();
} else {
let tls_config = RustlsConfig::from_pem_file(
PathBuf::from(&config.cert_file),
@ -331,10 +332,8 @@ async fn main() {
web_router = web_router.layer(TraceLayer::new_for_http());
}
axum_server::bind_rustls(addr, tls_config)
.serve(
// web_router.layer(TraceLayer::new_for_http())
web_router
.into_make_service_with_connect_info::<AppConnectInfo>()
.serve(
web_router.into_make_service_with_connect_info::<AppConnectInfo>()
)
.await
.unwrap();

View File

@ -1,5 +1,6 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs};
use chrono::NaiveDateTime;
use chrono::DateTime;
//use chrono::NaiveDateTime;
//use chrono::{DateTime,Local, Utc,NaiveDateTime};
//use std::time::{UNIX_EPOCH, Duration};
@ -7,13 +8,13 @@ pub fn get_socket_addr(bind: &str, port: u16) -> SocketAddr {
let url = format!("{}:{}",&bind,&port);
match url.to_socket_addrs() {
Ok(addrs_op) => if let Some(addr) = addrs_op.to_owned().next() {
addr
} else {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port.to_owned())
}
addr
} else {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port.to_owned())
}
Err(e) => {
eprintln!("Evironment load error: {} {}", e, url);
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port.to_owned())
eprintln!("Evironment load error: {} {}", e, url);
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port.to_owned())
}
}
}
@ -50,7 +51,7 @@ pub fn path_timestamp(filepath: &str) -> u32 {
pub fn str_date_from_timestamp(timestamp: &str) -> String {
if timestamp.is_empty() { return String::from(""); }
let val: i64 = timestamp.parse().unwrap_or_default();
let datetime = NaiveDateTime::from_timestamp_opt(val, 0).unwrap_or_default(); // (Local::now());
let datetime = DateTime::from_timestamp(val, 0).unwrap_or_default(); // (Local::now());
/*
let naive_utc = dt.naive_utc();
let offset = dt.offset().clone();
@ -63,5 +64,5 @@ pub fn str_date_from_timestamp(timestamp: &str) -> String {
// let str_timestamp = UNIX_EPOCH + Duration::from_millis(val);
// let datetime = DateTime::<Utc>::from(str_timestamp);
*/
datetime.format("%Y-%m-%d %H:%M:%S").to_string()
datetime.format("%Y-%m-%d %H:%M:%S").to_string()
}

View File

@ -489,6 +489,11 @@ impl OpenidUser {
println!("Error delete {}: {}",&itm.appkey,e);
false
});
} else {
let _res = OpenidUser::delete(itm.id, &store).await.unwrap_or_else(|e| {
println!("Error delete {}: {}",&itm.appkey,e);
false
});
}
}
}

View File

@ -6,6 +6,7 @@ use std::{
fmt,
collections::HashMap,
};
use zxcvbn::zxcvbn;
// use std::{
// // sync::Arc,
// fmt::Debug,
@ -37,7 +38,7 @@ use crate::{
const DISPLAY_SEPARATOR: &str = "=";
fn default_user_status() -> UserStatus {
UserStatus::default()
UserStatus::default()
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize,Default)]
@ -60,6 +61,7 @@ pub struct User {
pub status: UserStatus,
pub items: String,
pub isadmin: bool,
pub openids: String,
}
impl fmt::Display for User {
@ -86,7 +88,8 @@ Created{} {}
Last access{} {}
Status{} {}
Items{} {}
IsAdmin{} {}",
IsAdmin{} {}
OpenIds{} {}",
sep, self.id,
sep, self.name, sep, self.fullname,
sep, self.description,
@ -101,7 +104,8 @@ IsAdmin{} {}",
sep, self.created, sep, self.lastaccess,
sep, self.status,
sep, self.items,
sep, self.isadmin
sep, self.isadmin,
sep, self.openids
);
write!(f, "{}", content)
}
@ -128,6 +132,7 @@ impl Entry for User {
status: UserStatus::from_str(&parts[14].to_string()),
items: parts[15].to_string(),
isadmin: if parts[16] == "TRUE" { true } else { false},
openids: parts[17].to_string(),
})
}
}
@ -142,8 +147,8 @@ impl User {
otp_base32,
otp_auth_url,
otp_defs,
roles, created, lastaccess, status, items, isadmin
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", USERS_TABLENAME).as_str()
roles, created, lastaccess, status, items, openids, isadmin
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", USERS_TABLENAME).as_str()
)
.bind(self.name)
.bind(self.fullname)
@ -160,6 +165,7 @@ impl User {
.bind(self.lastaccess)
.bind(format!("{}",self.status))
.bind(self.items)
.bind(self.openids)
.bind(self.isadmin)
.execute(pool).await?;
Ok(query_result.last_insert_id().unwrap_or_default())
@ -236,6 +242,7 @@ impl User {
lastaccess,
status,
items: row.try_get("items")?,
openids: row.try_get("openids")?,
isadmin: row.try_get("isadmin")?,
})
} else {
@ -258,6 +265,7 @@ impl User {
"TRUE" => it.isadmin,
_ => !it.isadmin,
},
"openids" => it.openids == value,
_ => false,
}
)
@ -279,12 +287,12 @@ impl User {
)
.bind(id)
.execute(pool).await?;
Ok(query_result.rows_affected() > 0)
Ok(query_result.rows_affected() > 0)
},
UserStore::File(file_path) => {
let new_entries: Vec<String> =
Entries::<User>::new(&file_path).filter(|it| it.id != id).map(|user|
user.line_format()
user.line_format()
).collect();
let entries = Entries::<User>::new(&file_path);
match entries.write(&new_entries) {
@ -309,8 +317,9 @@ impl User {
created = ?, lastaccess = ?,
status = ?,
items = ?,
openids = ?,
isadmin = ?
WHERE id = ? ",
WHERE id = ? ",
USERS_TABLENAME
);
let query_result = sqlx::query(
@ -331,10 +340,11 @@ impl User {
.bind(self.lastaccess)
.bind(format!("{}",self.status))
.bind(self.items)
.bind(self.openids)
.bind(self.isadmin)
.bind(self.id)
.execute(pool).await?;
Ok(query_result.rows_affected() > 0)
Ok(query_result.rows_affected() > 0)
},
UserStore::File(file_path) => {
let new_entries: Vec<String> = Entries::new(&file_path).map(|user: User|{
@ -357,7 +367,7 @@ impl User {
}
pub fn show(&self, sep: &str) {
let content = if sep.is_empty() {
format!( "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
format!( "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
self.id,
self.name, self.fullname,
self.description,
@ -373,7 +383,8 @@ impl User {
self.lastaccess,
self.status,
self.items,
self.isadmin
self.isadmin,
self.openids
)
} else {
format!("{}",&self).replace(DISPLAY_SEPARATOR, sep)
@ -425,6 +436,7 @@ impl User {
status,
items: row.try_get("items")?,
isadmin: row.try_get("isadmin")?,
openids: row.try_get("openids")?,
};
if show { user.show(sep); }
usrs.push(user);
@ -461,7 +473,7 @@ impl User {
}
}
fn line_format(self) -> String {
format!( "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
format!( "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
self.id,
self.name, self.fullname,
self.description,
@ -476,7 +488,8 @@ impl User {
self.created, self.lastaccess,
self.status,
self.items,
self.isadmin
self.isadmin,
self.openids
)
}
pub fn hash_items(items: &str) -> HashMap<String,String> {
@ -527,6 +540,9 @@ impl User {
}
self.items = Self::json_items(items_hash);
}
if !user_data.openids.is_empty() {
self.openids = user_data.openids.to_owned();
}
}
pub fn disable_totp(&mut self) {
self.otp_base32 = String::from("");
@ -587,37 +603,23 @@ impl User {
)
}
pub fn estimate_password(word: &str) -> String {
match zxcvbn::zxcvbn(word, &[]) {
Ok(estimate) => {
if let Some(feedback) = estimate.feedback() {
let arr_suggestions: Vec<String> = feedback.suggestions().iter().map(|s| format!("{}",s)).collect();
let suggestions = arr_suggestions.join("\n");
if let Some(warning) = feedback.warning() {
let warning = format!("{}", warning);
format!("{}|{}|{}",estimate.score(),suggestions,warning)
} else {
format!("{}|{}|",estimate.score(),suggestions)
}
} else {
format!("{}||",estimate.score())
}
},
Err(e) => {
println!("Error password strength estimator: {}", e);
String::from("-1|||")
let estimate = zxcvbn(word, &[]);
if let Some(feedback) = estimate.feedback() {
let arr_suggestions: Vec<String> = feedback.suggestions().iter().map(|s| format!("{}",s)).collect();
let suggestions = arr_suggestions.join("\n");
if let Some(warning) = feedback.warning() {
let warning = format!("{}", warning);
format!("{}|{}|{}",estimate.score(),suggestions,warning)
} else {
format!("{}|{}|",estimate.score(),suggestions)
}
} else {
format!("{}||",estimate.score())
}
}
pub fn password_score(word: &str) -> u8 {
match zxcvbn::zxcvbn(word, &[]) {
Ok(estimate) => {
estimate.score()
},
Err(e) => {
println!("Error password strength estimator: {}", e);
0
}
}
let estimate = zxcvbn(word, &[]);
u8::from(estimate.score())
}
#[allow(dead_code)]
pub fn has_auth_role(&self, auth_roles: Vec<String>) -> bool {

View File

@ -57,7 +57,7 @@ pub struct UserData {
#[serde(default = "default_items")]
pub items: HashMap<String,String>,
#[serde(default = "default_empty")]
pub opendis: String,
pub openids: String,
}
// impl UserData {
// pub fn from_id(id: String, _app_dbs: &AppDBs) -> Self {