Spaces:
Sleeping
Sleeping
| import hashlib | |
| import os | |
| import secrets | |
| import sqlite3 | |
| from pathlib import Path | |
| from typing import Tuple | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| DATA_DIR = PROJECT_ROOT / "data" | |
| DB_PATH = DATA_DIR / "users.db" | |
| def _ensure_db() -> None: | |
| DATA_DIR.mkdir(parents=True, exist_ok=True) | |
| conn = sqlite3.connect(DB_PATH) | |
| try: | |
| conn.execute( | |
| """ | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| username TEXT UNIQUE NOT NULL, | |
| password_hash TEXT NOT NULL, | |
| salt TEXT NOT NULL, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| """ | |
| ) | |
| conn.commit() | |
| finally: | |
| conn.close() | |
| def _hash_password(password: str, salt_hex: str) -> str: | |
| salt = bytes.fromhex(salt_hex) | |
| digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 120_000) | |
| return digest.hex() | |
| def _normalize_username(username: str) -> str: | |
| return username.strip().lower() | |
| def create_user(username: str, password: str) -> Tuple[bool, str]: | |
| _ensure_db() | |
| normalized = _normalize_username(username) | |
| if len(normalized) < 3: | |
| return False, "Username must be at least 3 characters." | |
| if len(password) < 8: | |
| return False, "Password must be at least 8 characters." | |
| salt_hex = secrets.token_hex(16) | |
| password_hash = _hash_password(password, salt_hex) | |
| conn = sqlite3.connect(DB_PATH) | |
| try: | |
| conn.execute( | |
| "INSERT INTO users (username, password_hash, salt) VALUES (?, ?, ?)", | |
| (normalized, password_hash, salt_hex), | |
| ) | |
| conn.commit() | |
| return True, "Account created successfully." | |
| except sqlite3.IntegrityError: | |
| return False, "Username already exists." | |
| finally: | |
| conn.close() | |
| def authenticate_user(username: str, password: str) -> Tuple[bool, str]: | |
| _ensure_db() | |
| normalized = _normalize_username(username) | |
| conn = sqlite3.connect(DB_PATH) | |
| try: | |
| row = conn.execute( | |
| "SELECT password_hash, salt FROM users WHERE username = ?", | |
| (normalized,), | |
| ).fetchone() | |
| finally: | |
| conn.close() | |
| if not row: | |
| return False, "User not found." | |
| stored_hash, salt_hex = row | |
| candidate_hash = _hash_password(password, salt_hex) | |
| if candidate_hash != stored_hash: | |
| return False, "Incorrect password." | |
| return True, "Login successful." | |