Skip to content

Commit

Permalink
Do some fixing some vulnerability
Browse files Browse the repository at this point in the history
  • Loading branch information
cherriae committed Jan 9, 2025
1 parent b80a64d commit b008d79
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 143 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Bandit Code Security

on: [push, pull_request]

permissions:
contents: read
security-events: write

jobs:
bandit:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,7 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.vercel

# CodeQL
output.sarif
codeqldb/
3 changes: 2 additions & 1 deletion app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
port = int(os.getenv("PORT", 5000))

if debug_mode:
app.run(debug=True, host=host, port=port)
debug = os.getenv("DEBUG", "False").lower() == "true"
app.run(debug=debug, host=host, port=port)
else:
serve(app, host=host, port=port)
12 changes: 11 additions & 1 deletion app/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Flask, make_response, render_template, send_from_directory
from flask import Flask, make_response, render_template, send_from_directory, jsonify
from flask_login import LoginManager
from flask_pymongo import PyMongo
import os
Expand Down Expand Up @@ -26,6 +26,7 @@ def create_app():
)

mongo.init_app(app)
csrf.init_app(app)

with app.app_context():
if "team_data" not in mongo.db.list_collection_names():
Expand Down Expand Up @@ -73,6 +74,15 @@ def serve_service_worker():
response.headers["Service-Worker-Allowed"] = "/"
return response

@app.errorhandler(Exception)
def handle_exception(e):
# Log the real error with stack trace
app.logger.error(f"Unhandled exception: {str(e)}", exc_info=True)
# Return generic message to user
return jsonify({
"error": "An unexpected error occurred"
}), 500

return app


Expand Down
49 changes: 49 additions & 0 deletions app/auth/auth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@
import time
from functools import wraps
from gridfs import GridFS
from flask_login import current_user

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def with_mongodb_retry(retries=3, delay=2):
def decorator(f):
@wraps(f)
Expand Down Expand Up @@ -279,6 +287,47 @@ async def delete_user(self, user_id):
logger.error(f"Error deleting user: {str(e)}")
return False, f"Error deleting account: {str(e)}"

@with_mongodb_retry(retries=3, delay=2)
async def update_user_settings(self, user_id, form_data, profile_picture=None):
"""Update user settings including profile picture"""
self.ensure_connected()
try:
updates = {}

# Handle username update if provided
if new_username := form_data.get('username'):
if new_username != current_user.username:
# Check if username is taken
if self.db.users.find_one({"username": new_username}):
return False
updates['username'] = new_username

# Handle description update
if description := form_data.get('description'):
updates['description'] = description

# Handle profile picture
if profile_picture:
from werkzeug.utils import secure_filename
if profile_picture and allowed_file(profile_picture.filename):
fs = GridFS(self.db)
filename = secure_filename(profile_picture.filename)
file_id = fs.put(
profile_picture.stream.read(),
filename=filename,
content_type=profile_picture.content_type
)
updates['profile_picture_id'] = file_id

if updates:
success, message = await self.update_user_profile(user_id, updates)
return success

return True
except Exception as e:
logger.error(f"Error updating user settings: {str(e)}")
return False

def __del__(self):
"""Cleanup MongoDB connection"""
if self.client:
Expand Down
86 changes: 39 additions & 47 deletions app/auth/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
jsonify,
send_file,
current_app,
abort,
)
from flask_login import login_required, login_user, current_user, logout_user
from app.auth.auth_utils import UserManager
Expand All @@ -17,6 +18,8 @@
from werkzeug.utils import secure_filename
from bson import ObjectId
from gridfs import GridFS
from urllib.parse import urlparse, urljoin
from flask_pymongo import PyMongo

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

Expand Down Expand Up @@ -80,12 +83,22 @@ def wrapper(*args, **kwargs):

auth_bp = Blueprint("auth", __name__)
user_manager = None
mongo = None


@auth_bp.record
def on_blueprint_init(state):
global user_manager
user_manager = UserManager(state.app.config["MONGO_URI"])
global user_manager, mongo
app = state.app
mongo = PyMongo(app)
user_manager = UserManager(app.config["MONGO_URI"])


def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc


@auth_bp.route("/login", methods=["GET", "POST"])
Expand All @@ -110,9 +123,9 @@ async def login():
success, user = await user_manager.authenticate_user(login, password)
if success and user:
login_user(user, remember=remember)
next_page = request.args.get("next")
if not next_page or not next_page.startswith("/"):
next_page = url_for("index")
next_page = request.args.get('next')
if not next_page or not is_safe_url(next_page):
next_page = url_for('main.index')
flash("Successfully logged in", "success")
return redirect(next_page)
else:
Expand Down Expand Up @@ -172,50 +185,29 @@ def logout():

