dominusnode-langchain 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode-langchain
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: LangChain tools for Dominus Node rotating proxy service
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -15,6 +15,7 @@ Security:
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
+ import hashlib
18
19
  import ipaddress
19
20
  import math
20
21
  import os
@@ -63,6 +64,46 @@ def _sanitize_error(message: str) -> str:
63
64
  return _CREDENTIAL_RE.sub("***", message)
64
65
 
65
66
 
67
+ def _count_leading_zero_bits(data: bytes) -> int:
68
+ """Count leading zero bits in a byte array."""
69
+ count = 0
70
+ for byte in data:
71
+ if byte == 0:
72
+ count += 8
73
+ else:
74
+ mask = 0x80
75
+ while mask and not (byte & mask):
76
+ count += 1
77
+ mask >>= 1
78
+ break
79
+ return count
80
+
81
+
82
+ def _solve_pow(base_url: str) -> Optional[dict]:
83
+ """Solve a Proof-of-Work challenge for CAPTCHA-free registration."""
84
+ try:
85
+ pow_url = f"{base_url.rstrip('/')}/api/auth/pow/challenge"
86
+ with httpx.Client(timeout=30.0, follow_redirects=False) as client:
87
+ resp = client.post(pow_url, headers={"Content-Type": "application/json"})
88
+ if resp.status_code >= 400:
89
+ return None
90
+ challenge = resp.json()
91
+ prefix = challenge.get("prefix", "")
92
+ difficulty = challenge.get("difficulty", 20)
93
+ challenge_id = challenge.get("challengeId", "")
94
+ if not prefix or not challenge_id:
95
+ return None
96
+ nonce = 0
97
+ while nonce < 100_000_000:
98
+ h = hashlib.sha256((prefix + str(nonce)).encode()).digest()
99
+ if _count_leading_zero_bits(h) >= difficulty:
100
+ return {"challengeId": challenge_id, "nonce": str(nonce)}
101
+ nonce += 1
102
+ return None
103
+ except Exception:
104
+ return None
105
+
106
+
66
107
  # ──────────────────────────────────────────────────────────────────────
67
108
  # Prototype pollution prevention
68
109
  # ──────────────────────────────────────────────────────────────────────
@@ -2363,9 +2404,14 @@ class DominusNodeRegisterTool(BaseTool):
2363
2404
  if not password or len(password) < 8 or len(password) > 128:
2364
2405
  return "Error: Password must be between 8 and 128 characters."
2365
2406
  try:
2366
- data = _api_request_unauth_sync(self.base_url, "POST", "/api/auth/register", {"email": email, "password": password}, agent_secret=self.agent_secret)
2407
+ body: dict = {"email": email, "password": password}
2408
+ pow_result = _solve_pow(self.base_url)
2409
+ if pow_result:
2410
+ body["pow"] = pow_result
2411
+ data = _api_request_unauth_sync(self.base_url, "POST", "/api/auth/register", body, agent_secret=self.agent_secret)
2367
2412
  user = data.get("user", {})
2368
- return f"Account Created\n Email: {user.get('email', email)}\n User ID: {user.get('id', '?')}\n Email auto-verified (MCP agent)"
2413
+ pow_msg = "Email auto-verified via Proof-of-Work." if pow_result else "Email auto-verified (MCP agent)"
2414
+ return f"Account Created\n Email: {user.get('email', email)}\n User ID: {user.get('id', '?')}\n {pow_msg}"
2369
2415
  except Exception as exc:
2370
2416
  return f"Error: {_sanitize_error(str(exc))}"
2371
2417
 
@@ -2377,9 +2423,14 @@ class DominusNodeRegisterTool(BaseTool):
2377
2423
  if not password or len(password) < 8 or len(password) > 128:
2378
2424
  return "Error: Password must be between 8 and 128 characters."
2379
2425
  try:
2380
- data = await _api_request_unauth_async(self.base_url, "POST", "/api/auth/register", {"email": email, "password": password}, agent_secret=self.agent_secret)
2426
+ body: dict = {"email": email, "password": password}
2427
+ pow_result = _solve_pow(self.base_url)
2428
+ if pow_result:
2429
+ body["pow"] = pow_result
2430
+ data = await _api_request_unauth_async(self.base_url, "POST", "/api/auth/register", body, agent_secret=self.agent_secret)
2381
2431
  user = data.get("user", {})
2382
- return f"Account Created\n Email: {user.get('email', email)}\n User ID: {user.get('id', '?')}\n Email auto-verified (MCP agent)"
2432
+ pow_msg = "Email auto-verified via Proof-of-Work." if pow_result else "Email auto-verified (MCP agent)"
2433
+ return f"Account Created\n Email: {user.get('email', email)}\n User ID: {user.get('id', '?')}\n {pow_msg}"
2383
2434
  except Exception as exc:
2384
2435
  return f"Error: {_sanitize_error(str(exc))}"
2385
2436
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dominusnode-langchain"
7
- version = "1.2.3"
7
+ version = "1.2.4"
8
8
  description = "LangChain tools for Dominus Node rotating proxy service"
9
9
  readme = "README.md"
10
10
  license = "MIT"