rpawomaster 0.2.2

A secure password manager written in Rust
Documentation
use crate::configtool::*;
use crate::pwsmanager;
use crate::securecrypto::SecureCrypto;
use crate::securecrypto::decrypt_private_key;
use crate::passgen;
use crate::pwsmanager::PasswordPolicy;

pub fn add_password_interactive(user_arg: Option<String>, vault_arg: Option<String>) -> Result<(), String> {
    let user = get_username(user_arg)?;
    let core_password = prompt_core_password(user.clone())?;

    let config = load_config(&user, &core_password)?;
    let private_key = decrypt_private_key(&config.encrypted_private_key, &core_password)?;
    let crypto = SecureCrypto::from_pem_keys(&config.public_key, &private_key)
        .map_err(|e| format!("Failed to initialize crypto: {}", e))?;

    let vault = select_vault(&config, vault_arg)?;
    let manager = pwsmanager::PasswordManager::new(&vault.path)
        .map_err(|e| format!("Failed to open vault: {}", e))?;

    let name = prompt_input("Enter password name/label: ")?;
    let username = prompt_input("Enter username (optional): ");
    let url = prompt_input("Enter URL (optional): ");
    let note = prompt_input("Enter note (optional): ");

    let (password, policy, expiration_days) = loop {
        let choice = prompt_input("Generate password (g) or enter manually (m)? [g/m]: ")?;
        match choice.trim().to_lowercase().as_str() {
            "g" | "generate" => {
                let password_type = prompt_input("Generate random (r) or memorable (m) password? [r/m]: ")?;
                match password_type.trim().to_lowercase().as_str() {
                    "r" | "random" => {
                        let result = handle_random_password()?;
                        break result;
                    },
                    "m" | "memorable" => {
                        let result = handle_memorable_password()?;
                        break result;
                    },
                    _ => {
                        eprintln!("Invalid choice");
                        continue;
                    }
                };
            },
            "m" | "manual" => {
                let password = input_password_check()?;
                let expiration_days_input = prompt_input("Enter password expiration days (0 for no expiration, default 0): ")?;
                let expiration_days = if expiration_days_input.trim().is_empty() {
                    0
                } else {
                    expiration_days_input.parse().map_err(|_| "Invalid expiration days".to_string())?
                };
                break (password, None, expiration_days);
            },
            _ => {
                eprintln!("Invalid choice");
                continue;
            }
        }
    };

    let encrypted_bytes = crypto.encrypt_string(&password).expect("Encryption failed");

    let id = manager.add_password(
        name,
        username.ok(),
        encrypted_bytes,
        url.ok(),
        expiration_days,
        policy,
        note.ok(),
    ).map_err(|e| format!("Failed to add password: {}", e))?;

    {
        let mut vault_meta = VaultMetadata::get_vaultmetadata(&vault.path).unwrap();
        vault_meta.vault_updated();
    }

    println!("Password added with ID: {}", id);
    Ok(())
}

