Module de signature electronique en PHP 8.2 / Slim 4 / MySQL 8 / Ratchet. Conception detaillee dans docs/ et diagrams/.
- PHP 8.2 + Slim 4
- MySQL 8 (multi-tenant par colonne tenant_id)
- Ratchet (WebSocket) pour le canal SOTHIS
- Symfony Mailer
- PHPUnit pour les tests
- Docker Compose
cp .env.example .env
docker-compose up --build -d
# Installation des dependances PHP a l'interieur du conteneur
docker-compose exec app composer install
# Seed des locataires de demo (mot de passe : demo1234)
docker-compose exec app php bin/seed-users.phpL'API repond sur http://localhost:8080.
| Service | URL |
|---|---|
| API HTTP | http://localhost:8080 |
| WebSocket | ws://localhost:8081 |
| MySQL | localhost:3307 (user app / pass app_secret) |
Verification :
curl http://localhost:8080/health| Mot de passe | Documents | |
|---|---|---|
| alice@example.test | demo1234 | DOC-2026-0001 (bail) |
| bob@example.test | demo1234 | DOC-2026-0002 (avenant) |
| Methode | URL | Auth | CSRF | Description |
|---|---|---|---|---|
| GET | /health | - | - | Sante service + BDD |
| POST | /api/auth/login | - | - | Connexion par email/mot de passe |
| POST | /api/auth/logout | session | requis | Deconnexion |
| GET | /api/auth/me | session | - | Profil du locataire connecte |
| GET | /api/auth/csrf-token | session | - | Recupere un jeton CSRF |
| GET | /api/documents | session | - | Liste des documents du locataire |
| GET | /api/documents/{id} | session | - | Detail d'un document |
| POST | /api/documents/{id}/sign/start | session | requis | Lance la signature et envoie l'OTP |
| POST | /api/documents/{id}/sign/complete | session | requis | Valide la signature avec OTP + image |
| POST | /api/documents/{id}/refuse | session | requis | Refus motive |
| POST | /api/sothis/document/finalized | API key | - | Notification de finalisation par SOTHIS |
Le middleware SecurityHeadersMiddleware ajoute sur chaque reponse :
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Content-Security-Policy: default-src 'self'; img-src 'self' data:; ...
Sur les routes mutantes (logout, sign/start, sign/complete, refuse), un token CSRF est exige.
Cycle :
- Apres
/api/auth/login, la reponse contientcsrfToken. - Ou bien, le client peut le recuperer a la demande via
GET /api/auth/csrf-token. - Le client renvoie le token dans l'en-tete
X-CSRF-Token(ou dans le bodycsrf_token). - Si absent ou invalide, le serveur repond
403avec le codecsrf_invalid.
/api/auth/login est volontairement exempt (premier appel sans session).
/api/sothis/document/finalized est exempt (auth par cle API serveur a serveur).
Cle API statique passee en header X-Sothis-Key. La valeur attendue vient
de SOTHIS_API_KEY dans .env. Comparaison en temps constant (hash_equals).
Toutes les requetes utilisent les cookies de session : pensez a activer
"Send cookies" dans Postman ou a utiliser -c cookies.txt -b cookies.txt avec curl.
curl -i -c cookies.txt -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.test","password":"demo1234"}'curl -b cookies.txt http://localhost:8080/api/auth/mecurl -b cookies.txt http://localhost:8080/api/documentsTOKEN=$(curl -s -b cookies.txt http://localhost:8080/api/auth/csrf-token | jq -r .data.csrfToken)
curl -b cookies.txt -X POST http://localhost:8080/api/documents/1/sign/start \
-H "X-CSRF-Token: $TOKEN"L'OTP est trace dans var/log/app.log cote conteneur, et dans la table mail_queue :
docker-compose exec db mysql -uapp -papp_secret espace_privatif \
-e "SELECT variables FROM mail_queue ORDER BY id DESC LIMIT 1\G"curl -b cookies.txt -X POST http://localhost:8080/api/documents/1/sign/complete \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $TOKEN" \
-d '{
"otp": "123456",
"signature": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9ZwwQ9wAAAAASUVORK5CYII="
}'curl -b cookies.txt -X POST http://localhost:8080/api/documents/1/refuse \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $TOKEN" \
-d '{"reason":"Loyer incorrect"}'curl -i -X POST http://localhost:8080/api/sothis/document/finalized \
-H "Content-Type: application/json" \
-H "X-Sothis-Key: sothis-shared-key-dev" \
-d '{
"document_id": "DOC-2026-0001",
"pdf_url": "https://sothis.local/files/signed/DOC-2026-0001.pdf"
}'Codes de reponse :
200: document passe ensigne_valide, mail final mis en file200+idempotent: true: deja ensigne_valide, rien refait401: cle API invalide404: document inconnu409: document pas encore en etatsigne422: payload invalide
# Lancer toute la suite
docker-compose exec app vendor/bin/phpunit
# Lancer uniquement les tests unitaires
docker-compose exec app vendor/bin/phpunit --testsuite unitCouverture des cas critiques :
| Suite | Fichier | Cas couverts |
|---|---|---|
| unit | AuthServiceTest |
login OK, mot de passe faux, email inconnu, compte verrouille, declenchement du lockout |
| unit | OtpServiceTest |
absence de code, mauvais code, max attempts, code valide, generation + envoi |
| unit | SignatureServiceTest |
start OK, refus si deja signe, complete avec mauvais OTP, complete avec etat invalide |
| unit | CsrfTokenManagerTest |
creation stable, validation, rotation |
| unit | DocumentStateTest |
transitions autorisees, etats terminaux |
| integration | AuthMiddlewareTest |
acces refuse sans session, acces autorise avec session |
| integration | CsrfMiddlewareTest |
rejet sans token, acceptation avec header valide, GET non controles |
Aucun test ne depend de la base, on s'appuie uniquement sur des mocks PHPUnit.
app/
Controllers/ Endpoints HTTP
(Health, Auth, Document, Signature, Sothis)
Services/ Logique metier
(Auth, Document, Signature, Otp, Mail, SothisGateway, Audit)
Repositories/ Acces BDD via PDO
(User, Document, Signature, Otp, Outbox, AuditLog)
Models/ Objets de domaine (User, Document, DocumentState)
Middleware/ Session, Auth, JsonBody, SecurityHeaders, Csrf
Security/ CsrfTokenManager
Database/ Connection PDO
Http/ JsonResponse helper
bin/
ws-server.php Serveur Ratchet (canal SOTHIS)
mail-worker.php Worker batch pour la file mail_queue
seed-users.php Seed locataires + documents de demo
config/ settings.php, dependencies.php (DI), middleware.php
routes/ api.php
public/ index.php (entry point Slim)
migrations/ SQL initial + seed tenant/residence
docker/ Dockerfile + nginx
tests/ Unit/ + Integration/ (PHPUnit)
- Mail reel : transport
nullpar defaut, on bascule sur SMTP viaMAIL_DSN. - Pas de rate limiting applicatif (a ajouter en middleware si besoin).
- WebSocket sans TLS local (a placer derriere un reverse proxy en prod).
- Endpoint de depot SOTHIS (POST /api/sothis/documents) non encore branche : prochaine iteration.
- CSRF a token de session, pas a usage unique : suffisant pour cette V1.