"""Browser session authentication using Flask-Login + bcrypt. A single admin user is created via scripts/create_user.py. Device upload auth (X-Api-Key) is separate and unchanged. """ import bcrypt from functools import wraps from flask import redirect, url_for from flask_login import LoginManager, UserMixin, current_user from modules import db login_manager = LoginManager() login_manager.login_view = "login_page" class User(UserMixin): def __init__(self, id, username): self.id = id self.username = username @login_manager.user_loader def load_user(user_id): row = db.get_user_by_id(int(user_id)) if row is None: return None return User(row["id"], row["username"]) def check_password(plain, hashed): return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8")) def hash_password(plain): return bcrypt.hashpw(plain.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") def require_login(view): """Decorator: redirect to login page if not authenticated (HTML routes), or return 401 JSON for /api/* routes.""" @wraps(view) def wrapped(*args, **kwargs): if not current_user.is_authenticated: from flask import request, jsonify if request.path.startswith("/api/"): return jsonify(error="not authenticated"), 401 return redirect(url_for("login_page")) return view(*args, **kwargs) return wrapped