#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 裝飾器模組 Author: PANJIT IT Team Created: 2024-01-28 Modified: 2024-01-28 """ from functools import wraps from flask import session, jsonify, g, current_app from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt def login_required(f): """登入驗證裝飾器""" @wraps(f) def decorated_function(*args, **kwargs): from app.utils.logger import get_logger from flask import request logger = get_logger(__name__) user_id = session.get('user_id') # 調試:記錄 session 檢查 logger.info(f"🔐 [Session Check] Endpoint: {request.endpoint}, Method: {request.method}, URL: {request.url}") logger.info(f"🔐 [Session Data] UserID: {user_id}, SessionData: {dict(session)}, SessionID: {session.get('_id', 'unknown')}") if not user_id: logger.warning(f"❌ [Auth Failed] No user_id in session for {request.endpoint}") return jsonify({ 'success': False, 'error': 'AUTHENTICATION_REQUIRED', 'message': '請先登入' }), 401 # 取得使用者資訊並設定到 g 物件 from app.models import User user = User.query.get(user_id) if not user: # 清除無效的 session session.clear() return jsonify({ 'success': False, 'error': 'USER_NOT_FOUND', 'message': '使用者不存在' }), 401 g.current_user = user g.current_user_id = user.id g.is_admin = user.is_admin return f(*args, **kwargs) return decorated_function def jwt_login_required(f): """JWT 登入驗證裝飾器""" @wraps(f) @jwt_required() def decorated_function(*args, **kwargs): from app.utils.logger import get_logger from flask import request logger = get_logger(__name__) try: username = get_jwt_identity() claims = get_jwt() # 設定到 g 物件供其他地方使用 g.current_user_username = username g.current_user_id = claims.get('user_id') g.is_admin = claims.get('is_admin', False) logger.info(f"🔑 [JWT Auth] User: {username}, UserID: {claims.get('user_id')}, Admin: {claims.get('is_admin')}") except Exception as e: logger.error(f"❌ [JWT Auth] JWT validation failed: {str(e)}") return jsonify({ 'success': False, 'error': 'AUTHENTICATION_REQUIRED', 'message': '認證失效,請重新登入' }), 401 return f(*args, **kwargs) return decorated_function def admin_required(f): """管理員權限裝飾器""" @wraps(f) def decorated_function(*args, **kwargs): # 先檢查是否已登入 user_id = session.get('user_id') if not user_id: return jsonify({ 'success': False, 'error': 'AUTHENTICATION_REQUIRED', 'message': '請先登入' }), 401 # 取得使用者資訊 from app.models import User user = User.query.get(user_id) if not user: session.clear() return jsonify({ 'success': False, 'error': 'USER_NOT_FOUND', 'message': '使用者不存在' }), 401 # 檢查管理員權限 if not user.is_admin: return jsonify({ 'success': False, 'error': 'PERMISSION_DENIED', 'message': '權限不足,需要管理員權限' }), 403 g.current_user = user g.current_user_id = user.id g.is_admin = True return f(*args, **kwargs) return decorated_function def validate_json(required_fields=None): """JSON 驗證裝飾器""" def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): from flask import request if not request.is_json: return jsonify({ 'success': False, 'error': 'INVALID_CONTENT_TYPE', 'message': '請求必須為 JSON 格式' }), 400 data = request.get_json() if not data: return jsonify({ 'success': False, 'error': 'INVALID_JSON', 'message': 'JSON 資料格式錯誤' }), 400 # 檢查必要欄位 if required_fields: missing_fields = [field for field in required_fields if field not in data] if missing_fields: return jsonify({ 'success': False, 'error': 'MISSING_FIELDS', 'message': f'缺少必要欄位: {", ".join(missing_fields)}' }), 400 return f(*args, **kwargs) return decorated_function return decorator def rate_limit(max_requests=100, per_seconds=3600): """簡單的速率限制裝飾器""" def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): from flask import request import redis import time try: # 使用 Redis 進行速率限制 redis_client = redis.from_url(current_app.config['REDIS_URL']) # 使用 IP 地址作為 key client_id = request.remote_addr key = f"rate_limit:{f.__name__}:{client_id}" current_time = int(time.time()) window_start = current_time - per_seconds # 清理過期的請求記錄 redis_client.zremrangebyscore(key, 0, window_start) # 取得當前窗口內的請求數 current_requests = redis_client.zcard(key) if current_requests >= max_requests: return jsonify({ 'success': False, 'error': 'RATE_LIMIT_EXCEEDED', 'message': '請求過於頻繁,請稍後再試' }), 429 # 記錄當前請求 redis_client.zadd(key, {str(current_time): current_time}) redis_client.expire(key, per_seconds) except Exception: # 如果 Redis 不可用,不阻擋請求 pass return f(*args, **kwargs) return decorated_function return decorator