Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c02792f
Add GitHub OAuth authentication
snigdhachoudhury Sep 20, 2025
53a09dc
Fix GitHub auth redirect and improve activity heatmap display
snigdhachoudhury Sep 21, 2025
39f8be5
Separate GitHub authentication from dashboard data sync
snigdhachoudhury Sep 21, 2025
01c9a3e
Fix GitHub sync functionality to handle disabled backend route
snigdhachoudhury Sep 27, 2025
f3189ff
Remove GitHub data syncing features, keep only authentication functio…
snigdhachoudhury Sep 27, 2025
8d857dc
heatmap
snigdhachoudhury Sep 27, 2025
ec74a1c
Resolve merge conflicts in server.js
snigdhachoudhury Sep 27, 2025
64e0521
Merge branch 'main' into main
snigdhachoudhury Sep 27, 2025
11b8967
Update Dashboard.jsx to include ActivityHeatmap component
snigdhachoudhury Sep 27, 2025
f4b4ca5
Merge branch 'main' of https://github.com/snigdhachoudhury/DevSync
snigdhachoudhury Sep 27, 2025
6323aa5
Merge branch 'main' of https://github.com/DevSyncx/DevSync into upstr…
snigdhachoudhury Oct 3, 2025
047091b
Fix UI issues: Remove duplicate ProfileCard component and update Prof…
snigdhachoudhury Oct 4, 2025
4d22836
Fix Dashboard layout to match the repository owner's design while pre…
snigdhachoudhury Oct 4, 2025
d9dff96
Merge remote-tracking branch 'upstream/main' into integration-branch
snigdhachoudhury Oct 4, 2025
cdc4034
final
snigdhachoudhury Oct 4, 2025
a589b7e
Update backend auth files and clean up unused dashboard components
snigdhachoudhury Oct 13, 2025
aa1ab92
Merge branch 'main' into integration-branch
snigdhachoudhury Oct 13, 2025
32863ef
Implemented GitHub authentication with MongoDB integration
snigdhachoudhury Oct 15, 2025
16bda8a
Merge remote-tracking branch 'origin/integration-branch' into integra…
snigdhachoudhury Oct 15, 2025
a2a2eca
Fix GitHub authentication UI consistency issues and error handling
snigdhachoudhury Oct 15, 2025
f89e438
fixed dashboard
snigdhachoudhury Oct 15, 2025
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
Binary file added backend/.gitignore
Binary file not shown.
202 changes: 169 additions & 33 deletions backend/config/passport.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,186 @@
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const GitHubStrategy = require("passport-github2").Strategy;
const User = require("../models/User");

console.log('Initializing Google OAuth strategy...');
console.log('Callback URL:', process.env.GOOGLE_CALLBACK_URL);

passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || "http://localhost:5000/auth/callback",
},
async (accessToken, refreshToken, profile, done) => {
console.log('Google profile received:', profile);
try {
let user = await User.findOne({ googleId: profile.id });

if (!user) {
console.log('Creating new user from Google profile');
user = new User({
// Only use Google Strategy if credentials are provided
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
console.log('Initializing Google OAuth strategy...');
console.log('Callback URL:', process.env.GOOGLE_CALLBACK_URL);

passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || "http://localhost:5000/auth/callback",
},
async (accessToken, refreshToken, profile, done) => {
console.log('Google profile received:', profile);
try {
// Simplified to avoid MongoDB dependency
const user = {
id: profile.id,
googleId: profile.id,
name: profile.displayName,
email: profile.emails && profile.emails[0] ? profile.emails[0].value : null,
isEmailVerified: true
};
return done(null, user);
} catch (err) {
return done(err, null);
}
}
)
);
}

// GitHub OAuth Strategy - Only use if credentials are provided

if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
console.log("Initializing GitHub OAuth Strategy with:", {
clientID: process.env.GITHUB_CLIENT_ID?.substring(0, 5) + '...',
callbackURL: process.env.GITHUB_CALLBACK_URL
});

passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: process.env.GITHUB_CALLBACK_URL,
scope: ["user:email", "read:user", "public_repo"],
passReqToCallback: true
},
async (req, accessToken, refreshToken, profile, done) => {
try {
console.log("GitHub profile received:", profile.username);
console.log("GitHub auth successful, processing user data");

// Fetch additional GitHub data
let githubData = {};
try {
const res = await fetch(`https://api.github.com/user/${profile.id}`, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${accessToken}`
}
});
if (res.ok) githubData = await res.json();
} catch (err) {
console.error("Error fetching GitHub user data:", err);
}

// Find or create user manually instead of using the static method
let user = await User.findOne({
$or: [
{ githubId: profile.id },
{ email: profile.emails?.[0]?.value }
]
});

// If user exists, update GitHub information
if (user) {
console.log("Found existing user, updating GitHub info");
user.githubId = profile.id;
user.githubUsername = profile.username;
user.name = user.name || profile.displayName || profile.username;
user.accessToken = accessToken;

// Update email if not already set
if (!user.email && profile.emails && profile.emails[0]) {
user.email = profile.emails[0].value;
}

// Update avatar if using default
if (user.avatar === '/uploads/avatars/default-avatar.png' && profile.photos && profile.photos[0]) {
user.avatar = profile.photos[0].value;
}

// Update GitHub in social links
if (!user.socialLinks) user.socialLinks = {};
user.socialLinks.github = profile._json?.html_url || `https://github.com/${profile.username}`;
} else {
// Create new user if not found
console.log("Creating new user from GitHub profile");
user = new User({
githubId: profile.id,
githubUsername: profile.username,
name: profile.displayName || profile.username,
email: profile.emails?.[0]?.value || `${profile.username}@github.user`,
avatar: profile.photos?.[0]?.value || "/uploads/avatars/default-avatar.png",
accessToken: accessToken,
isEmailVerified: true, // GitHub emails are verified
socialLinks: {
github: profile._json?.html_url || `https://github.com/${profile.username}`
}
});
}

// Add GitHub platform data
if (Object.keys(githubData).length > 0) {
const platformData = {
name: 'GitHub',
username: profile.username,
url: profile._json?.html_url || `https://github.com/${profile.username}`,
followers: githubData?.followers || 0,
following: githubData?.following || 0,
repos: githubData?.public_repos || 0,
lastUpdated: new Date()
};

// Find or create the GitHub platform entry
if (!user.platforms) user.platforms = [];
const existingPlatform = user.platforms.findIndex(p => p.name === 'GitHub');

if (existingPlatform === -1) {
user.platforms.push(platformData);
} else {
user.platforms[existingPlatform] = {
...user.platforms[existingPlatform],
...platformData
};
}
}

await user.save();
}

return done(null, user);
} catch (err) {
return done(err, null);
return done(null, user);
} catch (err) {
console.error("Error in GitHub strategy:", err);
return done(err, null);
}
}
}
)
);
)
);
}

