Quatre taches de la matrice du rapport, toutes sans dependance, qu aucune liste de « ce qui reste » ne reprenait. OPS-003 — app/.env.exemple disait « copiez ce fichier et remplissez les valeurs pour la production », puis posait FLASK_DEBUG=true, SESSION_COOKIE_SECURE=false et FORCE_HTTPS=false. Le debogueur Werkzeug execute du code soumis par le navigateur : cette ligne transformait un copier-coller en shell distant. Chaque valeur est desormais sure a la copie, et le fichier refuse de demarrer tant que les deux secrets obligatoires ne sont pas remplis plutot que de demarrer grand ouvert. Renomme en .env.example : l orthographe francaise ne correspondait pas a l exception !.env.example du .gitignore, donc le fichier n etait suivi que par accident de l ordre des regles. Les deux points de la decision ouverte du §8 tombent d un seul git mv. OPS-002 — trusted_proxy='*' et HOST ne sont plus soudes dans wsgi.py. Les defauts sont **inchanges**, deliberement : choisir sans connaitre la topologie coupe la prod si nginx est ailleurs, ou casse la limitation de debit pour tout le monde si on cesse de croire X-Forwarded-For alors que c etait la seule source d adresses. Ce sont maintenant des variables, les valeurs sures sont dans .env.example pour un nouveau deploiement, et docs/deployment.md donne les quatre topologies avec la valeur de chacune. wsgi.py avertit au demarrage tant que les deux defauts sont en place. Le commentaire de HOST annoncait « bind to localhost by default » a cote d un defaut a 0.0.0.0 : il decrivait l intention pendant que le code faisait l inverse. Il dit maintenant ce qu il fait. QUA-004 — Font Awesome et FullCalendar etaient charges sans empreinte, depuis des hotes que la CSP autorise nommement. Qui controle ces CDN controlait ce qui s execute sur chaque page. Empreintes posees, avec ce que SRI promet et ce qu il ne promet pas ecrit a cote : ca fige le fichier, ca ne prouve pas qu il etait honnete au moment du calcul. **Le CSS de FullCalendar n existait pas.** La v6 embarque ses styles dans le JS et ce fichier n est pas publie : le <link> repondait 404 a chaque ouverture du calendrier depuis la montee de version. Une feuille de style en echec est silencieuse dans le navigateur, c est ce qui l a fait durer. CI-003 — actions epinglees sur un commit, version en commentaire, dans les deux forges. Un tag est un pointeur mobile : deplacer v4 fait executer du code arbitraire dans le job qui detient la cle SSH de production. Ce job recoit aussi enfin un bloc permissions. 517 tests.
134 lines
5.3 KiB
Markdown
134 lines
5.3 KiB
Markdown
# Plateforme centralisée de tryouts
|
|
|
|
Application interne du club e-sport de l'UdeS : inscriptions aux sélections,
|
|
évaluations, gestion des équipes, disponibilités, contrats, et notifications
|
|
Discord.
|
|
|
|
Le site est servi **en français**, l'anglais reste accessible par le sélecteur
|
|
de la barre latérale (voir `docs/translations.md`).
|
|
|
|
---
|
|
|
|
## Démarrer
|
|
|
|
```bash
|
|
python -m venv .venv
|
|
.venv/Scripts/pip install -r requirements.txt -r requirements-dev.txt
|
|
cp app/.env.example .env # puis remplir SECRET_KEY et DATABASE_URL
|
|
.venv/Scripts/python run.py # développement, http://127.0.0.1:5000
|
|
```
|
|
|
|
`SECRET_KEY` et `DATABASE_URL` sont **obligatoires** : `create_app()` refuse
|
|
de démarrer sans eux. `DATABASE_URL` doit pointer sur PostgreSQL ; le pilote
|
|
psycopg 3 est nommé automatiquement si l'URL n'en nomme pas.
|
|
|
|
Production : `python wsgi.py` (Waitress derrière nginx). Voir
|
|
`docs/deployment.md`.
|
|
|
|
Ce sont les **deux seuls** points d'entrée.
|
|
|
|
## Vérifier
|
|
|
|
```bash
|
|
.venv/Scripts/python -m pytest # suite complète
|
|
.venv/Scripts/python -m ruff check . # lint
|
|
.venv/Scripts/python -m ruff format --check .
|
|
```
|
|
|
|
Les trois tournent en CI et y sont bloquants.
|
|
|
|
---
|
|
|
|
## Ce que fait l'application
|
|
|
|
- **Comptes et rôles** — cinq rôles : président (`admin`), gérant
|
|
(`manager`), coach, joueur (`player`), recruteur (`scout`). Le président
|
|
attribue les rôles.
|
|
- **Sélections** — organisation des tryouts, trois formats de match
|
|
(équipe contre équipe, joueur contre joueur, scrim), évaluation des
|
|
joueurs sur dix critères.
|
|
- **Équipes** — effectifs de la saison, matchs et entraînements. Le
|
|
formulaire d'entraînement affiche les disponibilités des joueurs.
|
|
- **Disponibilités** — créneaux hebdomadaires des joueurs, créneaux
|
|
réservables des coachs.
|
|
- **Notes** — un coach écrit des notes d'équipe (visibles par l'équipe) et
|
|
des notes nominatives (visibles par le joueur concerné).
|
|
- **Un-à-un** — un joueur demande une séance à son coach ; le coach répond
|
|
depuis le site ou par une réaction sur le message privé Discord.
|
|
- **Contrats** — dépôt d'un contrat par le staff, signature par le joueur.
|
|
|
|
## Comment c'est construit
|
|
|
|
Backend Python 3.12 / Flask, rendu serveur en Jinja2, CSS et JavaScript
|
|
maison, sans framework front. Base PostgreSQL via SQLAlchemy. Bot Discord
|
|
(`discord.py`) dans un fil du même processus que le serveur web.
|
|
|
|
`docs/architecture.md` contient les diagrammes (classes, paquets, flux
|
|
d'une requête).
|
|
|
|
---
|
|
|
|
## Sécurité
|
|
|
|
En place et vérifié par des tests :
|
|
|
|
- **Limitation de débit** sur la connexion (10 requêtes/minute par IP).
|
|
- **Cookies de session** `HttpOnly`, `SameSite=Lax`, `Secure`, avec
|
|
expiration effective.
|
|
- **CSRF** sur tous les formulaires, y compris la déconnexion (en POST).
|
|
- **HTTPS** forcé en production, **HSTS**.
|
|
- **CSP sans `unsafe-inline`** sur `script-src` : aucun gestionnaire
|
|
d'événement en ligne, chaque bloc `<script>` porte un nonce par requête.
|
|
- **Redirections** validées (rien ne sort du site).
|
|
- **Validation** par schémas marshmallow sur les formulaires de compte, avec
|
|
politique de mot de passe.
|
|
- **Autorisation** centralisée dans `app/permissions.py`.
|
|
- **Journal d'authentification** (`logs/auth.log`) : connexions, échecs,
|
|
changements de rôle, suppressions de compte.
|
|
- **Téléversements** vérifiés par extension *et* par signature de fichier.
|
|
|
|
Ce qui **n'est pas** fait, pour que personne ne s'y fie :
|
|
|
|
- **Aucune migration de schéma.** `db.create_all()` crée les tables
|
|
manquantes et ne modifie jamais une table existante : une colonne ajoutée
|
|
à un modèle est absente de la production.
|
|
- **Les secrets de l'historique git ne sont pas révoqués** — jeton du bot,
|
|
mot de passe PostgreSQL, `SECRET_KEY`.
|
|
- **`trusted_proxy='*'`** dans `wsgi.py` : l'en-tête `X-Forwarded-For` est
|
|
accepté de n'importe quelle source, donc la limitation par IP est
|
|
contournable. À régler avec la topologie réelle du déploiement.
|
|
- **L'identité Discord** transite encore par un champ caché du formulaire
|
|
d'inscription : elle n'est pas prouvée par le passage OAuth2.
|
|
|
|
`docs/security-checklist.md` détaille la liste avant mise en production.
|
|
|
|
---
|
|
|
|
## Intégration Discord
|
|
|
|
Messages privés au coach lors d'une demande d'un-à-un, aux joueurs à la
|
|
création d'un match ou d'un entraînement les concernant, et rappel 24 h
|
|
avant un match. Les réponses se font par réaction sur le message ou depuis
|
|
le site.
|
|
|
|
L'état des messages en attente de réponse est dans `discord_pending.json`,
|
|
**non versionné** : c'est de l'état d'exécution, propre à chaque serveur.
|
|
|
|
### Mise en place
|
|
|
|
Le bot du club existe déjà ; ce qui suit ne concerne qu'une nouvelle
|
|
installation.
|
|
|
|
1. Créer une application sur le [portail développeur
|
|
Discord](https://discord.com/developers/applications), puis un bot.
|
|
2. Copier le jeton dans `DISCORD_BOT_TOKEN`.
|
|
3. Activer **Message Content Intent** dans les *Privileged Gateway Intents*.
|
|
C'est le seul intent privilégié demandé : il sert à lire le motif d'un
|
|
refus écrit en réponse au message.
|
|
4. Chaque personne doit partager un serveur avec le bot (ou l'avoir en ami)
|
|
pour recevoir un message privé, et renseigner son identifiant Discord
|
|
dans son profil (Discord → Paramètres → Avancés → Mode développeur, puis
|
|
clic droit sur son profil → Copier l'identifiant).
|
|
|
|
`/health` indique si le bot tourne et s'il est connecté.
|