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" }); } };