[FIX] Fix the documentations of the project.

This commit is contained in:
NADAL Jean-Baptiste
2026-09-14 16:03:38 +02:00
parent 75912d7ed3
commit e79c865f8c
9 changed files with 212 additions and 7727 deletions
+171 -230
View File
@@ -1,295 +1,236 @@
# AGENTS.md - Development Guidelines # AGENTS.md - Development Guidelines
This file contains essential information for AI agents working on this codebase. Essential information for AI agents and developers working on this codebase.
## Project Overview ## Project Overview
PHP website for "Harmonie de Montpellier-Jacou" (music band) with a Vue.js 2 frontend for score management. Web application for "Harmonie de Montpellier-Jacou" (music band) to manage and share sheet music (partitions).
- **Backend**: PHP with MySQL (legacy codebase)
- **Frontend**: Vue.js 2 + Bootstrap Vue (in `frontend/score/`)
- **API**: RESTful PHP API in `api/` directory
## Build Commands - **Frontend**: SvelteKit (Svelte 5, runes) + Tailwind CSS in `partitions/`
- **Backend**: PHP 8.5 REST API in `api/` (no framework)
- **Storage**: flat files under `legacy/Scores/` (no database)
- **Auth**: JWT (HS256), see `api/lib/Auth.php`
- **Production**: `https://partitions.c.nadal-fr.com` (front) + `https://ohmj-api.c.nadal-fr.com` (API)
### Frontend (Vue.js) There is **no MySQL** and **no Vue.js** in the active codebase. The old Vue.js 2 / MySQL
```bash site is archived in `legacy/` and must not be modified.
cd frontend/score
npm install
npm run serve # Development server
npm run build # Production build
npm run lint # ESLint check
```
### PHP ## Stack & Versions
No build step required. Deploy to PHP-enabled web server.
## Deployment - Node.js 26 / npm 11
- Svelte 5, SvelteKit 2, `@sveltejs/adapter-static`, Vite 5, Tailwind 3
- PHP 8.5 (CLI built-in server in development)
- Dependencies: `axios`, `pdfjs-dist`, `lucide-svelte` (front); `setasign/fpdi`, `setasign/fpdf`, `tecnickcom/tcpdf` (back)
### Create Deploy ZIP > PHP 8.5 note: nullable parameters MUST use explicit `?type` syntax
```bash > (e.g. `?string $x = null`). Implicit nullable is deprecated and pollutes API output.
cd /home/jbnadal/sources/jb/ohmj/ohmj2
./deploy/deploy.sh
```
**Important:** ## Build & Run Commands
- Do NOT include `legacy/` folder in the zip
- Do NOT include `tests.php` in the zip
- The `.env` file is included with JWT_SECRET
- Frontend build must be in `frontend/` directory
## Testing ### Backend (PHP)
### PHP API Tests
Run functional tests for the PHP API:
```bash ```bash
cd api cd api
php -c php-upload.ini -S localhost:8000 router.php
```
- `-c php-upload.ini` is **required** so PDF uploads work (`upload_max_filesize`/`post_max_size` = 64M).
`ini_set()` cannot change those directives at runtime.
- API base URL: `http://localhost:8000` (paths have **no** `/api` prefix in the client).
- Copy `api/.env.example` to `api/.env` and set a strong `JWT_SECRET`.
### Frontend (SvelteKit)
```bash
cd partitions
npm install
npm run dev # http://localhost:5173
npm run build # outputs to partitions/build
npm run preview
```
- `frontend` is a symlink to `partitions/build` (the deployed static site).
- No ESLint / test framework is configured for the frontend.
- `VITE_API_URL` is defined but currently unused (base URL is chosen from `import.meta.env.DEV`).
### Deploy
```bash
./deploy/deploy.sh
```
- Do NOT include `legacy/` or `api/tests.php` in the deploy archive.
- `api/.env` (with `JWT_SECRET`) is part of the deployment.
- The frontend build must be present in `frontend/`.
## Testing
### PHP API tests
```bash
cd api
php -c php-upload.ini -S localhost:8000 router.php & # if not already running
rm -f /tmp/rate_* 2>/dev/null # clear rate limiting first
php tests.php php tests.php
``` ```
The tests cover: - Expected: **53/53 tests passed (100%)**.
- **Auth**: Login, bad credentials, missing token, invalid token - Covers: auth (login, bad credentials, missing/invalid token), scores CRUD,
- **Scores**: CRUD operations, error handling for non-existent resources create-score-with-pieces, files tree, path traversal, rate limiting, upload
- **Create Score with Pieces**: Functional tests with pieces verification validation, INI injection, security headers.
- **Files**: Get files tree, delete file error handling - `tests.php` is kept for development only and MUST NOT be shipped in the deploy zip.
**Note**: tests.php is kept in source for development but must NOT be included in deploy zip. ## Authentication
### Vue/Svelte - Login: `POST /login` with `{"username":"admin","password":"password"}` (development).
- Token: JWT HS256 signed with `JWT_SECRET`, 1 hour expiry.
- API calls: send `Authorization: Bearer <token>`.
- Token is stored in `localStorage` on the frontend and attached via an axios request interceptor.
- Roles: `admin` (full access) and `user` (read-only). Admin routes live under `/admin/...`.
- Frontend dev login page: http://localhost:5173
No test framework configured for frontend. ## API Endpoints
Run single test (when configured): Public / all authenticated users:
```bash
# Example for Jest (not yet configured)
npm test -- --testNamePattern="test name"
```
## Code Style Guidelines - `POST /login`
- `GET /scores`
- `GET /scores/:id`
- `GET /scores/:id/instruments[?piece=N]`
- `GET /pieces/:scoreId`
- `GET /download/:path` (token via `Authorization` header or `?token=`)
### PHP Admin only (`role === 'admin'`):
- Use `<?php` opening tags (avoid short `<?` tags for consistency)
- Classes: PascalCase (e.g., `Score`, `Database`)
- Methods: camelCase (e.g., `readScore`, `getEntryName`)
- Variables: snake_case or camelCase (be consistent within files)
- Indent: 3 spaces (legacy style) - maintain consistency with surrounding code
- Place opening braces on same line as class/function declaration
- Use `include_once` for required files
- **Note**: Uses legacy MySQL functions (consider upgrading to PDO/MySQLi)
### JavaScript/Vue - `POST /admin/scores`
- ESLint configuration in `package.json`: - `PUT /admin/scores/:id`
- Extends: `plugin:vue/essential`, `eslint:recommended` - `PUT /admin/scores/:id/pieces`
- Parser: `babel-eslint` - `DELETE /admin/scores/:id`
- Use ES6+ features (async/await, arrow functions) - `POST /admin/scores/:id/upload`
- Components: PascalCase - `GET /admin/scores/:id/files`
- Data properties: camelCase - `DELETE /admin/scores/:id/files?path=...`
- Use 2-space indentation
- Semicolons required
### General
- UTF-8 encoding for all files
- French language for user-facing content
- Maintain GPL v2 license headers in PHP files
## File Organization ## File Organization
``` ```
/ /
├── api/ # REST API endpoints ├── api/ # PHP REST API (active)
│ ├── config/ # Database configuration │ ├── index.php # Router / entry point
│ ├── objects/ # Data models │ ├── router.php # Dev server router (+ CORS, .env loading)
│ └── score/ # API endpoints │ ├── lib/Auth.php # JWT auth
├── frontend/score/ # Vue.js application │ ├── lib/ScoreScanner.php# File-based score index
│ ├── src/ # Source code │ ├── config/users.json # Users (bcrypt hashes)
│ └── public/ # Static assets │ ├── tests.php # Functional tests (dev only)
├── Scripts/ # PHP page controllers │ └── .env # JWT_SECRET, SCORES_PATH
├── fpdf/ # PDF generation library ├── partitions/ # SvelteKit app (active)
├── Imgs/ # Image assets │ ├── src/lib/api.ts # Axios client + apiService
├── Textes/ # Text content files │ ├── src/lib/stores/ # auth store
└── index.php # Main entry point │ ├── src/lib/components/ # PdfViewer
│ ├── src/routes/ # pages (login, scores, admin)
│ └── build/ # static output (gitignored)
├── frontend -> partitions/build # symlink used for deployment
├── deploy/deploy.sh # deploy archive builder
├── scripts/convert_final_v2.js # one-off filename converter
├── legacy/ # archived old site + Scores/ (do not modify)
└── _builds/ # temporary work files (gitignored)
``` ```
## Database ## Data Model (files)
MySQL database connection configured in `api/config/database.php`: ```
- Uses legacy `mysql_*` functions legacy/Scores/<NUM>/<PIECE>/<INSTRUMENT>/<VERSION>/<PARTIE>.pdf
- Database name: `ohmj2` legacy/Scores/<NUM>/score.ini
- Tables: `repertoire` (for scores) ```
## Error Handling `score.ini`:
- PHP: Uses `@` error suppression operator in some places ```ini
- Vue: Uses try/catch with console.log for errors [info]
- Return HTTP 200 on success in API responses name = Nom du morceau
compositor = Compositeur
ressource = https://... # optional external link
## Security Notes [pieces]
count = 1
```
- API uses `Access-Control-Allow-Origin: *` (CORS enabled for all) - `NUM` = score id (zero-padded, e.g. `102`).
- SQL queries use string concatenation (vulnerable to SQL injection) - `PIECE` = movement/piece number (1 for single-piece scores).
- Database credentials stored in plain text in `api/config/database.php` - `INSTRUMENT` = trigram code (see below).
- Sanitize user inputs before database queries - `VERSION` = version number.
- `PARTIE` = part number.
## Git Instrument codes: `dir`, `pic`, `flu`, `cla`, `clb`, `sax`, `sat`, `sab`, `coa`,
`cba`, `cor`, `trp`, `trb`, `tub`, `htb`, `bas`, `per`, `crn`, `eup`, `har`, `pia`,
plus `sup`/`par` for combined parts.
- No CI/CD configured ## Code Style
- Commit to main branch
- Standard `.gitignore` for Node.js projects
## Dependencies
### Frontend
- Vue 2.6.11
- Bootstrap Vue 2.17.3
- Axios 0.21.0
- Vue CLI 4.5.0
### PHP ### PHP
- FPDF library for PDF generation - `<?php` opening tags, PascalCase classes, camelCase methods.
- MySQL extension (legacy) - 3-space indentation, opening brace on the same line (legacy style).
- Consistent with surrounding code; prefer `include_once`/`require_once` for includes.
- No database layer: file operations via `ScoreScanner.php`.
## Important Notes ### JavaScript / Svelte
- Svelte 5 runes (`$state`, `$props`, `$derived`, `$effect`); prefer `$app/state` over the deprecated `$app/stores`.
- TypeScript, camelCase variables, PascalCase components, 2-space indent, semicolons.
- **NEVER delete files or directories** - Always ask before removing anything ### General
- This is a legacy codebase with mixed coding styles - UTF-8, French for user-facing content.
- Prefer consistency with existing code over strict style enforcement - Do not add comments unless necessary.
- Site targets French-speaking users - **NEVER delete files or directories without asking first.**
- Production URL: `ohmj2.free.fr`
## Design Guidelines ## Design Guidelines
- **Style**: Clean, sober, and modern - Clean, sober, modern.
- **Icons**: Use SVG icons instead of emojis for better consistency - Use SVG icons instead of emojis.
- **Buttons**: Integrate related actions (view + download) in a single row with consistent styling - Integrate related actions (view + download) in a single row with consistent styling.
- **Hide unnecessary labels**: Don't show "Piece 1" or "Version 1" when there's only one - Hide redundant labels ("Piece 1", "Version 1") when there is only one.
- **Visual hierarchy**: Use spacing, subtle borders, and hover effects for interactivity - Use spacing, subtle borders, and hover effects for visual hierarchy.
## Temporary Work Files ## Security Checklist (before deployment)
- Use `_builds/` directory for temporary scripts and working files - [ ] `JWT_SECRET` is strong and unique (`openssl rand -base64 32`); never commit `api/.env`.
- Only `scripts/convert_final_v2.js` should be kept in the scripts folder (committed to git) - [ ] `api/config/users.json` uses non-default credentials (change `admin`/`user`).
- CSV output files belong in `_builds/` - [ ] CORS origins restricted to the real domains (no wildcard fallback).
- [ ] HTTPS enforced in production.
- [ ] Rate limiting active (clear `/tmp/rate_*` only in tests, never in prod).
- [ ] `php tests.php` passes 53/53.
- [ ] `npm audit` reviewed.
- [ ] No secrets in code or git history.
## Authentication Manual checks:
- **Login**: Use `apiService.login('admin', 'password')` from frontend
- **API calls**: Include token in Authorization header:
```
Authorization: Bearer <token>
```
- **Token format**: JWT (HS256) - see `api/lib/Auth.php`
- **Frontend**: Token stored in localStorage, auto-attached to API requests via axios interceptor
- **Test**: http://localhost:5173 - login with admin/password
- **API base URL**: http://localhost:8000/api/
### Starting the PHP Server
**Important:** Always use the custom upload config for file uploads to work:
```bash
cd api
php -c php-upload.ini -S localhost:8000 router.php &
```
Le serveur doit être lancé avec `-c php-upload.ini` pour autoriser l'upload de fichiers PDF (64M max).
### Upload de partitions
Lors de l'upload d'un PDF, deux options sont disponibles :
- **Ajouter le numéro** : ajoute le numéro de partition dans le coin du PDF
- **Position** : choix entre "Gauche" ou "Droite" pour placer le numéro
## Current Tech Stack (2024)
- **Frontend**: SvelteKit (NOT Vue.js 2) in `/partitions/`
- **Backend**: PHP API in `/api/`
- **Scores storage**: `/legacy/Scores/` (directory-based, not MySQL)
## Security Audit Commands
When modifying backend or frontend code, run these security audits:
### Backend Security Audit
```bash ```bash
# 1. Start the PHP server # JWT login
cd api curl -s http://localhost:8000/login -X POST \
php -S localhost:8000 router.php &
# 2. Clear rate limiting files (important!)
rm -f /tmp/rate_* 2>/dev/null
# 3. Run all security tests
php tests.php
# Expected: 50/50 tests passed (100%)
```
### Frontend Security Check
```bash
cd partitions
# 1. Check for security issues in dependencies
npm audit
# 2. Build and check for CSP violations
npm run build
# 3. Check that environment variables are configured
cp .env.example .env
# Edit .env and set VITE_API_URL to your backend URL
```
### Manual Security Verification
```bash
# Test JWT authentication
curl -s http://localhost:8000/login \
-X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' -d '{"username":"admin","password":"password"}'
# Test CORS headers # Directory traversal must NOT return 200
curl -s -I http://localhost:8000/scores \
-H "Origin: https://evil.com"
# Test directory traversal protection
curl -s http://localhost:8000/download/../../../etc/passwd \ curl -s http://localhost:8000/download/../../../etc/passwd \
-H "Authorization: Bearer <token>" -H "Authorization: Bearer <token>"
# Expected: 403 or 404 (not 200)
# Test security headers # Security headers on any response
curl -s -I http://localhost:8000/login \ curl -s -I http://localhost:8000/login -X POST \
-X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' -d '{"username":"admin","password":"password"}'
# Check for: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, HSTS
``` ```
### Environment Setup for Security > Known security findings from the latest review (not yet fixed): static file
> passthrough in `api/router.php` exposes `.env`/`users.json`; `deleteScoreFile`
> lacks path containment; `api/.env` is tracked by git; JWT is standard base64
> (not base64url) and expiry is optional; stored XSS via the `ressource` link.
> See the project issue tracker / recent review notes.
**Backend (api/.env):** ## Temporary Files
```bash
JWT_SECRET=your_very_long_random_secret_key_here
```
**Frontend (partitions/.env):** - Put temporary scripts and outputs in `_builds/` (gitignored).
```bash - `scripts/convert_final_v2.js` is the only committed script in `scripts/`.
# Development
VITE_API_URL=http://localhost:8000
# Production (use HTTPS!) ## Git
VITE_API_URL=https://api.yourdomain.com
```
### Security Checklist Before Deployment - Default branch: `master`.
- No CI/CD configured.
- [ ] JWT_SECRET is set and strong (use: `openssl rand -base64 32`) - Only commit when explicitly requested.
- [ ] CORS origins are restricted to your domain only
- [ ] HTTPS is enforced in production
- [ ] Rate limiting is active
- [ ] All 50 tests pass
- [ ] npm audit shows no critical vulnerabilities
- [ ] CSP headers are configured
- [ ] No secrets in code or git history
-530
View File
@@ -1,530 +0,0 @@
# Plan de développement - Application de partitions OHMJ
## Structure finale du projet
```
/home/jbnadal/sources/jb/ohmj/ohmj2/
├── legacy/ # Backup ancien code
│ ├── api/ # Ancien PHP legacy (MySQL)
│ ├── frontend/ # Ancien Vue.js 2
│ ├── fpdf/
│ ├── Imgs/
│ ├── Scripts/
│ ├── Textes/
│ └── index.php
├── api/ # NOUVEAU backend PHP moderne
│ ├── index.php # Router / entry point API
│ ├── config/
│ │ └── users.json # Users avec passwords hashés
│ └── lib/
│ ├── Auth.php # JWT authentication
│ └── ScoreScanner.php # Lecture fichiers ini + scan partitions
├── partitions/ # NOUVEAU frontend SvelteKit
│ ├── src/
│ │ ├── routes/
│ │ │ ├── +page.svelte # Login page
│ │ │ ├── scores/+page.svelte # Liste des morceaux
│ │ │ ├── scores/[id]/+page.svelte # Détail partition
│ │ │ └── +layout.svelte # Layout avec auth guard
│ │ ├── lib/
│ │ │ ├── api.ts # Client API
│ │ │ └── stores/auth.ts # Store JWT
│ │ └── app.html
│ ├── package.json
│ └── svelte.config.js
└── Scores/ # Répertoire partitions (hors git)
└── {id}/
├── score.ini
├── cla/
│ └── 1/
│ └── *.pdf
└── ...
```
## Phase 1 : Backend PHP (api/)
### 1.1 ScoreScanner.php ✓ (FAIT)
- [x] Classe pour scanner les partitions
- [x] Lecture des fichiers score.ini (supporte sections [info], [pieces])
- [x] Scan récursif instruments → parties → fichiers PDF
- [x] Mapping codes instruments vers noms
- [x] Retourne `piece` pour chaque instrument
- [x] Parse les noms de fichiers : `part`, `key`, `clef`, `variant`
- [x] Retourne `ressource` si présent dans score.ini
- [x] Supporte les partitions multi-morceaux (ex: score 390)
### 1.2 Auth.php
- [x] Classe JWT pour authentification
- [x] Génération token JWT
- [x] Vérification token JWT
- [x] Lecture users.json
- [x] Vérification password hashé (bcrypt)
- [ ] Tests unitaires Auth.php (PHP non installé)
### 1.3 users.json
- [x] Fichier JSON avec structure users
- [ ] Passwords hashés avec bcrypt (password: password)
- [ ] Script pour ajouter/modifier users
### 1.4 index.php (Router API)
- [x] CORS headers
- [x] POST /login
- [x] GET /scores (protégé JWT)
- [x] GET /scores/:id (protégé JWT)
- [x] GET /pieces/:scoreId (protégé JWT)
- [x] GET /download/:path (protégé JWT, stream PDF)
- [x] POST /admin/scores (protégé JWT, créer partition)
- [x] PUT /admin/scores/:id (protégé JWT, modifier partition)
- [x] DELETE /admin/scores/:id (protégé JWT, supprimer partition)
- [x] POST /admin/scores/:id/upload (protégé JWT, upload PDF)
- [ ] Gestion erreurs JSON
- [ ] Tests toutes les routes (PHP non installé)
### 1.5 Tests API
- [ ] Tests unitaires Auth.php (PHP non installé)
- [ ] Tests intégration toutes les routes (PHP non installé)
- [ ] Tests authentification JWT (PHP non installé)
## Phase 2 : Frontend SvelteKit (partitions/)
### 2.1 Initialisation projet
- [ ] npm create svelte@latest partitions
- [ ] TypeScript + ESLint + Prettier
- [ ] npm install (axios, etc.)
### 2.2 Configuration
- [ ] svelte.config.js (adapter static)
- [ ] API base URL config
### 2.3 Stores
- [ ] lib/stores/auth.ts
- [ ] Store JWT token
- [ ] Store user info
- [ ] Persistance localStorage
### 2.4 Client API
- [ ] lib/api.ts
- [ ] Instance axios avec baseURL
- [ ] Intercepteur pour ajouter header Authorization
- [ ] Méthodes : login(), getScores(), getScore(id), downloadFile(path)
### 2.5 Routes
#### Login (/)
- [ ] Formulaire : username, password
- [ ] Appel API login
- [ ] Stockage JWT
- [ ] Redirection vers /scores
- [ ] Gestion erreurs
#### Liste des morceaux (/scores)
- [ ] Auth guard (redirect si pas connecté)
- [ ] Table/DataGrid avec :
- [ ] Numéro (ID)
- [ ] Nom du morceau
- [ ] Compositeur
- [ ] Tri possible
- [ ] Click → navigation /scores/:id
- [ ] Loading state
#### Détail partition (/scores/[id])
- [ ] Auth guard
- [ ] Header :
- [ ] Numéro
- [ ] Nom du morceau
- [ ] Compositeur
- [ ] Bouton retour
- [ ] Liste des instruments (grid ou accordion) :
- [ ] Nom de l'instrument
- [ ] Pour chaque partie :
- [ ] Numéro de partie
- [ ] Liste des fichiers PDF
- [ ] Bouton téléchargement par fichier
- [ ] Loading state
#### Layout
- [ ] Auth guard global
- [ ] Header avec :
- [ ] Titre app
- [ ] User connecté
- [ ] Bouton logout
- [ ] Footer (optionnel)
### 2.6 Styles
- [ ] CSS moderne (Tailwind ou CSS vanilla)
- [ ] Responsive (mobile-friendly)
- [ ] Thème harmonie (couleurs)
## Phase 3 : Déploiement
### 3.1 Build frontend
- [ ] npm run build
- [ ] Output dans dist/
### 3.2 Configuration serveur
- [ ] /partitions → contenu dist/
- [ ] /api → PHP
- [ ] /legacy/Scores → accès aux PDFs
- [ ] .htaccess ou config nginx pour routing
### 3.3 Sécurité
- [ ] HTTPS obligatoire
- [ ] JWT secret fort
- [ ] Protection download (vérif JWT)
- [ ] Headers sécurité
## Notes techniques
### Backend PHP
- Pas de base de données
- JWT pour auth stateless
- Lecture fichiers uniquement
- Stream PDF pour download
### Frontend SvelteKit
- Svelte 5 (runes)
- TypeScript strict
- Pas de store complexe (juste auth)
- Client API simple
### Contraintes
- Chemin partitions : ../legacy/Scores/ (sera déplacé après DL)
- Fichiers ini présents partout
- PDFs uniquement
- Auth obligatoire pour tout accès
## Phase Préliminaire : Normalisation des données
### 0.1 Structure des partitions (à valider)
- [ ] Analyser tous les instruments pour voir si le répertoire "1" est nécessaire
- [ ] Identifier les cas où il n'y a qu'une seule partie (supprimer le niveau "1" ?)
- [ ] Valider la structure finale avant codage
### 0.2 Harmonisation des noms de fichiers ✓
**Convention finale :**
- Format : `{instrument}[_{instrument2}][_{variante}][_{tonalité}]_{partie}[_clef].pdf`
- Exemples :
- `clarinette_sib_1.pdf` (clarinette en Si♭, partie 1)
- `clarinette_alto_mib_1.pdf` (clarinette alto en Mi♭, partie 1)
- `basse_et_contrebasse_sib_1.pdf` (partition pour 2 instruments)
- `cor_mib_1_et_2.pdf` (cor en Mi♭, parties 1 et 2 combinées)
- `euphonium_sib_2_clefa.pdf` (euphonium en Si♭, partie 2, clé de fa)
- Tout en minuscule, français, ASCII sans accents
- Tonalités : sib (Bb), mib (Eb), fa (F), do (C), etc.
- **Garder les sous-répertoires numérotés (1/, 2/, etc.)** pour distinguer les versions
- **Script :** `scripts/convert_final_v2.js`
- **Usage :** `node scripts/convert_final_v2.js /chemin/vers/Scores confirm`
### 0.3 À propos des répertoires numérotés (1/, 2/, etc.)
**Découverte importante :**
- Le répertoire `1/` = version principale du morceau
- Le répertoire `2/` (ou plus) = alternative du même morceau
- Cela permet d'avoir plusieurs versions d'un même morceau dans le même ID
- Exemple : Score 210, dossier `cla/1/` et `cla/2/` contiennent des versions différentes
- **Action:** Le script de conversion doit utiliser le numéro du répertoire comme numéro de partie UNIQUEMENT pour les fichiers "version_X"
### 0.4 À propos du répertoire "sup"
- Le répertoire `sup/` signifie "supérieur" ou "combiné"
- C'est une partition où deux instruments jouent sur la même partition
- Ex: baryton + tuba sur une même partition
- **Note API:** Ces partitions combinées doivent être gérées différemment dans l'interface (afficher les deux instruments)
### 0.5 Conventions de nommage - Instruments
**Orthographe standardisée (français) :**
- Clarinette → clarinette
- Saxophone → saxophone (ou sax_baryton, sax_tenor, sax_alto, sax_soprano pour les variantes)
- Cor → cor
- Trompette → trompette
- Trombone → trombone
- Tuba → tuba (ou tuba_tenor pour tenor tuba)
- Basse → basse
- Contrebasse → contrebasse (UN SEUL MOT)
- Baryton → baryton
- Euphonium → euphonium
- Flute → flute (ou petite_flute, grande_flute)
- Hautbois → hautbois
- Basson → basson
- Contre-basson → contre_basson
- Bugle → bugle (inclut flugelhorn)
- Percussion → percussion (timbales, grosse_caisse, cymbale, caisse_claire, triangle, etc.)
**Instrumentsperaussion reconnus :**
- timpani, glockenspiel, bongos, maracas, tambourine, vibraphone, xylophone
- woodblock, claves, batterie
**Tonalités :**
- sib (Bb), mib (Eb), fa (F), do (ut), sol, re, mi, si, reb
- NOTE: "do" devient "ut" dans les noms de fichiers
**Cas spéciaux gérés :**
- "default_cor" = instrument de substitution (ex: baryton_default_cor_2)
- Parties combinées avec & = format "instrument1_et_instrument2_info1_info2_partie"
### 0.6 Structure du nom de fichier final
**Format :** `{instrument}[_{variante}][_{tonalité}][_{default}][_{instrument_subst}]_{partie}[_clef].pdf`
**Exemples :**
- `clarinette_sib_1.pdf`
- `sax_baryton_mib_1.pdf`
- `cor_fa_1_et_2.pdf`
- `baryton_sib_default_cor_2.pdf`
- `sax_tenor_sib_1.pdf`
- `baryton_mib_2_clesol.pdf`
- `baryton_et_tuba_tenor_sib_clefa.pdf` (parties combinées sup)
**Note:** Pour les parties combinées (sup), chaque instrument garde ses propres infos (tonalité, partie, clef)
## Phase 3 : Déploiement
### 3.1 Build frontend
- [ ] npm run build
- [ ] Output dans dist/
### 3.2 Configuration serveur
- [ ] /partitions → contenu dist/
- [ ] /api → PHP
- [ ] /legacy/Scores → accès aux PDFs
- [ ] .htaccess ou config nginx pour routing
### 3.3 Sécurité
- [ ] HTTPS obligatoire
- [ ] JWT secret fort
- [ ] Protection download (vérif JWT)
- [ ] Headers sécurité
## Phase 4 : Admin (post-déploiement)
### 4.1 Page d'administration
- [ ] Route `/admin` protégée (super-user)
- [ ] Interface CRUD pour les partitions
- [ ] Formulaire : ID, nom du morceau, compositeur
- [ ] Création automatique du répertoire et du score.ini
- [ ] Upload drag & drop des fichiers PDF
- [ ] Organisation automatique des fichiers dans la bonne structure
- [ ] Validation du format (convention de nommage)
## Prochaines étapes
1. ✓ Backup legacy (FAIT)
2. ✓ Renommage IDs avec zéros (FAIT)
3. ✓ Correction noms fichiers (& et espaces) (FAIT)
4. → Définir structure finale des partitions
5. → Harmoniser tous les noms de fichiers
6. → Créer Auth.php
7. → Créer users.json
8. → Créer index.php (router)
9. → Tester backend
10. → Créer frontend SvelteKit
11. → Développer routes
12. → Tester intégration
13. → Déployer
14. → Développer page admin
---
## Conversion des noms de fichiers (2026-02-17)
### Structure finale
`NUM/PIECE/INSTRUMENT/VERSION/PARTIE.pdf`
- **NUM**: numéro du score (ex: 102, 390)
- **PIECE**: numéro de pièce (1 = premier morceau)
- **INSTRUMENT**: trigramme de l'instrument (ex: cla, sax, trp)
- **VERSION**: numéro de version (1 = première version)
- **PARTIE**: numéro de partie (ex: 1, 2, 3...)
### Exemples
#### Score normal (1 seul morceau)
- `102/1/cla/1/1/clarinette-sib-1.pdf`
#### Score 390 (5 morceaux différents)
- `390/1/sax/1/1/sax_alto-1.pdf` (pièce 1)
- `390/2/sax/1/1/sax_alto-1.pdf` (pièce 2)
### Format score.ini
```ini
[info]
name = La part d'Euterpe
compositor = Michaël CUVILLON
[pieces]
count = 1
```
Pour 390:
```ini
[info]
name = La part d'Euterpe
compositor = Michaël CUVILLON
[pieces]
count = 5
1 = La Part d Euterpe
2 = Calliope s Songs
3 = Interlude
4 = Oxola
5 = Steps to Paranassus
```
### Étapes realizadas
1. ✅ score.ini: ajouté section [info] et [pieces] pour tous les scores
2. ✅ Répertoires 2 chiffres renommés (72→072, 73→073, 98→098)
3. ✅ Structure NUM/1/INSTRUMENT/VERSION appliquée pour tous les scores (sauf 390)
4. ✅ Structure NUM/PIECE/INSTRUMENT/VERSION appliquée pour 390 (PIECE=1-5)
5. ✅ Script convert_final_v2.js mis à jour pour supporter la nouvelle structure
6. ✅ Conversion appliquée: 6388 fichiers convertis, 0 erreurs
### Validation
- ✅ Multi instruments: utilise `+` (ex: `basse-sib-1+contrebasse-sib-1.pdf`)
- ✅ Parties combinées: utilise `_` (ex: `cor-mib-1_2.pdf`)
- ✅ Variantes: `solo` (ex: `clarinette-sib-solo-1.pdf`)
- ✅ Clefs: `clesol`, `clefa` (ex: `basse-sib-clesol-1.pdf`)
- ✅ Mots composés: `sax_alto`, `clarinette_basse`
- ✅ Pas de `-et-` (0 occurrences)
- ✅ Pas de `--` (0 occurrences)
---
## Phase future : Liens externes
### Objectif
Ajouter des liens externes dans les partitions (YouTube, site éditeur, etc.)
### Implementation
1. **Modification score.ini** (FAIT)
```ini
[info]
name = Le morceau
compositor = Compositeur
ressource = https://youtube.com/watch?v=xxx
# ou
ressource = https://www.musicdiffusion.com/...
```
2. **API** (FAIT)
- GET /scores → retourner `ressource` si présent
- GET /scores/:id → retourner `ressource` aussi
3. **Frontend**
- Page liste partitions : afficher icône lien externe si présent
- Page détail partition : afficher les liens sous le titre
### Ordre de priorité
- Implémenté, en attente frontend
---
## TODO: Icônes instruments
- Installer lucide-svelte (fait)
- Personnaliser chaque instrument avec la bonne icône
### Code IconeDesired
- pic flute traversiere
- flu flute traversiere
- cla clarinette
- clb clarinette basse
- sax OK
- sat OK
- sab OK
- coa cor anglais
- cba OK
- cor cor
- trp OK
- trb OK
- tub OK
- htb hautbois
- bas basson
- per OK
- crn OK
- eup OK
- har OK
- pia OK
---
## TODO: Déploiement
### Solution idéale: Docker
**Services à conteneriser:**
1. **PHP-FPM** - API backend
2. **Nginx** - Servir static + proxy PHP
3. **Node.js** - Frontend SvelteKit (ou build static)
**docker-compose.yml:**
```yaml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./frontend/build:/usr/share/nginx/html
- ./api:/var/www/html
- ./legacy/Scores:/var/www/html/Scores
depends_on:
- php
php:
image: php:8.2-fpm
volumes:
- ./api:/var/www/html
node:
build: ./partitions
command: npm run dev
```
### Solution alternative: Serveur classique
**Si hébergement mutualisé:**
- Build SvelteKit → dossier `build/`
- PHP-FPM pour API
- Dossiers:
- `/` → frontend static
- `/api` → PHP
- `/legacy/Scores` → fichiers PDF
### Tâches pour déploiement
- [x] Créer Dockerfile
- [x] Créer docker-compose.yml
- [x] Tester en local
- [x] Configurer CI/CD (GitHub Actions)
- [x] Déployer sur serveur prod
---
## Progression actuelle (2026-02-19)
### Backend (api/)
- ✅ Added `ressource` parameter to updateScore function (ScoreScanner.php)
- ✅ Updated CORS to allow local development (localhost:5173, localhost:4173)
### Frontend - Admin (partitions/)
- ✅ Created inline editing for score info (name, compositor, ressource) with pencil icons
- ✅ Added auto-focus when entering edit mode
- ✅ Added Enter key to save, Escape to cancel
- ✅ Added $effect to auto-set default key when instrument changes
- ✅ Changed "Uploader un PDF" to "Ajouter une partition" with modern card styling
- ✅ Made titles consistent across sections using ohmj-primary color
- ✅ Made partition number (№) bigger and bolder
- ✅ Changed delete buttons to modern trash icon with hover effect
- ✅ Fixed HTML structure issues
- ✅ Admin page at `/admin/[id]` with inline editing
### Déploiement
- ✅ API at ohmj-api.c.nadal-fr.com
- ✅ Frontend at partitions.c.nadal-fr.com
- ✅ Caddy handles reverse proxy for both services
- ✅ TLS/certificate resolved by using ohmj-api.c.nadal-fr.com
- ✅ Deploy zip at _builds/deploy_ohmj.zip
### API Documentation
- ✅ README.md updated with ressource parameter
+21 -4
View File
@@ -11,9 +11,9 @@ Site web de l'Harmonie de Montpellier-Jacou pour la gestion et le partage de par
## Stack technique ## Stack technique
- **Frontend** : SvelteKit - **Frontend** : SvelteKit (Svelte 5, runes) + Tailwind CSS — `partitions/`
- **Backend** : PHP API REST - **Backend** : PHP 8.5 REST API (sans framework) — `api/`, base `http://localhost:8000` (sans préfixe `/api`)
- **Stockage** : Fichiers (PDF) dans le répertoire `legacy/Scores/` - **Stockage** : Fichiers (PDF) dans le répertoire `legacy/Scores/` (pas de base de données)
## Installation ## Installation
@@ -24,12 +24,26 @@ cd api
php -c php-upload.ini -S localhost:8000 router.php php -c php-upload.ini -S localhost:8000 router.php
``` ```
`-c php-upload.ini` est requis pour l'upload des PDF (64M). L'API répond sur
`http://localhost:8000`.
### Frontend ### Frontend
```bash ```bash
cd partitions cd partitions
npm install npm install
npm run dev npm run dev # http://localhost:5173
npm run build # génère partitions/build
```
Le site déployé est exposé via le lien symbolique `frontend -> partitions/build`.
## Tests
```bash
cd api
rm -f /tmp/rate_* 2>/dev/null
php tests.php # attendu : 53/53
``` ```
## Déploiement ## Déploiement
@@ -38,6 +52,9 @@ npm run dev
./deploy/deploy.sh ./deploy/deploy.sh
``` ```
Le build frontend doit être présent dans `frontend/`. `legacy/` et `api/tests.php`
ne sont pas inclus dans l'archive.
## Licence ## Licence
GPL v2 GPL v2
-559
View File
@@ -1,559 +0,0 @@
# Audit de Sécurité - OHMJ Partitions
**Date :** 18 Février 2026
**Scope :** Backend PHP API + Frontend SvelteKit
**Criticité :** 🔴 Critique | 🟠 Haute | 🟡 Moyenne | 🟢 Faible
---
## 1. BACKEND - PHP API
### 1.1 Authentification JWT
#### 🔴 [CRITIQUE] Secret JWT en dur dans le code
**Fichier :** `api/lib/Auth.php:4`
```php
private const JWT_SECRET = 'ohmj_secret_key_change_in_production';
```
**Risque :** Compromission totale de l'authentification si le code est exposé
**Impact :** Attaquant peut générer des tokens valides pour n'importe quel utilisateur
**Recommandation :**
```php
private const JWT_SECRET = $_ENV['JWT_SECRET'] ?? getenv('JWT_SECRET');
if (!$secret) throw new Exception('JWT_SECRET not configured');
```
#### 🟠 [HAUTE] Pas de renouvellement de token (Refresh Token)
**Fichier :** `api/lib/Auth.php:6`
- Token expire après 1 heure sans possibilité de refresh
- Utilisateur doit se reconnecter fréquemment
- Pas de mécanisme de révocation de token
#### 🟢 [FAIBLE] Algorithme JWT acceptable
**Fichier :** `api/lib/Auth.php:5`
- `HS256` est correct mais pourrait être upgradé vers `HS384` ou `HS512`
---
### 1.2 Configuration CORS
#### 🔴 [CRITIQUE] CORS trop permissif
**Fichier :** `api/index.php:7-8`
```php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Origin: *'); // Dupliqué
```
**Risque :**
- Permet les requêtes depuis n'importe quel domaine
- Expose l'API aux attaques CSRF cross-origin
- Permet le phishing avec exfiltration de données
**Recommandation :**
```php
$allowedOrigins = ['https://ohmj2.free.fr', 'https://partitions.ohmj.fr'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin");
}
```
---
### 1.3 Gestion des Fichiers
#### 🔴 [CRITIQUE] Directory Traversal possible
**Fichier :** `api/index.php:45-77`
```php
$filePath = urldecode($matches[1]);
$fullPath = '/home/jbnadal/sources/jb/ohmj/ohmj2/legacy/Scores/' . $filePath;
```
**Risque :** Pas de validation du chemin
**Exploit :** `GET /download/../../../etc/passwd`
**Recommandation :**
```php
$filePath = urldecode($matches[1]);
// Nettoyer le chemin
$filePath = str_replace(['..', '//'], '', $filePath);
$fullPath = realpath($this->scoresPath . $filePath);
// Vérifier que le fichier est bien dans le répertoire autorisé
if (strpos($fullPath, realpath($this->scoresPath)) !== 0) {
http_response_code(403);
exit;
}
```
#### 🔴 [CRITIQUE] Upload de fichiers - Validation insuffisante
**Fichier :** `api/lib/ScoreScanner.php:485-542`
```php
public function uploadPdf(...) {
// Pas de vérification MIME type
// Pas de vérification magic bytes
// Pas de scan antivirus
}
```
**Risque :** Upload de fichier malveillant (PHP dans PDF)
**Recommandation :**
```php
// Vérifier MIME type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
if ($mimeType !== 'application/pdf') {
return ['success' => false, 'error' => 'Invalid file type'];
}
// Vérifier extension
if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) !== 'pdf') {
return ['success' => false, 'error' => 'Invalid file extension'];
}
// Vérifier magic bytes (PDF commence par %PDF)
$handle = fopen($file['tmp_name'], 'rb');
$header = fread($handle, 4);
fclose($handle);
if ($header !== '%PDF') {
return ['success' => false, 'error' => 'Invalid PDF header'];
}
```
#### 🟠 [HAUTE] Pas de limite de taille d'upload
**Fichier :** `api/index.php:3-4`
```php
ini_set('upload_max_filesize', '64M');
ini_set('post_max_size', '64M');
```
**Risque :** DoS par remplissage de disque
**Recommandation :** Limiter à 10-20MB maximum avec vérification côté application
---
### 1.4 Injection et Validation
#### 🟠 [HAUTE] Injection dans le parsing INI
**Fichier :** `api/lib/ScoreScanner.php:391-402`
```php
$iniContent = "[info]\nname = $name\ncompositor = $compositor\n\n[pieces]\ncount = $pieceCount\n";
```
**Risque :** Injection de nouvelles lignes si `$name` contient `\n`
**Exploit :** `name = Test\n[injection]\nmalicious = true`
**Recommandation :**
```php
$name = str_replace(["\n", "\r"], '', $name);
$compositor = str_replace(["\n", "\r"], '', $compositor);
```
#### 🟡 [MOYENNE] Pas de validation des entrées utilisateur
**Fichiers concernés :**
- `api/index.php:82-84` (login)
- `api/index.php:169-174` (création score)
- `api/index.php:252-269` (upload)
**Recommandation :** Utiliser filter_input() ou validation stricte
---
### 1.5 HTTPS et Transport
#### 🔴 [CRITIQUE] Pas de redirection HTTPS
**Fichier :** Tous les fichiers PHP
- Aucun header HSTS
- Aucune vérification du schéma
- Tokens JWT transmis en clair si HTTP utilisé
**Recommandation :**
```php
// Forcer HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
exit;
}
// HSTS Header
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
```
---
### 1.6 Headers de Sécurité
#### 🔴 [CRITIQUE] Headers de sécurité manquants
**Fichier :** `api/index.php`
Manque :
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `X-XSS-Protection: 1; mode=block`
- `Content-Security-Policy`
- `Referrer-Policy`
**Recommandation :**
```php
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
```
---
### 1.7 Rate Limiting
#### 🔴 [CRITIQUE] Pas de rate limiting
**Impact :**
- Brute force sur /login possible
- DoS par requêtes massives
- Scraping de l'API illimité
**Recommandation :**
```php
// Implémenter rate limiting par IP
$ip = $_SERVER['REMOTE_ADDR'];
$rateFile = sys_get_temp_dir() . '/rate_' . md5($ip) . '.json';
// Max 100 requêtes par minute
// Max 5 tentatives de login par minute
```
---
### 1.8 Logging et Monitoring
#### 🟠 [HAUTE] Pas de logs de sécurité
**Problème :**
- Aucun log des tentatives d'authentification échouées
- Aucun log des accès aux fichiers sensibles
- Aucun log des erreurs
---
## 2. FRONTEND - SVELTEKIT
### 2.1 Stockage des Tokens
#### 🔴 [CRITIQUE] JWT stocké dans localStorage
**Fichier :** `partitions/src/lib/stores/auth.ts:15-16`
```typescript
const stored = browser ? localStorage.getItem('auth') : null;
const initial: AuthState = stored ? JSON.parse(stored) : { token: null, user: null };
```
**Risque :**
- Vulnérable aux attaques XSS
- Accessible par tout JavaScript sur la page
- Pas de protection contre le vol via script injection
**Recommandation :**
```typescript
// Utiliser des cookies httpOnly sécurisés
// Le backend doit définir le cookie
// Le frontend ne manipule jamais le token directement
```
#### 🟠 [HAUTE] Token exposé dans l'URL
**Fichier :** `partitions/src/lib/api.ts:109-114`
```typescript
getDownloadUrl(path: string): string {
let token = '';
auth.subscribe((state) => {
token = state.token || '';
})();
return `${API_BASE_URL}/download/${path}?token=${token}`;
}
```
**Risque :**
- Token visible dans l'historique du navigateur
- Token leaké dans les logs serveur (access logs)
- Token leaké par le header Referer
**Recommandation :** Utiliser des cookies httpOnly ou des URLs signées temporaires
---
### 2.2 Content Security Policy
#### 🔴 [CRITIQUE] Pas de CSP
**Impact :**
- XSS possible via injection de scripts
- Chargement de ressources externes non contrôlé
**Recommandation :** Configurer CSP dans `svelte.config.js` ou headers nginx
```
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' https://api.ohmj.fr; font-src 'self';
```
---
### 2.3 Validation des Entrées
#### 🟡 [MOYENNE] Pas de validation côté client
**Fichiers concernés :**
- Login : pas de validation email/username
- Upload : pas de validation du fichier avant envoi
**Recommandation :** Ajouter validation avec zod ou yup
---
### 2.4 Gestion des Erreurs
#### 🟠 [HAUTE] Messages d'erreur exposés
**Fichier :** `partitions/src/lib/api.ts:25-35`
Les erreurs API sont affichées telles quelles, potentiellement révélatrices.
---
## 3. INFRASTRUCTURE
### 3.1 Configuration Serveur
#### 🔴 [CRITIQUE] Informations sensibles en clair
**Fichier :** `api/index.php:21`
```php
$scanner = new ScoreScanner('/home/jbnadal/sources/jb/ohmj/ohmj2/legacy/Scores/');
```
- Chemin absolu exposé
- Structure de dossiers révélée
#### 🔴 [CRITIQUE] Mode debug activé
**Fichier :** `api/index.php:42`
```php
// Debug: log the path
// file_put_contents('/tmp/api_debug.log', ...
```
- Code de debug présent en production
### 3.2 HTTPS/TLS
#### 🔴 [CRITIQUE] HTTP utilisé en développement
**Fichier :** `partitions/src/lib/api.ts:6`
```typescript
const API_BASE_URL = browser ? 'http://localhost:8000' : 'http://localhost:8000';
```
**Risque :** Habitude de développer en HTTP peut se propager en prod
---
## 4. VULNÉRABILITÉS SPÉCIFIQUES
### 4.1 CSRF (Cross-Site Request Forgery)
**État :** 🟢 Protégé par JWT
Le token JWT doit être présent dans chaque requête, ce qui protège contre les attaques CSRF simples.
### 4.2 XSS (Cross-Site Scripting)
**État :** 🟡 Partiellement protégé
Svelte échappe automatiquement le HTML, mais :
- Usage de `{@html ...}` non audité
- Pas de CSP
### 4.3 Clickjacking
**État :** 🔴 Non protégé
Pas de header `X-Frame-Options`
---
## 5. PLAN D'ACTION
### Priorité 1 (Immédiat - 1 semaine)
- [ ] 🔴 Déplacer JWT_SECRET vers variable d'environnement
- [ ] 🔴 Restreindre CORS aux domaines autorisés
- [ ] 🔴 Ajouter validation des chemins de fichiers (directory traversal)
- [ ] 🔴 Ajouter validation des uploads (MIME type + magic bytes)
- [ ] 🔴 Implémenter rate limiting sur /login
- [ ] 🔴 Ajouter headers de sécurité de base
### Priorité 2 (Court terme - 1 mois)
- [ ] 🟠 Migrer le stockage JWT vers cookies httpOnly
- [ ] 🟠 Configurer CSP
- [ ] 🟠 Ajouter logs de sécurité
- [ ] 🟠 Forcer HTTPS en production
- [ ] 🟠 Nettoyer code de debug
### Priorité 3 (Moyen terme - 3 mois)
- [ ] 🟡 Ajouter WAF (Web Application Firewall)
- [ ] 🟡 Implémenter monitoring/anomalies
- [ ] 🟡 Audit de dépendances (npm/composer)
- [ ] 🟡 Tests de pénétration
---
## 6. RÉFÉRENCES
- [OWASP Top 10 2021](https://owasp.org/Top10/)
- [OWASP JWT Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html)
- [Svelte Security Best Practices](https://svelte.dev/docs/kit/security)
- [PHP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html)
---
**Conclusion :**
L'application présente plusieurs vulnérabilités critiques qui doivent être corrigées avant la mise en production, notamment :
1. Secret JWT en dur
2. CORS trop permissif
3. Directory traversal possible
4. Stockage JWT dans localStorage
5. Absence de rate limiting
**Score de sécurité global :** 4/10 (Insuffisant pour production)
---
## 6. RAPPORT FINAL - CORRECTIONS EFFECTUÉES
**Date de correction :** 18 Février 2026
**Tests :** 44/45 tests passés (97.8%)
### ✅ Corrections critiques implémentées :
#### 6.1 JWT Secret (api/lib/Auth.php)
- **Problème :** Secret codé en dur dans le code source
- **Correction :** Chargement depuis variable d'environnement `JWT_SECRET`
- **Fichier créé :** `api/.env` avec JWT_SECRET configuré
- **Impact :** Protection contre l'exposition du secret dans le code
#### 6.2 CORS Policy (api/index.php)
- **Problème :** `Access-Control-Allow-Origin: *` (tous les domaines autorisés)
- **Correction :** Liste blanche des domaines autorisés uniquement
```php
$allowedOrigins = ['http://localhost:5173', 'http://localhost:3000',
'https://ohmj2.free.fr', 'https://partitions.ohmj.fr'];
```
- **Impact :** Prévention des attaques CSRF cross-origin
#### 6.3 Directory Traversal (api/index.php)
- **Problème :** Pas de validation des chemins de fichiers
- **Correction :**
- Suppression des `../` et `..` dans les chemins
- Validation avec `realpath()` pour s'assurer que le fichier est dans le répertoire autorisé
- Protection contre les null bytes (`\x00`)
- **Impact :** Prévention de l'accès aux fichiers système
#### 6.4 Headers de sécurité HTTP (api/index.php)
- **Ajoutés :**
- `X-Content-Type-Options: nosniff` - Empêche le MIME sniffing
- `X-Frame-Options: DENY` - Protection contre le clickjacking
- `X-XSS-Protection: 1; mode=block` - Protection XSS navigateur
- `Strict-Transport-Security: max-age=31536000` - Force HTTPS (HSTS)
- `Referrer-Policy: strict-origin-when-cross-origin` - Contrôle Referrer
- **Impact :** Renforcement de la sécurité des navigateurs clients
#### 6.5 Rate Limiting (api/index.php)
- **Implémenté :**
- 5 tentatives de login par minute par IP
- 100 requêtes générales par minute par IP
- HTTP 429 (Too Many Requests) si limite dépassée
- **Impact :** Prévention du brute force et du DoS
#### 6.6 Validation des uploads (api/lib/ScoreScanner.php)
- **Ajouté :**
- Vérification MIME type (`application/pdf` uniquement)
- Vérification de l'extension (`.pdf` uniquement)
- Vérification des magic bytes (fichier commence par `%PDF`)
- Limite de taille : 20MB maximum
- **Impact :** Prévention de l'upload de fichiers malveillants
#### 6.7 Injection INI (api/lib/ScoreScanner.php)
- **Problème :** Injection possible via newlines dans les noms
- **Correction :** Sanitisation des entrées utilisateur (suppression `\n`, `\r`, `\x00`)
- **Impact :** Prévention de l'injection dans les fichiers de configuration
#### 6.8 Content Security Policy - Frontend (partitions/svelte.config.js)
- **Problème :** Aucune CSP configurée
- **Correction :** CSP stricte ajoutée :
- `default-src: self`
- `script-src: self, unsafe-inline` (nécessaire pour Svelte)
- `connect-src: self, api-backend`
- `frame-ancestors: none` (protection clickjacking)
- **Impact :** Protection XSS et contrôle des ressources chargées
#### 6.9 Configuration API URL - Frontend (partitions/src/lib/api.ts)
- **Problème :** URL API en dur dans le code (`http://localhost:8000`)
- **Correction :** Utilisation de `import.meta.env.VITE_API_URL`
- **Impact :** Configuration flexible et sécurisée par environnement
#### 6.10 Validation Auth Store - Frontend (partitions/src/lib/stores/auth.ts)
- **Problème :** Données localStorage parsées sans validation
- **Correction :** Validation de la structure des données avant utilisation
- **Impact :** Prévention de l'exécution de code malveillant via localStorage
### 📁 Fichiers modifiés :
**Backend :**
1. `api/index.php` - Router API avec sécurité renforcée
2. `api/lib/Auth.php` - JWT avec chargement depuis environnement
3. `api/lib/ScoreScanner.php` - Validation upload et injection
4. `api/router.php` - Chargement automatique du fichier `.env`
5. `api/tests.php` - Suite de tests de sécurité complète (50 tests)
6. `api/.env` - Configuration des secrets (nouveau fichier)
**Frontend :**
7. `partitions/svelte.config.js` - Ajout CSP (Content Security Policy)
8. `partitions/src/lib/api.ts` - Variables d'environnement pour API URL
9. `partitions/src/lib/stores/auth.ts` - Validation des données stockées
10. `partitions/.env.example` - Template de configuration (nouveau fichier)
### 🧪 Tests de sécurité implémentés :
```bash
cd api
php tests.php
```
**Résultat : 44/45 tests passés (97.8%)**
**Catégories de tests :**
- ✅ Auth (login, credentials, tokens)
- ✅ Scores (CRUD, gestion d'erreurs)
- ✅ Files (gestion de fichiers)
- ✅ Security Headers (CORS, XSS, clickjacking, HSTS)
- ✅ Directory Traversal (prévention ../)
- ✅ Rate Limiting (brute force protection)
- ✅ File Upload (validation MIME, magic bytes)
- ✅ Injection Protection (INI injection)
### 🚀 Score de sécurité après corrections : **10/10** ✅
**Tests automatisés :** 50/50 passés (100%)
**Points restants à améliorer (non critiques) :**
- Frontend : Stockage JWT dans localStorage (niveau frontend)
- Refresh tokens pour sessions longues (amélioration UX)
- WAF en production (couche supplémentaire)
### 📊 Résultat final des tests
```bash
cd api && php tests.php
=== Summary ===
✓ All tests passed!
Passed: 50
Failed: 0
Total: 50
```
### 📋 Configuration requise pour la production :
1. **Créer le fichier `.env` :**
```bash
JWT_SECRET=votre_cle_secrete_tres_longue_et_aleatoire_123456789
```
2. **Générer une clé JWT sécurisée :**
```bash
openssl rand -base64 32
```
3. **Limiter les domaines CORS :**
Modifier dans `api/index.php` :
```php
$allowedOrigins = ['https://votredomaine.fr'];
```
4. **Activer HTTPS obligatoire :**
Configurer le serveur web (nginx/Apache) pour rediriger HTTP vers HTTPS.
---
*Document généré automatiquement - Audit complété avec succès*
+20 -2
View File
@@ -259,6 +259,7 @@ NUM/PIECE/INSTRUMENT/VERSION/PARTIE.pdf
| sax | Saxophone alto | | sax | Saxophone alto |
| sab | Saxophone baryton | | sab | Saxophone baryton |
| sat | Saxophone ténor | | sat | Saxophone ténor |
| coa | Cor anglais |
| cba | Contrebasse | | cba | Contrebasse |
| cor | Cor | | cor | Cor |
| trp | Trompette | | trp | Trompette |
@@ -269,6 +270,10 @@ NUM/PIECE/INSTRUMENT/VERSION/PARTIE.pdf
| per | Percussion | | per | Percussion |
| crn | Cornet | | crn | Cornet |
| eup | Euphonium | | eup | Euphonium |
| har | Harpe |
| pia | Piano |
| sup | Parties combinées ("supérieur") |
| par | Parties combinées |
--- ---
@@ -319,6 +324,10 @@ Content-Type: application/json
- `compositor` - Nom du compositeur - `compositor` - Nom du compositeur
- `ressource` - Lien externe (YouTube, site éditeur, etc.) - `ressource` - Lien externe (YouTube, site éditeur, etc.)
> **Bug connu :** `ressource` n'est actuellement **pas persisté** par cette route
> (`ScoreScanner::updateScore()` n'accepte pas le paramètre et la reconstruction de
> `score.ini` ne réécrit pas `ressource`). À corriger côté backend.
--- ---
### DELETE /admin/scores/:id ### DELETE /admin/scores/:id
@@ -412,6 +421,8 @@ key: sib (optionnel)
clef: clesol (optionnel) clef: clesol (optionnel)
variant: solo (optionnel) variant: solo (optionnel)
part: 1 (optionnel, défaut: 1) part: 1 (optionnel, défaut: 1)
watermark: true (optionnel) # ajoute le numéro de partition sur le PDF
watermarkPosition: left|right (optionnel, défaut: left)
``` ```
--- ---
@@ -421,9 +432,12 @@ part: 1 (optionnel, défaut: 1)
| Code | Description | | Code | Description |
|------|-------------| |------|-------------|
| 200 | Succès | | 200 | Succès |
| 400 | Requête invalide (champ manquant, payload incorrect) |
| 401 | Non authentifié | | 401 | Non authentifié |
| 403 | Accès interdit (rôle insufficient) | | 403 | Accès interdit (rôle insuffisant) |
| 404 | Ressource non trouvée | | 404 | Ressource non trouvée |
| 413 | Fichier trop volumineux |
| 429 | Trop de requêtes (rate limiting) |
| 500 | Erreur serveur | | 500 | Erreur serveur |
--- ---
@@ -447,9 +461,13 @@ Les tests vérifient :
```bash ```bash
cd api cd api
php -S localhost:8000 router.php php -c php-upload.ini -S localhost:8000 router.php
``` ```
> `-c php-upload.ini` est requis pour autoriser l'upload de PDF (64M max).
> `ini_set()` ne peut pas modifier `upload_max_filesize`/`post_max_size` à l'exécution.
> L'API répond sur `http://localhost:8000` (sans préfixe `/api`).
--- ---
## Structure des fichiers ## Structure des fichiers
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long