rpawomaster 0.2.2

A secure password manager written in Rust
Documentation
use crate::configtool::*;
use crate::pwsmanager::PasswordManager;
use crate::securecrypto::SecureCrypto;
use crate::securecrypto::decrypt_private_key;
use crate::xotp::{XOTP, XOTPType, generate_code};
use sled::IVec;

pub fn search_passwords(
    text: String,
    user: Option<String>,
    vault: Option<String>,
    exact: Option<bool>,
) -> Result<(), String> {
    let username = get_username(user)?;
    let core_password = prompt_core_password(username.clone())?;
    let config: ConfigFile = load_config(&username, &core_password)?;
    let target_vault = select_vault(&config, vault)?;
    let private_key_pem = decrypt_private_key(&config.encrypted_private_key, &core_password)?;
    let crypto = SecureCrypto::from_pem_keys(&config.public_key, &private_key_pem)
        .map_err(|e| format!("Failed to initialize crypto: {}", e))?;
    let exact_match = exact.unwrap_or(false);
    let pm = PasswordManager::new(&target_vault.path)
        .map_err(|e| format!("Failed to open vault: {}", e))?;
    let entries = pm.find_passwords(&text, exact_match)
        .map_err(|e| format!("Search failed: {}", e))?;

    if entries.is_empty() {
        println!("No passwords found matching '{}'", text);
        return Ok(());
    }

    let entry_count = entries.len();
    let selected_entry = if entry_count > 1 {
        println!("\nFound {} matching passwords:", entry_count.clone());
        for (i, entry) in entries.iter().enumerate() {
            println!("{}. Name: {}", i + 1, entry.name);
            if let Some(username) = &entry.username {
                println!("   Username: {}", username);
            }
            if let Some(url) = &entry.url {
                println!("   URL: {}", url);
            }
            if let Some(note) = &entry.note {
                println!("   Note: {}", note);
            }
            println!();
        }

        let selection = prompt_input("Enter the number of the password to view: ")?;
        let selection: usize = selection.trim().parse()
            .map_err(|_| "Invalid selection. Please enter a number.".to_string())?;
        if selection < 1 || selection > entry_count {
            return Err(format!("Invalid selection. Please enter a number between 1 and {}", entry_count));
        }
        &entries[selection - 1]
    } else {
        &entries[0]
    };

    let encrypted_bytes = selected_entry.current_password.clone();
    let decrypted_password = crypto.decrypt_string(&IVec::from(encrypted_bytes.clone()))
                                            .map_err(|e| format!("Decryption failed: {}", e))?;

    println!("\n--- Password Details ---");
    if let Some(username) = &selected_entry.username {
        println!("Username: {}", username);
    }
    if let Some(url) = &selected_entry.url {
        println!("URL: {}", url);
    }
    println!("Password: {}", decrypted_password.clone());
    if let Some((otpinfo, optcode)) = get_otp_code(&target_vault, &selected_entry.id) {
        println!("OTP Code: {}", optcode);
        match &otpinfo.otptype {
            XOTPType::TOTP => {
                let lesstime = otpinfo.get_remaining_seconds().unwrap();
                println!("Time Left: {} seconds", lesstime);
            }
            XOTPType::HOTP => {
                println!("");
            }
        }
    }
    println!("------------------------");

    Ok(())
}

fn get_otp_code(vault: &Vault, id: &uuid::Uuid) -> Option<(XOTP, String)> {
    let vault_meta = VaultMetadata::get_vaultmetadata(&vault.path).ok()?;
    let (private_key, public_key) = get_opt_password(&vault_meta).ok()?;
    let password_manager = PasswordManager::new(&vault.path).ok()?;
    let otp_config = password_manager.get_otp(*id).ok()?;
    if otp_config.is_none() {
        return None;
    }
    let mut otp_info = otp_config.unwrap();
    let crypto = SecureCrypto::from_pem_keys(&public_key, &private_key).ok()?;
    let code = generate_code(&mut otp_info, &crypto);
    Some((otp_info.clone(), code))
}