Эх сурвалжийг харах

Update CORS configuration and remove unused Nginx setup; enhance .gitignore for public directories

loic boulet 3 сар өмнө
parent
commit
7846ea94d1

+ 5 - 0
.gitignore

@@ -1 +1,6 @@
 node_modules
 node_modules
+
+##ignore all public folder and nested public folders
+
+**/public/
+**/build/

+ 4 - 17
docker-compose.yml

@@ -16,6 +16,7 @@ services:
       - NODE_ENV=production
       - NODE_ENV=production
       - PORT=3000
       - PORT=3000
       - DB_PATH=/app/data/framed.db
       - DB_PATH=/app/data/framed.db
+      - CORS_ORIGINS=https://${HOSTNAME:-localhost}
     
     
   # Service pour le frontend (en développement)
   # Service pour le frontend (en développement)
   framed-client-dev:
   framed-client-dev:
@@ -29,7 +30,9 @@ services:
       - ./client:/app
       - ./client:/app
     environment:
     environment:
       - PORT=3001
       - PORT=3001
-      - REACT_APP_API_URL=http://localhost:3000/api
+      ## use hostname env var or localhost
+      - REACT_APP_API_URL=http://${HOSTNAME:-localhost}/api
+      - REACT_APP_WS_URL=ws://${HOSTNAME:-localhost}
     command: sh -c "npm install && npm start"
     command: sh -c "npm install && npm start"
     depends_on:
     depends_on:
       - framed-server
       - framed-server
@@ -42,23 +45,7 @@ services:
     volumes:
     volumes:
       - ./client:/app
       - ./client:/app
       - ./server/public:/output
       - ./server/public:/output
-    environment:
-      - REACT_APP_API_URL=/api
     command: sh -c "npm install && npm run build && cp -r build/* /output/"
     command: sh -c "npm install && npm run build && cp -r build/* /output/"
     profiles:
     profiles:
       - build
       - build
 
 
-  # Service nginx pour servir l'application en production
-  framed-nginx:
-    image: nginx:alpine
-    container_name: framed-tracker-nginx
-    restart: unless-stopped
-    ports:
-      - "80:80"
-    volumes:
-      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
-      - ./server/public:/usr/share/nginx/html
-    depends_on:
-      - framed-server
-    profiles:
-      - prod

+ 0 - 38
nginx/default.conf

@@ -1,38 +0,0 @@
-server {
-    listen 80;
-    server_name localhost;
-
-    root /usr/share/nginx/html;
-    index index.html index.htm;
-
-    # Compression gzip
-    gzip on;
-    gzip_vary on;
-    gzip_min_length 10240;
-    gzip_proxied expired no-cache no-store private auth;
-    gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/xml;
-
-    # Cache des assets statiques
-    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ {
-        expires 1y;
-        add_header Cache-Control "public, max-age=31536000";
-    }
-
-    # Proxy les requêtes API vers le backend
-    location /api/ {
-        proxy_pass http://framed-server:3000/api/;
-        proxy_http_version 1.1;
-        proxy_set_header Upgrade $http_upgrade;
-        proxy_set_header Connection 'upgrade';
-        proxy_set_header Host $host;
-        proxy_cache_bypass $http_upgrade;
-    }
-
-    # Servir le frontend React
-    location / {
-        try_files $uri $uri/ /index.html;
-    }
-
-    # Erreur 404
-    error_page 404 /index.html;
-}

+ 76 - 43
server/server.js

