demander IA de faire tous les modifications pour que le webapp soit pret au déploiement.

Force connection HTTPS, proxy-inversé, WSGI de production, reset cookie de conncection à chaque reconnection, limite sur les mdp, fichiers et One on One par minute, verification d'injection de SQL dans les champs d'entrées. renommage des fichiers lors du téléchargement, fichier de backup quotidien pour la bd et j'ai oublié quelque chose :(
This commit is contained in:
cedrick2711
2026-07-25 18:47:27 -04:00
parent afdf6ded0e
commit 666673fa8f
24 changed files with 2513 additions and 171 deletions
+50 -29
View File
@@ -1,18 +1,25 @@
"""User management routes for profiles, disponibilities, and contracts.
This module handles user CRUD operations, profile editing, player availability,
and contract management.
and contract management with secure file upload handling.
"""
import os
import uuid
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file
from flask_login import login_required, current_user
from extensions import db, hash_password
from extensions import db, hash_password, csrf
from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam, CoachAvailability, TeamNote, PersonalNote, OneOnOneRequest, Evaluation, Match, Team, TeamMember, MatchParticipant, Tryout, TryoutRegistration, TeamPlayer
from werkzeug.utils import secure_filename
from datetime import datetime, timedelta, date as date_type
from marshmallow import ValidationError
from validators import CreateUserSchema, EditUserSchema, EditProfileSchema, UploadContractSchema, OneOnOneRequestSchema
import requests
# Allowed file extensions for uploads
ALLOWED_CONTRACT_EXTENSIONS = {'pdf'}
ALLOWED_SIGNED_EXTENSIONS = {'pdf'}
users_bp = Blueprint('users', __name__, url_prefix='/users')
@@ -641,8 +648,18 @@ def upload_contract():
players = User.query.filter_by(role='player').all()
if request.method == 'POST':
player_id = request.form.get('player_id', type=int)
notes = request.form.get('notes', '').strip()
# Validate form input
contract_schema = UploadContractSchema()
try:
validated = contract_schema.load(request.form)
except ValidationError as err:
for field, messages in err.messages.items():
for msg in messages:
flash(f'{field}: {msg}', 'danger')
return render_template('pages/upload_contract.html', players=players)
player_id = validated['player_id']
notes = validated.get('notes')
if not can_manage_player_contract(current_user, player_id):
flash('You do not have permission to upload a contract for this player.', 'danger')
@@ -657,6 +674,11 @@ def upload_contract():
flash('No file selected.', 'danger')
return redirect(url_for('users.upload_contract'))
# Validate file extension (PDF only)
if not file.filename.lower().endswith('.pdf'):
flash('Only PDF files are allowed for contracts.', 'danger')
return redirect(url_for('users.upload_contract'))
# Create upload directory
upload_dir = os.path.join(os.getcwd(), 'documents', 'contrats signés')
os.makedirs(upload_dir, exist_ok=True)
@@ -674,11 +696,10 @@ def upload_contract():
else:
final_dir = upload_dir
# Generate unique filename
# Generate UUID-based filename for security (prevents filename guessing)
original_filename = secure_filename(file.filename)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
stored_filename = f"{secure_filename(player.username)}_{timestamp}_{original_filename}"
stored_filename = stored_filename.replace(' ', '_')
file_uuid = str(uuid.uuid4())
stored_filename = f"{file_uuid}.pdf"
# Save file
file_path = os.path.join(final_dir, stored_filename)
@@ -1008,6 +1029,7 @@ def one_on_one():
@users_bp.route('/coach-availability', methods=['GET', 'POST'])
@login_required
@csrf.exempt
def manage_coach_availability():
"""Manage coach availability (for coaches).
@@ -1025,6 +1047,11 @@ def manage_coach_availability():
data = request.get_json()
slots = data.get('slots', [])
# Clear all existing availability slots first, then re-insert from client state.
# This fixes the bug where un-toggling a slot on the client did not delete it
# from the database (the old code only added, never removed).
CoachAvailability.query.filter_by(coach_id=current_user.id).delete()
created = []
for slot in slots:
day_of_week = slot.get('day_of_week')
@@ -1040,29 +1067,21 @@ def manage_coach_availability():
end_time = add_30_minutes(start_time)
# Check if this slot already exists for this coach
existing = CoachAvailability.query.filter_by(
availability = CoachAvailability(
coach_id=current_user.id,
day_of_week=day_of_week,
start_time=start_time
).first()
if not existing:
availability = CoachAvailability(
coach_id=current_user.id,
day_of_week=day_of_week,
start_time=start_time,
end_time=end_time
)
db.session.add(availability)
db.session.flush()
created.append({
'id': availability.id,
'day_of_week': availability.day_of_week,
'day_name': DAY_NAMES[availability.day_of_week],
'start_time': availability.start_time.strftime('%H:%M'),
'end_time': availability.end_time.strftime('%H:%M')
})
start_time=start_time,
end_time=end_time
)
db.session.add(availability)
db.session.flush()
created.append({
'id': availability.id,
'day_of_week': availability.day_of_week,
'day_name': DAY_NAMES[availability.day_of_week],
'start_time': availability.start_time.strftime('%H:%M'),
'end_time': availability.end_time.strftime('%H:%M')
})
db.session.commit()
return jsonify({'success': True, 'created': created})
@@ -1076,6 +1095,7 @@ def manage_coach_availability():
@users_bp.route('/coach-availability/clear', methods=['POST'])
@login_required
@csrf.exempt
def clear_coach_availability():
"""Clear all coach availability slots.
@@ -1092,6 +1112,7 @@ def clear_coach_availability():
@users_bp.route('/coach-availability/<int:availability_id>/delete', methods=['POST'])
@login_required
@csrf.exempt
def delete_coach_availability(availability_id):
"""Delete a coach availability slot.