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
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 🌟 **GitHub Tracker** 🌟

<!-- top -->

**Track Activity of Users on GitHub**
Expand Down Expand Up @@ -41,23 +42,28 @@ Welcome to **GitHub Tracker**, a web app designed to help you monitor and analyz
---

## 🚀 Setup Guide

1. Clone the repository to your local machine:

```bash
$ git clone https://github.com/yourusername/github-tracker.git
```

2. Navigate to the project directory:

```bash
$ cd github-tracker
```

3. Run the frontend

```bash
$ npm i
$ npm run dev
```

4. Run the backend

```bash
$ npm i
$ npm start
Expand All @@ -66,22 +72,27 @@ $ npm start
## 🧪 Backend Unit & Integration Testing with Jasmine

This project uses the Jasmine framework for backend unit and integration tests. The tests cover:

- User model (password hashing, schema, password comparison)
- Authentication routes (signup, login, logout)
- Passport authentication logic (via integration tests)

### Prerequisites

- **Node.js** and **npm** installed
- **MongoDB** running locally (default: `mongodb://127.0.0.1:27017`)

### Installation

Install all required dependencies:

```sh
npm install
npm install --save-dev jasmine @types/jasmine supertest express-session passport passport-local bcryptjs
```

### Running the Tests

1. **Start MongoDB** (if not already running):
```sh
mongod
Expand All @@ -92,24 +103,26 @@ npm install --save-dev jasmine @types/jasmine supertest express-session passport
```

### Test Files

- `spec/user.model.spec.cjs` — Unit tests for the User model
- `spec/auth.routes.spec.cjs` — Integration tests for authentication routes

### Jasmine Configuration

The Jasmine config (`spec/support/jasmine.mjs`) is set to recognize `.cjs`, `.js`, and `.mjs` test files:

```js
spec_files: [
"**/*[sS]pec.?(m)js",
"**/*[sS]pec.cjs"
]
spec_files: ["**/*[sS]pec.?(m)js", "**/*[sS]pec.cjs"];
```

### Troubleshooting

- **No specs found:** Ensure your test files have the correct extension and are in the `spec/` directory.
- **MongoDB connection errors:** Make sure MongoDB is running and accessible.
- **Missing modules:** Install any missing dev dependencies with `npm install --save-dev <module>`.

### What Was Covered

- Jasmine is set up and configured for backend testing.
- All major backend modules are covered by unit/integration tests.
- Tests are passing and verified.
Expand All @@ -126,13 +139,11 @@ spec_files: [
- Make sure you show some love by giving ⭐ to our repository.

<div align="center">
<a href="https://github.com/mehul-m-prajapati/github_tracker">
<img src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker&&max=1000" />
</a>
<a href="https://github.com/GitMetricsLab/github_tracker/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker" />
</a>
Comment on lines +142 to +144
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 | 🟡 Minor | ⚡ Quick win

Add alt text to the contributors image.

The image is missing alt text (MD045), which is an accessibility gap in documentation.

Proposed fix
-  <img src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker" />
+  <img
+    src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker"
+    alt="Contributors to GitMetricsLab/github_tracker"
+  />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a href="https://github.com/GitMetricsLab/github_tracker/graphs/contributors">
<img src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker" />
</a>
<a href="https://github.com/GitMetricsLab/github_tracker/graphs/contributors">
<img
src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker"
alt="Contributors to GitMetricsLab/github_tracker"
/>
</a>
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 143-143: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 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 `@README.md` around lines 142 - 144, The contributors image in the README (<img
src="https://contrib.rocks/image?repo=GitMetricsLab/github_tracker" />) lacks
alt text; update the <img> tag to include a meaningful alt attribute (e.g.,
alt="Contributors to GitMetricsLab/github_tracker") so the image is accessible
and fixes MD045.

</div>



---

<p align="center">
Expand Down
9 changes: 9 additions & 0 deletions backend/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Middleware to check if user is authenticated
const isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ message: 'Unauthorized - Please log in' });
};

