Manages authentication, hardware binding, and database encryption.
| 41 | |
| 42 | |
| 43 | class AuthManager: |
| 44 | """Manages authentication, hardware binding, and database encryption.""" |
| 45 | |
| 46 | PBKDF2_ITERATIONS = 200_000 # Secure for local device, fast on Pi |
| 47 | RECOVERY_CODE_COUNT = 10 |
| 48 | RECOVERY_CODE_LENGTH = 8 |
| 49 | |
| 50 | def __init__(self, shared_data): |
| 51 | self.shared_data = shared_data |
| 52 | self.datadir = getattr(shared_data, 'datadir', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')) |
| 53 | self._db_ready = not self._check_has_encrypted_db() # True if no decryption needed |
| 54 | self.auth_db_path = os.path.join(self.datadir, 'ragnar_auth.db') |
| 55 | self.main_db_path = os.path.join(self.datadir, 'ragnar.db') |
| 56 | self.encrypted_db_path = os.path.join(self.datadir, 'ragnar.db.enc') |
| 57 | self._lock = threading.RLock() |
| 58 | self._fernet_key = None # Cached in memory after login |
| 59 | self._secret_key = None |
| 60 | |
| 61 | os.makedirs(self.datadir, exist_ok=True) |
| 62 | self._init_auth_db() |
| 63 | self._handle_crash_recovery() |
| 64 | |
| 65 | def _check_has_encrypted_db(self): |
| 66 | """Check if an encrypted DB file exists (needs decryption on login).""" |
| 67 | datadir = getattr(self, 'datadir', '') |
| 68 | return os.path.exists(os.path.join(datadir, 'ragnar.db.enc')) |
| 69 | |
| 70 | @property |
| 71 | def db_ready(self): |
| 72 | return self._db_ready |
| 73 | |
| 74 | # ========================================================================= |
| 75 | # AUTH DB MANAGEMENT |
| 76 | # ========================================================================= |
| 77 | |
| 78 | def _init_auth_db(self): |
| 79 | """Create the auth database schema if it doesn't exist.""" |
| 80 | with self._get_auth_conn() as conn: |
| 81 | cursor = conn.cursor() |
| 82 | cursor.execute(""" |
| 83 | CREATE TABLE IF NOT EXISTS auth ( |
| 84 | id INTEGER PRIMARY KEY, |
| 85 | username TEXT NOT NULL, |
| 86 | password_hash TEXT NOT NULL, |
| 87 | password_salt TEXT NOT NULL, |
| 88 | hardware_fingerprint TEXT NOT NULL, |
| 89 | encrypted_fernet_key TEXT NOT NULL, |
| 90 | created_at TEXT DEFAULT CURRENT_TIMESTAMP, |
| 91 | updated_at TEXT DEFAULT CURRENT_TIMESTAMP |
| 92 | ) |
| 93 | """) |
| 94 | cursor.execute(""" |
| 95 | CREATE TABLE IF NOT EXISTS recovery_codes ( |
| 96 | id INTEGER PRIMARY KEY, |
| 97 | code_hash TEXT NOT NULL, |
| 98 | code_salt TEXT NOT NULL, |
| 99 | encrypted_fernet_key TEXT NOT NULL, |
| 100 | used INTEGER DEFAULT 0, |