diff --git a/README.md b/README.md index 8b2ea11..e3bc41a 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,13 @@ python src/main.py 5. Optionally specify the TOTP period and digit count. 6. SeedPass will display the URI and secret so you can add it to your authenticator app. +### Modifying a 2FA Entry + +1. From the main menu choose **Modify an Existing Entry** and enter the index of the 2FA code you want to edit. +2. SeedPass will show the current label, period, digit count, and blacklist status. +3. Enter new values or press **Enter** to keep the existing settings. +4. The updated entry is saved back to your encrypted vault. + ### Managing Multiple Seeds diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index 95539e5..e526b44 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -304,15 +304,22 @@ class EntryManager: url: Optional[str] = None, blacklisted: Optional[bool] = None, notes: Optional[str] = None, + *, + label: Optional[str] = None, + period: Optional[int] = None, + digits: Optional[int] = None, ) -> None: """ Modifies an existing entry based on the provided index and new values. :param index: The index number of the entry to modify. - :param username: (Optional) The new username. - :param url: (Optional) The new URL. + :param username: (Optional) The new username (password entries). + :param url: (Optional) The new URL (password entries). :param blacklisted: (Optional) The new blacklist status. :param notes: (Optional) New notes to attach to the entry. + :param label: (Optional) The new label for TOTP entries. + :param period: (Optional) The new TOTP period in seconds. + :param digits: (Optional) The new number of digits for TOTP codes. """ try: data = self.vault.load_index() @@ -330,13 +337,25 @@ class EntryManager: ) return - if username is not None: - entry["username"] = username - logger.debug(f"Updated username to '{username}' for index {index}.") + entry_type = entry.get("type", EntryType.PASSWORD.value) - if url is not None: - entry["url"] = url - logger.debug(f"Updated URL to '{url}' for index {index}.") + if entry_type == EntryType.TOTP.value: + if label is not None: + entry["label"] = label + logger.debug(f"Updated label to '{label}' for index {index}.") + if period is not None: + entry["period"] = period + logger.debug(f"Updated period to '{period}' for index {index}.") + if digits is not None: + entry["digits"] = digits + logger.debug(f"Updated digits to '{digits}' for index {index}.") + else: + if username is not None: + entry["username"] = username + logger.debug(f"Updated username to '{username}' for index {index}.") + if url is not None: + entry["url"] = url + logger.debug(f"Updated URL to '{url}' for index {index}.") if blacklisted is not None: entry["blacklisted"] = blacklisted diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 0750457..e500b90 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1121,76 +1121,165 @@ class PasswordManager: 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") - notes = entry.get("notes", "") + entry_type = entry.get("type", EntryType.PASSWORD.value) - # 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", - ) - ) + if entry_type == EntryType.TOTP.value: + label = entry.get("label", "") + period = int(entry.get("period", 30)) + digits = int(entry.get("digits", 6)) + blacklisted = entry.get("blacklisted", False) + notes = entry.get("notes", "") - # 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", + f"Modifying 2FA entry '{label}' (Index: {index}):", + "cyan", ) ) - new_blacklisted = blacklisted - - new_notes = ( - input( - f'Enter new notes (leave blank to keep "{notes or "N/A"}"): ' + print(colored(f"Current Period: {period}s", "cyan")) + print(colored(f"Current Digits: {digits}", "cyan")) + print( + colored( + f"Current Blacklist Status: {'Blacklisted' if blacklisted else 'Not Blacklisted'}", + "cyan", + ) + ) + new_label = ( + input(f'Enter new label (leave blank to keep "{label}"): ').strip() + or label + ) + period_input = input( + f"Enter new period in seconds (current: {period}): " ).strip() - or notes - ) + new_period = period + if period_input: + if period_input.isdigit(): + new_period = int(period_input) + else: + print( + colored("Invalid period value. Keeping current.", "yellow") + ) + digits_input = input( + f"Enter new digit count (current: {digits}): " + ).strip() + new_digits = digits + if digits_input: + if digits_input.isdigit(): + new_digits = int(digits_input) + else: + print( + colored( + "Invalid digits value. Keeping current.", + "yellow", + ) + ) + blacklist_input = ( + input( + f'Is this 2FA code 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, - new_notes, - ) + new_notes = ( + input( + f'Enter new notes (leave blank to keep "{notes or "N/A"}"): ' + ).strip() + or notes + ) + + self.entry_manager.modify_entry( + index, + blacklisted=new_blacklisted, + notes=new_notes, + label=new_label, + period=new_period, + digits=new_digits, + ) + else: + website_name = entry.get("website") + username = entry.get("username") + url = entry.get("url") + blacklisted = entry.get("blacklisted") + notes = entry.get("notes", "") + + 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", + ) + ) + + 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 + + new_notes = ( + input( + f'Enter new notes (leave blank to keep "{notes or "N/A"}"): ' + ).strip() + or notes + ) + + self.entry_manager.modify_entry( + index, + new_username, + new_url, + new_blacklisted, + new_notes, + ) # Mark database as dirty for background sync self.is_dirty = True