Files
seedPass/src/utils/password_prompt.py
2025-06-29 14:51:17 -04:00

191 lines
6.6 KiB
Python

# utils/password_prompt.py
"""
Password Prompt Module
This module provides functions to securely prompt users for passwords, ensuring that passwords
are entered and confirmed correctly. It handles both the creation of new passwords and the
input of existing passwords for decryption purposes. By centralizing password prompting logic,
this module enhances code reuse, security, and maintainability across the application.
Ensure that all dependencies are installed and properly configured in your environment.
"""
import os
import getpass
import logging
import sys
import unicodedata
import traceback
from termcolor import colored
from colorama import init as colorama_init
from constants import MIN_PASSWORD_LENGTH
# Initialize colorama for colored terminal text
colorama_init()
# Instantiate the logger
logger = logging.getLogger(__name__)
def prompt_new_password() -> str:
"""
Prompts the user to enter and confirm a new password for encrypting the parent seed.
This function ensures that the password meets the minimum length requirement and that the
password and confirmation match. It provides user-friendly messages and handles retries.
Returns:
str: The confirmed password entered by the user.
Raises:
SystemExit: If the user fails to provide a valid password after multiple attempts.
"""
max_retries = 5
attempts = 0
while attempts < max_retries:
try:
password = getpass.getpass(prompt="Enter a new password: ").strip()
confirm_password = getpass.getpass(prompt="Confirm your password: ").strip()
if not password:
print(
colored("Error: Password cannot be empty. Please try again.", "red")
)
logging.warning("User attempted to enter an empty password.")
attempts += 1
continue
if len(password) < MIN_PASSWORD_LENGTH:
print(
colored(
f"Error: Password must be at least {MIN_PASSWORD_LENGTH} characters long.",
"red",
)
)
logging.warning(
f"User entered a password shorter than {MIN_PASSWORD_LENGTH} characters."
)
attempts += 1
continue
if password != confirm_password:
print(
colored("Error: Passwords do not match. Please try again.", "red")
)
logging.warning("User entered mismatching passwords.")
attempts += 1
continue
# Normalize the password to NFKD form
normalized_password = unicodedata.normalize("NFKD", password)
logging.debug("User entered a valid and confirmed password.")
return normalized_password
except KeyboardInterrupt:
print(colored("\nOperation cancelled by user.", "yellow"))
logging.info("Password prompt interrupted by user.")
sys.exit(0)
except Exception as e:
logging.error(f"Unexpected error during password prompt: {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: {e}", "red"))
attempts += 1
print(colored("Maximum password attempts exceeded. Exiting.", "red"))
logging.error("User failed to provide a valid password after multiple attempts.")
sys.exit(1)
def prompt_existing_password(prompt_message: str = "Enter your password: ") -> str:
"""
Prompts the user to enter an existing password, typically used for decryption purposes.
This function ensures that the password is entered securely without echoing it to the terminal.
Parameters:
prompt_message (str): The message displayed to prompt the user. Defaults to "Enter your password: ".
Returns:
str: The password entered by the user.
Raises:
SystemExit: If the user interrupts the operation.
"""
try:
password = getpass.getpass(prompt=prompt_message).strip()
if not password:
print(colored("Error: Password cannot be empty.", "red"))
logging.warning("User attempted to enter an empty password.")
sys.exit(1)
# Normalize the password to NFKD form
normalized_password = unicodedata.normalize("NFKD", password)
logging.debug("User entered an existing password for decryption.")
return normalized_password
except KeyboardInterrupt:
print(colored("\nOperation cancelled by user.", "yellow"))
logging.info("Existing password prompt interrupted by user.")
sys.exit(0)
except Exception as e:
logging.error(f"Unexpected error during existing password prompt: {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: {e}", "red"))
sys.exit(1)
def confirm_action(
prompt_message: str = "Are you sure you want to proceed? (Y/N): ",
) -> bool:
"""
Prompts the user to confirm an action, typically used before performing critical operations.
Parameters:
prompt_message (str): The confirmation message displayed to the user. Defaults to
"Are you sure you want to proceed? (Y/N): ".
Returns:
bool: True if the user confirms the action, False otherwise.
Raises:
SystemExit: If the user interrupts the operation.
"""
try:
while True:
response = input(colored(prompt_message, "cyan")).strip().lower()
if response in ["y", "yes"]:
logging.debug("User confirmed the action.")
return True
elif response in ["n", "no"]:
logging.debug("User declined the action.")
return False
else:
print(colored("Please respond with 'Y' or 'N'.", "yellow"))
except KeyboardInterrupt:
print(colored("\nOperation cancelled by user.", "yellow"))
logging.info("Action confirmation interrupted by user.")
sys.exit(0)
except Exception as e:
logging.error(f"Unexpected error during action confirmation: {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: {e}", "red"))
sys.exit(1)
def prompt_for_password() -> str:
"""
Prompts the user to enter a new password by invoking the prompt_new_password function.
This function serves as an alias to maintain consistency with import statements in other modules.
Returns:
str: The confirmed password entered by the user.
"""
return prompt_new_password()