const jwt = require("jsonwebtoken"); const { User } = require("../models"); const logger = require("../utils/logger"); const cookie = require("cookie"); /** * Socket.io authentication middleware * Verifies JWT token and attaches user to socket * Tokens can be provided via: * 1. Cookie (accessToken) - preferred for browser clients * 2. Auth object (auth.token) - for mobile/native clients */ const authenticateSocket = async (socket, next) => { try { let token = null; // Try to get token from cookies first (browser clients) if (socket.handshake.headers.cookie) { const cookies = cookie.parse(socket.handshake.headers.cookie); token = cookies.accessToken; } // Auth object for mobile/native clients if (!token && socket.handshake.auth?.token) { token = socket.handshake.auth.token; } if (!token) { logger.warn("Socket connection rejected - no token provided", { socketId: socket.id, address: socket.handshake.address, }); return next(new Error("Authentication required")); } // Verify JWT (access tokens only) const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET); const userId = decoded.id; if (!userId) { logger.warn("Socket connection rejected - invalid token format", { socketId: socket.id, }); return next(new Error("Invalid token format")); } // Look up user const user = await User.findByPk(userId); if (!user) { logger.warn("Socket connection rejected - user not found", { socketId: socket.id, userId, }); return next(new Error("User not found")); } // Validate JWT version (invalidate old tokens after password change) if (decoded.jwtVersion !== user.jwtVersion) { logger.warn("Socket connection rejected - JWT version mismatch", { socketId: socket.id, userId, tokenVersion: decoded.jwtVersion, userVersion: user.jwtVersion, }); return next( new Error( "Session expired due to password change. Please log in again." ) ); } // Attach user to socket for use in event handlers socket.userId = user.id; socket.user = { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, }; logger.info("Socket authenticated successfully", { socketId: socket.id, userId: user.id, email: user.email, }); next(); } catch (error) { // Check if token is expired if (error.name === "TokenExpiredError") { logger.warn("Socket connection rejected - token expired", { socketId: socket.id, }); return next(new Error("Token expired")); } logger.error("Socket authentication error", { socketId: socket.id, error: error.message, stack: error.stack, }); return next(new Error("Authentication failed")); } }; module.exports = { authenticateSocket };