import { JwtUtils } from "@utils/jwt.utils.js";
import jwt from "jsonwebtoken";
import { logger } from "../config/logger.js";
import { User } from "../models/User.model.js";
/**
* Middleware verifying bearer tokens and populating `req.user`.
* @category Middlewares
* @param {IAuthRequest} req Express request containing auth headers.
* @param {Response} res Express response instance.
* @param {NextFunction} next Express next handler.
* @returns {Promise<void>} Resolves when the request is forwarded.
*/
export async function authenticateToken(req, res, next) {
try {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
if (!token) {
res.status(401).json({
success: false,
message: "Access token required",
});
return;
}
// Verify the token using JWT utils
const decoded = JwtUtils.verifyAccessToken(token);
// Check if user still exists
const user = await User.findById(decoded.userId).select("-passwordHash");
if (!user) {
res.status(401).json({
success: false,
message: "User not found",
});
return;
}
// Attach user info to request
req.user = {
userId: user._id.toString(),
email: user.email,
name: user.name,
role: user.role,
};
next();
}
catch (error) {
logger.error({
error,
message: "Authentication error",
});
if (error instanceof jwt.JsonWebTokenError) {
res.status(401).json({
success: false,
message: "Invalid token",
});
}
else if (error instanceof jwt.TokenExpiredError) {
res.status(401).json({
success: false,
message: "Token expired",
});
}
else {
res.status(500).json({
success: false,
message: "Authentication failed",
});
}
}
}
/**
* Middleware ensuring the requester is authenticated as an admin.
* @category Middlewares
* @param {IAuthRequest} req Express request with authenticated user context.
* @param {Response} res Express response instance.
* @param {NextFunction} next Express next handler.
* @returns {void}
*/
export const requireAdmin = (req, res, next) => {
if (!req.user) {
res.status(401).json({
success: false,
message: "Authentication required",
});
return;
}
if (req.user.role !== "admin") {
res.status(403).json({
success: false,
message: "Admin access required",
});
return;
}
next();
};
/**
* Middleware ensuring the requester is authenticated.
* @category Middlewares
* @param {IAuthRequest} req Express request with authenticated user context.
* @param {Response} res Express response instance.
* @param {NextFunction} next Express next handler.
* @returns {void}
*/
export const requireAuth = (req, res, next) => {
if (!req.user) {
res.status(401).json({
success: false,
message: "Authentication required",
});
return;
}
next();
};
/**
* Optional authentication middleware
*
* Attempts to authenticate the user if an access token is provided in the Authorization header,
* but never fails the request. This is useful for endpoints that should work both with and without
* authentication (e.g., logout endpoint that should clear cache even with expired tokens).
* @category Middlewares
* @param {IAuthRequest} req Express request containing auth headers.
* @param {Response} _res Express response instance.
* @param {NextFunction} next Express next handler.
* @returns {Promise<void>} Resolves when the request is forwarded.
*/
export async function optionalAuthenticateToken(req, _res, next) {
try {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
if (!token) {
// No token provided, continue without authentication
next();
return;
}
try {
// Try to verify the token first
const decoded = JwtUtils.verifyAccessToken(token);
// Check if user still exists
const user = await User.findById(decoded.userId).select("-passwordHash");
if (user) {
// Attach user info to request
req.user = {
userId: user._id.toString(),
email: user.email,
name: user.name,
role: user.role,
};
}
}
catch (error) {
// If verification fails (e.g., token expired), try to decode without verification
// This is useful for logout where we want to clear cache even with expired tokens
if (error instanceof jwt.TokenExpiredError || error instanceof jwt.JsonWebTokenError) {
try {
const decoded = jwt.decode(token);
if (decoded && typeof decoded === "object" && "userId" in decoded) {
// Attach minimal user info with just userId for cache operations
req.user = {
userId: decoded.userId,
email: "",
name: "",
role: "sme",
};
}
}
catch (decodeError) {
// If decode also fails, just continue without user info
logger.debug({ error: decodeError }, "Could not decode token in optional auth");
}
}
}
next();
}
catch (error) {
// On any unexpected error, just continue without authentication
logger.debug({ error }, "Optional authentication error, continuing without auth");
next();
}
}
Source