Agromind-backend / backend /controllers /authController.js
gh-action-hf-auto
auto: sync backend from github@32fb9685
8a6248c
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" });
}
};