""" Password Vault Manager This script provides various functions for managing password vaults. It allows users to create, list, edit and delete accounts. The `Account` class represents an individual account, with attributes for the application name, username, password, and URL. The database module is used to interact with the SQLite database file (`vault.sqlite`) that stores the accounts data. Functions: generate_characters(n): generates a list of random characters shuffle_characters(characters): shuffles the characters to create a password generate_passphrase(n, sep): generates an XKCD-style passphrase with n words and separator list_accounts(): lists all saved accounts in the database delete_account(uuid): deletes an account by its UUID purge_accounts(): purges the entire database (irreversible) create_account(): creates a new account by prompting user for details edit_account(uuid, edit_parameter): edits an existing account's details Usage: Run this script in your terminal to access these functions. """ from string import ascii_letters, punctuation, digits import random import secrets import uuid from prettytable import PrettyTable from yoshi.account import Account from yoshi import database from yoshi.wordlist import WORDLIST def generate_characters(n: int) -> list: """ Generates a list of n random characters from the set of ASCII letters, punctuation and digits. Args: n (int): The number of characters to generate Returns: list: A list of n random characters """ characters = [] password_format = ascii_letters + punctuation + digits for _ in range(n): characters.append(secrets.choice(password_format)) return characters def shuffle_characters(characters: list) -> str: """ Shuffles the characters to create a password. Args: characters (list): The list of characters Returns: str: A string representation of the shuffled characters """ random.shuffle(characters) character_string = "".join(characters) return character_string def generate_passphrase(n: int, sep: str) -> str: """ Generates an XKCD-style passphrase with n words and separator. Args: n (int): The number of words to include sep (str): The separator symbol Returns: str: A string representation of the passphrase """ phrases = [] lucky_number = secrets.choice(range(0, n)) for _ in range(n): word = secrets.choice(WORDLIST) if _ == lucky_number: phrases.append(word.capitalize() + str(_)) else: phrases.append(word.capitalize()) passphrase = sep.join(phrases) return passphrase def list_accounts() -> None: """ Lists all saved accounts in the database. Returns: None """ accounts = database.find_accounts() t = PrettyTable(["UUID", "Application", "Username", "Password", "URL"]) for account in accounts: t.add_row([account[0], account[1], account[2], account[3], account[4]]) print(t) def delete_account(account_uuid: str) -> None: """ Deletes an account by its UUID. Args: account_uuid (str): The UUID of the account to delete Returns: None """ account_record = database.find_account(account_uuid) account = Account( account_record[0][0], account_record[0][1], account_record[0][2], account_record[0][3], account_record[0][4], ) if account.delete_account(): print("Account successfully deleted.") def purge_accounts() -> None: """ Purges the entire database (irreversible). Returns: None """ check = input( """Are you absolutely sure you want to delete your password vault? This action is irreversible. (y/n): """ ) if check.lower() == "y": database.purge_table() database.purge_database() print( "The password vault has been purged. You may now exit or create a new one." ) def create_account() -> None: """ Creates a new account by prompting user for details. Returns: None """ application_string = input("Please enter a name for this account: ") username_string = input("Please enter your username for this account: ") url_string = input("(Optional) Please enter a URL for this account: ") password_type = input( """Do you want a random character password (p), an XKCD-style passphrase (x), or a custom password (c)? (p|x|c): """ ) if password_type not in ["p", "x", "c"]: print("Error: Invalid choice. Please choose p, x, or c.") return if password_type == "x": password_length = int( input("Please enter number of words to include (min. 2): ") ) if password_length < 3: print("Error: Your passphrase length must be at least 3 words.") return password_separator = input( "Please enter your desired separator symbol (_,-, ~, etc.): " ) password_string = generate_passphrase(password_length, password_separator) elif password_type == "p": password_length = int( input("Please enter your desired password length (min. 8): ") ) if password_length < 8: print("Error: Your password length must be at least 8 characters.") return password_characters = generate_characters(password_length) password_string = shuffle_characters(password_characters) else: password_string = input("Please enter your desired password: ") account = Account( str(uuid.uuid4()), application_string, username_string, password_string, url_string, ) account.save_account() print("Account saved to the vault. Use `--list` to see all saved accounts.") def edit_account(account_uuid: str, edit_parameter: int) -> None: """ Allow users to edit any account information except the UUID. Args: account_uuid (str): Unique identifier of the account. edit_parameter (int): Parameter indicating which field to edit. Valid values are 1 for application name, 2 for username, 3 for password, and 4 for URL. """ field_name, new_value = "" if edit_parameter == 1: field_name = "application" new_value = input("Please enter your desired Application name: ") elif edit_parameter == 2: field_name = "username" new_value = input("Please enter your desired username: ") elif edit_parameter == 3: field_name = "password" type_check = input( "Do you want a new random password or to enter a custom password? " "(random/custom): " ).lower() if type_check == "random": password_length = int(input("Please enter your desired password length: ")) if password_length < 8: print("Error: Your password length must be at least 8 characters.") else: password_characters = generate_characters(password_length) new_value = shuffle_characters(password_characters) else: new_value = input("Please enter your desired password: ") elif edit_parameter == 4: field_name = "url" new_value = input("Please enter your desired URL: ") database.update_account(field_name, new_value, account_uuid) print("Account successfully updated.")