Initial authentication setup. Creates user, generates encryption key, encrypts database, generates recovery codes. Returns dict with recovery_codes list on success.
(self, username: str, password: str)
| 333 | # ========================================================================= |
| 334 | |
| 335 | def setup(self, username: str, password: str) -> dict: |
| 336 | """ |
| 337 | Initial authentication setup. Creates user, generates encryption key, |
| 338 | encrypts database, generates recovery codes. |
| 339 | Returns dict with recovery_codes list on success. |
| 340 | """ |
| 341 | if not cryptography_available: |
| 342 | return {'success': False, 'error': 'cryptography package not installed. Run: pip install cryptography'} |
| 343 | |
| 344 | if self.is_configured(): |
| 345 | return {'success': False, 'error': 'Authentication is already configured'} |
| 346 | |
| 347 | if not username or not password: |
| 348 | return {'success': False, 'error': 'Username and password are required'} |
| 349 | |
| 350 | if len(password) < 8: |
| 351 | return {'success': False, 'error': 'Password must be at least 8 characters'} |
| 352 | |
| 353 | try: |
| 354 | hw_fingerprint = self.get_hardware_fingerprint() |
| 355 | |
| 356 | # Generate the master Fernet key (this is the actual encryption key) |
| 357 | fernet_key = Fernet.generate_key() |
| 358 | |
| 359 | # Hash the password |
| 360 | pw_hash, pw_salt = self._hash_password(password) |
| 361 | |
| 362 | # Wrap the Fernet key with password + HW fingerprint |
| 363 | wrapping_key = self._derive_wrapping_key(password, hw_fingerprint) |
| 364 | wrapped_fernet_key = self._wrap_fernet_key(fernet_key, wrapping_key) |
| 365 | |
| 366 | # Generate recovery codes and wrap Fernet key with each |
| 367 | recovery_codes = self._generate_recovery_codes() |
| 368 | recovery_entries = [] |
| 369 | for code in recovery_codes: |
| 370 | code_hash, code_salt = self._hash_password(code) |
| 371 | code_wrapping_key = self._derive_wrapping_key(code, hw_fingerprint) |
| 372 | code_wrapped_key = self._wrap_fernet_key(fernet_key, code_wrapping_key) |
| 373 | recovery_entries.append((code_hash, code_salt, code_wrapped_key)) |
| 374 | |
| 375 | # Store everything in auth DB |
| 376 | with self._get_auth_conn() as conn: |
| 377 | cursor = conn.cursor() |
| 378 | |
| 379 | # Clear any existing data (shouldn't exist, but be safe) |
| 380 | cursor.execute("DELETE FROM auth") |
| 381 | cursor.execute("DELETE FROM recovery_codes") |
| 382 | |
| 383 | # Insert auth record |
| 384 | cursor.execute(""" |
| 385 | INSERT INTO auth (username, password_hash, password_salt, |
| 386 | hardware_fingerprint, encrypted_fernet_key) |
| 387 | VALUES (?, ?, ?, ?, ?) |
| 388 | """, (username, pw_hash, pw_salt, hw_fingerprint, wrapped_fernet_key)) |
| 389 | |
| 390 | # Insert recovery codes |
| 391 | for code_hash, code_salt, code_wrapped_key in recovery_entries: |
| 392 | cursor.execute(""" |
no test coverage detected