Commit 598e71b5ea
Verified · cmc
Layout: unified · split
.github/workflows/pylint.yml added +23
| @@ -0,0 +1,23 @@ | ||
| 1 | name: Pylint | |
| 2 | ||
| 3 | on: [push] | |
| 4 | ||
| 5 | jobs: | |
| 6 | build: | |
| 7 | runs-on: ubuntu-latest | |
| 8 | strategy: | |
| 9 | matrix: | |
| 10 | python-version: ["3.8", "3.9", "3.10"] | |
| 11 | steps: | |
| 12 | - uses: actions/checkout@v4 | |
| 13 | - name: Set up Python ${{ matrix.python-version }} | |
| 14 | uses: actions/setup-python@v4 | |
| 15 | with: | |
| 16 | python-version: ${{ matrix.python-version }} | |
| 17 | - name: Install dependencies | |
| 18 | run: | | |
| 19 | python -m pip install --upgrade pip | |
| 20 | pip install pylint pandas dash plotly.express | |
| 21 | - name: Analysing the code with pylint | |
| 22 | run: | | |
| 23 | pylint -d R0801 $(git ls-files '*.py') | |
Account.py +16
| @@ -1,7 +1,16 @@ | ||
| 1 | """ | |
| 2 | This script imports necessary modules for database interactions. | |
| 3 | ||
| 4 | Modules imported: | |
| 5 | - database: A custom module providing database functionality. | |
| 6 | """ | |
| 7 | ||
| 1 | 8 | import database |
| 2 | 9 | |
| 3 | 10 | |
| 4 | 11 | class Account: |
| 12 | """Represents a login account.""" | |
| 13 | ||
| 5 | 14 | def __init__(self, uuid: str, application: str, username: str, |
| 6 | 15 | password: str, url: str) -> None: |
| 7 | 16 | self.uuid = uuid |
| @@ -11,6 +20,7 @@ class Account: | ||
| 11 | 20 | self.url = url |
| 12 | 21 | |
| 13 | 22 | def display_account(self) -> None: |
| 23 | """Print the account details.""" | |
| 14 | 24 | print('ID:', self.uuid) |
| 15 | 25 | print('Application:', self.application) |
| 16 | 26 | print('Username:', self.username) |
| @@ -18,9 +28,15 @@ class Account: | ||
| 18 | 28 | print('URL:', self.url) |
| 19 | 29 | |
| 20 | 30 | def save_account(self) -> None: |
| 31 | """Save the account details to the database.""" | |
| 21 | 32 | database.create_account( |
| 22 | 33 | self.uuid, self.application, self.username, self.password, self.url) |
| 23 | 34 | |
| 24 | 35 | def delete_account(self) -> bool: |
| 36 | """Delete the account from the database. | |
| 37 | ||
| 38 | Returns: | |
| 39 | bool: True if the deletion was successful. | |
| 40 | """ | |
| 25 | 41 | database.delete_account(self.uuid) |
| 26 | 42 | return True |
crypto.py +30 −15
| @@ -1,30 +1,45 @@ | ||
| 1 | """ | |
| 2 | This module imports the Fernet symmetric encryption algorithm from the cryptography library. | |
| 3 | ||
| 4 | It allows for secure encryption and decryption of data using a secret key. | |
| 5 | """ | |
| 6 | ||
| 1 | 7 | from cryptography.fernet import Fernet |
| 2 | 8 | |
| 3 | vault_file = 'vault.sqlite' | |
| 9 | VAULT_FILE = 'vault.sqlite' | |
| 4 | 10 | |
| 5 | 11 | |
| 6 | 12 | def generate_key() -> bytes: |
| 7 | new_key = Fernet.generate_key() | |
| 8 | return new_key | |
| 13 | """Generates a new encryption key.""" | |
| 14 | return Fernet.generate_key() | |
| 9 | 15 | |
| 10 | 16 | |
| 11 | 17 | def load_key(key_file: str) -> bytes: |
| 12 | return open(key_file, 'rb').read() | |
| 18 | """ | |
| 19 | Loads an existing encryption key from the file. | |
| 20 | ||
| 21 | Args: | |
| 22 | key_file (str): Path to the key file. | |
| 23 | """ | |
| 24 | with open(key_file, 'rb') as key: | |
| 25 | return key.read() | |
| 13 | 26 | |
| 14 | 27 | |
| 15 | def encrypt(key, filename=vault_file) -> None: | |
| 28 | def encrypt(key: bytes, filename: str = VAULT_FILE) -> None: | |
| 29 | """Encrypts the data in the specified file using the provided key.""" | |
| 16 | 30 | f = Fernet(key) |
| 17 | with open(filename, 'rb') as file: | |
| 18 | file_data = file.read() | |
| 19 | encrypted_data = f.encrypt(file_data) | |
| 20 | with open(filename, 'wb') as file: | |
| 21 | file.write(encrypted_data) | |
| 31 | with open(filename, 'rb') as vault: | |
| 32 | data = vault.read() | |
| 33 | encrypted_data = f.encrypt(data) | |
| 34 | with open(filename, 'wb') as vault: | |
| 35 | vault.write(encrypted_data) | |
| 22 | 36 | |
| 23 | 37 | |
| 24 | def decrypt(key, filename=vault_file) -> None: | |
| 38 | def decrypt(key: bytes, filename: str = VAULT_FILE) -> None: | |
| 39 | """Decrypts the data in the specified file using the provided key.""" | |
| 25 | 40 | f = Fernet(key) |
| 26 | with open(filename, 'rb') as file: | |
| 27 | encrypted_data = file.read() | |
| 41 | with open(filename, 'rb') as vault: | |
| 42 | encrypted_data = vault.read() | |
| 28 | 43 | decrypted_data = f.decrypt(encrypted_data) |
| 29 | with open(filename, 'wb') as file: | |
| 30 | file.write(decrypted_data) | |
| 44 | with open(filename, 'wb') as vault: | |
| 45 | vault.write(decrypted_data) | |
database.py +45 −32
| @@ -1,34 +1,41 @@ | ||
| 1 | # Import Python modules | |
| 1 | """ | |
| 2 | This module provides a basic interface for connecting to and interacting with a SQLite database. | |
| 3 | It includes functions for creating connections, executing queries, and retrieving results. | |
| 4 | """ | |
| 5 | ||
| 2 | 6 | import sqlite3 |
| 3 | 7 | import sys |
| 4 | 8 | import os |
| 5 | 9 | |
| 6 | # Specify the name of the vault database | |
| 7 | vault_decrypted = 'vault.sqlite' | |
| 8 | vault_encrypted = 'vault.sqlite.aes' | |
| 10 | VAULT_DECRYPTED = 'vault.sqlite' | |
| 11 | VAULT_ENCRYPTED = 'vault.sqlite.aes' | |
| 9 | 12 | |
| 10 | 13 | |
| 11 | # Create the accounts table inside the vault database | |
| 12 | 14 | def create_table() -> None: |
| 13 | db_connection = sqlite3.connect(vault_decrypted) | |
| 15 | """Create the accounts table within the vault database.""" | |
| 16 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 14 | 17 | cursor = db_connection.cursor() |
| 15 | 18 | cursor.execute( |
| 16 | ''' CREATE TABLE accounts (uuid text, application text, username text, password text, url text) ''') | |
| 19 | ''' CREATE TABLE IF NOT EXISTS accounts (uuid text, application text, | |
| 20 | username text, password text, url text) ''' | |
| 21 | ) | |
| 17 | 22 | db_connection.commit() |
| 18 | 23 | db_connection.close() |
| 19 | 24 | |
| 20 | 25 | |
| 21 | # Check if the account table exists within the database | |
| 22 | def table_check() -> bool: | |
| 26 | def check_table() -> bool: | |
| 27 | """Check if the 'accounts' table exists within the vault database.""" | |
| 23 | 28 | check = False |
| 24 | db_connection = sqlite3.connect(vault_decrypted) | |
| 29 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 25 | 30 | cursor = db_connection.cursor() |
| 26 | 31 | cursor.execute( |
| 27 | ''' SELECT count(name) FROM sqlite_master WHERE type='table' AND name='accounts' ''') | |
| 32 | ''' SELECT count(name) FROM sqlite_master WHERE type='table' | |
| 33 | AND name='accounts' ''' | |
| 34 | ) | |
| 28 | 35 | if cursor.fetchone()[0] != 1: |
| 29 | 36 | user_choice = input( |
| 30 | 37 | 'Password vault does not exist. Would you like to create it now? (y/n): ') |
| 31 | if user_choice == 'y' or user_choice == 'Y': | |
| 38 | if user_choice.lower() == 'y': | |
| 32 | 39 | create_table() |
| 33 | 40 | check = True |
| 34 | 41 | else: |
| @@ -40,44 +47,48 @@ def table_check() -> bool: | ||
| 40 | 47 | return check |
| 41 | 48 | |
| 42 | 49 | |
| 43 | # Add a new account to the database | |
| 44 | def create_account(uuid: str, application: str, username: str, password: str, | |
| 50 | def add_account(uuid: str, application: str, username: str, password: str, | |
| 45 | 51 | url: str) -> None: |
| 46 | db_connection = sqlite3.connect(vault_decrypted) | |
| 52 | """Add a new account within the vault database.""" | |
| 53 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 47 | 54 | cursor = db_connection.cursor() |
| 48 | 55 | cursor.execute( |
| 49 | ''' INSERT INTO accounts VALUES (:uuid,:application,:username,:password,:url) ''', | |
| 50 | {'uuid': uuid, 'application': application, 'username': username, | |
| 51 | 'password': password, 'url': url}) | |
| 56 | ''' INSERT INTO accounts VALUES (:uuid,:application,:username, | |
| 57 | :password,:url) ''', { | |
| 58 | 'uuid': uuid, 'application': application, 'username': username, | |
| 59 | 'password': password, 'url': url | |
| 60 | } | |
| 61 | ) | |
| 52 | 62 | db_connection.commit() |
| 53 | 63 | db_connection.close() |
| 54 | 64 | |
| 55 | 65 | |
| 56 | # Delete an account with a specified UUID | |
| 57 | 66 | def delete_account(uuid: str) -> None: |
| 58 | db_connection = sqlite3.connect(vault_decrypted) | |
| 67 | """Delete an account within the vault database by its unique ID.""" | |
| 68 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 59 | 69 | cursor = db_connection.cursor() |
| 60 | 70 | cursor.execute( |
| 61 | ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}) | |
| 71 | ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid} | |
| 72 | ) | |
| 62 | 73 | db_connection.commit() |
| 63 | 74 | db_connection.close() |
| 64 | 75 | |
| 65 | 76 | |
| 66 | # Find a specific account by UUID | |
| 67 | 77 | def find_account(uuid: str) -> list: |
| 68 | db_connection = sqlite3.connect(vault_decrypted) | |
| 78 | """Find an account within the vault database by its unique ID.""" | |
| 79 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 69 | 80 | cursor = db_connection.cursor() |
| 70 | 81 | cursor.execute( |
| 71 | ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}) | |
| 82 | ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid} | |
| 83 | ) | |
| 72 | 84 | account = cursor.fetchall() |
| 73 | 85 | db_connection.close() |
| 74 | 86 | return account |
| 75 | 87 | |
| 76 | 88 | |
| 77 | # Return all accounts found within the database table | |
| 78 | # The `accounts` variable is a list of tuples | |
| 79 | 89 | def find_accounts() -> list: |
| 80 | db_connection = sqlite3.connect(vault_decrypted) | |
| 90 | """Return all accounts stored within the vault database.""" | |
| 91 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 81 | 92 | cursor = db_connection.cursor() |
| 82 | 93 | cursor.execute(''' SELECT * FROM accounts ''') |
| 83 | 94 | accounts = cursor.fetchall() |
| @@ -86,22 +97,23 @@ def find_accounts() -> list: | ||
| 86 | 97 | |
| 87 | 98 | |
| 88 | 99 | def update_account(field_name: str, new_value: str, uuid: str) -> None: |
| 100 | """Update an account within the vault database by its unique ID.""" | |
| 89 | 101 | queries = { |
| 90 | 102 | 'application': 'UPDATE accounts SET application = :new_value WHERE uuid = :uuid', |
| 91 | 103 | 'username': 'UPDATE accounts SET username = :new_value WHERE uuid = :uuid', |
| 92 | 104 | 'password': 'UPDATE accounts SET password = :new_value WHERE uuid = :uuid', |
| 93 | 105 | 'url': 'UPDATE accounts SET url = :new_value WHERE uuid = :uuid' |
| 94 | 106 | } |
| 95 | db_connection = sqlite3.connect(vault_decrypted) | |
| 107 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 96 | 108 | cursor = db_connection.cursor() |
| 97 | cursor.execute(queries[field_name], | |
| 98 | {'new_value': new_value, 'uuid': uuid}) | |
| 109 | cursor.execute(queries[field_name], {'new_value': new_value, 'uuid': uuid}) | |
| 99 | 110 | db_connection.commit() |
| 100 | 111 | db_connection.close() |
| 101 | 112 | |
| 102 | 113 | |
| 103 | 114 | def purge_table() -> None: |
| 104 | db_connection = sqlite3.connect(vault_decrypted) | |
| 115 | """Purge the 'accounts' table within the vault database.""" | |
| 116 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | |
| 105 | 117 | cursor = db_connection.cursor() |
| 106 | 118 | cursor.execute(''' DROP TABLE accounts ''') |
| 107 | 119 | db_connection.commit() |
| @@ -109,4 +121,5 @@ def purge_table() -> None: | ||
| 109 | 121 | |
| 110 | 122 | |
| 111 | 123 | def purge_database() -> None: |
| 112 | os.remove(vault_decrypted) | |
| 124 | """Purge the entire vault database.""" | |
| 125 | os.remove(VAULT_DECRYPTED) | |
main.py +68 −19
| @@ -1,3 +1,9 @@ | ||
| 1 | """ | |
| 2 | This script uses argparse to parse command line arguments. | |
| 3 | ||
| 4 | It imports the required modules and sets up a parser with basic options for demonstration purposes. | |
| 5 | """ | |
| 6 | ||
| 1 | 7 | import argparse |
| 2 | 8 | import crypto |
| 3 | 9 | import database |
| @@ -5,47 +11,87 @@ import process | ||
| 5 | 11 | |
| 6 | 12 | if __name__ == '__main__': |
| 7 | 13 | parser = argparse.ArgumentParser( |
| 8 | description='Manage your username and passwords via a convenient CLI vault.') | |
| 14 | description='Manage your username and passwords via a convenient CLI vault.' | |
| 15 | ) | |
| 9 | 16 | |
| 10 | 17 | # Top-level arguments |
| 11 | 18 | group_one = parser.add_mutually_exclusive_group() |
| 12 | 19 | group_one.add_argument( |
| 13 | '-n', '--new', help='Create a new account', action='store_true') | |
| 20 | '-n', '--new', | |
| 21 | help='Create a new account.', | |
| 22 | action='store_true' | |
| 23 | ) | |
| 14 | 24 | group_one.add_argument( |
| 15 | '-l', '--list', help='List all saved accounts', action='store_true') | |
| 25 | '-l', '--list', | |
| 26 | help='List all saved accounts.', | |
| 27 | action='store_true' | |
| 28 | ) | |
| 16 | 29 | group_one.add_argument( |
| 17 | '-e', '--edit', help='Edit a saved account', action='store_true') | |
| 30 | '-e', '--edit', | |
| 31 | help='Edit a saved account.', | |
| 32 | action='store_true' | |
| 33 | ) | |
| 18 | 34 | group_one.add_argument( |
| 19 | '-d', '--delete', help='Delete a saved account', action='store_true') | |
| 35 | '-d', '--delete', | |
| 36 | help='Delete a saved account.', | |
| 37 | action='store_true' | |
| 38 | ) | |
| 20 | 39 | group_one.add_argument( |
| 21 | '--purge', help='Purge all accounts and delete the vault', | |
| 22 | action='store_true') | |
| 40 | '--purge', | |
| 41 | help=( | |
| 42 | 'Purge all accounts and delete the vault. ' | |
| 43 | '(Caution: this will irreversibly destroy your data.)' | |
| 44 | ), | |
| 45 | action='store_true' | |
| 46 | ) | |
| 23 | 47 | group_one.add_argument( |
| 24 | '--encrypt', help='Encrypt the vault', action='store_true') | |
| 48 | '--encrypt', | |
| 49 | help='Encrypt the vault.', | |
| 50 | action='store_true' | |
| 51 | ) | |
| 25 | 52 | group_one.add_argument( |
| 26 | '--decrypt', help='Decrypt the vault', action='store_true') | |
| 53 | '--decrypt', | |
| 54 | help='Decrypt the vault.', | |
| 55 | action='store_true' | |
| 56 | ) | |
| 27 | 57 | |
| 28 | 58 | # Encryption flags |
| 29 | 59 | group_two = parser.add_mutually_exclusive_group() |
| 30 | 60 | group_two.add_argument( |
| 31 | 61 | '-g', '--generate', |
| 32 | help='When using the --encrypt option, generate a new encryption key', | |
| 33 | action='store_true') | |
| 62 | help=( | |
| 63 | 'When using the --encrypt option, generate a new encryption key.' | |
| 64 | ), | |
| 65 | action='store_true' | |
| 66 | ) | |
| 34 | 67 | group_two.add_argument( |
| 35 | 68 | '-k', '--keyfile', |
| 36 | help='When using the --encrypt or --decrypt options, specify an existing key file path', | |
| 37 | action='store', nargs=1, type=str) | |
| 69 | help='Path to existing key file.', | |
| 70 | action='store', | |
| 71 | nargs=1, | |
| 72 | type=str | |
| 73 | ) | |
| 38 | 74 | |
| 39 | 75 | # Edit flags |
| 40 | 76 | group_three = parser.add_argument_group() |
| 41 | 77 | group_three.add_argument( |
| 42 | 78 | '-u', '--uuid', |
| 43 | help='When using the --edit or --delete options, provide the account UUID', | |
| 44 | action='store', nargs=1, type=str) | |
| 79 | help=( | |
| 80 | 'When using the --edit or --delete options, provide the account UUID.' | |
| 81 | ), | |
| 82 | action='store', | |
| 83 | nargs=1, | |
| 84 | type=str | |
| 85 | ) | |
| 45 | 86 | group_three.add_argument( |
| 46 | 87 | '-f', '--field', |
| 47 | help='When using the --edit option, provide the field to edit', | |
| 48 | action='store', nargs=1, type=int) | |
| 88 | help=( | |
| 89 | 'When using the --edit option, specify the field to edit (integer index).' | |
| 90 | ), | |
| 91 | action='store', | |
| 92 | nargs=1, | |
| 93 | type=int | |
| 94 | ) | |
| 49 | 95 | |
| 50 | 96 | args = parser.parse_args() |
| 51 | 97 | |
| @@ -59,7 +105,9 @@ if __name__ == '__main__': | ||
| 59 | 105 | if args.generate: |
| 60 | 106 | key = crypto.generate_key() |
| 61 | 107 | print( |
| 62 | 'WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT YOUR DATA WITHOUT IT!') | |
| 108 | 'WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT ' | |
| 109 | 'YOUR DATA WITHOUT IT!' | |
| 110 | ) | |
| 63 | 111 | print(key.decode()) |
| 64 | 112 | print('\n') |
| 65 | 113 | else: |
| @@ -81,4 +129,5 @@ if __name__ == '__main__': | ||
| 81 | 129 | process.purge_accounts() |
| 82 | 130 | else: |
| 83 | 131 | raise TypeError( |
| 84 | 'Please specify a command or use the --help flag for more information.') | |
| 132 | 'Please specify a command or use the --help flag for more information.' | |
| 133 | ) | |
process.py +124 −33
| @@ -1,4 +1,29 @@ | ||
| 1 | from Account import Account | |
| 1 | """ | |
| 2 | Password Vault Manager | |
| 3 | ||
| 4 | This script provides various functions for managing password vaults. | |
| 5 | It allows users to create, list, edit and delete accounts. | |
| 6 | ||
| 7 | The `Account` class represents an individual account, with attributes for | |
| 8 | the application name, username, password, and URL. The database module is used | |
| 9 | to interact with the SQLite database file (`vault.sqlite`) that stores the | |
| 10 | accounts data. | |
| 11 | ||
| 12 | Functions: | |
| 13 | generate_characters(n): generates a list of random characters | |
| 14 | shuffle_characters(characters): shuffles the characters to create a password | |
| 15 | generate_passphrase(n, sep): generates an XKCD-style passphrase with n words and separator | |
| 16 | list_accounts(): lists all saved accounts in the database | |
| 17 | delete_account(uuid): deletes an account by its UUID | |
| 18 | purge_accounts(): purges the entire database (irreversible) | |
| 19 | create_account(): creates a new account by prompting user for details | |
| 20 | edit_account(uuid, edit_parameter): edits an existing account's details | |
| 21 | ||
| 22 | Usage: | |
| 23 | Run this script in your terminal to access these functions. | |
| 24 | """ | |
| 25 | ||
| 26 | from account import Account | |
| 2 | 27 | import database |
| 3 | 28 | from string import ascii_letters, punctuation, digits |
| 4 | 29 | import random |
| @@ -6,39 +31,70 @@ import uuid | ||
| 6 | 31 | from prettytable import PrettyTable |
| 7 | 32 | |
| 8 | 33 | |
| 9 | # Generate a list of characters | |
| 10 | 34 | def generate_characters(n: int) -> list: |
| 11 | characters = list() | |
| 35 | """ | |
| 36 | Generates a list of n random characters from the set of ASCII letters, | |
| 37 | punctuation and digits. | |
| 38 | ||
| 39 | Args: | |
| 40 | n (int): The number of characters to generate | |
| 41 | ||
| 42 | Returns: | |
| 43 | list: A list of n random characters | |
| 44 | """ | |
| 45 | characters = [] | |
| 12 | 46 | password_format = ascii_letters + punctuation + digits |
| 13 | for x in range(n): | |
| 47 | for _ in range(n): | |
| 14 | 48 | characters.append(random.choice(password_format)) |
| 15 | 49 | return characters |
| 16 | 50 | |
| 17 | 51 | |
| 18 | # Randomly shuffle the characters | |
| 19 | 52 | def shuffle_characters(characters: list) -> str: |
| 53 | """ | |
| 54 | Shuffles the characters to create a password. | |
| 55 | ||
| 56 | Args: | |
| 57 | characters (list): The list of characters | |
| 58 | ||
| 59 | Returns: | |
| 60 | str: A string representation of the shuffled characters | |
| 61 | """ | |
| 20 | 62 | random.shuffle(characters) |
| 21 | 63 | character_string = ''.join(characters) |
| 22 | 64 | return character_string |
| 23 | 65 | |
| 24 | 66 | |
| 25 | # Generate a combination of passphrases | |
| 26 | 67 | def generate_passphrase(n: int, sep: str) -> str: |
| 68 | """ | |
| 69 | Generates an XKCD-style passphrase with n words and separator. | |
| 70 | ||
| 71 | Args: | |
| 72 | n (int): The number of words to include | |
| 73 | sep (str): The separator symbol | |
| 74 | ||
| 75 | Returns: | |
| 76 | str: A string representation of the passphrase | |
| 77 | """ | |
| 27 | 78 | phrases = [] |
| 28 | 79 | lucky_number = random.choice(range(0, n)) |
| 29 | for x in range(0, n): | |
| 80 | for _ in range(n): | |
| 30 | 81 | line = random.choice(open('wordlist.txt').readlines()) |
| 31 | 82 | line = line.replace('\n', '') |
| 32 | if x == lucky_number: | |
| 33 | phrases.append(line.strip().capitalize() + str(x)) | |
| 83 | if _ == lucky_number: | |
| 84 | phrases.append(line.strip().capitalize() + str(_)) | |
| 34 | 85 | else: |
| 35 | 86 | phrases.append(line.strip().capitalize()) |
| 36 | 87 | passphrase = sep.join(phrases) |
| 37 | 88 | return passphrase |
| 38 | 89 | |
| 39 | 90 | |
| 40 | # List all saved accounts | |
| 41 | 91 | def list_accounts() -> None: |
| 92 | """ | |
| 93 | Lists all saved accounts in the database. | |
| 94 | ||
| 95 | Returns: | |
| 96 | None | |
| 97 | """ | |
| 42 | 98 | accounts = database.find_accounts() |
| 43 | 99 | t = PrettyTable(['UUID', 'Application', 'Username', 'Password', 'URL']) |
| 44 | 100 | for account in accounts: |
| @@ -46,10 +102,18 @@ def list_accounts() -> None: | ||
| 46 | 102 | print(t) |
| 47 | 103 | |
| 48 | 104 | |
| 49 | # Delete a single account by UUID | |
| 50 | 105 | def delete_account(uuid: str) -> None: |
| 106 | """ | |
| 107 | Deletes an account by its UUID. | |
| 108 | ||
| 109 | Args: | |
| 110 | uuid (str): The UUID of the account to delete | |
| 111 | ||
| 112 | Returns: | |
| 113 | None | |
| 114 | """ | |
| 51 | 115 | account_record = database.find_account(uuid) |
| 52 | account: Account = Account(account_record[0][0], | |
| 116 | account = Account(account_record[0][0], | |
| 53 | 117 | account_record[0][1], |
| 54 | 118 | account_record[0][2], |
| 55 | 119 | account_record[0][3], |
| @@ -58,52 +122,79 @@ def delete_account(uuid: str) -> None: | ||
| 58 | 122 | print('Account successfully deleted.') |
| 59 | 123 | |
| 60 | 124 | |
| 61 | # Purge the `accounts` table and `vault.sqlite` file | |
| 62 | 125 | def purge_accounts() -> None: |
| 126 | """ | |
| 127 | Purges the entire database (irreversible). | |
| 128 | ||
| 129 | Returns: | |
| 130 | None | |
| 131 | """ | |
| 63 | 132 | check = input( |
| 64 | 133 | 'Are you absolutely sure you want to delete your password vault? This action is irreversible. (y/n): ') |
| 65 | if check: | |
| 134 | if check.lower() == 'y': | |
| 66 | 135 | database.purge_table() |
| 67 | 136 | database.purge_database() |
| 68 | 137 | print('The password vault has been purged. You may now exit or create a new one.') |
| 69 | 138 | |
| 70 | 139 | |
| 71 | # Request user input and create an account | |
| 72 | 140 | def create_account() -> None: |
| 141 | """ | |
| 142 | Creates a new account by prompting user for details. | |
| 143 | ||
| 144 | Returns: | |
| 145 | None | |
| 146 | """ | |
| 73 | 147 | application_string = input('Please enter a name for this account: ') |
| 74 | 148 | username_string = input('Please enter your username for this account: ') |
| 75 | 149 | url_string = input('(Optional) Please enter a URL for this account: ') |
| 150 | ||
| 76 | 151 | password_type = input( |
| 77 | '''Do you want a random character password (p), an XKCD-style passphrase | |
| 78 | (x), or a custom password (c)? (p|x|c): ''') | |
| 79 | if password_type == "x" or password_type == "xkcd": | |
| 152 | '''Do you want a random character password (p), an XKCD-style passphrase | |
| 153 | (x), or a custom password (c)? (p|x|c): ''' | |
| 154 | ) | |
| 155 | if password_type not in ['p', 'x', 'c']: | |
| 156 | print('Error: Invalid choice. Please choose p, x, or c.') | |
| 157 | return | |
| 158 | ||
| 159 | if password_type == 'x': | |
| 80 | 160 | password_length = int( |
| 81 | input('Please enter number of words to include (min. 2): ')) | |
| 82 | password_separator = input( | |
| 83 | 'Please enter your desired separator symbol (_, -, ~, etc.: ') | |
| 161 | input('Please enter number of words to include (min. 2): ') | |
| 162 | ) | |
| 84 | 163 | if password_length < 3: |
| 85 | 164 | print('Error: Your passphrase length must be at least 3 words.') |
| 86 | else: | |
| 87 | password_string = generate_passphrase( | |
| 88 | password_length, password_separator) | |
| 89 | elif password_type == "p": | |
| 165 | return | |
| 166 | password_separator = input( | |
| 167 | 'Please enter your desired separator symbol (_,-, ~, etc.): ' | |
| 168 | ) | |
| 169 | password_string = generate_passphrase(password_length, password_separator) | |
| 170 | elif password_type == 'p': | |
| 90 | 171 | password_length = int( |
| 91 | input('Please enter your desired password length (min. 8): ')) | |
| 172 | input('Please enter your desired password length (min. 8): ') | |
| 173 | ) | |
| 92 | 174 | if password_length < 8: |
| 93 | 175 | print('Error: Your password length must be at least 8 characters.') |
| 94 | else: | |
| 95 | password_characters = generate_characters(password_length) | |
| 96 | password_string = shuffle_characters(password_characters) | |
| 176 | return | |
| 177 | password_string = generate_password(password_length) | |
| 97 | 178 | else: |
| 98 | 179 | password_string = input('Please enter your desired password: ') |
| 180 | ||
| 99 | 181 | account = Account(str(uuid.uuid4()), application_string, |
| 100 | 182 | username_string, password_string, url_string) |
| 101 | 183 | account.save_account() |
| 102 | 184 | print('Account saved to the vault. Use `--list` to see all saved accounts.') |
| 103 | 185 | |
| 104 | 186 | |
| 105 | # Allow users to edit any account info except the UUID | |
| 187 | ||
| 106 | 188 | def edit_account(uuid: str, edit_parameter: int) -> None: |
| 189 | """ | |
| 190 | Allow users to edit any account information except the UUID. | |
| 191 | ||
| 192 | Args: | |
| 193 | uuid (str): Unique identifier of the account. | |
| 194 | edit_parameter (int): Parameter indicating which field to edit. | |
| 195 | Valid values are 1 for application name, 2 for username, | |
| 196 | 3 for password, and 4 for URL. | |
| 197 | """ | |
| 107 | 198 | if edit_parameter == 1: |
| 108 | 199 | field_name = 'application' |
| 109 | 200 | new_value = input('Please enter your desired Application name: ') |
| @@ -113,10 +204,10 @@ def edit_account(uuid: str, edit_parameter: int) -> None: | ||
| 113 | 204 | elif edit_parameter == 3: |
| 114 | 205 | field_name = 'password' |
| 115 | 206 | type_check = input( |
| 116 | 'Do you want a new random password or to enter a custom password? (random/custom): ') | |
| 207 | 'Do you want a new random password or to enter a custom password? ' | |
| 208 | '(random/custom): ').lower() | |
| 117 | 209 | if type_check == 'random': |
| 118 | password_length = int( | |
| 119 | input('Please enter your desired password length: ')) | |
| 210 | password_length = int(input('Please enter your desired password length: ')) | |
| 120 | 211 | if password_length < 8: |
| 121 | 212 | print('Error: Your password length must be at least 8 characters.') |
| 122 | 213 | else: |