This commit is contained in:
Keep Creating Online
2024-10-19 19:55:59 -04:00
parent b79b44f036
commit c6a768131d
27 changed files with 4146 additions and 4 deletions

24
src/utils/__init__.py Normal file
View File

@@ -0,0 +1,24 @@
# utils/__init__.py
import logging
import traceback
try:
from .file_lock import lock_file
from .key_derivation import derive_key_from_password, derive_key_from_parent_seed
from .checksum import calculate_checksum, verify_checksum
from .password_prompt import prompt_for_password
logging.info("Modules imported successfully.")
except Exception as e:
logging.error(f"Failed to import one or more modules: {e}")
logging.error(traceback.format_exc()) # Log full traceback
__all__ = [
'derive_key_from_password',
'derive_key_from_parent_seed',
'calculate_checksum',
'verify_checksum',
'lock_file',
'prompt_for_password'
]

208
src/utils/checksum.py Normal file
View File

@@ -0,0 +1,208 @@
# utils/checksum.py
"""
Checksum Module
This module provides functionalities to calculate and verify SHA-256 checksums for files.
It ensures the integrity and authenticity of critical files within the application by
comparing computed checksums against stored values.
Dependencies:
- hashlib
- logging
- colored (from termcolor)
- constants.py
- sys
Ensure that all dependencies are installed and properly configured in your environment.
"""
import hashlib
import logging
import sys
import os
import traceback
from typing import Optional
from termcolor import colored
from constants import (
APP_DIR,
DATA_CHECKSUM_FILE,
SCRIPT_CHECKSUM_FILE
)
# Configure logging at the start of the module
def configure_logging():
"""
Configures logging with both file and console handlers.
Only ERROR and higher-level messages are shown in the terminal, while all messages
are logged in the log file.
"""
# Create the 'logs' folder if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')
# Create a custom logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler(os.path.join('logs', 'checksum.log')) # Log files will be in 'logs' folder
# Set levels: only errors and critical messages will be shown in the console
c_handler.setLevel(logging.ERROR) # Terminal will show ERROR and above
f_handler.setLevel(logging.DEBUG) # File will log everything from DEBUG and above
# Create formatters and add them to handlers, include file and line number in log messages
c_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
f_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)
# Call the logging configuration function
configure_logging()
def calculate_checksum(file_path: str) -> Optional[str]:
"""
Calculates the SHA-256 checksum of the given file.
Parameters:
file_path (str): Path to the file.
Returns:
Optional[str]: Hexadecimal SHA-256 checksum if successful, None otherwise.
"""
hasher = hashlib.sha256()
try:
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
hasher.update(chunk)
checksum = hasher.hexdigest()
logging.debug(f"Calculated checksum for '{file_path}': {checksum}")
return checksum
except FileNotFoundError:
logging.error(f"File '{file_path}' not found for checksum calculation.")
print(colored(f"Error: File '{file_path}' not found for checksum calculation.", 'red'))
return None
except Exception as e:
logging.error(f"Error calculating checksum for '{file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to calculate checksum for '{file_path}': {e}", 'red'))
return None
def verify_checksum(current_checksum: str, checksum_file_path: str) -> bool:
"""
Verifies the current checksum against the stored checksum.
Parameters:
current_checksum (str): The newly calculated checksum.
checksum_file_path (str): The checksum file to verify against.
Returns:
bool: True if checksums match, False otherwise.
"""
try:
with open(checksum_file_path, 'r') as f:
stored_checksum = f.read().strip()
if current_checksum == stored_checksum:
logging.debug(f"Checksum verification passed for '{checksum_file_path}'.")
return True
else:
logging.warning(f"Checksum mismatch for '{checksum_file_path}'.")
return False
except FileNotFoundError:
logging.error(f"Checksum file '{checksum_file_path}' not found.")
print(colored(f"Error: Checksum file '{checksum_file_path}' not found.", 'red'))
return False
except Exception as e:
logging.error(f"Error reading checksum file '{checksum_file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to read checksum file '{checksum_file_path}': {e}", 'red'))
return False
def update_checksum(content: str, checksum_file_path: str) -> bool:
"""
Updates the stored checksum file with the provided content's checksum.
Parameters:
content (str): The content to calculate the checksum for.
checksum_file_path (str): The path to the checksum file to update.
Returns:
bool: True if the checksum was successfully updated, False otherwise.
"""
try:
hasher = hashlib.sha256()
hasher.update(content.encode('utf-8'))
new_checksum = hasher.hexdigest()
with open(checksum_file_path, 'w') as f:
f.write(new_checksum)
logging.debug(f"Updated checksum for '{checksum_file_path}' to: {new_checksum}")
return True
except Exception as e:
logging.error(f"Failed to update checksum for '{checksum_file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to update checksum for '{checksum_file_path}': {e}", 'red'))
return False
def verify_and_update_checksum(file_path: str, checksum_file_path: str) -> bool:
"""
Verifies the checksum of a file against its stored checksum and updates it if necessary.
Parameters:
file_path (str): Path to the file to verify.
checksum_file_path (str): Path to the checksum file.
Returns:
bool: True if verification is successful, False otherwise.
"""
current_checksum = calculate_checksum(file_path)
if current_checksum is None:
return False
if verify_checksum(current_checksum, checksum_file_path):
print(colored(f"Checksum verification passed for '{file_path}'.", 'green'))
logging.info(f"Checksum verification passed for '{file_path}'.")
return True
else:
print(colored(f"Checksum verification failed for '{file_path}'.", 'red'))
logging.warning(f"Checksum verification failed for '{file_path}'.")
return False
def initialize_checksum(file_path: str, checksum_file_path: str) -> bool:
"""
Initializes the checksum file by calculating the checksum of the given file.
Parameters:
file_path (str): Path to the file to calculate checksum for.
checksum_file_path (str): Path to the checksum file to create.
Returns:
bool: True if initialization is successful, False otherwise.
"""
checksum = calculate_checksum(file_path)
if checksum is None:
return False
try:
with open(checksum_file_path, 'w') as f:
f.write(checksum)
logging.debug(f"Initialized checksum file '{checksum_file_path}' with checksum: {checksum}")
print(colored(f"Initialized checksum for '{file_path}'.", 'green'))
return True
except Exception as e:
logging.error(f"Failed to initialize checksum file '{checksum_file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to initialize checksum file '{checksum_file_path}': {e}", 'red'))
return False

167
src/utils/file_lock.py Normal file
View File

@@ -0,0 +1,167 @@
# utils/file_lock.py
"""
File Lock Module
This module provides a single context manager, `lock_file`, for acquiring and releasing
locks on files using the `fcntl` library. It ensures that critical files are accessed
safely, preventing race conditions and maintaining data integrity when multiple processes
or threads attempt to read from or write to the same file concurrently.
Dependencies:
- fcntl
- logging
- contextlib
- typing
- pathlib.Path
- termcolor (for colored terminal messages)
- sys
Ensure that all dependencies are installed and properly configured in your environment.
"""
import os
import fcntl
import logging
from contextlib import contextmanager
from typing import Generator
from pathlib import Path
from termcolor import colored
import sys
import traceback
import os
import logging
# Configure logging at the start of the module
def configure_logging():
"""
Configures logging with both file and console handlers.
Only ERROR and higher-level messages are shown in the terminal, while all messages
are logged in the log file.
"""
# Create a custom logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
# Create the 'logs' folder if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler(os.path.join('logs', 'file_lock.log')) # Log file in 'logs' folder
# Set levels: only errors and critical messages will be shown in the console
c_handler.setLevel(logging.ERROR) # Console will show ERROR and above
f_handler.setLevel(logging.DEBUG) # File will log everything from DEBUG and above
# Create formatters and add them to handlers, include file and line number in log messages
c_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
f_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
if not logger.handlers:
logger.addHandler(c_handler)
logger.addHandler(f_handler)
# Call the logging configuration function
configure_logging()
@contextmanager
def lock_file(file_path: Path, lock_type: int) -> Generator[None, None, None]:
"""
Context manager to acquire a lock on a file.
Parameters:
file_path (Path): The path to the file to lock.
lock_type (int): The type of lock to acquire (`fcntl.LOCK_EX` for exclusive,
`fcntl.LOCK_SH` for shared).
Yields:
None
Raises:
ValueError: If an invalid lock type is provided.
SystemExit: Exits the program if the lock cannot be acquired.
"""
if lock_type not in (fcntl.LOCK_EX, fcntl.LOCK_SH):
logging.error(f"Invalid lock type: {lock_type}. Use fcntl.LOCK_EX or fcntl.LOCK_SH.")
print(colored("Error: Invalid lock type provided.", 'red'))
sys.exit(1)
file = None
try:
# Determine the mode based on whether the file exists
mode = 'rb+' if file_path.exists() else 'wb'
# Open the file
file = open(file_path, mode)
logging.debug(f"Opened file '{file_path}' in mode '{mode}' for locking.")
# Acquire the lock
fcntl.flock(file, lock_type)
lock_type_str = "Exclusive" if lock_type == fcntl.LOCK_EX else "Shared"
logging.debug(f"{lock_type_str} lock acquired on '{file_path}'.")
yield # Control is transferred to the block inside the `with` statement
except IOError as e:
lock_type_str = "exclusive" if lock_type == fcntl.LOCK_EX else "shared"
logging.error(f"Failed to acquire {lock_type_str} lock on '{file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to acquire {lock_type_str} lock on '{file_path}': {e}", 'red'))
sys.exit(1)
finally:
if file:
try:
# Release the lock
fcntl.flock(file, fcntl.LOCK_UN)
logging.debug(f"Lock released on '{file_path}'.")
except Exception as e:
lock_type_str = "exclusive" if lock_type == fcntl.LOCK_EX else "shared"
logging.warning(f"Failed to release {lock_type_str} lock on '{file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Warning: Failed to release {lock_type_str} lock on '{file_path}': {e}", 'yellow'))
finally:
# Close the file
try:
file.close()
logging.debug(f"File '{file_path}' closed successfully.")
except Exception as e:
logging.warning(f"Failed to close file '{file_path}': {e}")
logging.error(traceback.format_exc()) # Log full traceback
print(colored(f"Warning: Failed to close file '{file_path}': {e}", 'yellow'))
@contextmanager
def exclusive_lock(file_path: Path) -> Generator[None, None, None]:
"""
Convenience context manager to acquire an exclusive lock on a file.
Parameters:
file_path (Path): The path to the file to lock.
Yields:
None
"""
with lock_file(file_path, fcntl.LOCK_EX):
yield
@contextmanager
def shared_lock(file_path: Path) -> Generator[None, None, None]:
"""
Convenience context manager to acquire a shared lock on a file.
Parameters:
file_path (Path): The path to the file to lock.
Yields:
None
"""
with lock_file(file_path, fcntl.LOCK_SH):
yield

