diff --git a/backend/src/routes/authRoutes.js b/backend/src/routes/authRoutes.js index a3fc005..a415459 100644 --- a/backend/src/routes/authRoutes.js +++ b/backend/src/routes/authRoutes.js @@ -1030,12 +1030,21 @@ router.put( } const { username, email, first_name, last_name } = req.body; - const updateData = {}; + const updateData = { + updated_at: new Date(), + }; if (username) updateData.username = username; if (email) updateData.email = email; - if (first_name !== undefined) updateData.first_name = first_name || null; - if (last_name !== undefined) updateData.last_name = last_name || null; + // Handle first_name and last_name - allow empty strings to clear the field + if (first_name !== undefined) { + updateData.first_name = + first_name === "" ? null : first_name.trim() || null; + } + if (last_name !== undefined) { + updateData.last_name = + last_name === "" ? null : last_name.trim() || null; + } // Check if username/email already exists (excluding current user) if (username || email) { @@ -1060,6 +1069,7 @@ router.put( } } + // Update user with explicit commit const updatedUser = await prisma.users.update({ where: { id: req.user.id }, data: updateData, @@ -1076,9 +1086,39 @@ router.put( }, }); + // Explicitly refresh user data from database to ensure we return latest data + // This ensures consistency especially in high-concurrency scenarios + const freshUser = await prisma.users.findUnique({ + where: { id: req.user.id }, + select: { + id: true, + username: true, + email: true, + first_name: true, + last_name: true, + role: true, + is_active: true, + last_login: true, + updated_at: true, + }, + }); + + // Use fresh data if available, otherwise fallback to updatedUser + const responseUser = freshUser || updatedUser; + + // Log update for debugging (only log in non-production) + if (process.env.NODE_ENV !== "production") { + console.log("Profile updated:", { + userId: req.user.id, + first_name: responseUser.first_name, + last_name: responseUser.last_name, + updated_at: responseUser.updated_at, + }); + } + res.json({ message: "Profile updated successfully", - user: updatedUser, + user: responseUser, }); } catch (error) { console.error("Update profile error:", error); diff --git a/backend/src/server.js b/backend/src/server.js index 9315e45..f8feb8c 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -387,6 +387,7 @@ app.use( "Authorization", "Cookie", "X-Requested-With", + "X-Device-ID", // Allow device ID header for TFA remember-me functionality ], }), ); @@ -558,299 +559,6 @@ app.use(`/bullboard`, (req, res, next) => { return res.status(503).json({ error: "Bull Board not initialized yet" }); }); -/* -// OLD MIDDLEWARE - REMOVED FOR SIMPLIFICATION - DO NOT USE -if (false) { - const sessionId = req.cookies["bull-board-session"]; - console.log("Bull Board API call - Session ID:", sessionId ? "present" : "missing"); - console.log("Bull Board API call - Cookies:", req.cookies); - console.log("Bull Board API call - Bull Board token cookie:", req.cookies["bull-board-token"] ? "present" : "missing"); - console.log("Bull Board API call - Query token:", req.query.token ? "present" : "missing"); - console.log("Bull Board API call - Auth header:", req.headers.authorization ? "present" : "missing"); - console.log("Bull Board API call - Origin:", req.headers.origin || "missing"); - console.log("Bull Board API call - Referer:", req.headers.referer || "missing"); - - // Check if we have any authentication method available - const hasSession = !!sessionId; - const hasTokenCookie = !!req.cookies["bull-board-token"]; - const hasQueryToken = !!req.query.token; - const hasAuthHeader = !!req.headers.authorization; - const hasReferer = !!req.headers.referer; - - console.log("Bull Board API call - Auth methods available:", { - session: hasSession, - tokenCookie: hasTokenCookie, - queryToken: hasQueryToken, - authHeader: hasAuthHeader, - referer: hasReferer - }); - - // Check for valid session first - if (sessionId) { - const session = bullBoardSessions.get(sessionId); - console.log("Bull Board API call - Session found:", !!session); - if (session && Date.now() - session.timestamp < 3600000) { - // Valid session, extend it - session.timestamp = Date.now(); - console.log("Bull Board API call - Using existing session, proceeding"); - return next(); - } else if (session) { - // Expired session, remove it - console.log("Bull Board API call - Session expired, removing"); - bullBoardSessions.delete(sessionId); - } - } - - // No valid session, check for token as fallback - let token = req.query.token; - if (!token && req.headers.authorization) { - token = req.headers.authorization.replace("Bearer ", ""); - } - if (!token && req.cookies["bull-board-token"]) { - token = req.cookies["bull-board-token"]; - } - - // For API calls, also check if the token is in the referer URL - // This handles cases where the main page hasn't set the cookie yet - if (!token && req.headers.referer) { - try { - const refererUrl = new URL(req.headers.referer); - const refererToken = refererUrl.searchParams.get('token'); - if (refererToken) { - token = refererToken; - console.log("Bull Board API call - Token found in referer URL:", refererToken.substring(0, 20) + "..."); - } else { - console.log("Bull Board API call - No token found in referer URL"); - // If no token in referer and no session, return 401 with redirect info - if (!sessionId) { - console.log("Bull Board API call - No authentication available, returning 401"); - return res.status(401).json({ - error: "Authentication required", - message: "Please refresh the page to re-authenticate" - }); - } - } - } catch (error) { - console.log("Bull Board API call - Error parsing referer URL:", error.message); - } - } - - if (token) { - console.log("Bull Board API call - Token found, authenticating"); - // Add token to headers for authentication - req.headers.authorization = `Bearer ${token}`; - - // Authenticate the user - return authenticateToken(req, res, (err) => { - if (err) { - console.log("Bull Board API call - Token authentication failed"); - return res.status(401).json({ error: "Authentication failed" }); - } - return requireAdmin(req, res, (adminErr) => { - if (adminErr) { - console.log("Bull Board API call - Admin access required"); - return res.status(403).json({ error: "Admin access required" }); - } - console.log("Bull Board API call - Token authentication successful"); - return next(); - }); - }); - } - - // No valid session or token for API calls, deny access - console.log("Bull Board API call - No valid session or token, denying access"); - return res.status(401).json({ error: "Valid Bull Board session or token required" }); - } - - // Check for bull-board-session cookie first - const sessionId = req.cookies["bull-board-session"]; - if (sessionId) { - const session = bullBoardSessions.get(sessionId); - if (session && Date.now() - session.timestamp < 3600000) { - // 1 hour - // Valid session, extend it - session.timestamp = Date.now(); - return next(); - } else if (session) { - // Expired session, remove it - bullBoardSessions.delete(sessionId); - } - } - - // No valid session, check for token - let token = req.query.token; - if (!token && req.headers.authorization) { - token = req.headers.authorization.replace("Bearer ", ""); - } - if (!token && req.cookies["bull-board-token"]) { - token = req.cookies["bull-board-token"]; - } - - // If no token, deny access - if (!token) { - return res.status(401).json({ error: "Access token required" }); - } - - // Add token to headers for authentication - req.headers.authorization = `Bearer ${token}`; - - // Authenticate the user - return authenticateToken(req, res, (err) => { - if (err) { - return res.status(401).json({ error: "Authentication failed" }); - } - return requireAdmin(req, res, (adminErr) => { - if (adminErr) { - return res.status(403).json({ error: "Admin access required" }); - } - - // Authentication successful - create a session - const newSessionId = require("node:crypto") - .randomBytes(32) - .toString("hex"); - bullBoardSessions.set(newSessionId, { - timestamp: Date.now(), - userId: req.user.id, - }); - - // Set session cookie with proper configuration for domain access - const isHttps = process.env.NODE_ENV === "production" || process.env.SERVER_PROTOCOL === "https"; - const cookieOptions = { - httpOnly: true, - secure: isHttps, - maxAge: 3600000, // 1 hour - path: "/", // Set path to root so it's available for all Bull Board requests - }; - - // Configure sameSite based on protocol and environment - if (isHttps) { - cookieOptions.sameSite = "none"; // Required for HTTPS cross-origin - } else { - cookieOptions.sameSite = "lax"; // Better for HTTP same-origin - } - - res.cookie("bull-board-session", newSessionId, cookieOptions); - - // Clean up old sessions periodically - if (bullBoardSessions.size > 100) { - const now = Date.now(); - for (const [sid, session] of bullBoardSessions.entries()) { - if (now - session.timestamp > 3600000) { - bullBoardSessions.delete(sid); - } - } - } - - return next(); - }); - }); -}); -*/ - -// Second middleware block - COMMENTED OUT - using simplified version above instead -/* -app.use(`/bullboard`, (req, res, next) => { - if (bullBoardRouter) { - // If this is the main Bull Board page (not an API call), inject the token and create session - if (!req.path.includes("/api/") && !req.path.includes("/static/") && req.path === "/bullboard") { - const token = req.query.token; - console.log("Bull Board main page - Token:", token ? "present" : "missing"); - console.log("Bull Board main page - Query params:", req.query); - console.log("Bull Board main page - Origin:", req.headers.origin || "missing"); - console.log("Bull Board main page - Referer:", req.headers.referer || "missing"); - console.log("Bull Board main page - Cookies:", req.cookies); - - if (token) { - // Authenticate the user and create a session immediately on page load - req.headers.authorization = `Bearer ${token}`; - - return authenticateToken(req, res, (err) => { - if (err) { - console.log("Bull Board main page - Token authentication failed"); - return res.status(401).json({ error: "Authentication failed" }); - } - return requireAdmin(req, res, (adminErr) => { - if (adminErr) { - console.log("Bull Board main page - Admin access required"); - return res.status(403).json({ error: "Admin access required" }); - } - - console.log("Bull Board main page - Token authentication successful, creating session"); - - // Create a Bull Board session immediately - const newSessionId = require("node:crypto") - .randomBytes(32) - .toString("hex"); - bullBoardSessions.set(newSessionId, { - timestamp: Date.now(), - userId: req.user.id, - }); - - // Set session cookie with proper configuration for domain access - const sessionCookieOptions = { - httpOnly: true, - secure: false, // Always false for HTTP - maxAge: 3600000, // 1 hour - path: "/", // Set path to root so it's available for all Bull Board requests - sameSite: "lax", // Always lax for HTTP - }; - - res.cookie("bull-board-session", newSessionId, sessionCookieOptions); - console.log("Bull Board main page - Session created:", newSessionId); - console.log("Bull Board main page - Cookie options:", sessionCookieOptions); - - // Also set a token cookie for API calls as a fallback - const tokenCookieOptions = { - httpOnly: false, // Allow JavaScript to access it - secure: false, // Always false for HTTP - maxAge: 3600000, // 1 hour - path: "/", // Set path to root for broader compatibility - sameSite: "lax", // Always lax for HTTP - }; - - res.cookie("bull-board-token", token, tokenCookieOptions); - console.log("Bull Board main page - Token cookie also set for API fallback"); - - // Clean up old sessions periodically - if (bullBoardSessions.size > 100) { - const now = Date.now(); - for (const [sid, session] of bullBoardSessions.entries()) { - if (now - session.timestamp > 3600000) { - bullBoardSessions.delete(sid); - } - } - } - - // Now proceed to serve the Bull Board page - return bullBoardRouter(req, res, next); - }); - }); - } else { - console.log("Bull Board main page - No token provided, checking for existing session"); - // Check if we have an existing session - const sessionId = req.cookies["bull-board-session"]; - if (sessionId) { - const session = bullBoardSessions.get(sessionId); - if (session && Date.now() - session.timestamp < 3600000) { - console.log("Bull Board main page - Using existing session"); - // Extend session - session.timestamp = Date.now(); - return bullBoardRouter(req, res, next); - } else if (session) { - console.log("Bull Board main page - Session expired, removing"); - bullBoardSessions.delete(sessionId); - } - } - console.log("Bull Board main page - No valid session, denying access"); - return res.status(401).json({ error: "Access token required" }); - } - } - return bullBoardRouter(req, res, next); - } - return res.status(503).json({ error: "Bull Board not initialized yet" }); -}); -*/ - // Error handler specifically for Bull Board routes app.use("/bullboard", (err, req, res, _next) => { console.error("Bull Board error on", req.method, req.url); diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index eccb430..581788b 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -224,8 +224,30 @@ export const AuthProvider = ({ children }) => { const data = await response.json(); if (response.ok) { + console.log("Profile updated - received user data:", data.user); + + // Validate that we received user data with expected fields + if (!data.user || !data.user.id) { + console.error("Invalid user data in response:", data); + return { + success: false, + error: "Invalid response from server", + }; + } + + // Update both state and localStorage atomically setUser(data.user); localStorage.setItem("user", JSON.stringify(data.user)); + + // Log update for debugging (only in non-production) + if (process.env.NODE_ENV !== "production") { + console.log("User data updated in localStorage:", { + id: data.user.id, + first_name: data.user.first_name, + last_name: data.user.last_name, + }); + } + return { success: true, user: data.user }; } else { // Handle HTTP error responses (like 500 CORS errors) diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index d545d07..e9f1079 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -80,8 +80,10 @@ const Profile = () => { setIsLoading(true); setMessage({ type: "", text: "" }); + console.log("Submitting profile data:", profileData); try { const result = await updateProfile(profileData); + console.log("Profile update result:", result); if (result.success) { setMessage({ type: "success", text: "Profile updated successfully!" }); } else {