File size: 7,196 Bytes
8a6248c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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" });
}
};