2025-06-09 17:53:19 +08:00

94 lines
3.9 KiB
Python

# utils/auth.py (or wherever token_required is defined)
from functools import wraps
import secrets
import jwt
from flask import request, jsonify, current_app # <-- Import current_app
# Config might still be needed for default algorithm if not in app.config
# from backend.config import Config # Keep if needed for defaults, but prefer current_app.config
# TODO Flask cannot find config inside the utils
from .config import Config # Example if config.py is in the same dir
from bson.objectid import ObjectId
# Remove direct import of mongo
def token_required(f):
"""
Decorator to ensure a valid JWT token is present in the request header
and injects the corresponding user document into the decorated function.
"""
@wraps(f)
def decorated(*args, **kwargs):
token = None
auth_header = request.headers.get("Authorization")
if auth_header:
# Check for "Bearer " prefix and extract token
parts = auth_header.split()
if len(parts) == 2 and parts[0].lower() == "bearer":
token = parts[1]
# Optional: Allow raw token directly (as in original code)
elif len(parts) == 1:
token = auth_header
if not token:
return jsonify({"message": "Token is missing."}), 401
try:
# Use current_app.config to access SECRET_KEY and JWT_ALGORITHM
secret_key = current_app.config['SECRET_KEY']
# Provide a default algorithm if not explicitly configured
algorithm = current_app.config.get('JWT_ALGORITHM', Config.JWT_ALGORITHM or 'HS256')
# Decode the token
data = jwt.decode(token, secret_key, algorithms=[algorithm])
# --- Use current_app to access mongo ---
user_id_str = data.get("user_id")
if not user_id_str:
return jsonify({"message": "Token payload missing user_id."}), 401
# Access the 'users' collection via the mongo instance attached to current_app
current_user_doc = current_app.mongo.db.users.find_one({"_id": ObjectId(user_id_str)})
# --- End database access change ---
if not current_user_doc:
# Even if token is valid, user might have been deleted
return jsonify({"message": "User associated with token not found."}), 401
# Convert ObjectId back to string for consistency if needed,
# or pass the whole document as is. Passing document is often useful.
# current_user_doc['_id'] = str(current_user_doc['_id']) # Optional conversion
except jwt.ExpiredSignatureError:
# Specific error for expired token
return jsonify({"message": "Token has expired."}), 401
except jwt.InvalidTokenError as e:
# Specific error for other JWT validation issues
current_app.logger.warning(f"Invalid token encountered: {e}") # Log the specific error
return jsonify({"message": "Token is invalid."}), 401
except Exception as e:
# Catch other potential errors (e.g., ObjectId conversion, DB connection issues)
current_app.logger.error(f"Error during token verification: {e}", exc_info=True)
# Return a more generic message for unexpected
return jsonify({"message": "Token verification failed."}), 401
# Inject the fetched user document into the decorated function
return f(current_user_doc, *args, **kwargs)
return decorated
# This is a placeholder for background task functions.
# For example, you could use Celery to process URLs asynchronously.
def process_url(url_id):
# Retrieve URL document by url_id, perform scraping, summarization, and update processingStatus.
# This function should be called by a background worker.
pass
# This function will generate a pass key for frontend-backend communication
def generate_passkey():
return secrets.token_hex(16)