Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# URL of the backend API (no trailing slash).
# Must match the origin the backend server listens on.
VITE_BACKEND_URL=http://localhost:5000
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,35 @@ $ git clone https://github.com/yourusername/github-tracker.git
$ cd github-tracker
```

3. Run the frontend
3. Configure environment variables

Copy the example files and fill in your values:
```bash
# Frontend (.env in the repo root)
cp .env.example .env

# Backend (.env inside backend/)
cp backend/.env.example backend/.env
```

Key variables to set:

| Variable | Where | Description |
|---|---|---|
| `VITE_BACKEND_URL` | root `.env` | URL of the backend (default: `http://localhost:5000`) |
| `MONGO_URI` | `backend/.env` | MongoDB connection string |
| `SESSION_SECRET` | `backend/.env` | Long random string used to sign session cookies |
| `FRONTEND_ORIGIN` | `backend/.env` | URL of the frontend — restricts CORS. **Required in production.** Defaults to `http://localhost:5173` in development. |

4. Run the frontend
```bash
$ npm i
$ npm run dev
```

4. Run the backend
5. Run the backend
```bash
$ cd backend
$ npm i
$ npm start
```
Expand Down
37 changes: 37 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ---------------------------------------------------------------
# Server
# ---------------------------------------------------------------
PORT=5000
NODE_ENV=development

# ---------------------------------------------------------------
# MongoDB
# ---------------------------------------------------------------
MONGO_URI=mongodb://127.0.0.1:27017/github_tracker

# ---------------------------------------------------------------
# Session
# Generate a long random string for production, e.g.:
# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# ---------------------------------------------------------------
SESSION_SECRET=replace-with-a-long-random-string

# ---------------------------------------------------------------
# CORS — Frontend origin allowlist
#
# Set this to the exact URL of your frontend (no trailing slash).
# REQUIRED in production: the server will refuse to start without it.
# In development, the server defaults to http://localhost:5173 when
# this variable is not set.
#
# Examples:
# Development : FRONTEND_ORIGIN=http://localhost:5173
# Production : FRONTEND_ORIGIN=https://app.example.com
# ---------------------------------------------------------------
FRONTEND_ORIGIN=http://localhost:5173

# ---------------------------------------------------------------
# Logging
# Accepted values: error | warn | info | debug
# ---------------------------------------------------------------
LOG_LEVEL=debug
15 changes: 15 additions & 0 deletions backend/config/validateEnv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Validates required environment variables before the server starts.
* Throws so callers can decide whether to log + exit or handle otherwise,
* which keeps the logic unit-testable without spawning child processes.
*/
function validateEnv() {
if (process.env.NODE_ENV === 'production' && !process.env.FRONTEND_ORIGIN) {
throw new Error(
'FRONTEND_ORIGIN environment variable is required in production. ' +
'Set it to the URL of your frontend (e.g., https://app.example.com).'
);
}
}
Comment on lines +6 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require SESSION_SECRET validation in production.

validateEnv() currently checks only FRONTEND_ORIGIN, so production can still start with an empty or placeholder session secret, which weakens session integrity.