fn handle_random_password() -> Result<(String, Option<PasswordPolicy>, u32), String> {
    let length_input = prompt_input("Enter password length (default 16): ")?;
    let length = if length_input.trim().is_empty() {
        16
    } else {
        length_input.parse().map_err(|_| "Invalid length".to_string())?
    };
    let mut options = passgen::PasswordOptions::default();
    println!("Random password policy:");
    println!("- Length: {}", length);
    println!("- Includes uppercase: Yes");
    println!("- Includes lowercase: Yes");
    println!("- Includes numbers: Yes");
    println!("- Includes special characters: Yes");
    println!("- URL safe: No");
    println!("- Avoid confusion: No");
    let confirm_policy = prompt_input("Use this policy? [y/n]: ")?;
    options = if confirm_policy.trim().to_lowercase() != "y" {
        eprintln!("Customizing password policy...");
        let uppercase = prompt_input("Include uppercase letters? [y/n]: ")?.trim().to_lowercase() == "y";
        let lowercase = prompt_input("Include lowercase letters? [y/n]: ")?.trim().to_lowercase() == "y";
        let numbers = prompt_input("Include numbers? [y/n]: ")?.trim().to_lowercase() == "y";
        let special = prompt_input("Include special characters? [y/n]: ")?.trim().to_lowercase() == "y";
        let url_safe = prompt_input("Make URL safe? [y/n]: ")?.trim().to_lowercase() == "y";
        let avoid_confusion = prompt_input("Avoid confusing characters? [y/n]: ")?.trim().to_lowercase() == "y";
        passgen::PasswordOptions {
            length,
            include_uppercase: uppercase,
            include_lowercase: lowercase,
            include_numbers: numbers,
            include_special: special,
            url_safe,
            avoid_confusion,
        }
    } else {
        passgen::PasswordOptions {
            length,
            ..options
        }
    };
    let password = passgen::generate_password(&options)?;
    println!("Generated password: {}", password);
    
    let confirm = prompt_input("Use this password? [y/n]: ")?;
    if confirm.trim().to_lowercase() == "y" {
        let policy = pwsmanager::PasswordPolicy::Random {
            length: options.length,
            include_uppercase: options.include_uppercase,
            include_lowercase: options.include_lowercase,
            include_numbers: options.include_numbers,
            include_special: options.include_special,
            url_safe: options.url_safe,
            avoid_confusion: options.avoid_confusion,
        };
        let expiration_days_input = prompt_input("Enter password expiration days (0 for no expiration, default 0): ")?;
        let expiration_days = if expiration_days_input.trim().is_empty() {
            0
        } else {
            expiration_days_input.parse().map_err(|_| "Invalid expiration days".to_string())?
        };
        Ok((password, Some(policy), expiration_days))
    } else {
        handle_random_password()
    }
}

fn handle_memorable_password() -> Result<(String, Option<PasswordPolicy>, u32), String> {
    let words_input = prompt_input("Enter number of words (default 4): ")?;
    let words = if words_input.trim().is_empty() {
        4
    } else {
        words_input.parse().map_err(|_| "Invalid number of words".to_string())?
    };
    let mut options = passgen::MemorablePasswordOptions::default();
    println!("Memorable password policy:");
    println!("- Number of words: {}", words);
    println!("- Separator: '-'");
    println!("- Include numbers: Yes");
    println!("- Capitalization: CamelCase (first letter uppercase)");
    let confirm_policy = prompt_input("Use this policy? [y/n]: ")?;
    options = if confirm_policy.trim().to_lowercase() != "y" {
        eprintln!("Customizing memorable password policy...");
        let separator = prompt_input("Enter separator character: ")?;
        let separator = separator.trim().chars().next().unwrap_or('-');
        let include_numbers = prompt_input("Include numbers? [y/n]: ")?.trim().to_lowercase() == "y";
        let capitalization_input = prompt_input("Capitalization style (none/camel/random): ")?;
        let capitalization = match capitalization_input.trim().to_lowercase().as_str() {
            "camel" => passgen::Capitalization::CamelCase,
            "random" => passgen::Capitalization::RandomCase,
            _ => passgen::Capitalization::NoCapitalization,
        };
        passgen::MemorablePasswordOptions {
            word_count: words,
            include_numbers,
            separator,
            capitalization,
        }
    } else {
        passgen::MemorablePasswordOptions {
            word_count: words,
            ..options
        }
    };
    let password = passgen::generate_memorable_password(&options)?;
    println!("Generated password: {}", password);

    let confirm = prompt_input("Use this password? [y/n]: ")?;
    if confirm.trim().to_lowercase() == "y" {
        let policy = pwsmanager::PasswordPolicy::Memorable {
            words: options.word_count as u8,
            separator: options.separator,
            include_numbers: options.include_numbers,
            capitalization: options.capitalization,
        };
        let expiration_days_input = prompt_input("Enter password expiration days (0 for no expiration, default 0): ")?;
        let expiration_days = if expiration_days_input.trim().is_empty() {
            0
        } else {
            expiration_days_input.parse().map_err(|_| "Invalid expiration days".to_string())?
        };
        Ok((password, Some(policy), expiration_days))
    } else {
        handle_memorable_password()
    }
}