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
95 changes: 95 additions & 0 deletions GITHUB_OAUTH_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# GitHub OAuth Setup Guide

## 🚀 Setting up GitHub OAuth for GitHubTracker

To enable "Sign in with GitHub" functionality, you need to create a GitHub OAuth App and configure the environment variables.

### Step 1: Create a GitHub OAuth App

1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Click "New OAuth App"
3. Fill in the following details:
- **Application name**: `GitHubTracker` (or your preferred name)
- **Homepage URL**: `http://localhost:5174` (for development)
- **Authorization callback URL**: `http://localhost:5174/auth/github/callback`
- **Description**: `GitHub activity tracker application`

4. Click "Register application"
5. Copy the **Client ID** and **Client Secret** (you'll need these for the environment variables)

### Step 2: Configure Environment Variables

Create a `.env` file in the **root directory** with:
```
VITE_BACKEND_URL=http://localhost:5000
VITE_GITHUB_CLIENT_ID=your-github-client-id-here
```

Create a `.env` file in the **backend directory** with:
```
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/github_tracker
SESSION_SECRET=your-secret-key-here
GITHUB_CLIENT_ID=your-github-client-id-here
GITHUB_CLIENT_SECRET=your-github-client-secret-here
FRONTEND_URL=http://localhost:5174
```

### Step 3: Install MongoDB (if not already installed)

For the backend to work, you need MongoDB running:

**Windows:**
1. Download MongoDB Community Server from [mongodb.com](https://www.mongodb.com/try/download/community)
2. Install and start the MongoDB service

**Or use Docker:**
```bash
docker run -d -p 27017:27017 --name mongodb mongo:latest
```

### Step 4: Start the Application

1. **Start the backend:**
```bash
cd backend
npm run dev
```

2. **Start the frontend:**
```bash
npm run dev
```

3. **Visit the application:**
- Frontend: http://localhost:5174
- Backend: http://localhost:5000

### Step 5: Test GitHub OAuth

1. Go to http://localhost:5174/login
2. Click "Sign in with GitHub"
3. You should be redirected to GitHub for authorization
4. After authorizing, you'll be redirected back to the application

### Production Deployment

For production, update the GitHub OAuth App settings:
- **Homepage URL**: Your production domain
- **Authorization callback URL**: `https://yourdomain.com/auth/github/callback`

And update the environment variables accordingly.

### Troubleshooting

- **"Invalid client" error**: Check that your GitHub Client ID is correct
- **"Redirect URI mismatch"**: Ensure the callback URL in GitHub matches exactly
- **MongoDB connection errors**: Make sure MongoDB is running
- **CORS errors**: Check that the backend CORS configuration allows your frontend URL

### Security Notes

- Never commit your `.env` files to version control
- Use strong, unique session secrets
- Consider using environment-specific OAuth apps for development vs production
- Regularly rotate your GitHub OAuth app secrets
29 changes: 29 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ const UserSchema = new mongoose.Schema({
type: String,
required: true,
},
// GitHub OAuth fields
githubId: {
type: String,
unique: true,
sparse: true,
},
githubUsername: {
type: String,
unique: true,
sparse: true,
},
avatarUrl: {
type: String,
},
// Timestamps
createdAt: {
type: Date,
default: Date.now,
},
updatedAt: {
type: Date,
default: Date.now,
},
});

UserSchema.pre('save', async function (next) {
Expand All @@ -32,6 +55,12 @@ UserSchema.pre('save', async function (next) {
}
});

// Update the updatedAt field on save
UserSchema.pre('save', function(next) {
this.updatedAt = Date.now();
next();
});

// Compare passwords during login
UserSchema.methods.comparePassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
Expand Down
112 changes: 112 additions & 0 deletions backend/routes/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require("express");
const passport = require("passport");
const User = require("../models/User");
const crypto = require("crypto");
const router = express.Router();

// Signup route
Expand All @@ -27,6 +28,117 @@ router.post("/login", passport.authenticate('local'), (req, res) => {
res.status(200).json( { message: 'Login successful', user: req.user } );
});

// GitHub OAuth callback route
router.post("/github/callback", async (req, res) => {
const { code } = req.body;

if (!code) {
return res.status(400).json({ message: 'Authorization code is required' });
}

try {
// Exchange the authorization code for an access token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(10000), // 10 second timeout
body: JSON.stringify({
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code: code,
redirect_uri: `${process.env.FRONTEND_URL || 'http://localhost:5174'}/auth/github/callback`
}),
});

if (!tokenResponse.ok) {
return res.status(400).json({ message: 'Failed to authenticate with GitHub' });
}

const tokenData = await tokenResponse.json();

if (tokenData.error) {
console.error('GitHub token error:', tokenData.error);
return res.status(400).json({ message: 'Failed to authenticate with GitHub' });
}

const accessToken = tokenData.access_token;

// Get user information from GitHub
const userResponse = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.github.v3+json',
},
signal: AbortSignal.timeout(10000), // 10 second timeout
});

const userData = await userResponse.json();

if (!userResponse.ok) {
return res.status(400).json({ message: 'Failed to get user data from GitHub' });
}

// Get user emails from GitHub
const emailsResponse = await fetch('https://api.github.com/user/emails', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.github.v3+json',
},
signal: AbortSignal.timeout(10000), // 10 second timeout
});

const emailsData = await emailsResponse.json();
const primaryEmail = emailsData.find(email => email.primary)?.email || userData.email;

// Check if user exists, if not create one
let user = await User.findOne({ email: primaryEmail });

if (!user) {
// Create new user with GitHub data
user = new User({
username: userData.login,
email: primaryEmail,
githubId: userData.id.toString(), // Convert to string
githubUsername: userData.login,
avatarUrl: userData.avatar_url,
// Set a cryptographically secure random password
password: crypto.randomBytes(32).toString('hex')
});
await user.save();
} else {
// Update existing user with GitHub info
user.githubId = userData.id.toString(); // Convert to string
user.githubUsername = userData.login;
user.avatarUrl = userData.avatar_url;
await user.save();
}

// Log the user in
req.login(user, (err) => {
if (err) {
return res.status(500).json({ message: 'Login failed', error: err.message });
}
res.status(200).json({
message: 'GitHub authentication successful',
user: {
id: user._id,
username: user.username,
email: user.email,
githubUsername: user.githubUsername,
avatarUrl: user.avatarUrl
}
});
});

} catch (error) {
console.error('GitHub OAuth error:', error);
res.status(500).json({ message: 'GitHub authentication failed', error: error.message });
}
});

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

Expand Down
2 changes: 2 additions & 0 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Contributors from "../pages/Contributors/Contributors"
import Signup from "../pages/Signup/Signup.tsx"
import Login from "../pages/Login/Login.tsx"
import UserProfile from "../pages/UserProfile/UserProfile.tsx"
import GitHubCallback from "../pages/GitHubCallback/GitHubCallback.tsx"



Expand All @@ -15,6 +16,7 @@ const Router = () => {
{/* Redirect from root (/) to the home page */}
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/auth/github/callback" element={<GitHubCallback />} />
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
Expand Down
Loading