chore: add code to handle opendid and single sign-on

This commit is contained in:
Jesús Pérez 2023-09-19 02:27:36 +01:00
parent 42dc0ff7a3
commit 1f5c4bad4f
2 changed files with 221 additions and 353 deletions

View File

@ -30,6 +30,8 @@ pub struct OpenidClaims {
pub name: String,
pub owner: String,
pub email: String,
#[serde(default = "default_empty", rename(deserialize = "signupApplication"))]
pub signup_application: String,
// exp: i64,
}
#[derive(Default,Deserialize,Serialize,Debug,Clone)]
@ -67,6 +69,7 @@ pub struct OpenidConf {
pub certificate: String,
pub org_name: String,
pub app_name: String,
pub signup_application: String,
}
pub struct OpenidCli {

View File

@ -2,67 +2,50 @@ use serde::{Deserialize,Serialize};
use sqlx::Row;
use futures::TryStreamExt;
use anyhow::{anyhow,Context, Result};
use std::{
fmt,
collections::HashMap,
};
// use std::{
// // sync::Arc,
// fmt::Debug,
// // io::Write,
// // fs,
// // path::{Path, PathBuf},
// // io::{Error, ErrorKind},
// collections::HashMap,
// };
// use async_session::{MemoryStore, Session, SessionStore};
//use tiitls_utils::logs::file;
// use uuid::Uuid;
// SID_UI_FILE,
// UI_SETTINGS_FILE,
// SidSettings,
// Config as SessionsConfig,
use std::fmt;
use std::num::ParseIntError;
use crate::{
users::{
entries::{Entries,Entry},
UserData,
UserStore,
UserStatus,
},
USERS_TABLENAME,
OPENID_USERS_TABLENAME,
tools::str_date_from_timestamp,
};
const DISPLAY_SEPARATOR: &str = "=";
fn default_user_status() -> UserStatus {
UserStatus::default()
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize,Default)]
pub struct User {
pub struct OpenidUser {
pub id: i64,
pub name: String,
pub fullname: String,
pub email: String,
pub application: String,
pub appkey: String,
pub name: String,
pub userid: i64,
pub description: String,
pub password: String,
pub otp_enabled: bool,
pub otp_verified: bool,
pub otp_base32: String,
pub otp_auth_url: String,
pub otp_defs: String,
pub roles: String,
pub created: String,
pub lastaccess: String,
#[serde(default = "default_user_status")]
pub status: UserStatus,
pub items: String,
pub isadmin: bool,
}
impl fmt::Display for User {
fn default_empty() -> String {
"".to_string()
}
#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct OpenidUserData {
#[serde(default = "default_empty")]
pub id: String,
#[serde(default = "default_empty")]
pub name: String,
#[serde(default = "default_empty")]
pub application: String,
#[serde(default = "default_empty")]
pub appkey: String,
#[serde(default = "default_empty")]
pub userid: String,
#[serde(default = "default_empty")]
pub description: String,
}
impl fmt::Display for OpenidUser {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
@ -72,108 +55,73 @@ impl fmt::Display for User {
let content = format!(
"ID{} {}
Name{} {}
FullName{} {}
Application{} {}
AppKey {} {}
UserId{} {}
Description{} {}
Email{} {}
Password{} {}
otp_enabled{} {}
otp_verified{} {}
otp_base32{} {}
otp_auth_url{} {}
otp_defs{} {}
Roles{} {}
Created{} {}
Last access{} {}
Status{} {}
Items{} {}
IsAdmin{} {}",
Last access{} {}",
sep, self.id,
sep, self.name, sep, self.fullname,
sep, self.name,
sep, self.application,
sep, self.appkey,
sep, self.userid,
sep, self.description,
sep, self.email,
sep, self.password,
sep, self.otp_enabled,
sep, self.otp_verified,
sep, self.otp_base32,
sep, self.otp_auth_url,
sep, self.otp_defs,
sep, self.roles,
sep, self.created, sep, self.lastaccess,
sep, self.status,
sep, self.items,
sep, self.isadmin
sep, self.created,
sep, self.lastaccess,
);
write!(f, "{}", content)
}
}
impl Entry for User {
fn from_line(line: &str) -> Result<User, ParseIntError> {
impl Entry for OpenidUser {
fn from_line(line: &str) -> Result<OpenidUser, ParseIntError> {
let parts: Vec<&str> = line.split(":").map(|part| part.trim()).collect();
Ok(User {
Ok(OpenidUser {
id: parts[0].to_string().parse::<i64>().unwrap_or_default(),
name: parts[1].to_string(),
fullname: parts[2].to_string(),
description: parts[3].to_string(),
email: parts[4].to_string(),
password: parts[5].to_string(),
otp_enabled: if parts[6] == "TRUE" { true } else { false},
otp_verified: if parts[7] == "TRUE" { true } else { false},
otp_base32: parts[8].to_string(),
otp_auth_url: parts[9].to_string(),
otp_defs: parts[10].to_string(),
roles: parts[11].to_string(),
created: parts[12].to_string(),
lastaccess: parts[13].to_string(),
status: UserStatus::from_str(&parts[14].to_string()),
items: parts[15].to_string(),
isadmin: if parts[16] == "TRUE" { true } else { false},
application: parts[2].to_string(),
appkey: parts[3].to_string(),
description: parts[4].to_string(),
userid: parts[5].to_string().parse::<i64>().unwrap_or_default(),
created: parts[6].to_string(),
lastaccess: parts[7].to_string(),
})
}
}
impl User {
impl OpenidUser {
pub async fn add(self, store: &UserStore) -> Result<i64> {
match store {
UserStore::Sql(pool) => {
let query_result = sqlx::query(
format!("INSERT INTO {} (
name, fullname, email, description, password, otp_enabled, otp_verified,
otp_base32,
otp_auth_url,
otp_defs,
roles, created, lastaccess, status, items, isadmin
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", USERS_TABLENAME).as_str()
name, application, appkey,
userid,
description,
created, lastaccess
) VALUES ( ?, ?, ?, ?, ?, ?, ?)", OPENID_USERS_TABLENAME).as_str()
)
.bind(self.name)
.bind(self.fullname)
.bind(self.email)
.bind(self.application)
.bind(self.appkey)
.bind(self.userid)
.bind(self.description)
.bind(self.password)
.bind(self.otp_enabled)
.bind(self.otp_verified)
.bind(self.otp_base32)
.bind(self.otp_auth_url)
.bind(self.otp_defs)
.bind(self.roles)
.bind(self.created)
.bind(self.lastaccess)
.bind(format!("{}",self.status))
.bind(self.items)
.bind(self.isadmin)
.execute(pool).await?;
Ok(query_result.last_insert_id().unwrap_or_default())
},
UserStore::File(file_path) => {
// use itertools::Itertools;
// let entries: Vec<String> = concat(vec![
// Entries::new(Path::new(&file_path)).map(|user: User|{
// Entries::new(Path::new(&file_path)).map(|user: OpenidUser|{
// user.line_format()
// }).collect(),
// vec![ self.line_format()]
// ]);
// Entries::<User>::write(Path::new(&file_path), &entries);
let all: Vec<User> = Entries::new(&file_path).collect();
// Entries::<OpenidUser>::write(Path::new(&file_path), &entries);
let all: Vec<OpenidUser> = Entries::new(&file_path).collect();
let id = if all.len() > 0 {
all[all.len()-1].id + 1
} else {
@ -181,32 +129,32 @@ impl User {
};
let mut new_user = self.to_owned();
new_user.id = id;
let entries: Entries<User> = Entries::new(&file_path);
let entries: Entries<OpenidUser> = Entries::new(&file_path);
match entries.append(new_user.line_format()) {
Ok(_) => Ok(id),
Err(e) => {
println!("Error add item to file: {}",e);
Err(anyhow!("No data added")).context("User add")
Err(anyhow!("No data added")).context("OpenidUser add")
}
}
},
}
}
#[allow(dead_code)]
pub async fn select(field: &str, value: &str, human: bool, store: &UserStore) -> Result<Self> {
match store {
UserStore::Sql(pool) => {
let query_str = format!("SELECT * FROM {} WHERE {} = ? ", USERS_TABLENAME, field);
let query_str = format!("SELECT * FROM {} WHERE '{}' = ?", OPENID_USERS_TABLENAME, field);
dbg!(&query_str);
let mut stream = sqlx::query(
&query_str
)
.bind(value)
//.map(|row: PgRow| {
// map the row into a user-defined domain type
//})
.fetch(pool);
.bind(value)
//.map(|row: PgRow| {
// map the row into a user-defined domain type
//})
.fetch(pool);
if let Some(row) = stream.try_next().await? {
let str_status: String = row.try_get("status")?;
let status = UserStatus::from_str(&str_status);
let created = if human {
let created: String = row.try_get("created")?;
str_date_from_timestamp(&created)
@ -222,49 +170,92 @@ impl User {
Ok(Self{
id: row.try_get("id")?,
name: row.try_get("name")?,
fullname: row.try_get("fullname")?,
email: row.try_get("email")?,
application: row.try_get("application")?,
appkey: row.try_get("appkey")?,
userid: row.try_get("userid")?,
description: row.try_get("description")?,
password: row.try_get("password")?,
otp_enabled: row.try_get("otp_enabled")?,
otp_verified: row.try_get("otp_verified")?,
otp_base32: row.try_get("otp_base32")?,
otp_auth_url: row.try_get("otp_auth_url")?,
otp_defs: row.try_get("otp_defs")?,
roles: row.try_get("roles")?,
created,
lastaccess,
status,
items: row.try_get("items")?,
isadmin: row.try_get("isadmin")?,
})
} else {
Err(anyhow!("No data found")).context("User select")
Err(anyhow!("No data found")).context("OpenidUser select")
}
},
UserStore::File(file_path) => {
if let Some(user) =
Entries::<User>::new(&file_path).find(|it|
if let Some(user) =
Entries::<OpenidUser>::new(&file_path).find(|it|
match field {
"id" => it.id == value.parse::<i64>().unwrap_or_default(),
"name" => it.name == value,
"fullname" => it.fullname == value,
"email" => it.email == value,
"description" => it.description == value,
"otp_base32" => it.otp_base32 == value,
"roles" => it.roles == value,
"items" => it.items == value,
"isadmin" => match value {
"TRUE" => it.isadmin,
_ => !it.isadmin,
},
"application" => it.application == value,
"appkey" => it.appkey == value,
"userid" => it.userid == value.parse::<i64>().unwrap_or_default(),
_ => false,
}
)
{
Ok(user)
} else {
Err(anyhow!("No data found")).context("User select")
Err(anyhow!("No data found")).context("OpenidUser select")
}
},
}
}
pub async fn app_select(app: &str , field: &str, value: &str, human: bool, store: &UserStore) -> Result<Self> {
match store {
UserStore::Sql(pool) => {
let query_str = format!("SELECT * FROM {} WHERE application = '{}' AND {} = ?", OPENID_USERS_TABLENAME, app, field);
let mut stream = sqlx::query(
&query_str
)
.bind(value)
//.map(|row: PgRow| {
// map the row into a user-defined domain type
//})
.fetch(pool);
if let Some(row) = stream.try_next().await? {
let created = if human {
let created: String = row.try_get("created")?;
str_date_from_timestamp(&created)
} else {
row.try_get("created")?
};
let lastaccess = if human {
let lastaccess: String = row.try_get("lastaccess")?;
str_date_from_timestamp(&lastaccess)
} else {
row.try_get("lastaccess")?
};
Ok(Self{
id: row.try_get("id")?,
name: row.try_get("name")?,
application: row.try_get("application")?,
appkey: row.try_get("appkey")?,
userid: row.try_get("userid")?,
description: row.try_get("description")?,
created,
lastaccess,
})
} else {
Err(anyhow!("No data found")).context("OpenidUser select")
}
},
UserStore::File(file_path) => {
if let Some(user) =
Entries::<OpenidUser>::new(&file_path).find(|it|
match field {
"id" => it.id == value.parse::<i64>().unwrap_or_default(),
"name" => it.name == value,
"application" => it.application == app,
"appkey" => it.application == value,
"userid" => it.userid == value.parse::<i64>().unwrap_or_default(),
_ => false,
}
)
{
Ok(user)
} else {
Err(anyhow!("No data found")).context("OpenidUser select")
}
},
}
@ -273,7 +264,7 @@ impl User {
pub async fn delete(id: i64, store: &UserStore) -> Result<bool> {
match store {
UserStore::Sql(pool) => {
let query_str = format!("DELETE FROM {} WHERE id = ?", USERS_TABLENAME);
let query_str = format!("DELETE FROM {} WHERE id = ?", OPENID_USERS_TABLENAME);
let query_result = sqlx::query(
&query_str
)
@ -283,15 +274,15 @@ impl User {
},
UserStore::File(file_path) => {
let new_entries: Vec<String> =
Entries::<User>::new(&file_path).filter(|it| it.id != id).map(|user|
Entries::<OpenidUser>::new(&file_path).filter(|it| it.id != id).map(|user|
user.line_format()
).collect();
let entries = Entries::<User>::new(&file_path);
let entries = Entries::<OpenidUser>::new(&file_path);
match entries.write(&new_entries) {
Ok(_) => Ok(true),
Err(e) => {
println!("Error data delete '{}': {}", id, e);
Err(anyhow!("No data delete").context("User delete"))
Err(anyhow!("No data delete").context("OpenidUser delete"))
}
}
},
@ -300,56 +291,44 @@ impl User {
pub async fn update(self, store: &UserStore) -> anyhow::Result<bool> {
match store {
UserStore::Sql(pool) => {
let query_str = format!("UPDATE {} SET name = ?, fullname = ?,
email = ?, description = ?, password = ?,
otp_enabled = ?, otp_verified = ?,
otp_base32 = ?, otp_auth_url = ?,
otp_defs = ?,
roles = ?,
created = ?, lastaccess = ?,
status = ?,
items = ?,
isadmin = ?
let query_str = format!("UPDATE {} SET name = ?,
application = ?,
appkey = ?,
userid = ?,
description = ?,
created = ?,
lastaccess = ?
WHERE id = ? ",
USERS_TABLENAME
OPENID_USERS_TABLENAME
);
let query_result = sqlx::query(
&query_str
)
.bind(self.name)
.bind(self.fullname)
.bind(self.email)
.bind(self.application)
.bind(self.appkey)
.bind(self.userid)
.bind(self.description)
.bind(self.password)
.bind(self.otp_enabled)
.bind(self.otp_verified)
.bind(self.otp_base32)
.bind(self.otp_auth_url)
.bind(self.otp_defs)
.bind(self.roles)
.bind(self.created)
.bind(self.lastaccess)
.bind(format!("{}",self.status))
.bind(self.items)
.bind(self.isadmin)
.bind(self.id)
.execute(pool).await?;
Ok(query_result.rows_affected() > 0)
},
UserStore::File(file_path) => {
let new_entries: Vec<String> = Entries::new(&file_path).map(|user: User|{
let new_entries: Vec<String> = Entries::new(&file_path).map(|user: OpenidUser|{
if user.id == self.id {
self.to_owned().line_format()
} else {
user.line_format()
}
}).collect();
let entries = Entries::<User>::new(&file_path);
let entries = Entries::<OpenidUser>::new(&file_path);
match entries.write(&new_entries) {
Ok(_) => Ok(true),
Err(e) => {
println!("Error data update '{}': {}", self.id, e);
Err(anyhow!("No data updated").context("User update"))
Err(anyhow!("No data updated").context("OpenidUser update"))
}
}
},
@ -357,34 +336,25 @@ 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{}",
self.id,
self.name, self.fullname,
self.name,
self.application,
self.appkey,
self.userid,
self.description,
self.email,
self.password,
self.otp_enabled,
self.otp_verified,
self.otp_base32,
self.otp_auth_url,
self.otp_defs,
self.roles,
self.created,
self.lastaccess,
self.status,
self.items,
self.isadmin
)
} else {
format!("{}",&self).replace(DISPLAY_SEPARATOR, sep)
format!("{}",self).replace(DISPLAY_SEPARATOR, sep)
};
println!("{}\n_____________________________",content);
}
pub async fn list(store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<User>> {
let mut usrs: Vec<User> = Vec::new();
pub async fn users_list( query_str: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
let mut usrs: Vec<OpenidUser> = Vec::new();
match store {
UserStore::Sql(pool) => {
let query_str = format!("SELECT * FROM {}", USERS_TABLENAME);
let mut stream = sqlx::query(
&query_str
)
@ -393,8 +363,6 @@ impl User {
//})
.fetch(pool);
while let Some(row) = stream.try_next().await? {
let str_status: String = row.try_get("status")?;
let status = UserStatus::from_str(&str_status);
let created = if human {
let created: String = row.try_get("created")?;
str_date_from_timestamp(&created)
@ -410,21 +378,12 @@ impl User {
let user = Self{
id: row.try_get("id")?,
name: row.try_get("name")?,
fullname: row.try_get("fullname")?,
email: row.try_get("email")?,
application: row.try_get("application")?,
appkey: row.try_get("appkey")?,
userid: row.try_get("userid")?,
description: row.try_get("description")?,
password: row.try_get("password")?,
otp_enabled: row.try_get("otp_enabled")?,
otp_verified: row.try_get("otp_verified")?,
otp_base32: row.try_get("otp_base32")?,
otp_auth_url: row.try_get("otp_auth_url")?,
otp_defs: row.try_get("otp_defs")?,
roles: row.try_get("roles")?,
created,
lastaccess,
status,
items: row.try_get("items")?,
isadmin: row.try_get("isadmin")?,
};
if show { user.show(sep); }
usrs.push(user);
@ -432,200 +391,106 @@ impl User {
Ok(usrs)
},
UserStore::File(file_path) => {
let all: Vec<User> = Entries::new(&file_path).collect();
let all: Vec<OpenidUser> = Entries::new(&file_path).collect();
if show {
for user in all.to_owned() { if show { user.show(sep); } }
}
Ok(all)
},
// UserStore::None => Err(anyhow!("No store set")).context("Users list"),
// UserStore::None => Err(anyhow!("No store set")).context("OpenidUsers list"),
}
}
}
#[allow(dead_code)]
pub async fn list(store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
OpenidUser::users_list(
&format!("SELECT * FROM {}", OPENID_USERS_TABLENAME),
store, human, show, sep
).await
}
pub async fn list_selection(field: &str, value: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result<Vec<OpenidUser>> {
OpenidUser::users_list(
&format!("SELECT * FROM {} WHERE {} = '{}'", OPENID_USERS_TABLENAME, &field, &value),
store, human, show, sep
).await
}
#[allow(dead_code)]
pub async fn count(store: &UserStore) -> anyhow::Result<i64> {
match store {
UserStore::Sql(pool) => {
let query_str = format!("SELECT count(*) as total FROM {}", USERS_TABLENAME);
let query_str = format!("SELECT count(*) as total FROM {}", OPENID_USERS_TABLENAME);
let row = sqlx::query(
&query_str
)
.fetch_one(pool).await?;
let total: i64 = row.try_get("total")?;
Ok(total)
},
UserStore::File(file_path) => {
let all: Vec<User> = Entries::new(&file_path).collect();
let all: Vec<OpenidUser> = Entries::new(&file_path).collect();
Ok(all.len() as i64)
},
}
}
fn line_format(self) -> String {
format!( "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
format!( "{}:{}:{}:{}:{}:{}:{}:{}",
self.id,
self.name, self.fullname,
self.name,
self.application,
self.appkey,
self.description,
self.email,
self.password,
self.otp_enabled,
self.otp_verified,
self.otp_base32,
self.otp_auth_url,
self.otp_defs,
self.roles,
self.created, self.lastaccess,
self.status,
self.items,
self.isadmin
self.userid,
self.created,
self.lastaccess,
)
}
pub fn hash_items(items: &str) -> HashMap<String,String> {
if items.is_empty() {
HashMap::new()
} else {
serde_json::from_str(items).unwrap_or_else(|e|{
println!("Error to convert user items to json: {}",e);
HashMap::new()
})
}
}
pub fn items(&self) -> HashMap<String,String> {
Self::hash_items(&self.items)
}
pub fn json_items(items: HashMap<String,String>) -> String {
serde_json::to_string(&items).unwrap_or_else(|e|{
println!("Error to convert user items to string: {}",e);
String::from("")
})
}
pub fn from_data(&mut self, user_data: UserData) {
/*
pub fn from_data(&mut self, user_data: OpenidUserData) {
if !user_data.name.is_empty() {
self.name = user_data.name.to_owned();
}
if !user_data.fullname.is_empty() {
self.fullname = user_data.fullname.to_owned();
if !user_data.application.is_empty() {
self.application = user_data.application.to_owned();
}
if !user_data.description.is_empty() {
self.description = user_data.description.to_owned();
}
if !user_data.email.is_empty() {
self.email = user_data.email.to_owned();
}
if !user_data.otp_code.is_empty() {
self.otp_base32 = user_data.otp_code.to_owned();
}
if !user_data.otp_url.is_empty() {
self.otp_auth_url = user_data.otp_url.to_owned();
}
if !user_data.roles.is_empty() {
self.roles = user_data.roles.to_owned();
}
if !user_data.items.is_empty() {
let mut items_hash = self.items();
for (key,val) in user_data.items {
items_hash.insert(key,val);
}
self.items = Self::json_items(items_hash);
}
}
pub fn disable_totp(&mut self) {
self.otp_base32 = String::from("");
self.otp_auth_url = String::from("");
self.otp_defs = String::from("");
self.otp_verified = false;
self.otp_enabled = false;
}
pub fn from_user(&mut self, new_user: User) {
pub fn from_user(&mut self, new_user: OpenidUser) {
if !new_user.name.is_empty() {
self.name = new_user.name.to_owned();
}
if !new_user.fullname.is_empty() {
self.fullname = new_user.fullname.to_owned();
if !new_user.application.is_empty() {
self.application = new_user.application.to_owned();
}
if !new_user.description.is_empty() {
self.description = new_user.description.to_owned();
}
if !new_user.email.is_empty() {
self.email = new_user.email.to_owned();
}
if !new_user.password.is_empty() {
self.password = new_user.password.to_owned();
}
if new_user.otp_enabled {
self.disable_totp();
} else {
if !new_user.otp_base32.is_empty() {
self.otp_base32 = new_user.otp_base32.to_owned();
}
if !new_user.otp_auth_url.is_empty() {
self.otp_auth_url = new_user.otp_auth_url.to_owned();
}
if !new_user.otp_defs.is_empty() {
self.otp_defs = new_user.otp_defs.to_owned();
}
self.otp_verified = new_user.otp_verified;
}
if !new_user.roles.is_empty() {
self.roles = new_user.roles.to_owned();
}
if !new_user.items.is_empty() {
self.items = new_user.items.to_owned();
}
if new_user.status != self.status {
self.status = new_user.status.to_owned();
}
}
pub fn session_data(&self) -> String {
format!("{}|{}|{}|{}|{}|{}|{}",
format!("{}|{}|{}|{}|{}",
&self.id,
&self.name,
&self.email,
&self.roles,
&self.items,
&self.isadmin,
&self.status
&self.application,
&self.appkey,
&self.userid
)
}
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|||")
*/
pub async fn sync_ids(userid: &str, open_ids: &str, store: &UserStore) {
let openid_sel = OpenidUser::list_selection(
"userid", userid, store, true,false, "|"
).await.unwrap_or_else(|e| {
println!("Error list selection {}: {}", &userid, e);
Vec::new()
});
let new_open_ids: Vec<String> = open_ids.split(",").map(|s| s.to_string()).collect();
for itm in openid_sel {
if new_open_ids.len() == 0 || ! new_open_ids.contains(&itm.appkey) {
let _res = OpenidUser::delete(itm.id, &store).await.unwrap_or_else(|e| {
println!("Error delete {}: {}",&itm.appkey,e);
false
});
}
}
}
pub fn password_score(word: &str) -> u8 {
match zxcvbn::zxcvbn(word, &[]) {
Ok(estimate) => {
estimate.score()
},
Err(e) => {
println!("Error password strength estimator: {}", e);
0
}
}
}
#[allow(dead_code)]
pub fn has_auth_role(&self, auth_roles: Vec<String>) -> bool {
for role in auth_roles {
if self.roles.contains(&role) {
return true;
}
}
return false;
}
}