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:
+50
-29
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user