diff --git a/backend/.env.sample b/backend/.env.sample index 98f9688..526da94 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -1,3 +1,5 @@ PORT=5000 MONGO_URI=mongodb://localhost:27017/githubTracker SESSION_SECRET=your-secret-key +# CLIENT_URL=http://localhost:5173 +CLIENT_URL=https://your-frontend-domain.com \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 38e15b8..d7b0bdd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,6 @@ "dev": "nodemon server.js", "start": "node server.js", "test": "jasmine spec/**/*.spec.cjs" - }, "keywords": [], "author": "", @@ -15,10 +14,12 @@ "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", - "cors": "^2.8.5", + "cors": "^2.8.6", "dotenv": "^16.4.5", "express": "^4.21.1", + "express-rate-limit": "^8.5.2", "express-session": "^1.18.1", + "helmet": "^8.1.0", "mongoose": "^8.8.2", "passport": "^0.7.0", "passport-local": "^1.0.0", @@ -27,4 +28,4 @@ "devDependencies": { "nodemon": "^3.1.9" } -} +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 3f19f00..8e9f036 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,35 +5,143 @@ const passport = require('passport'); const bodyParser = require('body-parser'); require('dotenv').config(); const cors = require('cors'); +const helmet = require('helmet'); +const rateLimit = require('express-rate-limit'); // Passport configuration require('./config/passportConfig'); const app = express(); -// CORS configuration -app.use(cors('*')); +const isProduction = process.env.NODE_ENV === 'production'; + +// Trust proxy for production deployments +if (isProduction) { + app.set('trust proxy', 1); +} + +/* ========================= + Security Middleware +========================= */ + +// Helmet security headers +app.use(helmet()); + +// Rate Limiter +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 mins + max: 100, + message: 'Too many requests from this IP, please try again later.', + standardHeaders: true, + legacyHeaders: false, +}); + +app.use('/api', limiter); + +/* ========================= + CORS Configuration +========================= */ + +const rawAllowedOrigins = + process.env.ALLOWED_ORIGINS || + process.env.CLIENT_URL || + 'http://localhost:5173'; + +const allowedOrigins = rawAllowedOrigins + .split(',') + .map(origin => origin.trim()); + +app.use(cors({ + origin: (origin, callback) => { + // Allow server-to-server requests + if (!origin) { + return callback(null, true); + } + + if (allowedOrigins.includes(origin)) { + return callback(null, true); + } + + return callback(new Error('Not allowed by CORS')); + }, + credentials: true, +})); + +/* ========================= + Body Parser +========================= */ -// Middleware app.use(bodyParser.json()); + +/* ========================= + Session Configuration +========================= */ + +const sessionSecret = process.env.SESSION_SECRET; + +if (isProduction && !sessionSecret) { + throw new Error('SESSION_SECRET is required in production'); +} + +if (!isProduction && !sessionSecret) { + console.warn('⚠ SESSION_SECRET is missing in .env'); +} + app.use(session({ - secret: process.env.SESSION_SECRET, + secret: sessionSecret || 'dev-secret', resave: false, saveUninitialized: false, + cookie: { + httpOnly: true, + secure: isProduction, + sameSite: 'lax', + maxAge: 1000 * 60 * 60 * 24, // 1 day + }, })); + +/* ========================= + Passport Middleware +========================= */ + app.use(passport.initialize()); app.use(passport.session()); -// Routes +/* ========================= + Routes +========================= */ + const authRoutes = require('./routes/auth'); + app.use('/api/auth', authRoutes); -// Connect to MongoDB -mongoose.connect(process.env.MONGO_URI, {}).then(() => { - console.log('Connected to MongoDB'); - app.listen(process.env.PORT, () => { - console.log(`Server running on port ${process.env.PORT}`); - }); -}).catch((err) => { - console.log('MongoDB connection error:', err); -}); +/* ========================= + MongoDB Connection +========================= */ + +const PORT = process.env.PORT || 5000; + +const MONGO_URI = + process.env.MONGO_URI || + (!isProduction + ? 'mongodb://127.0.0.1:27017/githubTracker' + : undefined); + +if (isProduction && !process.env.MONGO_URI) { + throw new Error('MONGO_URI is required in production'); +} + +if (!isProduction && !process.env.MONGO_URI) { + console.warn('⚠ MONGO_URI missing in .env'); +} + +mongoose.connect(MONGO_URI) + .then(() => { + console.log('✅ Connected to MongoDB'); + + app.listen(PORT, () => { + console.log(`🚀 Server running on port ${PORT}`); + }); + }) + .catch((err) => { + console.error('❌ MongoDB connection error:', err); + }); \ No newline at end of file