krz/yoshi-cli
A password manager for the command line.
clone: git clone https://gitbay.org/krz/yoshi-cli.git
v0.2.2: yoshi/process.py · raw
1"""
2Password Vault Manager
3
4This script provides various functions for managing password vaults.
5It allows users to create, list, edit and delete accounts.
6
7The `Account` class represents an individual account, with attributes for
8the application name, username, password, and URL. The database module is used
9to interact with the SQLite database file (`vault.sqlite`) that stores the
10accounts data.
11
12Functions:
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
22Usage:
23 Run this script in your terminal to access these functions.
24"""
25
26from string import ascii_letters, punctuation, digits
27import random
28import secrets
29import uuid
30from prettytable import PrettyTable
31from yoshi.account import Account
32from yoshi import database
33from yoshi.wordlist import WORDLIST
34
35
36def generate_characters(n: int) -> list:
37 """
38 Generates a list of n random characters from the set of ASCII letters,
39 punctuation and digits.
40
41 Args:
42 n (int): The number of characters to generate
43
44 Returns:
45 list: A list of n random characters
46 """
47 characters = []
48 password_format = ascii_letters + punctuation + digits
49 for _ in range(n):
50 characters.append(secrets.choice(password_format))
51 return characters
52
53
54def shuffle_characters(characters: list) -> str:
55 """
56 Shuffles the characters to create a password.
57
58 Args:
59 characters (list): The list of characters
60
61 Returns:
62 str: A string representation of the shuffled characters
63 """
64 random.shuffle(characters)
65 character_string = "".join(characters)
66 return character_string
67
68
69def generate_passphrase(n: int, sep: str) -> str:
70 """
71 Generates an XKCD-style passphrase with n words and separator.
72
73 Args:
74 n (int): The number of words to include
75 sep (str): The separator symbol
76
77 Returns:
78 str: A string representation of the passphrase
79 """
80 phrases = []
81 lucky_number = secrets.choice(range(0, n))
82 for _ in range(n):
83 word = secrets.choice(WORDLIST)
84 if _ == lucky_number:
85 phrases.append(word.capitalize() + str(_))
86 else:
87 phrases.append(word.capitalize())
88 passphrase = sep.join(phrases)
89 return passphrase
90
91
92def list_accounts() -> None:
93 """
94 Lists all saved accounts in the database.
95
96 Returns:
97 None
98 """
99 accounts = database.find_accounts()
100 t = PrettyTable(["UUID", "Application", "Username", "Password", "URL"])
101 for account in accounts:
102 t.add_row([account[0], account[1], account[2], account[3], account[4]])
103 print(t)
104
105
106def delete_account(account_uuid: str) -> None:
107 """
108 Deletes an account by its UUID.
109
110 Args:
111 account_uuid (str): The UUID of the account to delete
112
113 Returns:
114 None
115 """
116 account_record = database.find_account(account_uuid)
117 account = Account(
118 account_record[0][0],
119 account_record[0][1],
120 account_record[0][2],
121 account_record[0][3],
122 account_record[0][4],
123 )
124 if account.delete_account():
125 print("Account successfully deleted.")
126
127
128def purge_accounts() -> None:
129 """
130 Purges the entire database (irreversible).
131
132 Returns:
133 None
134 """
135 check = input(
136 """Are you absolutely sure you want to delete your password vault?
137 This action is irreversible. (y/n): """
138 )
139 if check.lower() == "y":
140 database.purge_table()
141 database.purge_database()
142 print(
143 "The password vault has been purged. You may now exit or create a new one."
144 )
145
146
147def create_account() -> None:
148 """
149 Creates a new account by prompting user for details.
150
151 Returns:
152 None
153 """
154 application_string = input("Please enter a name for this account: ")
155 username_string = input("Please enter your username for this account: ")
156 url_string = input("(Optional) Please enter a URL for this account: ")
157
158 password_type = input(
159 """Do you want a random character password (p), an XKCD-style passphrase
160(x), or a custom password (c)? (p|x|c): """
161 )
162 if password_type not in ["p", "x", "c"]:
163 print("Error: Invalid choice. Please choose p, x, or c.")
164 return
165
166 if password_type == "x":
167 password_length = int(
168 input("Please enter number of words to include (min. 2): ")
169 )
170 if password_length < 3:
171 print("Error: Your passphrase length must be at least 3 words.")
172 return
173 password_separator = input(
174 "Please enter your desired separator symbol (_,-, ~, etc.): "
175 )
176 password_string = generate_passphrase(password_length, password_separator)
177 elif password_type == "p":
178 password_length = int(
179 input("Please enter your desired password length (min. 8): ")
180 )
181 if password_length < 8:
182 print("Error: Your password length must be at least 8 characters.")
183 return
184 password_characters = generate_characters(password_length)
185 password_string = shuffle_characters(password_characters)
186 else:
187 password_string = input("Please enter your desired password: ")
188
189 account = Account(
190 str(uuid.uuid4()),
191 application_string,
192 username_string,
193 password_string,
194 url_string,
195 )
196 account.save_account()
197 print("Account saved to the vault. Use `--list` to see all saved accounts.")
198
199
200def edit_account(account_uuid: str, edit_parameter: int) -> None:
201 """
202 Allow users to edit any account information except the UUID.
203
204 Args:
205 account_uuid (str): Unique identifier of the account.
206 edit_parameter (int): Parameter indicating which field to edit.
207 Valid values are 1 for application name, 2 for username,
208 3 for password, and 4 for URL.
209 """
210 field_name, new_value = ""
211 if edit_parameter == 1:
212 field_name = "application"
213 new_value = input("Please enter your desired Application name: ")
214 elif edit_parameter == 2:
215 field_name = "username"
216 new_value = input("Please enter your desired username: ")
217 elif edit_parameter == 3:
218 field_name = "password"
219 type_check = input(
220 "Do you want a new random password or to enter a custom password? "
221 "(random/custom): "
222 ).lower()
223 if type_check == "random":
224 password_length = int(input("Please enter your desired password length: "))
225 if password_length < 8:
226 print("Error: Your password length must be at least 8 characters.")
227 else:
228 password_characters = generate_characters(password_length)
229 new_value = shuffle_characters(password_characters)
230 else:
231 new_value = input("Please enter your desired password: ")
232 elif edit_parameter == 4:
233 field_name = "url"
234 new_value = input("Please enter your desired URL: ")
235 database.update_account(field_name, new_value, account_uuid)
236 print("Account successfully updated.")