Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@
"eslint": "^9.37.0",
"nodemon": "^3.1.10"
}
}
}
6 changes: 6 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import express from 'express';
import cors from 'cors';
import passport from './config/passport.config.js';
import productRoutes from './routes/product.routes.js';
import cartRoutes from './routes/cart.routes.js';
import collectionRoutes from './routes/collection.routes.js';
import authRoutes from './routes/auth.routes.js';
import errorHandler from './middleware/error-handler.middleware.js';
import notFound from './middleware/notFound.middleware.js'
const app = express();
app.use(cors());
app.use(express.json());

// Initialize Passport
app.use(passport.initialize());

app.get('/',(req,res)=>{
res.send("Welcome to Homepage");
})
Expand All @@ -17,6 +22,7 @@ app.get('/',(req,res)=>{
app.use('/api/products', productRoutes);
app.use('/api/cart', cartRoutes);
app.use('/api/collections', collectionRoutes);
app.use('/auth', authRoutes);

// Middleware for not found 404
app.use(notFound);
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
});
}
};
106 changes: 106 additions & 0 deletions src/models/user.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
googleId: {
type: String,
unique: true,
sparse: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
name: {
type: String,
required: true,
trim: true
},
firstName: {
type: String,
trim: true
},
lastName: {
type: String,
trim: true
},
profilePicture: {
type: String,
default: null
},
authProvider: {
type: String,
enum: ['google'],
required: true,
default: 'google'
},
isActive: {
type: Boolean,
default: true
},
lastLogin: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});

// Indexes
userSchema.index({ email: 1 });
userSchema.index({ googleId: 1 });

// Method to update last login
userSchema.methods.updateLastLogin = function() {
this.lastLogin = new Date();
return this.save();
};

// Static method to find or create user from Google profile
userSchema.statics.findOrCreateFromGoogle = async function(profile) {
try {
// Try to find existing user by Google ID first
let user = await this.findOne({ googleId: profile.id });

if (user) {
// Update last login and return user
await user.updateLastLogin();
return user;
}

// Try to find existing user by email
user = await this.findOne({ email: profile.emails[0].value });

if (user) {
// Link Google account to existing user
user.googleId = profile.id;
user.authProvider = 'google';
await user.save();
await user.updateLastLogin();
return user;
}

// Create new user
const nameParts = profile.displayName ? profile.displayName.split(' ') : ['', ''];
user = new this({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName || profile.emails[0].value,
firstName: nameParts[0] || '',
lastName: nameParts.slice(1).join(' ') || '',
profilePicture: profile.photos && profile.photos[0] ? profile.photos[0].value : null,
authProvider: 'google'
});

await user.save();
return user;
} catch (error) {
throw new Error(`Failed to find or create user: ${error.message}`);
}
};

const User = mongoose.model('User', userSchema);

export default User;
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;