Files
TurboTrades/middleware/auth.js
iDefineHD d794c5ad48
All checks were successful
Build Frontend / Build Frontend (push) Successful in 10s
added ban redirect correctly
2026-01-11 03:45:47 +00:00

336 lines
9.0 KiB
JavaScript

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,
};