🔧 Suggested patch
 function validateEnv() {
-  if (process.env.NODE_ENV === 'production' && !process.env.FRONTEND_ORIGIN) {
+  const isProduction = process.env.NODE_ENV === 'production';
+  const frontendOrigin = process.env.FRONTEND_ORIGIN?.trim();
+  const sessionSecret = process.env.SESSION_SECRET?.trim();
+
+  if (isProduction && !frontendOrigin) {
     throw new Error(
       'FRONTEND_ORIGIN environment variable is required in production. ' +
       'Set it to the URL of your frontend (e.g., https://app.example.com).'
     );
   }
+
+  if (isProduction && (!sessionSecret || sessionSecret === 'replace-with-a-long-random-string')) {
+    throw new Error(
+      'SESSION_SECRET must be set to a strong random value in production.'
+    );
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/config/validateEnv.js` around lines 6 - 13, The validateEnv function
only enforces FRONTEND_ORIGIN in production but must also ensure a strong
session secret; update validateEnv() to check process.env.SESSION_SECRET when
NODE_ENV === 'production' and throw a descriptive Error if it's missing or
clearly insecure (e.g., empty, placeholder like "changeme", or shorter than a
minimum length such as 32 chars). Reference the validateEnv function and add the
SESSION_SECRET checks and error message so production cannot start without a
valid SESSION_SECRET.


module.exports = { validateEnv };
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"zod": "^4.4.3"
},
"devDependencies": {
"nodemon": "^3.1.9"
"jasmine": "^5.0.0",
"nodemon": "^3.1.9",
"supertest": "^7.0.0"
}
}
64 changes: 52 additions & 12 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,62 @@ const bodyParser = require('body-parser');
require('dotenv').config();
const cors = require('cors');

const { validateEnv } = require('./config/validateEnv');
const logger = require('./logger');

// Fail fast in production when required env vars are absent.
try {
validateEnv();
} catch (err) {
logger.error(`[FATAL] ${err.message}`);
process.exit(1);
}

// Passport configuration
require('./config/passportConfig');

const logger = require('./logger');

const app = express();

// CORS configuration
app.use(cors('*'));
// In development, fall back to localhost:5173 if FRONTEND_ORIGIN is not set so
// that contributors can run the stack without a full .env file.
const corsOrigin = process.env.FRONTEND_ORIGIN || 'http://localhost:5173';

if (!process.env.FRONTEND_ORIGIN) {
logger.warn(
'FRONTEND_ORIGIN is not set; defaulting to http://localhost:5173. ' +
'Set this variable in production.'
);
}

// CORS — explicit allowlist with credentials support.
// A function-based origin is required so that the header is only set (and
// reflected) for allowed origins; a static string would send the header on
// every response regardless of the requesting origin.
app.use(cors({
origin: (requestOrigin, callback) => {
// Allow same-origin requests (no Origin header) and the configured origin.
if (!requestOrigin || requestOrigin === corsOrigin) {
return callback(null, true);
}
callback(null, false);
},
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type'],
}));

// Middleware
app.use(bodyParser.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
// Only transmit the cookie over HTTPS in production.
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
Comment on lines +59 to +63
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether secure cookies are enabled without trust proxy in backend/server.js
rg -n -C2 "secure:\s*process\.env\.NODE_ENV === 'production'|app\.set\(\s*['\"]trust proxy['\"]" backend/server.js

Repository: GitMetricsLab/github_tracker

Length of output: 245


Configure Express trust proxy for production secure cookies

backend/server.js enables secure via secure: process.env.NODE_ENV === 'production', but it doesn’t set app.set('trust proxy', ...) in this file. Behind a TLS-terminating proxy, Express must trust the proxy for secure-cookie handling.

🔧 Suggested patch
 const app = express();
+
+if (process.env.NODE_ENV === 'production') {
+  app.set('trust proxy', 1);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/server.js` around lines 59 - 63, The cookie config sets secure:
process.env.NODE_ENV === 'production' but Express is not configured to trust a
TLS-terminating proxy, so secure cookies will not be sent; add app.set('trust
proxy', true) (or a more specific trust value) in backend/server.js during app
initialization (before middleware that sets cookies/session) so Express will
honor the secure flag behind proxies; update any related comments and ensure
this runs only in environments where a proxy is present (e.g., conditionally
when NODE_ENV === 'production' or when an env var like TRUST_PROXY is set).

},
}));
app.use(passport.initialize());
app.use(passport.session());
Expand All @@ -32,10 +72,10 @@ app.use('/api/auth', authRoutes);

// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI, {}).then(() => {
logger.info('Connected to MongoDB');
app.listen(process.env.PORT, () => {
logger.info(`Server running on port ${process.env.PORT}`);
});
logger.info('Connected to MongoDB');
app.listen(process.env.PORT, () => {
logger.info(`Server running on port ${process.env.PORT}`);
});
}).catch((err) => {
logger.error('MongoDB connection error', err);
logger.error('MongoDB connection error', err);
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

"autoprefixer": "^10.4.20",
"bcryptjs": "^3.0.3",
"cors": "^2.8.5",

"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
Expand Down
Loading
Loading