Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 84 additions & 32 deletions server/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { verifyToken, verifyRefreshToken, generateAccessToken, setAccessTokenCookie } from "../utils/tokenHelper.js";
import { verifyToken, verifyRefreshToken, generateAccessToken, setAccessTokenCookie, clearAuthCookies } from "../utils/tokenHelper.js";
import User from "../models/User.js";
import ApiError from "../utils/ApiError.js";
import bcrypt from "bcryptjs";

/**
* Auth Middleware
Expand All @@ -13,6 +14,7 @@ import ApiError from "../utils/ApiError.js";
* we transparently issue a new access token cookie (silent refresh).
*/
const authMiddleware = async (req, res, next) => {

try {
let token = null;
let tokenSource = null;
Expand All @@ -35,6 +37,8 @@ const authMiddleware = async (req, res, next) => {
throw new ApiError(401, "Access denied. No token provided.");
}

let prefetchedUser = null;

let decoded;
try {
decoded = verifyToken(token);
Expand All @@ -44,50 +48,98 @@ const authMiddleware = async (req, res, next) => {
try {
const refreshDecoded = verifyRefreshToken(req.cookies.refreshToken);
const userId = refreshDecoded.userId || refreshDecoded.id || refreshDecoded._id;

// Validate refresh token against stored hash
const refreshUser = await User.findById(userId).select("+security.refreshTokenHash");
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (!refreshUser) {
throw new ApiError(401, "User not found.");
}

const storedHash = refreshUser.security?.refreshTokenHash;
if (!storedHash) {
clearAuthCookies(res);
throw new ApiError(401, "Session has been revoked. Please log in again.");
}

const isValid = await bcrypt.compare(
req.cookies.refreshToken,
storedHash
);

if (!isValid) {
await User.findByIdAndUpdate(userId, {
$unset: { "security.refreshTokenHash": "" }
});

clearAuthCookies(res);

throw new ApiError(
401,
"Invalid session. Please log in again."
);
}
const newAccessToken = generateAccessToken({
userId,
email: refreshDecoded.email,
role: refreshDecoded.role
userId: refreshUser._id,
email: refreshUser.email,
role: refreshUser.role
});
// Set only the new access token cookie — refresh token stays unchanged.
// Cookie options are centralised in setAccessTokenCookie (tokenHelper.js).

// Set only the new access token cookie
setAccessTokenCookie(res, newAccessToken);
decoded = { userId, email: refreshDecoded.email, role: refreshDecoded.role };
} catch {
throw new ApiError(401, "Session expired. Please log in again.");
}

decoded = {
userId: refreshUser._id,
email: refreshUser.email,
role: refreshUser.role
};

if (refreshUser.security) {
refreshUser.security.refreshTokenHash = undefined;
}
prefetchedUser = refreshUser;

} catch (error) {
if (error instanceof ApiError) {
throw error;
}

throw new ApiError(
401,
"Session expired. Please log in again."
);
}
} else {
throw new ApiError(401, "Invalid or expired token.");
}
}

const userId = decoded.userId || decoded.id || decoded._id;
const userId = decoded.userId || decoded.id || decoded._id;

if (!userId) {
throw new ApiError(401, "Invalid token payload.");
}
if (!userId) {
throw new ApiError(401, "Invalid token payload.");
}

const user = await User.findById(userId).select("-password");
const user = prefetchedUser ?? await User.findById(userId).select("-password");
if (!user) {
throw new ApiError(401, "User not found or account deleted.");
}

if (!user) {
throw new ApiError(401, "User not found or account deleted.");
}
req.user = user;
next();
} catch (error) {
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
success: false,
message: error.message
});
}

req.user = user;
next();
} catch (error) {
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
return res.status(401).json({
success: false,
message: error.message
});
message: "Invalid or expired token."
});
}

return res.status(401).json({
success: false,
message: "Invalid or expired token."
});
}
};
};

export default authMiddleware;