From 18cfab079a966e0a50f786c7971dd2ca58c6d29d Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Thu, 5 Feb 2026 16:32:17 -0500 Subject: [PATCH] fixed wordlist download and unit testing for live api --- hate_crack/api.py | 75 +++++++++++++++++++++++++++++++++--------- tests/test_hashview.py | 13 ++++++++ 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/hate_crack/api.py b/hate_crack/api.py index 023b8ec..ecfcd4e 100644 --- a/hate_crack/api.py +++ b/hate_crack/api.py @@ -479,6 +479,9 @@ def weakpass_wordlist_menu(rank=-1): # Hashview Integration - Real API implementation matching hate_crack.py class HashviewAPI: + def _auth_headers(self): + return {"Cookie": f"uuid={self.api_key}"} + def upload_wordlist_file(self, wordlist_path, wordlist_name=None): """Directly upload a wordlist file to Hashview (non-interactive).""" if wordlist_name is None: @@ -1240,19 +1243,28 @@ def download_hashes_from_hashview( try: customer_hashfiles = api_harness.get_customer_hashfiles(customer_id) if customer_hashfiles: - print_fn("\n" + "=" * 100) + print_fn("\n" + "=" * 120) print_fn(f"Hashfiles for Customer ID {customer_id}:") - print_fn("=" * 100) - print_fn(f"{'ID':<10} {'Name':<88}") - print_fn("-" * 100) + print_fn("=" * 120) + print_fn(f"{'ID':<10} {'Hash Type':<10} {'Name':<96}") + print_fn("-" * 120) + hashfile_map = {} for hf in customer_hashfiles: - hf_id = hf.get("id", "N/A") + hf_id = hf.get("id") hf_name = hf.get("name", "N/A") - if len(str(hf_name)) > 88: - hf_name = str(hf_name)[:85] + "..." - print_fn(f"{hf_id:<10} {hf_name:<88}") - print_fn("=" * 100) - print_fn(f"Total: {len(customer_hashfiles)} hashfile(s)") + hf_type = hf.get("hash_type") or hf.get("hashtype") or "N/A" + if hf_id is None: + continue + if len(str(hf_name)) > 96: + hf_name = str(hf_name)[:93] + "..." + if debug_mode: + print_fn( + f"[DEBUG] Hashfile {hf_id}: hash_type={hf.get('hash_type')}, hashtype={hf.get('hashtype')}, combined={hf_type}" + ) + print_fn(f"{hf_id:<10} {hf_type:<10} {hf_name:<96}") + hashfile_map[int(hf_id)] = hf_type + print_fn("=" * 120) + print_fn(f"Total: {len(hashfile_map)} hashfile(s)") else: print_fn(f"\nNo hashfiles found for customer ID {customer_id}") print_fn("This customer needs to have hashfiles uploaded before downloading left hashes.") @@ -1263,14 +1275,45 @@ def download_hashes_from_hashview( except Exception as exc: print_fn(f"\nWarning: Could not list hashfiles: {exc}") print_fn("You may need to manually find the hashfile ID in the web interface.") - hashfile_raw = _safe_input("\nEnter hashfile ID: ").strip() - if hashfile_raw.lower() == "q": - raise ValueError("cancelled") - hashfile_id = int(hashfile_raw) - hcat_hash_type = "1000" + hashfile_map = {} + + while True: + hashfile_raw = _safe_input("\nEnter hashfile ID: ").strip() + if hashfile_raw.lower() == "q": + raise ValueError("cancelled") + try: + hashfile_id = int(hashfile_raw) + except ValueError: + print_fn("\n✗ Error: Invalid ID entered. Please enter a numeric ID.") + continue + if hashfile_map and hashfile_id not in hashfile_map: + print_fn("\n✗ Error: Hashfile ID not in the list. Please try again.") + continue + break + + selected_hash_type = hashfile_map.get(hashfile_id) if hashfile_map else None + if debug_mode: + print_fn(f"[DEBUG] selected_hash_type from map: {selected_hash_type}") + if not selected_hash_type or selected_hash_type == "N/A": + try: + details = api_harness.get_hashfile_details(hashfile_id) + selected_hash_type = details.get("hashtype") + if debug_mode: + print_fn( + f"[DEBUG] selected_hash_type from get_hashfile_details: {selected_hash_type}" + ) + except Exception as exc: + if debug_mode: + print_fn(f"[DEBUG] Error fetching hashfile details: {exc}") + selected_hash_type = None + + hcat_hash_type = str(selected_hash_type) if selected_hash_type else "1000" output_file = f"left_{customer_id}_{hashfile_id}.txt" download_result = api_harness.download_left_hashes( - customer_id, hashfile_id, output_file + customer_id, + hashfile_id, + output_file, + hash_type=selected_hash_type, ) print_fn(f"\n✓ Success: Downloaded {download_result['size']} bytes") print_fn(f" File: {download_result['output_file']}") diff --git a/tests/test_hashview.py b/tests/test_hashview.py index 998f8a8..8dcd7bb 100644 --- a/tests/test_hashview.py +++ b/tests/test_hashview.py @@ -334,6 +334,19 @@ class TestHashviewAPI: assert content == b"gzipdata" assert result["size"] == len(content) + @pytest.mark.skipif( + os.environ.get("HASHVIEW_TEST_REAL", "").lower() not in ("1", "true", "yes"), + reason="Set HASHVIEW_TEST_REAL=1 to run live Hashview list_wordlists test.", + ) + def test_list_wordlists_live(self): + """Live test for Hashview wordlist listing with auth headers.""" + hashview_url, hashview_api_key = self._get_hashview_config() + if not hashview_url or not hashview_api_key: + pytest.skip("Missing hashview_url/hashview_api_key in config.json or env.") + real_api = HashviewAPI(hashview_url, hashview_api_key) + wordlists = real_api.list_wordlists() + assert isinstance(wordlists, list) + def test_create_job_workflow(self, api, test_hashfile): """Test creating a job in Hashview (option 2 complete workflow)""" print("\n" + "=" * 60)