From 1f5c4bad4fee57b3555e925f39509ae03eee5386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20P=C3=A9rez?= Date: Tue, 19 Sep 2023 02:27:36 +0100 Subject: [PATCH] chore: add code to handle opendid and single sign-on --- src/defs/openid.rs | 3 + src/users/openid.rs | 571 +++++++++++++++++--------------------------- 2 files changed, 221 insertions(+), 353 deletions(-) diff --git a/src/defs/openid.rs b/src/defs/openid.rs index bb55838..bfcef24 100644 --- a/src/defs/openid.rs +++ b/src/defs/openid.rs @@ -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 { diff --git a/src/users/openid.rs b/src/users/openid.rs index 776c5b9..22a5bde 100644 --- a/src/users/openid.rs +++ b/src/users/openid.rs @@ -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 { +impl Entry for OpenidUser { + fn from_line(line: &str) -> Result { let parts: Vec<&str> = line.split(":").map(|part| part.trim()).collect(); - Ok(User { + Ok(OpenidUser { id: parts[0].to_string().parse::().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::().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 { 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 = 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::::write(Path::new(&file_path), &entries); - let all: Vec = Entries::new(&file_path).collect(); + // Entries::::write(Path::new(&file_path), &entries); + let all: Vec = 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 = Entries::new(&file_path); + let entries: Entries = 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 { 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::::new(&file_path).find(|it| + if let Some(user) = + Entries::::new(&file_path).find(|it| match field { "id" => it.id == value.parse::().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::().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 { + 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::::new(&file_path).find(|it| + match field { + "id" => it.id == value.parse::().unwrap_or_default(), + "name" => it.name == value, + "application" => it.application == app, + "appkey" => it.application == value, + "userid" => it.userid == value.parse::().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 { 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 = - Entries::::new(&file_path).filter(|it| it.id != id).map(|user| + Entries::::new(&file_path).filter(|it| it.id != id).map(|user| user.line_format() ).collect(); - let entries = Entries::::new(&file_path); + let entries = Entries::::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 { 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 = Entries::new(&file_path).map(|user: User|{ + let new_entries: Vec = 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::::new(&file_path); + let entries = Entries::::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> { - let mut usrs: Vec = Vec::new(); + pub async fn users_list( query_str: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result> { + let mut usrs: Vec = 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 = Entries::new(&file_path).collect(); + let all: Vec = 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> { + 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> { + 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 { 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 = Entries::new(&file_path).collect(); + let all: Vec = 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 { - 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 { - Self::hash_items(&self.items) - } - pub fn json_items(items: HashMap) -> 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 = 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 = 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) -> bool { - for role in auth_roles { - if self.roles.contains(&role) { - return true; - } - } - return false; - } } \ No newline at end of file