neuronum 10.0.1__tar.gz → 10.1.0__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.
Potentially problematic release.
This version of neuronum might be problematic. Click here for more details.
- {neuronum-10.0.1/neuronum.egg-info → neuronum-10.1.0}/PKG-INFO +1 -1
- {neuronum-10.0.1 → neuronum-10.1.0}/cli/main.py +151 -32
- {neuronum-10.0.1 → neuronum-10.1.0/neuronum.egg-info}/PKG-INFO +1 -1
- {neuronum-10.0.1 → neuronum-10.1.0}/setup.py +1 -1
- {neuronum-10.0.1 → neuronum-10.1.0}/LICENSE.md +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/README.md +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/cli/__init__.py +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum/__init__.py +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum/neuronum.py +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum.egg-info/SOURCES.txt +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum.egg-info/dependency_links.txt +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum.egg-info/entry_points.txt +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum.egg-info/requires.txt +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/neuronum.egg-info/top_level.txt +0 -0
- {neuronum-10.0.1 → neuronum-10.1.0}/setup.cfg +0 -0
|
@@ -64,6 +64,25 @@ def derive_keys_from_mnemonic(mnemonic: str):
|
|
|
64
64
|
click.echo(f"❌ Error generating keys from mnemonic: {e}")
|
|
65
65
|
return None, None, None, None
|
|
66
66
|
|
|
67
|
+
def base64url_encode(data: bytes) -> str:
|
|
68
|
+
"""Base64url encodes bytes (no padding, URL-safe characters)."""
|
|
69
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
|
|
70
|
+
|
|
71
|
+
def create_dns_challenge_value(public_key_pem: bytes) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Creates a DNS TXT challenge value from the public key.
|
|
74
|
+
|
|
75
|
+
This simulates creating an ACME-style key authorization by hashing
|
|
76
|
+
the public key (a proxy for account key) and then base64url encoding it.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
# A simple, secure challenge value: SHA256(PublicKey_PEM) base64url encoded
|
|
80
|
+
key_hash = hashlib.sha256(public_key_pem).digest()
|
|
81
|
+
challenge_value = base64url_encode(key_hash)
|
|
82
|
+
return challenge_value
|
|
83
|
+
except Exception as e:
|
|
84
|
+
click.echo(f"❌ Error creating DNS challenge value: {e}")
|
|
85
|
+
return ""
|
|
67
86
|
|
|
68
87
|
def save_credentials(host: str, mnemonic: str, pem_public: bytes, pem_private: bytes):
|
|
69
88
|
"""Saves host, mnemonic, and keys to the .neuronum directory."""
|
|
@@ -131,52 +150,152 @@ def cli():
|
|
|
131
150
|
|
|
132
151
|
# --- CLI Commands ---
|
|
133
152
|
|
|
153
|
+
# ... (existing CLI Group and Commands)
|
|
154
|
+
|
|
134
155
|
@click.command()
|
|
135
156
|
def create_cell():
|
|
136
|
-
"""Creates a new
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
"""Creates a new Cell with a randomly generated key pair."""
|
|
158
|
+
cell_type = questionary.select(
|
|
159
|
+
"Choose Cell type:",
|
|
160
|
+
choices=["business", "community"]
|
|
161
|
+
).ask()
|
|
162
|
+
|
|
163
|
+
if not cell_type:
|
|
164
|
+
click.echo("Cell creation canceled.")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# 1. Generate Mnemonic and Keys for both types
|
|
139
168
|
mnemonic = Bip39MnemonicGenerator().FromWordsNumber(12)
|
|
140
169
|
private_key, public_key, pem_private, pem_public = derive_keys_from_mnemonic(mnemonic)
|
|
141
170
|
|
|
142
171
|
if not private_key:
|
|
143
172
|
return
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
public_key_pem_str = pem_public.decode("utf-8")
|
|
175
|
+
|
|
176
|
+
# --- Business Cell Logic (DNS Challenge) ---
|
|
177
|
+
if cell_type == "business":
|
|
178
|
+
company_name = questionary.text("Enter your full Company Name e.g., Neuronum Cybernetics UG").ask()
|
|
179
|
+
domain = questionary.text("Enter your FQDN (e.g., mycompany.com):").ask()
|
|
180
|
+
if not domain:
|
|
181
|
+
click.echo("Business cell creation canceled. Host is required.")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Generate the DNS challenge value
|
|
185
|
+
challenge_value = create_dns_challenge_value(pem_public)
|
|
186
|
+
|
|
187
|
+
if not challenge_value:
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
# 2. Instruct User on DNS TXT Record
|
|
191
|
+
click.echo("\n" + "=" * 60)
|
|
192
|
+
click.echo("⚠️ DNS TXT Challenge Required")
|
|
193
|
+
click.echo("=" * 60)
|
|
194
|
+
click.echo(f"To prove ownership of '{domain}', please create a **DNS TXT record**.")
|
|
195
|
+
click.echo(f"This record must be placed on the subdomain **_neuronum.{domain}**.")
|
|
196
|
+
click.echo(f"\nName: **_neuronum.{domain}**")
|
|
197
|
+
click.echo(f"Type: **TXT**")
|
|
198
|
+
click.echo(f"Value: **{challenge_value}**")
|
|
199
|
+
click.echo("-" * 60)
|
|
200
|
+
|
|
201
|
+
# Pause for user action
|
|
202
|
+
questionary.press_any_key_to_continue("Press any key to continue once the DNS record is published...").ask()
|
|
203
|
+
click.echo("Attempting verification...")
|
|
204
|
+
|
|
205
|
+
# 3. Call API to Create/Verify Cell (Pass public_key, host, and challenge)
|
|
206
|
+
url = f"{API_BASE_URL}/create_business_cell"
|
|
207
|
+
create_data = {
|
|
208
|
+
"public_key": public_key_pem_str,
|
|
209
|
+
"domain": domain,
|
|
210
|
+
"challenge_value": challenge_value,
|
|
211
|
+
"company_name": company_name # Optional: Server might re-calculate but good to send
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
# Server will check DNS for the TXT record, then create the cell
|
|
216
|
+
response = requests.post(url, json=create_data, timeout=30) # Increased timeout for DNS propagation
|
|
217
|
+
response.raise_for_status()
|
|
218
|
+
response_data = response.json()
|
|
219
|
+
|
|
220
|
+
# Check if server confirmed verification and creation
|
|
221
|
+
if response_data.get("status") == "verified" and response_data.get("host"):
|
|
222
|
+
# 4. Save Credentials
|
|
223
|
+
host = response_data.get("host")
|
|
224
|
+
if host:
|
|
225
|
+
if save_credentials(host, mnemonic, pem_public, pem_private):
|
|
226
|
+
click.echo("\n" + "=" * 50)
|
|
227
|
+
click.echo(" ✅ BUSINESS CELL CREATED! DNS verified and keys saved.")
|
|
228
|
+
click.echo(f" Host: {host}")
|
|
229
|
+
click.echo(f" Mnemonic (CRITICAL! Back this up!):")
|
|
230
|
+
click.echo(f" {mnemonic}")
|
|
231
|
+
click.echo("-" * 50)
|
|
232
|
+
click.echo(f"Credentials saved to: {NEURONUM_PATH}")
|
|
233
|
+
# else: Error saving already echoed in helper
|
|
234
|
+
else:
|
|
235
|
+
click.echo(f"❌ Verification failed. Server response: {response_data.get('detail', 'Unknown failure.')}")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
except requests.exceptions.HTTPError as e:
|
|
239
|
+
# This catches all 4xx and 5xx errors.
|
|
240
|
+
try:
|
|
241
|
+
# Attempt to parse the server's detailed JSON error body (FastAPI format)
|
|
242
|
+
error_data = e.response.json()
|
|
243
|
+
error_detail = error_data.get("detail", "Unknown server error.")
|
|
244
|
+
|
|
245
|
+
# Print the specific detail message provided by the server
|
|
246
|
+
click.echo(f"❌ Verification failed. HTTP {e.response.status_code} Error: {error_detail}")
|
|
247
|
+
|
|
248
|
+
# Specific handling for the DNS verification failure (403)
|
|
249
|
+
if e.response.status_code == 403:
|
|
250
|
+
click.echo("\n👉 Please double-check that the TXT record is published and correctly set.")
|
|
251
|
+
|
|
252
|
+
except:
|
|
253
|
+
# If the response isn't JSON or doesn't have a 'detail' field
|
|
254
|
+
click.echo(f"❌ Server Error ({e.response.status_code}): {e.response.text}")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
except requests.exceptions.RequestException as e:
|
|
258
|
+
# This catches network issues (DNS failure, connection refused, timeout, etc.)
|
|
259
|
+
click.echo(f"❌ Network Error: Could not communicate with the server. Details: {e}")
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
# --- Community Cell Logic (Existing Logic) ---
|
|
263
|
+
if cell_type == "community":
|
|
264
|
+
# 2. Call API to Create Cell
|
|
265
|
+
click.echo("🔗 Requesting new cell creation from server...")
|
|
266
|
+
url = f"{API_BASE_URL}/create_cell"
|
|
267
|
+
create_data = {"public_key": public_key_pem_str}
|
|
158
268
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
click.echo(f"
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
269
|
+
try:
|
|
270
|
+
response = requests.post(url, json=create_data, timeout=10)
|
|
271
|
+
response.raise_for_status()
|
|
272
|
+
host = response.json().get("host")
|
|
273
|
+
|
|
274
|
+
except requests.exceptions.RequestException as e:
|
|
275
|
+
click.echo(f"❌ Error communicating with the server: {e}")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# 3. Save Credentials
|
|
279
|
+
if host:
|
|
280
|
+
if save_credentials(host, mnemonic, pem_public, pem_private):
|
|
281
|
+
click.echo("\n" + "=" * 50)
|
|
282
|
+
click.echo(" ✅ WELCOME TO NEURONUM! Cell Created Successfully.")
|
|
283
|
+
click.echo("=" * 50)
|
|
284
|
+
click.echo(f" Host: {host}")
|
|
285
|
+
click.echo(f" Mnemonic (CRITICAL! Back this up!):")
|
|
286
|
+
click.echo(f" {mnemonic}")
|
|
287
|
+
click.echo("-" * 50)
|
|
288
|
+
click.echo(f"Credentials saved to: {NEURONUM_PATH}")
|
|
289
|
+
else:
|
|
290
|
+
# Error saving credentials already echoed in helper
|
|
291
|
+
pass
|
|
170
292
|
else:
|
|
171
|
-
|
|
172
|
-
pass
|
|
173
|
-
else:
|
|
174
|
-
click.echo("❌ Error: Server did not return a host. Cell creation failed.")
|
|
293
|
+
click.echo("❌ Error: Server did not return a host. Cell creation failed.")
|
|
175
294
|
|
|
176
295
|
|
|
177
296
|
@click.command()
|
|
178
297
|
def connect_cell():
|
|
179
|
-
"""Connects to an existing
|
|
298
|
+
"""Connects to an existing Cell using a 12-word mnemonic."""
|
|
180
299
|
|
|
181
300
|
# 1. Get and Validate Mnemonic
|
|
182
301
|
mnemonic = questionary.text("Enter your 12-word BIP-39 mnemonic (space separated):").ask()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|