module.exports = { isAuthenticated };
67 changes: 46 additions & 21 deletions backend/routes/auth.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,67 @@
const express = require("express");
const passport = require("passport");
const User = require("../models/User");
const { isAuthenticated } = require("../middleware/authMiddleware"); // I'm calling the Middleware I just created here
const router = express.Router();

// Signup route
router.post("/signup", async (req, res) => {
const { username, email, password } = req.body;

const { username, email, password } = req.body;
try {
const existingUser = await User.findOne({ email });

try {
const existingUser = await User.findOne( {email} );
if (existingUser)
return res.status(400).json({ message: "User already exists" });

if (existingUser)
return res.status(400).json( {message: 'User already exists'} );
const newUser = new User({ username, email, password });
await newUser.save();
res.status(201).json({ message: "User created successfully" });
} catch (err) {
res
.status(500)
.json({ message: "Error creating user", error: err.message });
}
Comment on lines +21 to +24
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

Avoid returning raw internal error messages to clients.

These responses expose err.message, which can leak backend internals. Return a generic client message and log details server-side.

Suggested change
-    res
-      .status(500)
-      .json({ message: "Error creating user", error: err.message });
+    console.error("Signup error:", err);
+    res.status(500).json({ message: "Error creating user" });
-      return res
-        .status(500)
-        .json({ message: "Login error", error: err.message });
+      console.error("Login error:", err);
+      return res.status(500).json({ message: "Login error" });
-        return res
-          .status(500)
-          .json({ message: "Session error", error: err.message });
+        console.error("Session error:", err);
+        return res.status(500).json({ message: "Session error" });
-      return res
-        .status(500)
-        .json({ message: "Logout failed", error: err.message });
+      console.error("Logout error:", err);
+      return res.status(500).json({ message: "Logout failed" });

Also applies to: 31-34, 42-45, 60-63

🤖 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/routes/auth.js` around lines 21 - 24, Replace all direct exposure of
err.message in backend/routes/auth.js (the res.status(500).json(...) calls that
currently include err.message) with a generic client-safe message like "Internal
server error" and move the detailed error into a server-side log (e.g.,
console.error(err) or your app logger) immediately before sending the response;
update each occurrence (the res.status(500).json(...) blocks at the shown
locations and the similar blocks around lines 31-34, 42-45, 60-63) so clients
never receive internal error text while full error details remain logged for
debugging.

});

const newUser = new User( {username, email, password} );
await newUser.save();
res.status(201).json( {message: 'User created successfully'} );
} catch (err) {
res.status(500).json({ message: 'Error creating user', error: err.message });
// I'm writing a much more complex login route here because I want to handle all the possible errors that can happen during the login process, and I also want to return the user data if the login is successful. This way, the frontend can easily access the user information after logging in without needing to make an additional request to get the current user.
router.post("/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) {
return res
.status(500)
.json({ message: "Login error", error: err.message });
}
if (!user) {
return res
.status(401)
.json({ message: info.message || "Invalid credentials" });
}
req.logIn(user, (err) => {
if (err) {
return res
.status(500)
.json({ message: "Session error", error: err.message });
}
res.status(200).json({ message: "Login successful", user: req.user });
});
})(req, res, next);
});

// Login route
router.post("/login", passport.authenticate('local'), (req, res) => {
res.status(200).json( { message: 'Login successful', user: req.user } );
// Get current authenticated user
router.get("/me", isAuthenticated, (req, res) => {
res.status(200).json({ user: req.user });
});

// Logout route
router.get("/logout", (req, res) => {

req.logout((err) => {

if (err)
return res.status(500).json({ message: 'Logout failed', error: err.message });
else
res.status(200).json({ message: 'Logged out successfully' });
});
req.logout((err) => {
if (err)
return res
.status(500)
.json({ message: "Logout failed", error: err.message });
else res.status(200).json({ message: "Logged out successfully" });
});
Comment on lines 57 to +64
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

Use POST /logout instead of GET /logout.

Logout mutates server session state and should not be a GET endpoint. Keeping it as GET makes cross-site triggering easier and weakens CSRF posture.

Suggested change
-router.get("/logout", (req, res) => {
+router.post("/logout", (req, res) => {
   req.logout((err) => {
🤖 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/routes/auth.js` around lines 57 - 64, Change the logout route from a
GET to a POST to avoid state-changing operations over GET: replace
router.get("/logout", ...) with router.post("/logout", ...) and keep the
existing req.logout callback logic (the handler that checks err and returns 500
or 200). Also update any client-side code or tests that call GET /logout to POST
/logout and ensure any CSRF middleware or authentication checks that apply to
POST are applied to this route (refer to the router.post("/logout") handler and
the req.logout callback).

});

module.exports = router;
50 changes: 31 additions & 19 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
const bodyParser = require('body-parser');
require('dotenv').config();
const cors = require('cors');
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("passport");
const bodyParser = require("body-parser");
require("dotenv").config();
const cors = require("cors");

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

const app = express();

// CORS configuration
app.use(cors('*'));
//Ok so this needs to be taken care of, Cannot expose the BE like that, only FE can call it, else you'll face issues
// For testing, if anyone else uses any other port, pls change the default vite port to that port, and also change the FRONTEND_URL in .env to that port, else you'll face CORS issues, and you won't be able to call the BE from the FE.
app.use(
cors({
origin: process.env.FRONTEND_URL || "http://localhost:5173",
credentials: true,
}),
);

// Middleware
app.use(bodyParser.json());
app.use(session({
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
}),
Comment on lines +27 to +31
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

Harden session configuration for authenticated deployments.

Add explicit cookie security settings and fail fast when SESSION_SECRET is missing; current config is too permissive for a security-focused auth flow.

Suggested change
+if (!process.env.SESSION_SECRET) {
+  throw new Error("SESSION_SECRET is required");
+}
+
 app.use(
   session({
     secret: process.env.SESSION_SECRET,
     resave: false,
     saveUninitialized: false,
+    cookie: {
+      httpOnly: true,
+      sameSite: "lax",
+      secure: process.env.NODE_ENV === "production",
+    },
   }),
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
}),
if (!process.env.SESSION_SECRET) {
throw new Error("SESSION_SECRET is required");
}
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "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/server.js` around lines 27 - 31, The session middleware is too
permissive: ensure process.env.SESSION_SECRET is required (throw or exit early
if missing) and harden the session cookie in the session({...}) config by adding
an explicit cookie object with secure: true (only in production), httpOnly:
true, sameSite: 'lax' or 'strict' as appropriate, and a sensible maxAge; also
set rolling/session expiration behavior if needed (e.g., rolling: true or
cookie.maxAge) and keep resave/saveUninitialized as false. Update the code
around session(...) and the SESSION_SECRET check so the app fails fast when
SESSION_SECRET is absent and the session() call includes the cookie security
options.

);
app.use(passport.initialize());
app.use(passport.session());

// Routes
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
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');
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}`);
console.log(`Server running on port ${process.env.PORT}`);
});
}).catch((err) => {
console.log('MongoDB connection error:', err);
});
})
.catch((err) => {
console.log("MongoDB connection error:", err);
});
10 changes: 9 additions & 1 deletion src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ import Signup from "../pages/Signup/Signup.tsx";
import Login from "../pages/Login/Login.tsx";
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
import Home from "../pages/Home/Home.tsx";
import ProtectedRoute from "../components/ProtectedRoute";

const Router = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/track" element={<Tracker />} />
<Route
path="/track"
element={
<ProtectedRoute>
<Tracker />
</ProtectedRoute>
}
/>
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/about" element={<About />} />
Expand Down
Loading
Loading