Spaces:
Running
Running
| import User from '../models/auth.model.js'; | |
| import bcrypt from 'bcrypt'; | |
| import jwt from 'jsonwebtoken'; | |
| import mongoose from 'mongoose'; | |
| import { validateRole } from '../utils/roleValidator.js'; | |
| /** | |
| * POST /api/auth/sync-user | |
| * Called by the frontend after a successful Firebase/Google sign-in to ensure | |
| * the user exists in MongoDB. Creates a new record for first-time Google users | |
| * or links an existing email-password account to the Firebase UID. | |
| * Requires a valid Firebase ID token (verified by verifyToken middleware). | |
| */ | |
| export const syncGoogleUser = async (req, res) => { | |
| try { | |
| if (!req.firebaseUser) { | |
| return res.status(401).json({ message: 'Unauthorized' }); | |
| } | |
| const { uid, email, name, picture } = req.firebaseUser; | |
| const requestedRole = req.body?.role || 'farmer'; | |
| const userRole = validateRole(requestedRole); | |
| // Find existing record by Firebase UID first, then fall back to email | |
| let user = await User.findOne({ firebaseUid: uid }); | |
| if (!user && email) { | |
| user = await User.findOne({ email: email.trim().toLowerCase() }); | |
| } | |
| if (!user) { | |
| // First-time Google/Firebase user — create their MongoDB record | |
| user = await User.create({ | |
| name: name || email?.split('@')[0] || 'User', | |
| email: email?.trim().toLowerCase(), | |
| firebaseUid: uid, | |
| role: userRole, | |
| // password is intentionally omitted for OAuth users | |
| }); | |
| } else { | |
| // Existing user — backfill firebaseUid if missing | |
| if (!user.firebaseUid) { | |
| user.firebaseUid = uid; | |
| await user.save(); | |
| } | |
| } | |
| return res.status(200).json({ | |
| message: 'User synced', | |
| role: user.role, | |
| userId: user._id, | |
| }); | |
| } catch (err) { | |
| console.error('syncGoogleUser error:', err); | |
| return res.status(500).json({ message: 'Something went wrong' }); | |
| } | |
| }; | |
| export const signup = async (req, res) => { | |
| const { name, email, password, role } = req.body; | |
| try { | |
| const jwtSecret = process.env.JWT_KEY || process.env.JWT_SECRET; | |
| const normalizedEmail = email?.trim().toLowerCase(); | |
| // Ensure all fields are provided | |
| if (!name || !normalizedEmail || !password) { | |
| return res.status(400).json({ message: "All fields are required" }); | |
| } | |
| if (!jwtSecret) { | |
| return res.status(500).json({ message: "Server configuration error" }); | |
| } | |
| // Validate role - default to 'farmer' if not provided or invalid | |
| const userRole = validateRole(role); | |
| // Check if user already exists | |
| const existingUser = await User.findOne({ email: normalizedEmail }); | |
| if (existingUser) return res.status(400).json({ message: "User already exists" }); | |
| // Hash password | |
| const hashedPassword = await bcrypt.hash(password, 12); | |
| // Create new user | |
| const newUser = await User.create({ name, email: normalizedEmail, password: hashedPassword, role: userRole }); | |
| // Generate JWT token | |
| const token = jwt.sign( | |
| { id: newUser._id, role: newUser.role, email: newUser.email, name: newUser.name }, | |
| jwtSecret, | |
| { expiresIn: "10h" } | |
| ); | |
| // Set token in cookie. Use secure cookies only in production (HTTPS). | |
| const cookieOptions = process.env.NODE_ENV === 'production' | |
| ? { secure: true, sameSite: 'None', path: '/', maxAge: 86400000 } | |
| : { secure: false, sameSite: 'Lax', path: '/' }; | |
| return res | |
| .cookie('token', token, cookieOptions) | |
| .status(201) | |
| .json({ message: "User created successfully", token, role: newUser.role, name: newUser.name, email: newUser.email }); | |
| } catch (err) { | |
| console.error(err); | |
| return res.status(500).json({ message: "Something went wrong" }); | |
| } | |
| }; | |
| export const signin = async (req, res) => { | |
| const { email, password, role } = req.body; | |
| try { | |
| const jwtSecret = process.env.JWT_KEY || process.env.JWT_SECRET; | |
| const normalizedEmail = email?.trim().toLowerCase(); | |
| if (!normalizedEmail || !password) { | |
| return res.status(400).json({ message: "All fields are required" }); | |
| } | |
| if (!jwtSecret) { | |
| return res.status(500).json({ message: "Server configuration error" }); | |
| } | |
| // Validate role only when explicitly provided | |
| const userRole = role ? validateRole(role) : null; | |
| const user = await User.findOne({ email: normalizedEmail }); | |
| if (!user) return res.status(401).json({ message: "Invalid credentials" }); | |
| // Google/OAuth users have no password — they must use Google sign-in | |
| if (!user.password) return res.status(401).json({ message: "This account uses Google sign-in. Please sign in with Google." }); | |
| // Check if the user's role matches the requested role | |
| if (userRole && userRole !== user.role) return res.status(401).json({ message: "Invalid credentials" }); | |
| // Validate password | |
| const isPasswordCorrect = await bcrypt.compare(password, user.password); | |
| if (!isPasswordCorrect) return res.status(401).json({ message: "Invalid credentials" }); | |
| // Generate JWT token | |
| const token = jwt.sign( | |
| { id: user._id, role: user.role, email: user.email, name: user.name }, | |
| jwtSecret, | |
| { expiresIn: "1d" } | |
| ); | |
| // Set token in cookie. Use secure cookies only in production (HTTPS). | |
| if (token.length > 0) { | |
| const cookieOptions = process.env.NODE_ENV === 'production' | |
| ? { secure: true, sameSite: 'None', path: '/', maxAge: 86400000 } | |
| : { secure: false, sameSite: 'Lax', path: '/' }; | |
| return res | |
| .cookie('token', token, cookieOptions) | |
| .status(200) | |
| .json({ message: "Logged in successfully", token, role: user.role, name: user.name, email: user.email }); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| return res.status(500).json({ message: "Something went wrong" }); | |
| } | |
| }; | |
| export const signout = async (req, res) => { | |
| // Clear the cookie on logout | |
| const cookieOptions = process.env.NODE_ENV === 'production' | |
| ? { secure: true, sameSite: 'None', path: '/' } | |
| : { secure: false, sameSite: 'Lax', path: '/' }; | |
| res.clearCookie('token', cookieOptions); | |
| return res.status(200).json({ message: 'Logged out successfully' }); | |
| }; | |
| export const getUserProfile = async (req, res) => { | |
| try { | |
| let user = null; | |
| if (req.userId && mongoose.isValidObjectId(req.userId)) { | |
| // Legacy JWT path: req.userId is a Mongo ObjectId string | |
| user = await User.findById(req.userId).select('-password'); | |
| } else if (req.firebaseUser?.uid) { | |
| // Firebase path: look up by firebaseUid field, fallback to email | |
| user = await User.findOne({ firebaseUid: req.firebaseUser.uid }).select('-password'); | |
| if (!user && req.userEmail) { | |
| user = await User.findOne({ email: req.userEmail }).select('-password'); | |
| } | |
| } else if (req.userEmail) { | |
| // Final fallback: lookup by email | |
| user = await User.findOne({ email: req.userEmail }).select('-password'); | |
| } | |
| if (!user) return res.status(404).json({ message: "User not found" }); | |
| res.json(user); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ message: "Internal server error" }); | |
| } | |
| }; | |