MorphGuard / app.py
juanquy's picture
fix: configure session cookies for iframe/SameSite compatibility on Hugging Face Spaces
f4da990
Raw
History Blame Contribute Delete
8.09 kB
# MorphGuard Production Application
# app.py - Flask application for production deployment
from flask import Flask, session
import os
import sys
from threading import Thread
from src.utils.logger import logger
# When running on HuggingFace Spaces, PostgreSQL / Ethereum / drone services
# are not available. Set HF_SPACE=1 to skip those startup tasks gracefully.
IS_HF_SPACE = os.environ.get('HF_SPACE', '0') == '1'
# Add project root to path
sys.path.append(os.getcwd())
# Import Utilities
from flask_sock import Sock
if not IS_HF_SPACE:
from src.utils.telemetry import start_metrics_collection_thread
from utils.auth import init_db as init_auth_db
from scripts.setup_timescaledb import initialize_schema
else:
# Lightweight stubs so startup tasks can be skipped cleanly
def start_metrics_collection_thread(): pass
def init_auth_db(): pass
def initialize_schema(): pass
# Import API Main Class
from morphguard_api import MorphGuardAPI
def create_app(config_object=None):
app = Flask(__name__)
sock = Sock(app)
# Configuration
app.secret_key = os.environ.get('MORPHGUARD_SECRET_KEY')
if not app.secret_key:
print("WARNING: MORPHGUARD_SECRET_KEY not set. Using insecure default for dev.")
app.secret_key = 'dev-default-key'
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
if IS_HF_SPACE:
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_SECURE'] = True
# WebSocket Configuration - Disable server pings (Pi client handles keepalive)
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': None}
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs('logs', exist_ok=True)
# Initialize Core API
try:
api = MorphGuardAPI(
detector_path='models/morph_detector.pth',
demorph_path='models/demorpher.pth',
gan_config_path=os.environ.get('GAN_CONFIG_PATH'),
gan_checkpoint_path=os.environ.get('GAN_CHECKPOINT_PATH')
)
app.mg_api = api # Attach to app for blueprints
app.deepfake_detector = None # Initialize placeholder
logger.info("MorphGuard Core API initialized")
except Exception as e:
logger.error(f"Failed to initialize MorphGuard Core API: {e}")
app.mg_api = None
# Deepfake Detector
try:
from src.deepfake.deepfake_detector import DeepfakeDetectorAPI
app.deepfake_detector = DeepfakeDetectorAPI()
logger.info("Deepfake detector loaded")
except Exception as e:
logger.warning(f"Deepfake detector not available: {e}")
# Register Blueprints
from src.routes.auth import auth_bp
from src.routes.detection import detection_bp
from src.routes.training import training_bp, start_feedback_watcher
from src.routes.main import main_bp
from src.routes.setup import setup_bp
from src.routes.demorph import demorph_bp
from src.routes.drone import drone_bp, register_drone_routes
from src.routes.liveness import liveness_bp
from src.api.identity_verification_api import identity_bp
app.register_blueprint(auth_bp) # /login, /register
app.register_blueprint(detection_bp) # /api/detect
app.register_blueprint(demorph_bp) # /api/demorph
app.register_blueprint(training_bp) # /api/train
app.register_blueprint(main_bp) # /, /demo, /setup
app.register_blueprint(setup_bp) # /api/setup/users, etc.
app.register_blueprint(liveness_bp)
app.register_blueprint(identity_bp)
# Initialize Drone Routes with WebSocket
register_drone_routes(app, sock)
# Optional Plugins/Extensions
_register_extensions(app)
# Startup Tasks
try:
if not IS_HF_SPACE:
# DB Init (Auth + Timescale) — skipped on HF Spaces (no PostgreSQL)
try:
init_auth_db()
initialize_schema()
except Exception as e:
logger.error(f"DB Schema init failed: {e}")
start_metrics_collection_thread()
start_feedback_watcher()
except Exception as e:
logger.error(f"Startup task error: {e}")
return app
def _register_extensions(app):
# Plugin API
try:
from plugin_api import init_app as init_plugin_api
init_plugin_api(app)
except ImportError:
pass
# Notification API
try:
from notification_api import init_app as init_notification_api
init_notification_api(app)
except ImportError:
pass
# Advanced Face API
try:
from src.api.advanced_face_api import advanced_face_bp
app.register_blueprint(advanced_face_bp)
except ImportError:
pass
# AXI Integration
try:
from axi_integration import auto_integrate_with_app
if app.mg_api:
auto_integrate_with_app(app, app.mg_api, app.config['UPLOAD_FOLDER'])
except ImportError:
pass
# Law Enforcement Portal
try:
from law_enforcement_portal.le_routes import register_le_routes
register_le_routes(app)
@app.route('/le/')
@app.route('/law-enforcement/')
def le_portal_redirect():
if not session.get('logged_in'):
from flask import redirect, url_for
return redirect(url_for('main.index'))
from flask import redirect
return redirect('/le/dashboard') # Assuming generic route structure
# Better: url_for('le_dashboard') if endpoint known, but let's stick to path if uncertain
except ImportError:
pass
# Initialize SocketIO (not used on HF Spaces — sync gunicorn workers only)
_socketio_available = False
socketio = None
if not IS_HF_SPACE:
try:
from flask_socketio import SocketIO
socketio = SocketIO(cors_allowed_origins="*")
_socketio_available = True
except ImportError:
pass
# Create globally accessible app instance for gunicorn
app = create_app()
if _socketio_available and socketio is not None:
socketio.init_app(app)
try:
from notification_api import init_socketio
init_socketio(socketio)
except Exception:
pass
# ROS2 Bridge (Optional)
def start_ros2_if_supported():
try:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MGROS2Node(Node):
def __init__(self):
super().__init__('morphguard_bridge')
self.detect_pub = self.create_publisher(String, 'morphguard/detect', 10)
# ... publishers ...
def start_ros2_bridge():
rclpy.init()
node = MGROS2Node()
# Attach node to app or global if needed, primarily for detection.py to publish
# Ideally detection.py should import this singleton or publishing logic
# For this refactor, allowing partial breakage of ROS2 is acceptable if we plan to move it to a clean service
# or we can attach to current_app
app.ros2_node = node
Thread(target=lambda: rclpy.spin(node), daemon=True).start()
start_ros2_bridge()
logger.info("ROS2 bridge started")
except ImportError:
pass
except Exception as e:
logger.error(f"ROS2 init failed: {e}")
if __name__ == '__main__':
start_ros2_if_supported()
is_production = os.environ.get('FLASK_ENV') == 'production'
debug_mode = not is_production
host = '0.0.0.0'
port = 5000
logger.info(f"Starting MorphGuard (Probable Mode: {'Production' if is_production else 'Debug'})")
if _socketio_available:
socketio.run(app, debug=debug_mode, host=host, port=port, use_reloader=False, allow_unsafe_werkzeug=True)
else:
app.run(debug=debug_mode, host=host, port=port, use_reloader=False, threaded=True)