use async_std::task; use golgi::{sbot::Keystore, Sbot}; use log::debug; use nanorand::{Rng, WyRand}; use sha3::{Digest, Sha3_256}; use crate::{config_manager, error::PeachError, sbot::SbotConfig}; /// Returns Ok(()) if the supplied password is correct, /// and returns Err if the supplied password is incorrect. pub fn verify_password(password: &str) -> Result<(), PeachError> { let real_admin_password_hash = config_manager::get_admin_password_hash()?; let password_hash = hash_password(password); if real_admin_password_hash == password_hash { Ok(()) } else { Err(PeachError::PasswordIncorrect) } } /// Checks if the given passwords are valid, and returns Ok() if they are and /// a PeachError otherwise. /// Currently this just checks that the passwords are the same, /// but could be extended to test if they are strong enough. pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Result<(), PeachError> { if new_password1 == new_password2 { Ok(()) } else { Err(PeachError::PasswordMismatch) } } /// Sets a new password for the admin user pub fn set_new_password(new_password: &str) -> Result<(), PeachError> { let new_password_hash = hash_password(new_password); config_manager::set_admin_password_hash(new_password_hash)?; Ok(()) } /// Creates a hash from a password string pub fn hash_password(password: &str) -> String { let mut hasher = Sha3_256::new(); // write input message hasher.update(password); // read hash digest let result = hasher.finalize(); // convert `u8` to `String` result[0].to_string() } /// Sets a new temporary password for the admin user /// which can be used to reset the permanent password pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> { let new_password_hash = hash_password(new_password); config_manager::set_temporary_password_hash(new_password_hash)?; Ok(()) } /// Returns Ok(()) if the supplied temp_password is correct, /// and returns Err if the supplied temp_password is incorrect pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> { let temporary_admin_password_hash = config_manager::get_temporary_password_hash()?; let password_hash = hash_password(password); if temporary_admin_password_hash == password_hash { Ok(()) } else { Err(PeachError::PasswordIncorrect) } } /// Generates a temporary password and sends it via ssb dm /// to the ssb id configured to be the admin of the peachcloud device pub fn send_password_reset() -> Result<(), PeachError> { // initialise random number generator let mut rng = WyRand::new(); // generate a new password of random numbers let temporary_password = rng.generate::().to_string(); // save this string as a new temporary password set_new_temporary_password(&temporary_password)?; let domain = config_manager::get_peachcloud_domain()?; // then send temporary password as a private ssb message to admin let mut msg = format!( "Your new temporary password is: {} If you are on the same WiFi network as your PeachCloud device you can reset your password \ using this link: http://peach.local/auth/reset", temporary_password ); // if there is an external domain, then include remote link in message // otherwise dont include it let remote_link = match domain { Some(domain) => { format!( "\n\nOr if you are on a different WiFi network, you can reset your password \ using the the following link: {}/auth/reset", domain ) } None => "".to_string(), }; msg += &remote_link; // finally send the message to the admins let ssb_admin_ids = config_manager::get_ssb_admin_ids()?; for ssb_admin_id in ssb_admin_ids { // use golgi to send a private message on scuttlebutt match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) { Ok(_) => (), Err(e) => return Err(PeachError::Sbot(e)), } } Ok(()) } async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); let msg = msg.to_string(); let recipient = vec![recipient.to_string()]; // initialise sbot connection with ip:port and shscap from config file let mut sbot_client = match sbot_config { // TODO: panics if we pass `Some(conf.shscap)` as second arg Some(conf) => { let ip_port = conf.lis.clone(); Sbot::init(Keystore::GoSbot, Some(ip_port), None) .await .map_err(|e| e.to_string())? } None => Sbot::init(Keystore::GoSbot, None, None) .await .map_err(|e| e.to_string())?, }; debug!("Publishing a Scuttlebutt private message with temporary password"); match sbot_client.publish_private(msg, recipient).await { Ok(_) => Ok(()), Err(e) => Err(format!("Failed to publish private message: {}", e)), } }