Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"mongoose": "^8.19.0"
"express-session": "^1.18.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.19.0",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0"
},
"devDependencies": {
"eslint": "^9.37.0",
Expand Down
88 changes: 88 additions & 0 deletions src/config/passport.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import User from '../models/user.model.js';
import jwt from 'jsonwebtoken';

// Configure Google OAuth Strategy
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user from Google profile
const user = await User.findOrCreateFromGoogle(profile);

// Generate JWT token
const token = jwt.sign(
{
userId: user._id,
email: user.email,
authProvider: user.authProvider
},
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);

// Attach token to user object
user.token = token;

return done(null, user);
} catch (error) {
console.error('Google OAuth error:', error);
return done(error, null);
}
}));

// Serialize user for session
passport.serializeUser((user, done) => {
done(null, user._id);
});

// Deserialize user from session
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (error) {
done(error, null);
}
});

// JWT verification middleware
export const verifyJWT = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');

if (!token) {
return res.status(401).json({
success: false,
message: 'Access denied. No token provided.'
});
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(400).json({
success: false,
message: 'Invalid token.'
});
}
};

// Generate JWT token utility
export const generateToken = (user) => {
return jwt.sign(
{
userId: user._id,
email: user.email,
authProvider: user.authProvider
},
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
};

export default passport;
124 changes: 124 additions & 0 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import passport from '../config/passport.config.js';
import User from '../models/user.model.js';
import { generateToken } from '../config/passport.config.js';

// Redirect to Google OAuth
export const googleAuth = passport.authenticate('google', {
scope: ['profile', 'email']
});

// Handle Google OAuth callback
export const googleCallback = (req, res, next) => {
passport.authenticate('google', (err, user, info) => {
if (err) {
console.error('Google OAuth error:', err);
return res.status(500).json({
success: false,
message: 'Authentication failed',
error: err.message
});
}

if (!user) {
return res.status(401).json({
success: false,
message: 'Authentication failed'
});
}

// Generate JWT token
const token = generateToken(user);

// Return success response with token and user info
res.json({
success: true,
message: 'Authentication successful',
token,
user: {
id: user._id,
email: user.email,
name: user.name,
firstName: user.firstName,
lastName: user.lastName,
profilePicture: user.profilePicture,
authProvider: user.authProvider,
lastLogin: user.lastLogin
}
});
})(req, res, next);
};

// Get current user profile
export const getProfile = async (req, res) => {
try {
const user = await User.findById(req.user.userId).select('-__v');

if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}

res.json({
success: true,
user: {
id: user._id,
email: user.email,
name: user.name,
firstName: user.firstName,
lastName: user.lastName,
profilePicture: user.profilePicture,
authProvider: user.authProvider,
isActive: user.isActive,
createdAt: user.createdAt,
lastLogin: user.lastLogin
}
});
} catch (error) {
console.error('Get profile error:', error);
res.status(500).json({
success: false,
message: 'Failed to get user profile',
error: error.message
});
}
};

// Logout (invalidate token on client side)
export const logout = (req, res) => {
res.json({
success: true,
message: 'Logout successful. Please remove the token from client storage.'
});
};

// Refresh token
export const refreshToken = async (req, res) => {
try {
const user = await User.findById(req.user.userId);

if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}

// Generate new token
const token = generateToken(user);

res.json({
success: true,
message: 'Token refreshed successfully',
token
});
} catch (error) {
console.error('Refresh token error:', error);
res.status(500).json({
success: false,
message: 'Failed to refresh token',
error: error.message
});
}
};
22 changes: 22 additions & 0 deletions src/routes/auth.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import express from 'express';
import {
googleAuth,
googleCallback,
getProfile,
logout,
refreshToken
} from '../controllers/auth.controller.js';
import { verifyJWT } from '../config/passport.config.js';

const router = express.Router();

// Google OAuth routes
router.get('/google', googleAuth);
router.get('/google/callback', googleCallback);

// Protected routes (require JWT authentication)
router.get('/profile', verifyJWT, getProfile);
router.post('/logout', verifyJWT, logout);
router.post('/refresh', verifyJWT, refreshToken);

export default router;