// serialize + deserialize
// serialize + deserialize (improved to store full user object)
passport.serializeUser((user, done) => {
done(null, user.id);
console.log("Serializing user:", user.id || user.githubId || user.googleId);
// Store the whole user object instead of just the ID
// This avoids needing to retrieve the user from the database on every request

// Make sure we're preserving the access token
if (user.accessToken) {
console.log("Access token preserved in session");
} else {
console.log("WARNING: No access token available in user object during serialization");
}

done(null, user);
});

passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (err) {
done(err, null);
passport.deserializeUser((user, done) => {
console.log("Deserializing user:", user.id || user.githubId || user.googleId);

// Confirm access token availability during deserialization
if (user.accessToken) {
console.log("Access token available during deserialization");
} else {
console.log("WARNING: Access token missing during deserialization");
}

// Simply pass through the user object
done(null, user);
});
16 changes: 11 additions & 5 deletions backend/db/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
require('dotenv').config();
const mongoose = require('mongoose');

// Only connect to MongoDB if MONGODB_URI is provided and not testing mode
const dburl = process.env.MONGODB_URI;
mongoose.connect(dburl).then(() => {
console.log("Connected to DB Successfully ");
}).catch((err) => {
console.log(err.message);
});
if (dburl && process.env.NODE_ENV !== 'test-auth') {
mongoose.connect(dburl).then(() => {
console.log("Connected to DB Successfully ");
}).catch((err) => {
console.log("MongoDB connection error:", err.message);
console.log("Continuing without MongoDB for authentication testing...");
});
} else {
console.log("MongoDB connection skipped for authentication testing");
}
3 changes: 3 additions & 0 deletions backend/env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ JWT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=
CLIENT_URL=
SESSION_SECRET=
ADMIN_EMAIL=
Expand Down
48 changes: 30 additions & 18 deletions backend/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();
const jwt = require("jsonwebtoken");
require("dotenv").config();
const User = require("../models/User");

// Use a fallback JWT secret if env variable is missing
const JWT_SECRET = process.env.JWT_SECRET || 'devsync_secure_jwt_secret_key_for_authentication';
const JWT_SECRET = process.env.JWT_SECRET || "devsync_secure_jwt_secret_key_for_authentication";

module.exports = function(req, res, next) {
// Get token from header
const token = req.header('x-auth-token');
module.exports = async function (req, res, next) {
try {
const token = req.header("x-auth-token");

// Check if no token
if (!token) {
return res.status(401).json({ errors: [{ msg: 'No token, authorization denied' }] });
}
if (token) {
try {
const decoded = jwt.verify(token, JWT_SECRET);
const user = await User.findById(decoded.user.id).select("-password");
if (!user) return res.status(401).json({ errors: [{ msg: "Unauthorized user" }] });

// Verify token
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded.user;
next();
req.user = user;
req.authMethod = "token";
return next();
} catch (err) {
console.error("JWT verification failed:", err.message);
return res.status(401).json({ errors: [{ msg: "Token is not valid" }] });
}
}

if (req.isAuthenticated && req.isAuthenticated()) {
req.user = req.user;
req.authMethod = "session";
return next();
}

return res.status(401).json({ errors: [{ msg: "Not authenticated" }] });
} catch (err) {
console.error('Token verification error:', err.message);
res.status(401).json({ errors: [{ msg: 'Token is not valid' }] });
console.error("Auth middleware error:", err);
res.status(500).json({ errors: [{ msg: "Server error" }] });
}
};
6 changes: 3 additions & 3 deletions backend/middleware/rateLimit/authLimiterMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { RateLimiterMemory } = require('rate-limiter-flexible');

exports.authLimiter = new RateLimiterMemory({
points: 5,
duration: 60,
blockDuration: 60 * 5,
points: 20, // Increased from 5 to 20 attempts
duration: 60, // Per minute
blockDuration: 60 * 2, // Reduced block time to 2 minutes
})
Loading