From ca161a754fdee21358b662eec62514f5f7213986 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sun, 24 May 2026 21:02:27 +0530 Subject: [PATCH 1/2] mime --- backend/app.py | 71 +++++++++++++++++++++++++++++++++++++++ backend/requirements.txt | Bin 918 -> 946 bytes 2 files changed, 71 insertions(+) diff --git a/backend/app.py b/backend/app.py index bdf05170c..173b1c0a5 100644 --- a/backend/app.py +++ b/backend/app.py @@ -20,6 +20,8 @@ import requests import secrets from urllib.parse import urlencode +from werkzeug.utils import secure_filename +import magic import logging from datetime import datetime, timedelta, timezone @@ -1904,6 +1906,75 @@ def logout(): return resp, status +UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads', 'avatars') +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} +ALLOWED_MIME_TYPES = {'image/png', 'image/jpeg', 'image/gif'} + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +@app.route('/api/v1/users//avatar', methods=['POST']) +@jwt_required() +def upload_avatar(user_id): + """Upload and update user avatar with MIME type and magic number validation.""" + current_user_id = get_jwt_identity() + + if str(user_id) != str(current_user_id): + return forbidden_error("Unauthorized access to another user's profile") + + if 'file' not in request.files: + return jsonify({"error": "No file part in the request"}), 400 + + file = request.files['file'] + + if file.filename == '': + return jsonify({"error": "No file selected for uploading"}), 400 + + if not allowed_file(file.filename): + return jsonify({"error": "Allowed file types are png, jpg, jpeg, gif"}), 400 + + # Validate Content-Type header + file_content_type = file.content_type + if file_content_type not in ALLOWED_MIME_TYPES: + return jsonify({"error": "Invalid Content-Type"}), 400 + + # Verify file magic numbers + file_bytes = file.read(2048) + file.seek(0) # Reset file pointer after reading + + try: + mime = magic.Magic(mime=True) + file_mime = mime.from_buffer(file_bytes) + + if file_mime not in ALLOWED_MIME_TYPES: + return jsonify({"error": "Invalid file content (magic number mismatch)"}), 400 + except Exception as e: + logger.error(f"Error validating file magic numbers: {e}") + return jsonify({"error": "Could not validate file format"}), 500 + + filename = secure_filename(file.filename) + unique_filename = f"user_{user_id}_{filename}" + filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) + + try: + file.save(filepath) + + user = User.query.get(user_id) + if user: + user.profile_picture = f"/uploads/avatars/{unique_filename}" + db.session.commit() + + return jsonify({ + "message": "Avatar uploaded successfully", + "profile_picture": f"/uploads/avatars/{unique_filename}" + }), 200 + except Exception as e: + logger.error(f"Failed to save avatar: {e}") + return jsonify({"error": "Failed to save file"}), 500 + + @app.route('/api/v1/auth/forgot-password', methods=['POST']) @csrf.exempt @limiter.limit("5 per minute") diff --git a/backend/requirements.txt b/backend/requirements.txt index 5726fbba4e24da96d1988c98013d039f57f4edc6..1940cad79a1f2293a24b2e366f2d6acf620c3940 100644 GIT binary patch delta 36 rcmbQnzKMOqG-jCshDwGKh75*$hCBvchFpe3hIEEZhGYg_1}+8wu4o6N delta 7 OcmdnQK8=0DG-dz`SOTB` From 0f68832c37bd08fb499176a497d06bafe20d8868 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sun, 24 May 2026 21:16:34 +0530 Subject: [PATCH 2/2] gltf --- backend/app.py | 5 +++++ netlify.toml | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/backend/app.py b/backend/app.py index 173b1c0a5..8305b82b7 100644 --- a/backend/app.py +++ b/backend/app.py @@ -250,6 +250,11 @@ def _apply_security_headers(response): response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()' response.headers['X-Frame-Options'] = 'DENY' + + # Add Cache-Control for 3D assets and static images + if request.path.endswith(('.gltf', '.obj', '.png')): + response.headers['Cache-Control'] = 'public, max-age=31536000, immutable' + return response # ===================================================================== diff --git a/netlify.toml b/netlify.toml index 730429c19..202d90407 100644 --- a/netlify.toml +++ b/netlify.toml @@ -5,3 +5,18 @@ functions = "netlify/functions" [build.environment] PYTHON_VERSION = "3.10" + +[[headers]] + for = "/*.gltf" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" + +[[headers]] + for = "/*.obj" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" + +[[headers]] + for = "/*.png" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable"