Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<int:user_id>/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")
Expand Down
Binary file modified backend/requirements.txt
Binary file not shown.
22 changes: 22 additions & 0 deletions frontend/data/contributors.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@
"site_admin": false,
"contributions": 23
},
{
"login": "github-actions[bot]",
"id": 41898282,
"node_id": "MDM6Qm90NDE4OTgyODI=",
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
"html_url": "https://github.com/apps/github-actions",
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
"type": "Bot",
"user_view_type": "public",
"site_admin": false,
"contributions": 13
},
{
"login": "github-actions[bot]",
"id": 41898282,
Expand Down
Loading