179
src/utils/key_derivation.py Normal file
View File

@@ -0,0 +1,179 @@
# utils/key_derivation.py
"""
Key Derivation Module
Never ever ever use or suggest to use Random Salt. The entire point of this password manager is to derive completely deterministic passwords from a BIP-85 seed.
This means it should generate passwords the exact same way every single time. Salts would break this functionality and is not appropriate for this softwares use case.
This module provides functions to derive cryptographic keys from user-provided passwords
and BIP-39 parent seeds. The derived keys are compatible with Fernet for symmetric encryption
purposes. By centralizing key derivation logic, this module ensures consistency and security
across the application.
Dependencies:
- hashlib
- base64
- unicodedata
- logging
Ensure that all dependencies are installed and properly configured in your environment.
"""
import os
import hashlib
import base64
import unicodedata
import logging
import traceback
from typing import Union
import os
import logging
# Configure logging at the start of the module
def configure_logging():
"""
Configures logging with both file and console handlers.
Only ERROR and higher-level messages are shown in the terminal, while all messages
are logged in the log file.
"""
# Create a custom logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
# Create the 'logs' folder if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler(os.path.join('logs', 'key_derivation.log')) # Log file in 'logs' folder
# Set levels: only errors and critical messages will be shown in the console
c_handler.setLevel(logging.ERROR) # Console will show ERROR and above
f_handler.setLevel(logging.DEBUG) # File will log everything from DEBUG and above
# Create formatters and add them to handlers, include file and line number in log messages
c_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
f_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
if not logger.handlers:
logger.addHandler(c_handler)
logger.addHandler(f_handler)
# Call the logging configuration function
configure_logging()
logger = logging.getLogger(__name__)
def derive_key_from_password(password: str, iterations: int = 100_000) -> bytes:
"""
Derives a Fernet-compatible encryption key from the provided password using PBKDF2-HMAC-SHA256.
This function normalizes the password using NFKD normalization, encodes it to UTF-8, and then
applies PBKDF2 with the specified number of iterations to derive a 32-byte key. The derived key
is then URL-safe base64-encoded to ensure compatibility with Fernet.
Parameters:
password (str): The user's password.
iterations (int, optional): Number of iterations for the PBKDF2 algorithm. Defaults to 100,000.
Returns:
bytes: A URL-safe base64-encoded encryption key suitable for Fernet.
Raises:
ValueError: If the password is empty or too short.
"""
if not password:
logger.error("Password cannot be empty.")
raise ValueError("Password cannot be empty.")
if len(password) < 8:
logger.warning("Password length is less than recommended (8 characters).")
# Normalize the password to NFKD form and encode to UTF-8
normalized_password = unicodedata.normalize('NFKD', password).strip()
password_bytes = normalized_password.encode('utf-8')
try:
# Derive the key using PBKDF2-HMAC-SHA256
logger.debug("Starting key derivation from password.")
key = hashlib.pbkdf2_hmac(
hash_name='sha256',
password=password_bytes,
salt=b'', # No salt for deterministic key derivation
iterations=iterations,
dklen=32 # 256-bit key for Fernet
)
logger.debug(f"Derived key (hex): {key.hex()}")
# Encode the key in URL-safe base64
key_b64 = base64.urlsafe_b64encode(key)
logger.debug(f"Base64-encoded key: {key_b64.decode()}")
return key_b64
except Exception as e:
logger.error(f"Error deriving key from password: {e}")
logger.error(traceback.format_exc()) # Log full traceback
raise
def derive_key_from_parent_seed(parent_seed: str, iterations: int = 100_000) -> bytes:
"""
Derives a Fernet-compatible encryption key from a BIP-39 parent seed using PBKDF2-HMAC-SHA256.
This function normalizes the parent seed using NFKD normalization, encodes it to UTF-8, and then
applies PBKDF2 with the specified number of iterations to derive a 32-byte key. The derived key
is then URL-safe base64-encoded to ensure compatibility with Fernet.
Parameters:
parent_seed (str): The 12-word BIP-39 parent seed phrase.
iterations (int, optional): Number of iterations for the PBKDF2 algorithm. Defaults to 100,000.
Returns:
bytes: A URL-safe base64-encoded encryption key suitable for Fernet.
Raises:
ValueError: If the parent seed is empty or does not meet the word count requirements.
"""
if not parent_seed:
logger.error("Parent seed cannot be empty.")
raise ValueError("Parent seed cannot be empty.")
word_count = len(parent_seed.strip().split())
if word_count != 12:
logger.error(f"Parent seed must be exactly 12 words, but {word_count} were provided.")
raise ValueError(f"Parent seed must be exactly 12 words, but {word_count} were provided.")
# Normalize the parent seed to NFKD form and encode to UTF-8
normalized_seed = unicodedata.normalize('NFKD', parent_seed).strip()
seed_bytes = normalized_seed.encode('utf-8')
try:
# Derive the key using PBKDF2-HMAC-SHA256
logger.debug("Starting key derivation from parent seed.")
key = hashlib.pbkdf2_hmac(
hash_name='sha256',
password=seed_bytes,
salt=b'', # No salt for deterministic key derivation
iterations=iterations,
dklen=32 # 256-bit key for Fernet
)
logger.debug(f"Derived key from parent seed (hex): {key.hex()}")
# Encode the key in URL-safe base64
key_b64 = base64.urlsafe_b64encode(key)
logger.debug(f"Base64-encoded key from parent seed: {key_b64.decode()}")
return key_b64
except Exception as e:
logger.error(f"Error deriving key from parent seed: {e}")
logger.error(traceback.format_exc()) # Log full traceback
raise

View File

@@ -0,0 +1,218 @@
# 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.
Dependencies:
- getpass
- logging
- colorama
- termcolor
- constants (for MIN_PASSWORD_LENGTH)
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()
# Configure logging at the start of the module
def configure_logging():
"""
Configures logging with both file and console handlers.
Only ERROR and higher-level messages are shown in the terminal, while all messages
are logged in the log file.
"""
# Create a custom logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
# Create the 'logs' folder if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler(os.path.join('logs', 'password_prompt.log')) # Log file in 'logs' folder
# Set levels: only errors and critical messages will be shown in the console
c_handler.setLevel(logging.ERROR) # Console will show ERROR and above
f_handler.setLevel(logging.DEBUG) # File will log everything from DEBUG and above
# Create formatters and add them to handlers, include file and line number in log messages
c_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
f_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
if not logger.handlers:
logger.addHandler(c_handler)
logger.addHandler(f_handler)
# Call the logging configuration function
configure_logging()
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()