Verify credentials and start DB decryption in background. Returns immediately.
(self, username: str, password: str)
| 430 | # ========================================================================= |
| 431 | |
| 432 | def login(self, username: str, password: str) -> dict: |
| 433 | """Verify credentials and start DB decryption in background. Returns immediately.""" |
| 434 | if not self.is_configured(): |
| 435 | return {'success': False, 'error': 'Authentication not configured'} |
| 436 | |
| 437 | try: |
| 438 | with self._get_auth_conn() as conn: |
| 439 | cursor = conn.cursor() |
| 440 | cursor.execute("SELECT * FROM auth WHERE username = ?", (username,)) |
| 441 | row = cursor.fetchone() |
| 442 | |
| 443 | if not row: |
| 444 | return {'success': False, 'error': 'Invalid username or password'} |
| 445 | |
| 446 | # Verify password |
| 447 | if not self._verify_password(password, row['password_hash'], row['password_salt']): |
| 448 | return {'success': False, 'error': 'Invalid username or password'} |
| 449 | |
| 450 | # Verify hardware fingerprint |
| 451 | hw_fingerprint = self.get_hardware_fingerprint() |
| 452 | if not secrets.compare_digest(row['hardware_fingerprint'], hw_fingerprint): |
| 453 | logger.warning("Hardware fingerprint mismatch during login!") |
| 454 | return { |
| 455 | 'success': False, |
| 456 | 'error': 'Hardware mismatch - this Ragnar instance is bound to different hardware', |
| 457 | 'hw_mismatch': True |
| 458 | } |
| 459 | |
| 460 | # Unwrap the Fernet key |
| 461 | wrapping_key = self._derive_wrapping_key(password, hw_fingerprint) |
| 462 | try: |
| 463 | fernet_key = self._unwrap_fernet_key(row['encrypted_fernet_key'], wrapping_key) |
| 464 | except InvalidToken: |
| 465 | return {'success': False, 'error': 'Failed to decrypt encryption key'} |
| 466 | |
| 467 | self._fernet_key = fernet_key |
| 468 | |
| 469 | # If encrypted DB exists, decrypt in background so this response returns fast |
| 470 | if os.path.exists(self.encrypted_db_path): |
| 471 | self._db_ready = False |
| 472 | threading.Thread(target=self._background_decrypt, daemon=True).start() |
| 473 | else: |
| 474 | self._db_ready = True |
| 475 | |
| 476 | logger.info(f"User '{username}' logged in successfully") |
| 477 | return {'success': True} |
| 478 | |
| 479 | except Exception as e: |
| 480 | logger.error(f"Login failed: {e}") |
| 481 | return {'success': False, 'error': f'Login failed: {str(e)}'} |
| 482 | |
| 483 | def _background_decrypt(self): |
| 484 | """Decrypt DB and reinitialize in a background thread.""" |
no test coverage detected