@@ -21,16 +21,16 @@ const clients = new Set();
 wss.on('connection', (ws) => {
 wss.on('connection', (ws) => {
   console.log('Client connected');
   console.log('Client connected');
   clients.add(ws);
   clients.add(ws);
-  
+
   // Send a welcome message
   // Send a welcome message
   ws.send(JSON.stringify({ type: 'connection', message: 'Connected to WebSocket server' }));
   ws.send(JSON.stringify({ type: 'connection', message: 'Connected to WebSocket server' }));
-  
+
   // Handle client disconnection
   // Handle client disconnection
   ws.on('close', () => {
   ws.on('close', () => {
     console.log('Client disconnected');
     console.log('Client disconnected');
     clients.delete(ws);
     clients.delete(ws);
   });
   });
-  
+
   // Handle messages from clients (not used in this implementation, but available for future use)
   // Handle messages from clients (not used in this implementation, but available for future use)
   ws.on('message', (message) => {
   ws.on('message', (message) => {
     console.log('Received message:', message);
     console.log('Received message:', message);
@@ -47,17 +47,50 @@ function broadcastMessage(type, data) {
   });
   });
 }
 }
 
 
+const getAllowedOrigins = () => {
+  // Récupérer les origines depuis les variables d'environnement ou utiliser des valeurs par défaut
+  const corsOrigins = process.env.CORS_ORIGINS || 'http://localhost:3000,http://localhost:3001,http://localhost';
+
+  // Convertir en tableau et ajouter undefined pour les requêtes sans origine
+  const origins = corsOrigins.split(',').map(origin => origin.trim());
+  origins.push(undefined); // Pour les requêtes sans origine (comme curl ou Postman)
+
+  console.log('Origines CORS autorisées:', origins.filter(o => o !== undefined));
+  return origins;
+};
+
+const corsOptions = {
+  origin: function (origin, callback) {
+    const allowedOrigins = getAllowedOrigins();
+
+    if (!origin || allowedOrigins.indexOf(origin) !== -1) {
+      callback(null, true);
+    } else {
+      console.log(`Origine bloquée par CORS: ${origin}`);
+      callback(null, false);
+    }
+  },
+  credentials: true,
+  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
+};
+
+
+// Log CORS options
+app.use((req, res, next) => {
+  console.log(`${new Date().toISOString()} - ${req.method} ${req.url} - Origin: ${req.headers.origin || 'Aucune'}`);
+  next();
+});
+
 // Middleware
 // Middleware
 app.use(express.json());
 app.use(express.json());
-app.use(cors({
-  origin: ['http://localhost:3001', 'http://localhost:3000', 'http://localhost'],
-  credentials: true
-}));
+app.use(cors(corsOptions));
 app.use(express.static(path.join(__dirname, 'public')));
 app.use(express.static(path.join(__dirname, 'public')));
 
 
 // Connexion à la base de données SQLite
 // Connexion à la base de données SQLite
 const DB_PATH = process.env.DB_PATH || '/app/data/framed.db';
 const DB_PATH = process.env.DB_PATH || '/app/data/framed.db';
 console.log(`Tentative de connexion à la BD: ${DB_PATH}`);
 console.log(`Tentative de connexion à la BD: ${DB_PATH}`);
+console.log(`Origines CORS autorisées: ${getAllowedOrigins().filter(o => o !== undefined).join(', ')}`);
 
 
 const db = new sqlite3.Database(DB_PATH, (err) => {
 const db = new sqlite3.Database(DB_PATH, (err) => {
   if (err) {
   if (err) {
@@ -122,26 +155,26 @@ app.post('/api/register', async (req, res) => {
       if (err) {
       if (err) {
         return res.status(500).json({ error: err.message });
         return res.status(500).json({ error: err.message });
       }
       }
-      
+
       if (user) {
       if (user) {
         return res.status(400).json({ error: 'Cet utilisateur existe déjà' });
         return res.status(400).json({ error: 'Cet utilisateur existe déjà' });
       }
       }
-      
+
       // Hasher le mot de passe
       // Hasher le mot de passe
       const hashedPassword = await bcrypt.hash(password, 10);
       const hashedPassword = await bcrypt.hash(password, 10);
-      
+
       // Insérer le nouvel utilisateur
       // Insérer le nouvel utilisateur
-      db.run('INSERT INTO users (username, password) VALUES (?, ?)', 
-        [username, hashedPassword], 
-        function(err) {
+      db.run('INSERT INTO users (username, password) VALUES (?, ?)',
+        [username, hashedPassword],
+        function (err) {
           if (err) {
           if (err) {
             return res.status(500).json({ error: err.message });
             return res.status(500).json({ error: err.message });
           }
           }
-          
-          res.status(201).json({ 
+
+          res.status(201).json({
             message: 'Utilisateur créé avec succès',
             message: 'Utilisateur créé avec succès',
             userId: this.lastID,
             userId: this.lastID,
-            username 
+            username
           });
           });
         }
         }
       );
       );
@@ -163,18 +196,18 @@ app.post('/api/login', (req, res) => {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     if (!user) {
     if (!user) {
       return res.status(401).json({ error: 'Identifiants invalides' });
       return res.status(401).json({ error: 'Identifiants invalides' });
     }
     }
-    
+
     // Vérifier le mot de passe
     // Vérifier le mot de passe
     const validPassword = await bcrypt.compare(password, user.password);
     const validPassword = await bcrypt.compare(password, user.password);
-    
+
     if (!validPassword) {
     if (!validPassword) {
       return res.status(401).json({ error: 'Identifiants invalides' });
       return res.status(401).json({ error: 'Identifiants invalides' });
     }
     }
-    
+
     res.json({
     res.json({
       userId: user.id,
       userId: user.id,
       username: user.username
       username: user.username
@@ -194,11 +227,11 @@ app.post('/api/scores', (req, res) => {
   db.run(
   db.run(
     'INSERT INTO scores (user_id, date, game_type, game_number, score, comment) VALUES (?, ?, ?, ?, ?, ?)',
     'INSERT INTO scores (user_id, date, game_type, game_number, score, comment) VALUES (?, ?, ?, ?, ?, ?)',
     [userId, date, gameType, gameNumber, score, comment || null],
     [userId, date, gameType, gameNumber, score, comment || null],
-    function(err) {
+    function (err) {
       if (err) {
       if (err) {
         return res.status(500).json({ error: err.message });
         return res.status(500).json({ error: err.message });
       }
       }
-      
+
       const newScore = {
       const newScore = {
         id: this.lastID,
         id: this.lastID,
         userId,
         userId,
@@ -208,17 +241,17 @@ app.post('/api/scores', (req, res) => {
         score,
         score,
         comment
         comment
       };
       };
-      
+
       // Get username for the new score
       // Get username for the new score
       db.get('SELECT username FROM users WHERE id = ?', [userId], (err, user) => {
       db.get('SELECT username FROM users WHERE id = ?', [userId], (err, user) => {
         if (!err && user) {
         if (!err && user) {
           newScore.username = user.username;
           newScore.username = user.username;
-          
+
           // Broadcast the new score to all connected clients
           // Broadcast the new score to all connected clients
           broadcastMessage('new-score', newScore);
           broadcastMessage('new-score', newScore);
         }
         }
       });
       });
-      
+
       res.status(201).json(newScore);
       res.status(201).json(newScore);
     }
     }
   );
   );
@@ -232,7 +265,7 @@ app.get('/api/scores/user/:userId', (req, res) => {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     res.json(scores);
     res.json(scores);
   });
   });
 });
 });
@@ -240,22 +273,22 @@ app.get('/api/scores/user/:userId', (req, res) => {
 // Récupérer tous les scores (pour le classement)
 // Récupérer tous les scores (pour le classement)
 app.get('/api/scores', (req, res) => {
 app.get('/api/scores', (req, res) => {
   const gameType = req.query.gameType || 'all';
   const gameType = req.query.gameType || 'all';
-  
+
   let query = `
   let query = `
     SELECT s.*, u.username 
     SELECT s.*, u.username 
     FROM scores s
     FROM scores s
     JOIN users u ON s.user_id = u.id
     JOIN users u ON s.user_id = u.id
   `;
   `;
-  
+
   if (gameType !== 'all') {
   if (gameType !== 'all') {
     query += ` WHERE s.game_type = '${gameType}'`;
     query += ` WHERE s.game_type = '${gameType}'`;
   }
   }
-  
+
   db.all(query, (err, scores) => {
   db.all(query, (err, scores) => {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     res.json(scores);
     res.json(scores);
   });
   });
 });
 });
@@ -269,18 +302,18 @@ app.delete('/api/scores/:id', (req, res) => {
     return res.status(400).json({ error: 'Utilisateur non spécifié' });
     return res.status(400).json({ error: 'Utilisateur non spécifié' });
   }
   }
 
 
-  db.run('DELETE FROM scores WHERE id = ? AND user_id = ?', [scoreId, userId], function(err) {
+  db.run('DELETE FROM scores WHERE id = ? AND user_id = ?', [scoreId, userId], function (err) {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     if (this.changes === 0) {
     if (this.changes === 0) {
       return res.status(404).json({ error: 'Score non trouvé ou non autorisé' });
       return res.status(404).json({ error: 'Score non trouvé ou non autorisé' });
     }
     }
-    
+
     // Broadcast the deleted score to all connected clients
     // Broadcast the deleted score to all connected clients
     broadcastMessage('delete-score', { id: scoreId, userId });
     broadcastMessage('delete-score', { id: scoreId, userId });
-    
+
     res.json({ message: 'Score supprimé avec succès' });
     res.json({ message: 'Score supprimé avec succès' });
   });
   });
 });
 });
@@ -288,7 +321,7 @@ app.delete('/api/scores/:id', (req, res) => {
 // Récupérer les statistiques d'un utilisateur
 // Récupérer les statistiques d'un utilisateur
 app.get('/api/stats/:userId', (req, res) => {
 app.get('/api/stats/:userId', (req, res) => {
   const userId = req.params.userId;
   const userId = req.params.userId;
-  
+
   const query = `
   const query = `
     SELECT 
     SELECT 
       game_type,
       game_type,
@@ -299,12 +332,12 @@ app.get('/api/stats/:userId', (req, res) => {
     WHERE user_id = ?
     WHERE user_id = ?
     GROUP BY game_type
     GROUP BY game_type
   `;
   `;
-  
+
   db.all(query, [userId], (err, stats) => {
   db.all(query, [userId], (err, stats) => {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     res.json(stats);
     res.json(stats);
   });
   });
 });
 });
@@ -312,7 +345,7 @@ app.get('/api/stats/:userId', (req, res) => {
 // Récupérer le classement
 // Récupérer le classement
 app.get('/api/leaderboard', (req, res) => {
 app.get('/api/leaderboard', (req, res) => {
   const gameType = req.query.gameType || 'all';
   const gameType = req.query.gameType || 'all';
-  
+
   let query = `
   let query = `
     SELECT 
     SELECT 
       u.id,
       u.id,
@@ -323,21 +356,21 @@ app.get('/api/leaderboard', (req, res) => {
     FROM scores s
     FROM scores s
     JOIN users u ON s.user_id = u.id
     JOIN users u ON s.user_id = u.id
   `;
   `;
-  
+
   if (gameType !== 'all') {
   if (gameType !== 'all') {
     query += ` WHERE s.game_type = '${gameType}'`;
     query += ` WHERE s.game_type = '${gameType}'`;
   }
   }
-  
+
   query += `
   query += `
     GROUP BY u.id
     GROUP BY u.id
     ORDER BY average_score ASC, best_score ASC
     ORDER BY average_score ASC, best_score ASC
   `;
   `;
-  
+
   db.all(query, (err, leaderboard) => {
   db.all(query, (err, leaderboard) => {
     if (err) {
     if (err) {
       return res.status(500).json({ error: err.message });
       return res.status(500).json({ error: err.message });
     }
     }
-    
+
     res.json(leaderboard);
     res.json(leaderboard);
   });
   });
 });
 });
@@ -346,7 +379,7 @@ app.get('/api/leaderboard', (req, res) => {
 if (process.env.NODE_ENV === 'production') {
 if (process.env.NODE_ENV === 'production') {
   // En production, servir les fichiers statiques
   // En production, servir les fichiers statiques
   app.use(express.static(path.join(__dirname, 'public')));
   app.use(express.static(path.join(__dirname, 'public')));
-  
+
   // Route par défaut pour servir l'application React
   // Route par défaut pour servir l'application React
   app.get('*', (req, res) => {
   app.get('*', (req, res) => {
     res.sendFile(path.join(__dirname, 'public', 'index.html'));
     res.sendFile(path.join(__dirname, 'public', 'index.html'));