mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
569 lines
25 KiB
Python
569 lines
25 KiB
Python
# password_manager/manager.py
|
|
|
|
"""
|
|
Password Manager Module
|
|
|
|
This module implements the PasswordManager class, which orchestrates various functionalities
|
|
of the deterministic password manager, including encryption, entry management, password
|
|
generation, backup, and checksum verification. It serves as the core interface for interacting
|
|
with the password manager functionalities.
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import logging
|
|
import getpass
|
|
import os
|
|
from typing import Optional
|
|
|
|
from colorama import Fore
|
|
from termcolor import colored
|
|
|
|
from password_manager.encryption import EncryptionManager
|
|
from password_manager.entry_management import EntryManager
|
|
from password_manager.password_generation import PasswordGenerator
|
|
from password_manager.backup import BackupManager
|
|
from utils.key_derivation import derive_key_from_parent_seed, derive_key_from_password
|
|
from utils.checksum import calculate_checksum, verify_checksum
|
|
from utils.password_prompt import prompt_for_password, prompt_existing_password, confirm_action
|
|
|
|
from constants import (
|
|
APP_DIR,
|
|
INDEX_FILE,
|
|
PARENT_SEED_FILE,
|
|
DATA_CHECKSUM_FILE,
|
|
SCRIPT_CHECKSUM_FILE,
|
|
MIN_PASSWORD_LENGTH,
|
|
MAX_PASSWORD_LENGTH,
|
|
DEFAULT_PASSWORD_LENGTH,
|
|
HASHED_PASSWORD_FILE, # Ensure this constant is defined in constants.py
|
|
DEFAULT_SEED_BACKUP_FILENAME
|
|
)
|
|
|
|
import traceback # Added for exception traceback logging
|
|
import bcrypt # Ensure bcrypt is installed in your environment
|
|
from pathlib import Path # Required for handling file paths
|
|
|
|
# Configure logging at the start of the module
|
|
def configure_logging():
|
|
"""
|
|
Configures logging with both file and console handlers.
|
|
Logs include the timestamp, log level, message, filename, and line number.
|
|
Only ERROR and higher-level messages are shown in the terminal, while all messages
|
|
are logged in the log file.
|
|
"""
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
|
|
|
|
# Prevent adding multiple handlers if configure_logging is called multiple times
|
|
if not logger.handlers:
|
|
# 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_manager.log'))
|
|
|
|
# Set levels: only errors and critical messages will be shown in the console
|
|
c_handler.setLevel(logging.ERROR)
|
|
f_handler.setLevel(logging.DEBUG)
|
|
|
|
# Create formatters and add them to handlers, include file and line number in log messages
|
|
formatter = logging.Formatter(
|
|
'%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]'
|
|
)
|
|
c_handler.setFormatter(formatter)
|
|
f_handler.setFormatter(formatter)
|
|
|
|
# Add handlers to the logger
|
|
logger.addHandler(c_handler)
|
|
logger.addHandler(f_handler)
|
|
|
|
# Call the logging configuration function
|
|
configure_logging()
|
|
|
|
class PasswordManager:
|
|
"""
|
|
PasswordManager Class
|
|
|
|
Manages the generation, encryption, and retrieval of deterministic passwords using a BIP-39 seed.
|
|
It handles file encryption/decryption, password generation, entry management, backups, and checksum
|
|
verification, ensuring the integrity and confidentiality of the stored password database.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initializes the PasswordManager by setting up encryption, loading or setting up the parent seed,
|
|
and initializing other components like EntryManager, PasswordGenerator, and BackupManager.
|
|
"""
|
|
self.encryption_manager: Optional[EncryptionManager] = None
|
|
self.entry_manager: Optional[EntryManager] = None
|
|
self.password_generator: Optional[PasswordGenerator] = None
|
|
self.backup_manager: Optional[BackupManager] = None
|
|
self.parent_seed: Optional[str] = None # Added parent_seed attribute
|
|
|
|
self.setup_parent_seed()
|
|
self.initialize_managers()
|
|
|
|
def setup_parent_seed(self) -> None:
|
|
if os.path.exists(PARENT_SEED_FILE):
|
|
# Parent seed file exists, prompt for password to decrypt
|
|
password = getpass.getpass(prompt='Enter your login password: ').strip()
|
|
try:
|
|
# Derive encryption key from password
|
|
key = derive_key_from_password(password)
|
|
self.encryption_manager = EncryptionManager(key)
|
|
self.parent_seed = self.encryption_manager.decrypt_parent_seed(PARENT_SEED_FILE)
|
|
|
|
# Validate the decrypted seed
|
|
if not self.validate_seed_phrase(self.parent_seed):
|
|
logging.error("Decrypted seed is invalid. Exiting.")
|
|
print(colored("Error: Decrypted seed is invalid.", 'red'))
|
|
sys.exit(1)
|
|
|
|
logging.debug("Parent seed decrypted and validated successfully.")
|
|
except Exception as e:
|
|
logging.error(f"Failed to decrypt parent seed: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to decrypt parent seed: {e}", 'red'))
|
|
sys.exit(1)
|
|
else:
|
|
# First-time setup: prompt for parent seed and password
|
|
try:
|
|
parent_seed = getpass.getpass(prompt='Enter your 12-word parent seed: ').strip()
|
|
# Validate parent seed (basic validation)
|
|
parent_seed = self.basic_validate_seed_phrase(parent_seed)
|
|
if not parent_seed:
|
|
logging.error("Invalid seed phrase. Exiting.")
|
|
sys.exit(1)
|
|
except KeyboardInterrupt:
|
|
logging.info("Operation cancelled by user.")
|
|
print(colored("\nOperation cancelled by user.", 'yellow'))
|
|
sys.exit(0)
|
|
|
|
# Prompt for password
|
|
password = prompt_for_password()
|
|
|
|
# Derive encryption key from password
|
|
key = derive_key_from_password(password)
|
|
self.encryption_manager = EncryptionManager(key)
|
|
|
|
# Encrypt and save the parent seed
|
|
try:
|
|
self.encryption_manager.encrypt_parent_seed(parent_seed, PARENT_SEED_FILE)
|
|
logging.info("Parent seed encrypted and saved successfully.")
|
|
# Store the hashed password
|
|
self.store_hashed_password(password)
|
|
logging.info("User password hashed and stored successfully.")
|
|
except Exception as e:
|
|
logging.error(f"Failed to encrypt and save parent seed: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to encrypt and save parent seed: {e}", 'red'))
|
|
sys.exit(1)
|
|
|
|
self.parent_seed = parent_seed
|
|
|
|
def basic_validate_seed_phrase(self, seed_phrase: str) -> Optional[str]:
|
|
"""
|
|
Performs basic validation on the seed phrase without relying on EncryptionManager.
|
|
|
|
Parameters:
|
|
seed_phrase (str): The seed phrase to validate.
|
|
|
|
Returns:
|
|
Optional[str]: The validated seed phrase or None if invalid.
|
|
"""
|
|
try:
|
|
words = seed_phrase.split()
|
|
if len(words) != 12:
|
|
logging.error("Seed phrase must contain exactly 12 words.")
|
|
print(colored("Error: Seed phrase must contain exactly 12 words.", 'red'))
|
|
return None
|
|
# Additional basic validations can be added here (e.g., word list checks)
|
|
logging.debug("Seed phrase validated successfully.")
|
|
return seed_phrase
|
|
except Exception as e:
|
|
logging.error(f"Error during basic seed validation: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: {e}", 'red'))
|
|
return None
|
|
|
|
def validate_seed_phrase(self, seed_phrase: str) -> bool:
|
|
"""
|
|
Validates the seed phrase using the EncryptionManager if available,
|
|
otherwise performs basic validation.
|
|
|
|
Parameters:
|
|
seed_phrase (str): The seed phrase to validate.
|
|
|
|
Returns:
|
|
bool: True if valid, False otherwise.
|
|
"""
|
|
try:
|
|
if self.encryption_manager:
|
|
# Use EncryptionManager to validate seed
|
|
is_valid = self.encryption_manager.validate_seed(seed_phrase)
|
|
if is_valid:
|
|
logging.debug("Seed phrase validated successfully using EncryptionManager.")
|
|
else:
|
|
logging.error("Invalid seed phrase.")
|
|
print(colored("Error: Invalid seed phrase.", 'red'))
|
|
return is_valid
|
|
else:
|
|
# Perform basic validation
|
|
return self.basic_validate_seed_phrase(seed_phrase) is not None
|
|
except Exception as e:
|
|
logging.error(f"Error validating seed phrase: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to validate seed phrase: {e}", 'red'))
|
|
return False
|
|
|
|
def initialize_managers(self) -> None:
|
|
"""
|
|
Initializes the EntryManager, PasswordGenerator, and BackupManager with the EncryptionManager
|
|
and parent seed.
|
|
"""
|
|
try:
|
|
self.entry_manager = EntryManager(self.encryption_manager)
|
|
self.password_generator = PasswordGenerator(self.encryption_manager, self.parent_seed)
|
|
self.backup_manager = BackupManager()
|
|
logging.debug("EntryManager, PasswordGenerator, and BackupManager initialized.")
|
|
except Exception as e:
|
|
logging.error(f"Failed to initialize managers: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to initialize managers: {e}", 'red'))
|
|
sys.exit(1)
|
|
|
|
def handle_generate_password(self) -> None:
|
|
try:
|
|
website_name = input('Enter the website name: ').strip()
|
|
if not website_name:
|
|
print(colored("Error: Website name cannot be empty.", 'red'))
|
|
return
|
|
|
|
username = input('Enter the username (optional): ').strip()
|
|
url = input('Enter the URL (optional): ').strip()
|
|
|
|
length_input = input(f'Enter desired password length (default {DEFAULT_PASSWORD_LENGTH}): ').strip()
|
|
length = DEFAULT_PASSWORD_LENGTH
|
|
if length_input:
|
|
if not length_input.isdigit():
|
|
print(colored("Error: Password length must be a number.", 'red'))
|
|
return
|
|
length = int(length_input)
|
|
if not (MIN_PASSWORD_LENGTH <= length <= MAX_PASSWORD_LENGTH):
|
|
print(colored(f"Error: Password length must be between {MIN_PASSWORD_LENGTH} and {MAX_PASSWORD_LENGTH}.", 'red'))
|
|
return
|
|
|
|
# Add the entry to the index and get the assigned index
|
|
index = self.entry_manager.add_entry(website_name, length, username, url, blacklisted=False)
|
|
|
|
# Generate the password using the assigned index
|
|
password = self.password_generator.generate_password(length, index)
|
|
|
|
# Provide user feedback
|
|
print(colored(f"\n[+] Password generated and indexed with ID {index}.\n", 'green'))
|
|
print(colored(f"Password for {website_name}: {password}\n", 'yellow'))
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error during password generation: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to generate password: {e}", 'red'))
|
|
|
|
def handle_retrieve_password(self) -> None:
|
|
"""
|
|
Handles retrieving a password from the index by prompting the user for the index number
|
|
and displaying the corresponding password and associated details.
|
|
"""
|
|
try:
|
|
index_input = input('Enter the index number of the password to retrieve: ').strip()
|
|
if not index_input.isdigit():
|
|
print(colored("Error: Index must be a number.", 'red'))
|
|
return
|
|
index = int(index_input)
|
|
|
|
# Retrieve entry details
|
|
entry = self.entry_manager.retrieve_entry(index)
|
|
if not entry:
|
|
return
|
|
|
|
# Display entry details
|
|
website_name = entry.get('website')
|
|
length = entry.get('length')
|
|
username = entry.get('username')
|
|
url = entry.get('url')
|
|
blacklisted = entry.get('blacklisted')
|
|
|
|
print(colored(f"Retrieving password for '{website_name}' with length {length}.", 'cyan'))
|
|
if username:
|
|
print(colored(f"Username: {username}", 'cyan'))
|
|
if url:
|
|
print(colored(f"URL: {url}", 'cyan'))
|
|
if blacklisted:
|
|
print(colored(f"Warning: This password is blacklisted and should not be used.", 'red'))
|
|
|
|
# Generate the password
|
|
password = self.password_generator.generate_password(length, index)
|
|
|
|
# Display the password and associated details
|
|
if password:
|
|
print(colored(f"\n[+] Retrieved Password for {website_name}:\n", 'green'))
|
|
print(colored(f"Password: {password}", 'yellow'))
|
|
print(colored(f"Associated Username: {username or 'N/A'}", 'cyan'))
|
|
print(colored(f"Associated URL: {url or 'N/A'}", 'cyan'))
|
|
print(colored(f"Blacklist Status: {'Blacklisted' if blacklisted else 'Not Blacklisted'}", 'cyan'))
|
|
else:
|
|
print(colored("Error: Failed to retrieve the password.", 'red'))
|
|
except Exception as e:
|
|
logging.error(f"Error during password retrieval: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to retrieve password: {e}", 'red'))
|
|
|
|
def handle_modify_entry(self) -> None:
|
|
"""
|
|
Handles modifying an existing password entry by prompting the user for the index number
|
|
and new details to update.
|
|
"""
|
|
try:
|
|
index_input = input('Enter the index number of the entry to modify: ').strip()
|
|
if not index_input.isdigit():
|
|
print(colored("Error: Index must be a number.", 'red'))
|
|
return
|
|
index = int(index_input)
|
|
|
|
# Retrieve existing entry
|
|
entry = self.entry_manager.retrieve_entry(index)
|
|
if not entry:
|
|
return
|
|
|
|
website_name = entry.get('website')
|
|
length = entry.get('length')
|
|
username = entry.get('username')
|
|
url = entry.get('url')
|
|
blacklisted = entry.get('blacklisted')
|
|
|
|
# Display current values
|
|
print(colored(f"Modifying entry for '{website_name}' (Index: {index}):", 'cyan'))
|
|
print(colored(f"Current Username: {username or 'N/A'}", 'cyan'))
|
|
print(colored(f"Current URL: {url or 'N/A'}", 'cyan'))
|
|
print(colored(f"Current Blacklist Status: {'Blacklisted' if blacklisted else 'Not Blacklisted'}", 'cyan'))
|
|
|
|
# Prompt for new values (optional)
|
|
new_username = input(f'Enter new username (leave blank to keep "{username or "N/A"}"): ').strip() or username
|
|
new_url = input(f'Enter new URL (leave blank to keep "{url or "N/A"}"): ').strip() or url
|
|
blacklist_input = input(f'Is this password blacklisted? (Y/N, current: {"Y" if blacklisted else "N"}): ').strip().lower()
|
|
if blacklist_input == '':
|
|
new_blacklisted = blacklisted
|
|
elif blacklist_input == 'y':
|
|
new_blacklisted = True
|
|
elif blacklist_input == 'n':
|
|
new_blacklisted = False
|
|
else:
|
|
print(colored("Invalid input for blacklist status. Keeping the current status.", 'yellow'))
|
|
new_blacklisted = blacklisted
|
|
|
|
# Update the entry
|
|
self.entry_manager.modify_entry(index, new_username, new_url, new_blacklisted)
|
|
|
|
print(colored(f"Entry updated successfully for index {index}.", 'green'))
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error during modifying entry: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to modify entry: {e}", 'red'))
|
|
|
|
def handle_verify_checksum(self) -> None:
|
|
"""
|
|
Handles verifying the script's checksum against the stored checksum to ensure integrity.
|
|
"""
|
|
try:
|
|
current_checksum = calculate_checksum(__file__)
|
|
if verify_checksum(current_checksum, SCRIPT_CHECKSUM_FILE):
|
|
print(colored("Checksum verification passed.", 'green'))
|
|
logging.info("Checksum verification passed.")
|
|
else:
|
|
print(colored("Checksum verification failed. The script may have been modified.", 'red'))
|
|
logging.error("Checksum verification failed.")
|
|
except Exception as e:
|
|
logging.error(f"Error during checksum verification: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to verify checksum: {e}", 'red'))
|
|
|
|
def get_encrypted_data(self) -> Optional[bytes]:
|
|
"""
|
|
Retrieves the encrypted password index data.
|
|
|
|
:return: The encrypted data as bytes, or None if retrieval fails.
|
|
"""
|
|
try:
|
|
encrypted_data = self.encryption_manager.get_encrypted_index()
|
|
if encrypted_data:
|
|
logging.debug("Encrypted index data retrieved successfully.")
|
|
return encrypted_data
|
|
else:
|
|
logging.error("Failed to retrieve encrypted index data.")
|
|
print(colored("Error: Failed to retrieve encrypted index data.", 'red'))
|
|
return None
|
|
except Exception as e:
|
|
logging.error(f"Error retrieving encrypted data: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to retrieve encrypted data: {e}", 'red'))
|
|
return None
|
|
|
|
def decrypt_and_save_index_from_nostr(self, encrypted_data: bytes) -> None:
|
|
"""
|
|
Decrypts the encrypted data retrieved from Nostr and updates the local index.
|
|
|
|
:param encrypted_data: The encrypted data retrieved from Nostr.
|
|
"""
|
|
try:
|
|
self.encryption_manager.decrypt_and_save_index_from_nostr(encrypted_data)
|
|
logging.info("Index file updated from Nostr successfully.")
|
|
print(colored("Index file updated from Nostr successfully.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to decrypt and save data from Nostr: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to decrypt and save data from Nostr: {e}", 'red'))
|
|
|
|
def backup_database(self) -> None:
|
|
"""
|
|
Creates a backup of the encrypted JSON index file.
|
|
"""
|
|
try:
|
|
self.backup_manager.create_backup()
|
|
print(colored("Backup created successfully.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to create backup: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to create backup: {e}", 'red'))
|
|
|
|
def restore_database(self) -> None:
|
|
"""
|
|
Restores the encrypted JSON index file from the latest backup.
|
|
"""
|
|
try:
|
|
self.backup_manager.restore_latest_backup()
|
|
print(colored("Database restored from the latest backup successfully.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to restore backup: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to restore backup: {e}", 'red'))
|
|
|
|
def handle_backup_reveal_parent_seed(self) -> None:
|
|
"""
|
|
Handles the backup and reveal of the parent seed.
|
|
"""
|
|
try:
|
|
print(colored("\n=== Backup/Reveal Parent Seed ===", 'yellow'))
|
|
print(colored("Warning: Revealing your parent seed is a highly sensitive operation.", 'red'))
|
|
print(colored("Ensure you're in a secure, private environment and no one is watching your screen.", 'red'))
|
|
|
|
# Verify user's identity with secure password verification
|
|
password = prompt_existing_password("Enter your master password to continue: ")
|
|
if not self.verify_password(password):
|
|
print(colored("Incorrect password. Operation aborted.", 'red'))
|
|
return
|
|
|
|
# Double confirmation
|
|
if not confirm_action("Are you absolutely sure you want to reveal your parent seed? (Y/N): "):
|
|
print(colored("Operation cancelled by user.", 'yellow'))
|
|
return
|
|
|
|
# Reveal the parent seed
|
|
print(colored("\n=== Your Parent Seed ===", 'green'))
|
|
print(colored(self.parent_seed, 'yellow'))
|
|
print(colored("\nPlease write this down and store it securely. Do not share it with anyone.", 'red'))
|
|
|
|
# Option to save to file with default filename
|
|
if confirm_action("Do you want to save this to an encrypted backup file? (Y/N): "):
|
|
filename = input(f"Enter filename to save (default: {DEFAULT_SEED_BACKUP_FILENAME}): ").strip()
|
|
filename = filename if filename else DEFAULT_SEED_BACKUP_FILENAME
|
|
backup_path = Path(APP_DIR) / filename
|
|
|
|
# Validate filename
|
|
if not self.is_valid_filename(filename):
|
|
print(colored("Invalid filename. Operation aborted.", 'red'))
|
|
return
|
|
|
|
self.encryption_manager.encrypt_parent_seed(self.parent_seed, backup_path)
|
|
print(colored(f"Encrypted seed backup saved to '{backup_path}'. Ensure this file is stored securely.", 'green'))
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error during parent seed backup/reveal: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to backup/reveal parent seed: {e}", 'red'))
|
|
|
|
def verify_password(self, password: str) -> bool:
|
|
"""
|
|
Verifies the provided password against the stored hashed password.
|
|
"""
|
|
try:
|
|
if not os.path.exists(HASHED_PASSWORD_FILE):
|
|
logging.error("Hashed password file not found.")
|
|
print(colored("Error: Hashed password file not found.", 'red'))
|
|
return False
|
|
with open(HASHED_PASSWORD_FILE, 'rb') as f:
|
|
stored_hash = f.read()
|
|
is_correct = bcrypt.checkpw(password.encode('utf-8'), stored_hash)
|
|
if is_correct:
|
|
logging.debug("Password verification successful.")
|
|
else:
|
|
logging.warning("Password verification failed.")
|
|
return is_correct
|
|
except Exception as e:
|
|
logging.error(f"Error verifying password: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to verify password: {e}", 'red'))
|
|
return False
|
|
|
|
def is_valid_filename(self, filename: str) -> bool:
|
|
"""
|
|
Validates the provided filename to prevent directory traversal and invalid characters.
|
|
"""
|
|
# Basic validation: filename should not contain path separators or be empty
|
|
invalid_chars = ['/', '\\', '..']
|
|
if any(char in filename for char in invalid_chars) or not filename:
|
|
logging.warning(f"Invalid filename attempted: {filename}")
|
|
return False
|
|
return True
|
|
|
|
def store_hashed_password(self, password: str) -> None:
|
|
"""
|
|
Hashes and stores the user's password securely using bcrypt.
|
|
This should be called during the initial setup.
|
|
"""
|
|
try:
|
|
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
|
with open(HASHED_PASSWORD_FILE, 'wb') as f:
|
|
f.write(hashed)
|
|
# Set file permissions to read/write for the user only
|
|
os.chmod(HASHED_PASSWORD_FILE, 0o600)
|
|
logging.info("User password hashed and stored successfully.")
|
|
except Exception as e:
|
|
logging.error(f"Failed to store hashed password: {e}")
|
|
logging.error(traceback.format_exc())
|
|
print(colored(f"Error: Failed to store hashed password: {e}", 'red'))
|
|
raise
|
|
|
|
# Example usage (this part should be removed or commented out when integrating into the larger application)
|
|
if __name__ == "__main__":
|
|
from nostr.client import NostrClient # Ensure this import is correct based on your project structure
|
|
|
|
# Initialize PasswordManager
|
|
manager = PasswordManager()
|
|
|
|
# Initialize NostrClient with the parent seed from PasswordManager
|
|
nostr_client = NostrClient(parent_seed=manager.parent_seed)
|
|
|
|
# Example operations
|
|
# These would typically be triggered by user interactions, e.g., via a CLI menu
|
|
# manager.handle_generate_password()
|
|
# manager.handle_retrieve_password()
|
|
# manager.handle_modify_entry()
|
|
# manager.handle_verify_checksum()
|
|
# manager.post_to_nostr(nostr_client)
|
|
# manager.retrieve_from_nostr(nostr_client)
|
|
# manager.backup_database()
|
|
# manager.restore_database()
|