mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
Add manual 2FA entry option
This commit is contained in:
11
README.md
11
README.md
@@ -173,6 +173,17 @@ python src/main.py
|
|||||||
Enter your choice (1-5):
|
Enter your choice (1-5):
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When choosing **Add Entry**, you can now select **Password** or **2FA (TOTP)**.
|
||||||
|
|
||||||
|
### Adding a 2FA Entry
|
||||||
|
|
||||||
|
1. From the main menu choose **Add Entry** and select **2FA (TOTP)**.
|
||||||
|
2. Provide a label for the account (for example, `GitHub`).
|
||||||
|
3. Enter the derivation index you wish to use for this 2FA code.
|
||||||
|
4. Optionally specify the TOTP period and digit count.
|
||||||
|
5. SeedPass will display an `otpauth://` URI and secret that you can manually
|
||||||
|
enter into your authenticator app.
|
||||||
|
|
||||||
|
|
||||||
### Managing Multiple Seeds
|
### Managing Multiple Seeds
|
||||||
|
|
||||||
|
@@ -591,13 +591,17 @@ def display_menu(
|
|||||||
while True:
|
while True:
|
||||||
print("\nAdd Entry:")
|
print("\nAdd Entry:")
|
||||||
print("1. Password")
|
print("1. Password")
|
||||||
print("2. Back")
|
print("2. 2FA (TOTP)")
|
||||||
|
print("3. Back")
|
||||||
sub_choice = input("Select entry type: ").strip()
|
sub_choice = input("Select entry type: ").strip()
|
||||||
password_manager.update_activity()
|
password_manager.update_activity()
|
||||||
if sub_choice == "1":
|
if sub_choice == "1":
|
||||||
password_manager.handle_add_password()
|
password_manager.handle_add_password()
|
||||||
break
|
break
|
||||||
elif sub_choice == "2":
|
elif sub_choice == "2":
|
||||||
|
password_manager.handle_add_totp()
|
||||||
|
break
|
||||||
|
elif sub_choice == "3":
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print(colored("Invalid choice.", "red"))
|
print(colored("Invalid choice.", "red"))
|
||||||
|
@@ -862,6 +862,62 @@ class PasswordManager:
|
|||||||
logging.error(f"Error during password generation: {e}", exc_info=True)
|
logging.error(f"Error during password generation: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to generate password: {e}", "red"))
|
print(colored(f"Error: Failed to generate password: {e}", "red"))
|
||||||
|
|
||||||
|
def handle_add_totp(self) -> None:
|
||||||
|
"""Prompt for details and add a new TOTP entry."""
|
||||||
|
try:
|
||||||
|
label = input("Enter the account label: ").strip()
|
||||||
|
if not label:
|
||||||
|
print(colored("Error: Label cannot be empty.", "red"))
|
||||||
|
return
|
||||||
|
|
||||||
|
index_input = input("Enter derivation index (number): ").strip()
|
||||||
|
if not index_input.isdigit():
|
||||||
|
print(colored("Error: Index must be a number.", "red"))
|
||||||
|
return
|
||||||
|
totp_index = int(index_input)
|
||||||
|
|
||||||
|
period_input = input("TOTP period in seconds (default 30): ").strip()
|
||||||
|
period = 30
|
||||||
|
if period_input:
|
||||||
|
if not period_input.isdigit():
|
||||||
|
print(colored("Error: Period must be a number.", "red"))
|
||||||
|
return
|
||||||
|
period = int(period_input)
|
||||||
|
|
||||||
|
digits_input = input("Number of digits (default 6): ").strip()
|
||||||
|
digits = 6
|
||||||
|
if digits_input:
|
||||||
|
if not digits_input.isdigit():
|
||||||
|
print(colored("Error: Digits must be a number.", "red"))
|
||||||
|
return
|
||||||
|
digits = int(digits_input)
|
||||||
|
|
||||||
|
entry_id = self.entry_manager.get_next_index()
|
||||||
|
uri = self.entry_manager.add_totp(label, totp_index, period, digits)
|
||||||
|
|
||||||
|
self.is_dirty = True
|
||||||
|
self.last_update = time.time()
|
||||||
|
|
||||||
|
secret = TotpManager.derive_secret(self.parent_seed, totp_index)
|
||||||
|
|
||||||
|
print(colored(f"\n[+] TOTP entry added with ID {entry_id}.\n", "green"))
|
||||||
|
print(colored("Add this URI to your authenticator app:", "cyan"))
|
||||||
|
print(colored(uri, "yellow"))
|
||||||
|
print(colored(f"Secret: {secret}\n", "cyan"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.sync_vault()
|
||||||
|
logging.info("Encrypted index posted to Nostr after TOTP add.")
|
||||||
|
except Exception as nostr_error:
|
||||||
|
logging.error(
|
||||||
|
f"Failed to post updated index to Nostr: {nostr_error}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error during TOTP setup: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: Failed to add TOTP: {e}", "red"))
|
||||||
|
|
||||||
def handle_retrieve_entry(self) -> None:
|
def handle_retrieve_entry(self) -> None:
|
||||||
"""
|
"""
|
||||||
Handles retrieving a password from the index by prompting the user for the index number
|
Handles retrieving a password from the index by prompting the user for the index number
|
||||||
|
@@ -39,6 +39,7 @@ def _make_pm(called, locked=None):
|
|||||||
last_activity=time.time(),
|
last_activity=time.time(),
|
||||||
nostr_client=SimpleNamespace(close_client_pool=lambda: None),
|
nostr_client=SimpleNamespace(close_client_pool=lambda: None),
|
||||||
handle_add_password=add,
|
handle_add_password=add,
|
||||||
|
handle_add_totp=lambda: None,
|
||||||
handle_retrieve_entry=retrieve,
|
handle_retrieve_entry=retrieve,
|
||||||
handle_modify_entry=modify,
|
handle_modify_entry=modify,
|
||||||
update_activity=update,
|
update_activity=update,
|
||||||
@@ -76,7 +77,7 @@ def test_out_of_range_menu(monkeypatch, capsys):
|
|||||||
def test_invalid_add_entry_submenu(monkeypatch, capsys):
|
def test_invalid_add_entry_submenu(monkeypatch, capsys):
|
||||||
called = {"add": False, "retrieve": False, "modify": False}
|
called = {"add": False, "retrieve": False, "modify": False}
|
||||||
pm, _ = _make_pm(called)
|
pm, _ = _make_pm(called)
|
||||||
inputs = iter(["1", "3", "2", "5"])
|
inputs = iter(["1", "4", "3", "5"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
Reference in New Issue
Block a user