Files
2026-07-26 18:43:51 +02:00

10 KiB
Raw Permalink Blame History

CaseOrion — Analyse & améliorations

Date : 2026-07-25 · MAJ : 2026-07-26
Périmètre : analyse complète (server + client + Docker), sans modification de code.
Stack : Express / Prisma / SQLite / Socket.IO · React (Vite) · ~16k LOC client+server.


Synthèse

Le projet est une API jeu cohérente en mono-process, avec un effort sérieux sur l’économie haute (crédits en strings + BigInt via cents.js).
Les risques les plus graves restants : paiements mock en prod, db push --accept-data-loss au boot, et état in-memory (sessions, timers battles, présence).

Zone État
Cases / wear / keys Solide (roll serveur, BigInt value)
Auto-sell Logique OK (value < seuil) ; UX/confusion catalogue vs instance
Battle (jackpot only) Un seul mode ; 1vX / coinflip / duel retirés
Vault / prestige Fonctionnel ; settle concurrent risqué
Shop Mock OK en dev ; dangereux si prod
Docker Marche en single-node ; pas scale-ready

P0 — Critique (corriger en premier)

1. Odds des duels faussées (multi-items)résolu par suppression

Le mode 1vX Case (rooms custom / duel.js) a été retiré (2026-07-25). Il ne reste que le Battle MultiPlayers (jackpot.js). Les anciennes routes /battle/1vx et /battle/coinflip redirigent vers /battle.

1. Paiements mock utilisables en production — en cours (B Stripe)

Fichiers : server/src/payments.js, routes/shop.js, index.js (webhook raw body), .env.prod.example

Stripe Checkout + webhook checkout.session.completed branchés. En prod Docker : PAYMENTS_PROVIDER=stripe + STRIPE_* keys + STRIPE_WEBHOOK_SECRET + URL publique HTTPS vers /api/shop/osu/webhook.

Reste : désactiver mock en prod (A) si quelquun oublie PAYMENTS_PROVIDER ; bascule live keys quand le compte est vérifié.


3. prisma db push --accept-data-loss à chaque démarrage Docker

Fichiers : Dockerfile, docker-compose.yml

Peut détruire / tronquer des colonnes au restart. Inadapté à une vraie prod.

| Proposition | A (recommandé) : Prisma Migrate (migrate deploy) ; push manuel hors boot · B : push sans --accept-data-loss + fail loud · C : job one-shot de migration séparé du CMD app |


4. db.js coerce tous les BigInt → Numbercorrigé

reviveMoney supprimé. Choix retenu : argent en strings + toCentsBigInt (pas de plafond Number), BigInt Prisma éventuels laissés intacts jusquau JSON (toString dans index.js). Pas de conversion globale silencieuse.


5. Uploads SVG → XSS stockécorrigé

