Skip to content

fix: audit critique - sécurité, UX mobile et qualité de code - #38

Closed
mcinquin wants to merge 1 commit into
mainfrom
cursor/audit-improvements-6a23
Closed

fix: audit critique - sécurité, UX mobile et qualité de code#38
mcinquin wants to merge 1 commit into
mainfrom
cursor/audit-improvements-6a23

Conversation

@mcinquin

Copy link
Copy Markdown
Owner

Audit critique du projet HobbyHoops

Suite à un audit complet du code source, cette PR apporte des améliorations dans trois axes : sécurité, UX mobile et qualité de code.

Note : les en-têtes HTTP de sécurité (HSTS, X-Content-Type-Options, etc.) sont gérés par le reverse-proxy et ne sont pas modifiés ici.


Sécurité

Problème identifié Correction
Les routes POST /api/auth/login et POST /api/auth/bootstrap ne vérifiaient pas l'origine de la requête (pas de protection CSRF) Ajout de rejectCrossSiteMutation sur ces deux routes pour prévenir les attaques CSRF de type "login CSRF"
Énumération d'utilisateurs possible via différence de timing (pas de hash scrypt quand l'utilisateur n'existe pas) Ajout d'un dummyVerify qui exécute un hash scrypt factice quand l'utilisateur n'existe pas, rendant le temps de réponse constant
Aucune limite de taille sur les corps de requête JSON Ajout d'un guard rejectOversizedBody (512 KB max) sur toutes les routes de mutation (POST/PUT/PATCH /api/cards, PATCH /api/references)
Le endpoint /api/health exposait des détails internes (existence de données par table) Simplifié pour ne retourner que { status: "healthy" } ou { status: "degraded" }
DELETE /api/cards utilisait un paramètre GET (?id=...) — incompatible avec la protection CSRF sec-fetch-site sur certains navigateurs Migration vers un corps JSON { id: "..." } avec fallback sur query params pour rétro-compatibilité
Le nonce CSP était généré à partir d'un UUID (entropie limitée à 122 bits et format prévisible) Remplacement par crypto.getRandomValues(new Uint8Array(16)) (128 bits d'entropie pure)

UX Mobile

Problème identifié Correction
Pas de balise viewport configurée — rendu incorrect sur mobile Ajout de viewport export Next.js avec width: device-width, initialScale: 1, maximumScale: 1, viewportFit: cover
Délai de 300ms au tap sur mobile Ajout de touch-action: manipulation sur <html>
Flash de highlight au tap sur iOS/Android Ajout de -webkit-tap-highlight-color: transparent sur <body>
Zoom automatique iOS Safari lors du focus sur les inputs (font-size < 16px) Ajout de font-size: 16px minimum sur input, select, textarea
Le dialogue de formulaire carte était tronqué sur petit écran Le dialog passe en plein écran sur mobile (max-h-dvh, inset-0) et reste centré sur desktop

Performance

Amélioration Détail
Pas de skeleton loading pendant le chargement des pages Ajout de loading.tsx avec des squelettes animés pour les routes dashboard, collection et joueurs

Qualité de code

Problème identifié Correction
Composant combobox dupliqué entre card-form.tsx (~100 lignes) et admin-cards-section.tsx (~100 lignes) Extraction dans un composant partagé src/components/ui/combobox.tsx, utilisé par les deux fichiers

Fichiers modifiés

  • src/app/api/auth/login/route.ts — CSRF + timing-safe
  • src/app/api/auth/bootstrap/route.ts — CSRF
  • src/app/api/cards/route.ts — body size limit + DELETE via body
  • src/app/api/references/route.ts — body size limit
  • src/app/api/health/route.ts — réponse minimale
  • src/lib/password.tsdummyVerify()
  • src/lib/request-guard.tsrejectOversizedBody()
  • src/lib/csp.ts — nonce via getRandomValues
  • src/lib/db.ts / src/lib/data.ts — signature getDatabaseHealth simplifiée
  • src/app/layout.tsx — viewport meta
  • src/app/globals.css — optimisations touch mobile
  • src/components/ui/combobox.tsx — nouveau composant partagé
  • src/components/card-form.tsx — utilise Combobox + dialog mobile
  • src/components/admin/admin-cards-section.tsx — utilise Combobox + DELETE body
  • src/app/(app)/loading.tsx — skeleton dashboard
  • src/app/(app)/collection/loading.tsx — skeleton collection
  • src/app/(app)/player/loading.tsx — skeleton joueurs
  • messages/fr.json / messages/en.json — clé payloadTooLarge
Open in Web Open in Cursor 

Security:
- Add CSRF protection (rejectCrossSiteMutation) to login and bootstrap routes
- Prevent user enumeration via timing-safe dummy password hash on failed lookups
- Add request body size limit (512KB) guard on mutation API routes
- Minimize health endpoint info leakage (no longer exposes table-level details)
- Move card DELETE id from query params to request body for CSRF consistency
- Harden CSP nonce generation using crypto.getRandomValues (16 bytes)

Mobile UX:
- Add viewport meta tag with device-width, maximumScale=1, viewportFit=cover
- Add touch-action: manipulation to prevent 300ms tap delay
- Remove -webkit-tap-highlight-color flash on mobile
- Set min font-size 16px on inputs to prevent iOS Safari auto-zoom
- Make card-form dialog full-screen on mobile viewports

Performance:
- Add loading.tsx skeleton screens for dashboard, collection, and player routes

Code quality:
- Extract shared Combobox component from card-form and admin-cards-section
- Add payloadTooLarge translation key (fr/en)
@mcinquin mcinquin closed this May 20, 2026
@mcinquin
mcinquin deleted the cursor/audit-improvements-6a23 branch July 19, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants