From 16562a77c128eb4ea43113aacf35d2c4dacf4759 Mon Sep 17 00:00:00 2001 From: dikamjit-borah Date: Fri, 10 Oct 2025 20:50:59 +0530 Subject: [PATCH 1/5] add dependencies --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e62917e..387f9df 100644 --- a/package.json +++ b/package.json @@ -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", From 677d912f3bc6ca870a1fbaad6ea56ae46ef05026 Mon Sep 17 00:00:00 2001 From: dikamjit-borah Date: Fri, 10 Oct 2025 20:52:08 +0530 Subject: [PATCH 2/5] add passport config, add auth routes --- src/config/passport.config.js | 88 ++++++++++++++++++++ src/controllers/auth.controller.js | 124 +++++++++++++++++++++++++++++ src/routes/auth.routes.js | 22 +++++ 3 files changed, 234 insertions(+) create mode 100644 src/config/passport.config.js create mode 100644 src/controllers/auth.controller.js create mode 100644 src/routes/auth.routes.js diff --git a/src/config/passport.config.js b/src/config/passport.config.js new file mode 100644 index 0000000..97d1494 --- /dev/null +++ b/src/config/passport.config.js @@ -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; diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js new file mode 100644 index 0000000..debcf09 --- /dev/null +++ b/src/controllers/auth.controller.js @@ -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 + }); + } +}; diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js new file mode 100644 index 0000000..424ae54 --- /dev/null +++ b/src/routes/auth.routes.js @@ -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; From a09cc46667d64da9b33f08a0a7e1413115f2ca08 Mon Sep 17 00:00:00 2001 From: dikamjit-borah Date: Sat, 11 Oct 2025 10:03:15 +0530 Subject: [PATCH 3/5] add UserModel --- src/app.js | 6 +++ src/models/user.model.js | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/models/user.model.js diff --git a/src/app.js b/src/app.js index 2b47f5c..f205915 100644 --- a/src/app.js +++ b/src/app.js @@ -1,8 +1,10 @@ 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 dbConnection from '../scripts/dbConfig.js'; import errorHandler from './middleware/error-handler.middleware.js'; import notFound from './middleware/notFound.middleware.js' @@ -12,6 +14,9 @@ dbConnection(); app.use(cors()); app.use(express.json()); +// Initialize Passport +app.use(passport.initialize()); + app.get('/',(req,res)=>{ res.send("Welcome to Homepage"); }) @@ -20,6 +25,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); diff --git a/src/models/user.model.js b/src/models/user.model.js new file mode 100644 index 0000000..a0a78c8 --- /dev/null +++ b/src/models/user.model.js @@ -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; From 7383901ec2db797448d70c9065e317dd70b2bf1c Mon Sep 17 00:00:00 2001 From: dikamjit-borah Date: Sat, 11 Oct 2025 10:06:17 +0530 Subject: [PATCH 4/5] delete package.json --- package.json | 59 ---------------------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 package.json diff --git a/package.json b/package.json deleted file mode 100644 index 387f9df..0000000 --- a/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "hacktoberfest-2025-backend", - "version": "1.0.0", - "description": "Open Code Chicago's official backend project for for CoreX (Hacktoberfest 2025) - Node + Express + MongoDB. Open source, community-driven, and welcoming contributions from everyone.", - "homepage": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend", - "repository": { - "type": "git", - "url": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend.git" - }, - "bugs": { - "url": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend/issues" - }, - "contributors": [ - { - "name": "Open Code Chicago Team", - "email": "info@opencodechicago.org" - } - ], - "main": "server.js", - "type": "module", - "directories": { - "doc": "docs" - }, - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "lint": "eslint . --ext .js", - "seed": "node scripts/seed.js" - }, - "keywords": [ - "hacktoberfest", - "hacktoberfest2025", - "backend", - "nodejs", - "express", - "mongoose", - "mongodb", - "javascript", - "open-source", - "open-code-chicago", - "community" - ], - "author": "Open Code Chicago", - "license": "MIT", - "dependencies": { - "cors": "^2.8.5", - "dotenv": "^17.2.3", - "express": "^5.1.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", - "nodemon": "^3.1.10" - } -} From d517c5408e9441d4ec4d8e0b8956059ff42a9540 Mon Sep 17 00:00:00 2001 From: dikamjit-borah Date: Sat, 11 Oct 2025 10:09:00 +0530 Subject: [PATCH 5/5] pull package.json --- package.json | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..3dcf329 --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "hacktoberfest-2025-backend", + "version": "1.0.0", + "description": "Open Code Chicago's official backend project for for CoreX (Hacktoberfest 2025) - Node + Express + MongoDB. Open source, community-driven, and welcoming contributions from everyone.", + "homepage": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend", + "repository": { + "type": "git", + "url": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend.git" + }, + "bugs": { + "url": "https://github.com/OpenCodeChicago/hacktoberfest-2025-backend/issues" + }, + "contributors": [ + { + "name": "Open Code Chicago Team", + "email": "info@opencodechicago.org" + } + ], + "main": "server.js", + "type": "module", + "directories": { + "doc": "docs" + }, + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "lint": "eslint . --ext .js", + "seed": "node scripts/seed.js" + }, + "keywords": [ + "hacktoberfest", + "hacktoberfest2025", + "backend", + "nodejs", + "express", + "mongoose", + "mongodb", + "javascript", + "open-source", + "open-code-chicago", + "community" + ], + "author": "Open Code Chicago", + "license": "MIT", + "dependencies": { + "@types/passport-google-oauth20": "^2.0.16", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "express-session": "^1.18.2", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.19.0", + "passport": "^0.7.0" + }, + "devDependencies": { + "eslint": "^9.37.0", + "nodemon": "^3.1.10" + } +} \ No newline at end of file