#!/usr/bin/python3 # -*- coding: utf-8 -*- """ Flask-Login authentication provider """ import logging from functools import wraps from flask import redirect, request, session, url_for from .base import AuthProvider class FlaskLoginProvider(AuthProvider): """Flask-Login based authentication provider. Configuration: users: Dict of {username: password_hash} or path to user file secret_key: Flask secret key for sessions login_view: Route name for login page (default: 'login') use_bcrypt: If True, passwords are bcrypt hashes (default: True) """ def __init__(self, config): super().__init__(config) self.logger = logging.getLogger("tisbackup.auth") self.login_manager = None self.users = config.get("users", {}) self.login_view = config.get("login_view", "login") self.use_bcrypt = config.get("use_bcrypt", True) if self.use_bcrypt: try: import bcrypt self.bcrypt = bcrypt except ImportError: raise ImportError("bcrypt library required. Install with: pip install bcrypt") # Load users from file if path provided users_file = config.get("users_file") if users_file: self._load_users_from_file(users_file) def _load_users_from_file(self, filepath): """Load users from file (username:password_hash per line).""" try: with open(filepath) as f: for line in f: line = line.strip() if line and not line.startswith('#'): username, password_hash = line.split(':', 1) self.users[username.strip()] = password_hash.strip() except Exception as e: self.logger.error(f"Failed to load users from {filepath}: {e}") raise def init_app(self, app): """Initialize Flask-Login with the app.""" try: from flask_login import LoginManager, UserMixin except ImportError: raise ImportError("flask-login library required. Install with: pip install flask-login") self.login_manager = LoginManager() self.login_manager.init_app(app) self.login_manager.login_view = self.login_view # Simple User class class User(UserMixin): def __init__(self, username): self.id = username self.username = username self.User = User @self.login_manager.user_loader def load_user(user_id): if user_id in self.users: return User(user_id) return None def verify_password(self, username, password): """Verify username and password. Args: username: Username to check password: Password to verify Returns: bool: True if valid, False otherwise """ if username not in self.users: return False stored_hash = self.users[username] if self.use_bcrypt: password_bytes = password.encode('utf-8') hash_bytes = stored_hash.encode('utf-8') if isinstance(stored_hash, str) else stored_hash return self.bcrypt.checkpw(password_bytes, hash_bytes) else: return password == stored_hash def login_user(self, username): """Login a user by username. Args: username: Username to login Returns: bool: True if successful """ try: from flask_login import login_user except ImportError: return False if username in self.users: user = self.User(username) login_user(user) return True return False def is_authenticated(self): """Check if current user is authenticated.""" try: from flask_login import current_user return current_user.is_authenticated except ImportError: return False def handle_unauthorized(self): """Redirect to login page.""" return redirect(url_for(self.login_view)) def get_current_user(self): """Get current authenticated user.""" try: from flask_login import current_user if current_user.is_authenticated: return { "username": current_user.username, "auth_type": "flask-login" } except ImportError: pass return None def logout(self): """Logout current user.""" try: from flask_login import logout_user logout_user() except ImportError: pass