Add @csrf.exempt to all API routes in app/api/routes.py and app/upload/routes.py

- Added CSRF exemption to all JWT-authenticated API endpoints
- API routes use JWT tokens, not session cookies, so CSRF protection is not needed
parent b19fc3ac
......@@ -4,7 +4,7 @@ from flask import request, jsonify, current_app
from flask_jwt_extended import jwt_required, get_jwt_identity
from sqlalchemy import func, desc
from app.api import bp
from app import db
from app import db, csrf
from app.utils.security import require_admin, require_active_user
from app.utils.logging import log_api_request
from app.upload.file_handler import get_file_upload_handler
......@@ -13,6 +13,7 @@ from app.upload.fixture_parser import get_fixture_parser
logger = logging.getLogger(__name__)
@bp.route('/matches', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_get_matches():
"""Get matches with pagination and filtering"""
......@@ -116,6 +117,7 @@ def api_get_matches():
return jsonify({'error': 'Failed to retrieve matches'}), 500
@bp.route('/matches/<int:match_id>', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_get_match(match_id):
"""Get specific match details"""
......@@ -149,6 +151,7 @@ def api_get_match(match_id):
return jsonify({'error': 'Failed to retrieve match'}), 500
@bp.route('/matches/<int:match_id>', methods=['PUT'])
@csrf.exempt
@jwt_required()
def api_update_match(match_id):
"""Update match details"""
......@@ -207,6 +210,7 @@ def api_update_match(match_id):
return jsonify({'error': 'Failed to update match'}), 500
@bp.route('/matches/<int:match_id>', methods=['DELETE'])
@csrf.exempt
@jwt_required()
def api_delete_match(match_id):
"""Delete match (admin only)"""
......@@ -244,6 +248,7 @@ def api_delete_match(match_id):
return jsonify({'error': 'Failed to delete match'}), 500
@bp.route('/statistics', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_get_statistics():
"""Get comprehensive statistics"""
......@@ -312,6 +317,7 @@ def api_get_statistics():
return jsonify({'error': 'Failed to retrieve statistics'}), 500
@bp.route('/admin/users', methods=['GET'])
@csrf.exempt
@jwt_required()
@require_admin
def api_admin_get_users():
......@@ -367,6 +373,7 @@ def api_admin_get_users():
return jsonify({'error': 'Failed to retrieve users'}), 500
@bp.route('/admin/users/<int:user_id>', methods=['PUT'])
@csrf.exempt
@jwt_required()
@require_admin
def api_admin_update_user(user_id):
......@@ -408,6 +415,7 @@ def api_admin_update_user(user_id):
return jsonify({'error': 'Failed to update user'}), 500
@bp.route('/admin/logs', methods=['GET'])
@csrf.exempt
@jwt_required()
@require_admin
def api_admin_get_logs():
......@@ -452,6 +460,7 @@ def api_admin_get_logs():
return jsonify({'error': 'Failed to retrieve logs'}), 500
@bp.route('/admin/system-info', methods=['GET'])
@csrf.exempt
@jwt_required()
@require_admin
def api_admin_system_info():
......@@ -498,6 +507,7 @@ def api_admin_system_info():
return jsonify({'error': 'Failed to retrieve system information'}), 500
@bp.route('/admin/cleanup', methods=['POST'])
@csrf.exempt
@jwt_required()
@require_admin
def api_admin_cleanup():
......
......@@ -248,6 +248,7 @@ def delete_zip(match_id):
return redirect(request.referrer or url_for('main.fixtures'))
@bp.route('/api/fixture', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_fixture():
"""Upload fixture file (CSV/XLSX) - API endpoint"""
......@@ -304,6 +305,7 @@ def api_upload_fixture():
return jsonify({'error': 'Upload processing failed'}), 500
@bp.route('/api/zip/<int:match_id>', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_zip(match_id):
"""Upload ZIP file for specific match - API endpoint"""
......@@ -370,6 +372,7 @@ def api_upload_zip(match_id):
return jsonify({'error': 'Upload processing failed'}), 500
@bp.route('/api/upload-async', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_async():
"""Start asynchronous file upload"""
......@@ -416,6 +419,7 @@ def api_upload_async():
return jsonify({'error': 'Upload start failed'}), 500
@bp.route('/api/upload-status/<upload_id>', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_upload_status(upload_id):
"""Get status of asynchronous upload"""
......@@ -430,6 +434,7 @@ def api_upload_status(upload_id):
return jsonify({'error': 'Status check failed'}), 500
@bp.route('/api/upload-cancel/<upload_id>', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_cancel(upload_id):
"""Cancel asynchronous upload"""
......@@ -447,6 +452,7 @@ def api_upload_cancel(upload_id):
return jsonify({'error': 'Cancel operation failed'}), 500
@bp.route('/api/progress/<int:upload_id>', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_upload_progress(upload_id):
"""Get upload progress for specific upload record"""
......@@ -476,6 +482,7 @@ def api_upload_progress(upload_id):
return jsonify({'error': 'Progress check failed'}), 500
@bp.route('/api/uploads', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_list_uploads():
"""List user's uploads with pagination"""
......@@ -516,6 +523,7 @@ def api_list_uploads():
return jsonify({'error': 'Upload list failed'}), 500
@bp.route('/api/statistics', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_upload_statistics():
"""Get upload statistics"""
......@@ -556,6 +564,7 @@ def api_upload_statistics():
return jsonify({'error': 'Statistics calculation failed'}), 500
@bp.route('/api/cleanup', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_cleanup_uploads():
"""Clean up failed uploads (admin only)"""
......@@ -577,6 +586,7 @@ def api_cleanup_uploads():
return jsonify({'error': 'Cleanup failed'}), 500
@bp.route('/api/zip/<int:match_id>/stream', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_zip_stream(match_id):
"""Upload ZIP file for specific match using streaming - API endpoint for large files"""
......@@ -700,6 +710,7 @@ def api_upload_zip_stream(match_id):
return jsonify({'error': 'Streaming upload failed'}), 500
@bp.route('/api/zip/<int:match_id>/resume', methods=['POST'])
@csrf.exempt
@jwt_required()
def api_upload_zip_resume(match_id):
"""Resume ZIP file upload for specific match - API endpoint"""
......@@ -787,6 +798,7 @@ def api_upload_zip_resume(match_id):
return jsonify({'error': 'Resume upload failed'}), 500
@bp.route('/api/upload-info/<int:upload_id>', methods=['GET'])
@csrf.exempt
@jwt_required()
def api_upload_info(upload_id):
"""Get detailed upload information including resume capability"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment