10 KiB
CaseOrion — Analyse & améliorations
Date : 2026-07-25
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 sont : odds duel cassées, paiements mock en prod, db push --accept-data-loss au boot, comparaisons argent via Number(), 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 |
| Jackpot / Battle | Agrégation BigInt correcte |
| Duels 1vX | Supprimé (2026-07-25) |
| 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 redirigent vers /battle.
1. Paiements mock utilisables en production
Fichiers : server/src/payments.js, routes/shop.js, .env.prod.example
PAYMENTS_PROVIDER=mock + POST /api/shop/osu/confirm crédite l’osu sans paiement.
| Proposition | Détail |
|---|---|
| A | Refuser mock si NODE_ENV=production sauf ALLOW_MOCK_PAYMENTS=1 |
| B | Brancher Stripe (webhook raw body + signature) |
| C | Désactiver toute la section osu en prod tant que non prêt |
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 | Détail |
|---|---|
| A (recommandé) | Prisma Migrate (migrate deploy) ; push manuel hors boot |
| B | Garder 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 → Number — corrigé
db.js coerce tous les BigInt → NumberreviveMoney supprimé. Choix retenu : argent en strings + toCentsBigInt (pas de plafond Number), BigInt Prisma éventuels laissés intacts jusqu’au 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é & robustesse — partiel (A)
Won banner case : toujours wear + valeur d’instance ; 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ées — corrigé (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 ↔ duel
Serialize, finish spin, auto-sell winner : quasi copy-paste.
| Proposition | A : module services/battleCommon.js · B : unifier modèle “room” (plus gros refactor) |
17. serializeInv dupliqué
inventory.js / profile.js (et variantes).
| Proposition | A : serializeInventoryItem partagé |
18. Client — listeners socket.on('connect') jamais retirés
Layout, BattleHub, BattleRoom, Duel*, DropFeed → fuite de handlers au remount.
| Proposition | A : cleanup socket.off dans chaque useEffect |
19. Battle hub incomplet
Routes 1vX existent ; hub n’expose que MultiPlayers + Coinflip stub. Copy “MultiPlayers” / redirects legacy.
| Proposition | A : cartes hub 1vX + Coinflip disabled clair · B : retirer Coinflip jusqu’à implémentation |
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 d’inté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 / duel 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 d’instance |
| 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 ; pas de bug “35T vendu sous 15T” si prestige max + vrai 35T instance |
| Keys | Progression documentée ; keyOpenReduction non branché |
| Vault | Timer partagé ; risque double settle |
| Jackpot | Poids valeur OK |
| Duel | Poids multi-items KO |
| Prestige joueur | Wipe bal/inv/keys ; keep skills/Pr/catalog |
| Server prestige | Wipe économie globale ; keep account/achievements/stats |
Roadmap proposée (ordre d’exécution)
Semaine 1 (P0)
├─ Fix duel toCentsBigInt + test
├─ Gate mock payments en prod
├─ Retirer reviveMoney Number(bigint)
├─ Allowlist uploads (no SVG)
└─ Plan migration Docker (stop accept-data-loss au boot)
Semaine 2 (P1)
├─ BigInt-safe client (ItemTile, sorts, prestige shortfall, admin inputs)
├─ Balance updates atomiques + vault settle idempotent
└─ Brancher ou supprimer prestige fields morts
Semaine 3 (P2–P3)
├─ Rate limit + CORS prod + session store
├─ Refactor battle common + socket cleanup
├─ Hub 1vX + tests unitaires cents/odds
└─ Confirms UX (prestige, admin adjust)
Alternatives d’architecture (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
- Implémentation Coinflip
- Contenu (balancing drops / prix items)
Document généré pour pilotage : aucune modification code associée. Prioriser P0 avant tout déploiement Docker “réel”.