use ::log::*; use bcrypt::verify;
use std::{thread, time};
use std::collections::HashMap;
use crate::db;
use crate::utils;
use crate::messages::Msg;
#[derive(Debug)]
pub struct Login {
email: String,
password: String,
status: String,
}
impl Login {
pub fn authenticate(email: &str, password: &str) -> Result<Msg, Msg> {
debug!("Inside login::authenticate({:?}, {:?})", email, password);
let login = Login {
email: email.to_string(),
password: password.to_string(),
status: String::new()
};
login.process()
}
pub fn email(&self) -> String {
self.email.clone()
}
pub fn password(&self) -> String {
self.password.clone()
}
fn process(&self) -> Result<Msg, Msg> {
debug!("Inside login.process()...");
let user = db::get_user_for(&self.email, &Msg::Active.description()).unwrap_or(db::User::default());
debug!("User received: {:?}", &user);
if user.email().is_empty() {
debug!("User is empty...returning InvalidCredentials");
return Err(Msg::InvalidCredentials);
}
debug!("User is: {:?}", &user);
debug!("Verify between self password: {:?} and user's {:?}", &self.password(), &user.password());
if verify(&self.password(), &user.password()).unwrap() {
debug!("Login Success");
Ok(Msg::LoginSucceeded)
} else {
error!("Invalid password");
Err(Msg::InvalidCredentials)
}
}
pub fn forgot_password(email: &str) -> Result<Msg, Msg> {
debug!("forgot_password({:?})............................<<", email);
if db::email_record_count(email) < 1 {
return Err(Msg::EmailNotExist);
}
let mut user = db::get_user_for_email(email)?;
let result = Msg::key_for(&user.status());
debug!("Msg key: {:?}", &result);
match result {
Msg::Cancelled => Err(Msg::Cancelled),
Msg::Dormant => Err(Msg::Dormant),
Msg::ConfirmationPending => Err(Msg::ConfirmationPending),
_ => {
user.set_status(&Msg::ForgotPasswordPending.description());
let token = utils::generate_token();
let row_count = user.update_with_token(&token);
debug!("{:?} row(s) updated in user with token for forgot password", &row_count);
Self::send_forgot_password_email(&user.email(), &token);
if row_count == 1 {
Ok(Msg::ForgotPasswordProcessed)
} else {
Err(Msg::ForgotPasswordFailed)
}
}
}
}
pub fn forgot_password_expired(email: &str) -> Result<Msg, Msg> {
let mut user = db::get_user_for(email, &Msg::ForgotPasswordPending.description()).unwrap_or(db::User::default());
if user.email().is_empty() { return Err(Msg::ForgotPasswordFailed) }
user.set_status(&Msg::Active.description());
let row_count = user.clear_token();
if row_count == 1 {
Ok(Msg::ForgotPasswordTokenExpired)
} else {
Err(Msg::ForgotPasswordFailed)
}
}
fn send_forgot_password_email(email_to: &str, token: &str) {
let email_enabled: bool = super::app_config("email_enabled") == "true".to_string();
if !email_enabled { return (); }
let email_from = super::app_config("email_from");
let message_body = format!("Forgot Password Token: {}.\n\nPlease copy this token into Forgot Password Confirmation window to complete reset process", &token);
let subject = "Login-app: Forgot Password regarding...";
let email = utils::Email::new(&email_from, &email_to, &subject, &message_body);
email.send();
}
pub fn reset_password(map: &HashMap<String, String>, email: String) -> Result<Msg, Vec<Msg>> {
let new_pwd = map.get("password").unwrap();
let repeat_pwd = map.get("repeat-password").unwrap();
if new_pwd != repeat_pwd {
return Err(vec![Msg::PasswordsDoNotMatch])
}
let messages = utils::Validator::validate_password(new_pwd.to_string());
if !messages.is_empty() { return Err(messages); }
let mut user = db::get_user_for(&email, &Msg::ForgotPasswordPending.description()).unwrap_or(db::User::default());
if user.email().is_empty() { return Err( vec![Msg::ResetPasswordFailed] ) }
user.set_status(&Msg::Active.description());
let hashed_pwd = utils::hash(&new_pwd);
user.set_password(&hashed_pwd);
let count = user.update_for_password();
debug!("{:?} row(s) affected for user.update_for_password", &count);
if count != 1 { return Err( vec![Msg::ResetPasswordFailed] ) };
Ok(Msg::ResetPasswordProcessed)
}
pub fn wait_to_expire_forgot_password_token(delay: u64, email: String ) {
debug!("wait_to_expire_forgot_password_token({:?}, {:?}).......................<<", &delay, &email);
thread::spawn(move || {
let duration = time::Duration::from_millis(delay+3000); debug!("Time before sleep: {:?}", time::Instant::now());
thread::sleep(duration);
debug!("Time after sleep: {:?}", time::Instant::now());
let result = Self::forgot_password_expired(&email);
match result {
Ok(msg) => debug!("Successfully expired forgot password token: {:?}", msg),
Err(msg) => error!("Failed to expire forgot password token: {:?}", msg),
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::registration::Registration;
use crate::messages::Msg;
#[test]
fn utest_email_not_exist() {
let login = Login {
email: "invalid email $ unit - test".to_string(),
password: "".to_string(),
status: "".to_string(),
};
let result = login.process().unwrap_or_else(|error| error);
assert_eq!(result, Msg::InvalidCredentials);
}
#[test]
fn utest_invalid_credentials() {
let email = "invalid_credentials@unit.tst";
Registration::new(&email, "Pass123~", "Pass123~").unwrap_or_else(|_| Msg::NewRegistrationFailed);
let login = Login {
email: email.to_string(),
password: "".to_string(),
status: "".to_string(),
};
let result = login.process().unwrap_or_else(|error| error);
assert_eq!(result, Msg::InvalidCredentials);
Registration::cancel(&email).unwrap_or_else(|error| error); }
}