# 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é** `reviveMoney` **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 : `` / `` · 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”.*