@auth_bp.route("/settings", methods=["GET", "POST"])
@login_required
@async_route
async def settings():
if request.method == "POST":
updates = {}

# Handle profile picture upload
if 'profile_picture' in request.files:
file = request.files['profile_picture']
if file and allowed_file(file.filename):
try:
# Save new file to GridFS
fs = GridFS(user_manager.db)
filename = secure_filename(f"profile_{current_user.id}_{file.filename}")
file_id = fs.put(
file.stream.read(),
filename=filename,
content_type=file.content_type
)

# Update user's profile picture and clean up old one
success, message = await user_manager.update_profile_picture(current_user.id, file_id)
if not success:
# If update failed, delete the newly uploaded file
fs.delete(file_id)
flash(message, "error")
else:
updates['profile_picture_id'] = file_id
except Exception as e:
flash(f"Error uploading profile picture: {str(e)}", "error")

# Handle other profile updates
if username := request.form.get('username'):
updates['username'] = username
if description := request.form.get('description'):
updates['description'] = description

if updates:
success, message = await user_manager.update_user_profile(current_user.id, updates)
flash(message, "success" if success else "error")
def settings():
try:
if request.method == "POST":
# Handle form submission
form_data = request.form
file = request.files.get("profile_picture")

success = user_manager.update_user_settings(
current_user.get_id(),
form_data,
file
)

if success:
return redirect(url_for('auth.settings'))

return render_template("auth/settings.html", user=current_user)
flash("Settings updated successfully", "success")
else:
flash("Unable to update settings", "error")

return render_template("auth/settings.html")
except Exception as e:
current_app.logger.error(f"Error in settings: {str(e)}", exc_info=True)
flash("An error occurred while processing your request", "error")
return redirect(url_for("auth.settings"))


@auth_bp.route("/profile/<username>")
Expand Down
21 changes: 9 additions & 12 deletions app/scout/routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
from functools import wraps
import aiohttp
from flask import Blueprint, flash, render_template, request, redirect, url_for, jsonify
from flask import Blueprint, current_app, flash, render_template, request, redirect, url_for, jsonify
from flask_login import login_required, current_user
from app.scout.scouting_utils import ScoutingManager
from .TBA import TBAInterface
Expand Down Expand Up @@ -54,7 +54,8 @@ def list_scouting_data():
team_data = scouting_manager.get_all_scouting_data()
return render_template("scouting/list.html", team_data=team_data)
except Exception as e:
flash(f"Error fetching data: {str(e)}", "error")
current_app.logger.error(f"Error fetching scouting data: {str(e)}", exc_info=True)
flash("Unable to fetch scouting data. Please try again later.", "error")
return render_template("scouting/list.html", team_data=[])


Expand All @@ -65,27 +66,23 @@ def edit_scouting_data(id):
team_data = scouting_manager.get_team_data(id, current_user.get_id())

if not team_data:
flash(
"Team data not found or you do not have permission to edit it", "error"
)
flash("Team data not found", "error")
return redirect(url_for("scouting.list_scouting_data"))

if not team_data.is_owner:
flash("You do not have permission to edit this entry", "error")
flash("Access denied", "error")
return redirect(url_for("scouting.list_scouting_data"))

if request.method == "POST":
if scouting_manager.update_team_data(
id, request.form, current_user.get_id()
):
if scouting_manager.update_team_data(id, request.form, current_user.get_id()):
flash("Data updated successfully", "success")
return redirect(url_for("scouting.list_scouting_data"))
else:
flash("Error updating data", "error")
flash("Unable to update data", "error")

return render_template("scouting/edit.html", team_data=team_data)
except Exception as e:
flash(f"Error: {str(e)}", "error")
current_app.logger.error(f"Error in edit_scouting_data: {str(e)}", exc_info=True)
flash("An error occurred while processing your request", "error")
return redirect(url_for("scouting.list_scouting_data"))


Expand Down
1 change: 1 addition & 0 deletions app/scout/scouting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ def update_team_data(self, team_id, data, scouter_id):
# Defense
"defense_rating": int(data.get("defense_rating", 1)),
"defense_notes": data.get("defense_notes", ""),


# Auto
"auto_path": data.get("auto_path", ""),
Expand Down
8 changes: 4 additions & 4 deletions app/static/js/radar.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ function createRadarChart(canvasId, data, isCombo = false) {
}

function calculateConsistency(matchData) {
if (matchData.length < 2 && matchData.length != 0) {
return 10;
}
if (matchData.length == 0) {
if (matchData.length === 0) {
return 0;
}
if (matchData.length === 1) {
return 10;
}

const scores = matchData.map(m => m.total_points);
const mean = scores.reduce((a, b) => a + b) / scores.length;
Expand Down
Loading

0 comments on commit b008d79

Please sign in to comment.