import { verifyAccessToken } from "../utils/jwt.js"; import User from "../models/User.js"; import config from "../config/index.js"; /** * Middleware to verify JWT access token from cookies or Authorization header */ export const authenticate = async (request, reply) => { try { let token = null; // DEBUG: Log incoming request details (only in development) if (config.isDevelopment) { console.log("\n=== AUTH MIDDLEWARE DEBUG ==="); console.log("URL:", request.url); console.log("Method:", request.method); console.log("Cookies present:", Object.keys(request.cookies || {})); console.log("Has accessToken cookie:", !!request.cookies?.accessToken); console.log( "Authorization header:", request.headers.authorization ? "Present" : "Missing" ); console.log("Origin:", request.headers.origin); console.log("Referer:", request.headers.referer); } // Try to get token from Authorization header const authHeader = request.headers.authorization; if (authHeader && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); if (config.isDevelopment) { console.log("✓ Token found in Authorization header"); } } // If not in header, try cookies if (!token && request.cookies && request.cookies.accessToken) { token = request.cookies.accessToken; if (config.isDevelopment) { console.log("✓ Token found in cookies"); } } if (!token) { if (config.isDevelopment) { console.log("✗ No token found in cookies or headers"); console.log("=== END AUTH DEBUG ===\n"); } return reply.status(401).send({ error: "Unauthorized", message: "No access token provided", }); } // Verify token const decoded = verifyAccessToken(token); if (config.isDevelopment) { console.log("✓ Token verified, userId:", decoded.userId); } // Fetch user from database const user = await User.findById(decoded.userId).select( "-twoFactor.secret" ); // Store token on request for session tracking request.token = token; // Find the session associated with this token try { const Session = (await import("../models/Session.js")).default; const session = await Session.findOne({ token: token, userId: decoded.userId, isActive: true, }); if (session) { request.sessionId = session._id; if (config.isDevelopment) { console.log("✓ Session found:", session._id); } } } catch (sessionError) { console.error("Error fetching session:", sessionError); // Don't fail auth if session lookup fails } if (!user) { if (config.isDevelopment) { console.log("✗ User not found in database"); console.log("=== END AUTH DEBUG ===\n"); } return reply.status(401).send({ error: "Unauthorized", message: "User not found", }); } if (config.isDevelopment) { console.log("✓ User authenticated:", user.username); console.log("=== END AUTH DEBUG ===\n"); } // Check if user is banned if (user.ban && user.ban.banned) { // Check if ban has expired if (user.ban.expires && new Date(user.ban.expires) <= new Date()) { // Ban expired, clear it user.ban.banned = false; user.ban.reason = null; user.ban.expires = null; await user.save(); } else { // User is currently banned // Allow access to /api/auth/me so frontend can get ban info and redirect const url = request.url || ""; const routeUrl = request.routeOptions?.url || ""; const isAuthMeEndpoint = url.includes("/auth/me") || routeUrl === "/me" || routeUrl.endsWith("/me"); if (!isAuthMeEndpoint) { // Block access to all other endpoints if (user.ban.expires) { return reply.status(403).send({ error: "Forbidden", message: "Your account is banned", reason: user.ban.reason, expires: user.ban.expires, }); } else { return reply.status(403).send({ error: "Forbidden", message: "Your account is permanently banned", reason: user.ban.reason, }); } } // If it's /api/auth/me, continue and attach user with ban info } } // Attach user to request request.user = user; } catch (error) { if (config.isDevelopment) { console.log("✗ Authentication error:", error.message); console.log("=== END AUTH DEBUG ===\n"); } if (error.message.includes("expired")) { return reply.status(401).send({ error: "TokenExpired", message: "Access token has expired", }); } return reply.status(401).send({ error: "Unauthorized", message: "Invalid access token", }); } }; /** * Optional authentication - doesn't fail if no token provided */ export const optionalAuthenticate = async (request, reply) => { try { let token = null; // Try to get token from Authorization header const authHeader = request.headers.authorization; if (authHeader && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); } // If not in header, try cookies if (!token && request.cookies && request.cookies.accessToken) { token = request.cookies.accessToken; } if (!token) { request.user = null; return; } // Verify token const decoded = verifyAccessToken(token); // Fetch user from database const user = await User.findById(decoded.userId).select( "-twoFactor.secret" ); if (user) { request.user = user; } else { request.user = null; } } catch (error) { // Don't fail on optional auth request.user = null; } }; /** * Middleware to check if user has required staff level * @param {number} requiredLevel - Minimum staff level required */ export const requireStaffLevel = (requiredLevel) => { return async (request, reply) => { if (!request.user) { return reply.status(401).send({ error: "Unauthorized", message: "Authentication required", }); } if (request.user.staffLevel < requiredLevel) { return reply.status(403).send({ error: "Forbidden", message: "Insufficient permissions", required: requiredLevel, current: request.user.staffLevel, }); } }; }; /** * Middleware to check if user has verified email */ export const requireVerifiedEmail = async (request, reply) => { if (!request.user) { return reply.status(401).send({ error: "Unauthorized", message: "Authentication required", }); } if (!request.user.email || !request.user.email.verified) { return reply.status(403).send({ error: "Forbidden", message: "Email verification required", }); } }; /** * Middleware to check if user has 2FA enabled when required */ export const require2FA = async (request, reply) => { if (!request.user) { return reply.status(401).send({ error: "Unauthorized", message: "Authentication required", }); } if (!request.user.twoFactor || !request.user.twoFactor.enabled) { return reply.status(403).send({ error: "Forbidden", message: "Two-factor authentication required", }); } }; /** * Middleware to verify refresh token */ export const verifyRefreshTokenMiddleware = async (request, reply) => { try { let token = null; // Try to get refresh token from cookies if (request.cookies && request.cookies.refreshToken) { token = request.cookies.refreshToken; } // Try to get from body if (!token && request.body && request.body.refreshToken) { token = request.body.refreshToken; } if (!token) { return reply.status(401).send({ error: "Unauthorized", message: "No refresh token provided", }); } // Import verifyRefreshToken here to avoid circular dependency const { verifyRefreshToken } = await import("../utils/jwt.js"); const decoded = verifyRefreshToken(token); // Fetch user from database const user = await User.findById(decoded.userId); if (!user) { return reply.status(401).send({ error: "Unauthorized", message: "User not found", }); } // Attach user and token to request request.user = user; request.refreshToken = token; } catch (error) { if (error.message.includes("expired")) { return reply.status(401).send({ error: "TokenExpired", message: "Refresh token has expired", }); } return reply.status(401).send({ error: "Unauthorized", message: "Invalid refresh token", }); } }; export default { authenticate, optionalAuthenticate, requireStaffLevel, requireVerifiedEmail, require2FA, verifyRefreshTokenMiddleware, };