Allowlist PNG / JPEG / WebP / GIF (MIME + extension + vérif sharp du format réel). SVG et autres image/* refusés (admin + avatar).


P1 — Économie & précision (haute priorité)

6. Finir la migration argent BigInt-safe (bout en bout)corrigé (A)

Helpers client dans api.js : toCentsBigInt, compareCents, centsJson (miroir serveur). Branchés sur ItemTile, CasePage, Dashboard, Battle, Prestige shortfall, Admin sorts/inputs. Affichage via les mêmes helpers.


7. Courses sur le solde (TOCTOU)corrigé (A)

spendUserBalance / creditUserBalance / adjustUserBalance dans cents.js : update SQL atomique balance = balance ± x WHERE balance >= cost (INTEGER SQLite) ou CAS string pour montants > Int64. Branché open case, prestige, sell/auto-sell, vault, shop, jackpot.

Même famille restante : double settle vault (login + socket + intervalle) — le crédit est atomique, mais le calcul de ticks peut encore se chevaucher.


8. Auto-sell — clarté & robustessepartiel (A)

Won banner case : toujours wear + valeur dinstance ; si auto-sold, ajoute aussi le payout.

Reste ouvert : B immédiat serveur · C TTL claim · D confirm prestige.


9. Colonnes prestige mortes / non branchéescorrigé (B)

Supprimées du schema + reset server-prestige : prestigeKeyOpenReduction, prestigeStartBonus, prestigeAdBonus, prestigeAdCooldownReduction, prestigeShopDiscount, prestigeStartOsu, prestigeDropValueBonus, prestigeCaseDiscount.


P2 — Sécurité & prod

10. Sessions MemoryStore

Perdues au restart ; incompatible multi-replicas.

| Proposition | A : SQLite/Redis store · B : sticky single node documenté · C : JWT (changement plus large) |

11. CORS trop permissif

index.js autorise toujours localhost + LAN HTTP, même “prod”.

| Proposition | A : en prod, uniquement CLIENT_ORIGIN + CORS_ORIGINS |

12. Rate limiting absent

Login / register / open / ad / shop confirm non limités.

| Proposition | A : express-rate-limit par IP+user · B : captcha register · C : cooldowns serveur déjà partiels (ad) à généraliser |

13. Admin via session.role sans re-check DB

Élévation stale possible.

| Proposition | A : requireAdmin recharge le user · B : claim roleVersion en session |

14. Secrets & config

SESSION_SECRET=dev-secret par défaut ; pas de server/.env.example local (seulement README + .env.prod.example).

| Proposition | A : fail boot si secret faible en prod · B : ajouter .env.example |

15. Mots de passe faibles

Min 6 caractères seulement.

| Proposition | A : min 8 + zxcvbn soft · B : garder simple (jeu privé) mais documenter |


P3 — Qualité code / refactor

16. Duplication jackpot ↔ duelobsolète

duel.js / 1vX / coinflip retirés ; un seul type de battle (services/jackpot.js). Plus de duplication cross-mode à factoriser.

| Ancienne prop. | A : services/battleCommon.js · B : unifier modèle “room” — non pertinent tant quil ny a quun mode |

Si un 2ᵉ mode battle revient plus tard, reprendre A dabord (helpers serialize / finish / auto-sell), B seulement si les rooms divergent vraiment.

17. serializeInv dupliqué

inventory.js / profile.js (et variantes).

| Proposition | A (retenu) : serializeInventoryItem partagé (module commun serveur) |

18. Client — listeners socket.on('connect') jamais retirés

Fichiers : Layout.jsx, BattleHub.jsx, BattleRoomPage.jsx, DropFeed.jsx
(les pages Duel* nexistent plus)

Les useEffect font souvent socket.off des events métier, mais pas du handler connect → fuite au remount / Strict Mode.

| Proposition | A (retenu) : nommer le handler onConnect et socket.off('connect', onConnect) dans chaque cleanup |

19. Battle hub incompletrésolu / simplifié

Hub = un seul mode Battle (jackpot MultiPlayers). 1vX et Coinflip nexistent plus (redirects legacy vers /battle). Plus de cartes stub / copy multi-modes à aligner.


20. Guards auth copiés partout

Chaque page : loading / !user / admin redirect.

| Proposition | A : <RequireAuth> / <RequirePlayer> · B : loader React Router |

21. CasePage — complexité

State machine respin/auto-sell/announce via refs : difficile à maintenir, mais globalement correcte.

| Proposition | A : extraire hook useCaseOpenFlow · B : machine XState · C : laisser tel quel + tests dintégration |

22. Admin UX

  • Inputs prix en cents bruts + Number
  • Pas d’édition packs crédits / ads (seulement osu packs)
  • Adjust player sans confirm
  • Pas de lien “voir le site joueur”

| Proposition | A : champs crédits + confirm adjust · B : admin shop packs crédits |

23. Tests absents

Aucun test automatisé visible sur la logique critique (odds, cents, auto-sell, vault).

| Proposition | A : Vitest unitaire cents / wear / autoSell / jackpot weights · B : smoke API Playwright |

24. Observabilité

console.error local ; pas de métriques / tracing.

| Proposition | A : structured logs (pino) + request id · B : health DB ready |


P4 — Produit / design (nice to have)

# Sujet Proposition
25 Prestige sans confirm Dialog “tu perds inventaire + keys”
26 Catalog conservé au prestige Documenter (déjà le cas) ou option reset
27 Favoris bettables Clarifier UI ou bloquer en battle
28 Feed montre parfois FT Afficher valeur dinstance
29 Lazy routes / code-split React.lazy admin vs player
30 SQLite scale Postgres si charge / multi-instance
31 Achievements / shop ads hardcodés Config admin
32 Animation CaseReel perf Lazy images ; réduire slots si animReduction

Vérification logique (résumé métier)

Système Verdict
Drop case Serveur fait foi ; luck = catalog chapters + Fortune
Wear → prix Correct (FT = ref) ; Polish/Quality biaisent vers le haut
Auto-sell instance < seuil ; Yield après coup ; respin + auto-sell : keepers (≥ seuil) conservés
Keys Progression documentée ; reset keys au prestige joueur, totalOpens conservé
Vault Timer partagé ; risque double settle
Battle / Jackpot Seul mode battle ; poids valeur OK
1vX / Duel / Coinflip Retirés
Prestige joueur Wipe bal/inv/keys ; keep skills/Pr/catalog/opens-by-case
Server prestige Wipe économie globale ; keep account/achievements/stats

Roadmap proposée (ordre dexécution)

Semaine 1 (P0 restant)
  ├─ Gate mock payments en prod
  └─ Plan migration Docker (stop accept-data-loss au boot)

Semaine 2 (P2P3)
  ├─ Rate limit + CORS prod + session store
  ├─ serializeInventoryItem partagé
  ├─ socket.off('connect', onConnect) (Layout, Battle*, DropFeed)
  ├─ Tests unitaires cents / wear / autoSell / jackpot
  └─ Confirms UX (prestige, admin adjust)

Alternatives darchitecture (si le projet grossit)

Option Quand Coût
Rester mono SQLite LAN / petits effectifs Faible — documenter single-node
Postgres + Redis (sessions, presence, timers) Multi-instances / uptime Moyen
Queue économie (BullMQ) Anti race balance/vault Moyen
Séparer worker (spins, vault ticks) Charge Socket.IO Élevé
Stripe live Monétisation réelle Moyen + conformité

Hors scope de cette analyse

  • Audit pentest complet / dépendances CVE détaillées
  • Refonte UI/UX visuelle
  • Réintroduction Coinflip / 1vX (non prévu)
  • Contenu (balancing drops / prix items)

Document de pilotage. Prioriser P0 restant avant tout déploiement Docker “réel”.