Files
starr-philproperties/middleware/auth.middleware.js
T
kennethobsequio 89acdfc239 push
pushy

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-28 11:29:07 +08:00

112 lines
4.2 KiB
JavaScript

/***********************************************************************************************************************************************************************
* File Name: auth.middleware.js
* Type of Program: Middleware
* Description: JWT authentication guard.
* Reads the Bearer token from the Authorization header,
* verifies it, attaches decoded payload to req.user,
* and verifies the session is still active in the DB.
* Author: rgrgogu
* Date Created: Oct. 6, 2025
***********************************************************************************************************************************************************************
* HOW TO USE:
* const { authenticate } = require('../middleware/auth.middleware');
* router.get('/profile', authenticate, handler);
***********************************************************************************************************************************************************************/
const { verifyAccessToken } = require('../utils/token.util');
const mdl_Users = require('../models/users/users.mdl');
const R = require('../utils/response.util');
/**
* Validates JWT and loads user from DB.
* Attaches the full user record to req.user.
*/
const authenticate = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer '))
return R.error(res, 'No token provided.', 401);
const token = authHeader.split(' ')[1];
const decoded = verifyAccessToken(token);
const user = await mdl_Users.findByPk(decoded.user_id, {
attributes: { exclude: ['password', 'otp_code', 'otp_expires_at'] },
});
if (!user) return R.error(res, 'User not found.', 401);
if (!user.is_active) return R.error(res, 'Account is deactivated.', 403);
if (user.is_banned) {
const stillBanned = !user.ban_expires_at || new Date() < new Date(user.ban_expires_at);
if (stillBanned) {
return R.error(res, 'Your account has been suspended.', 403, {
banned: true,
ban_expires_at: user.ban_expires_at ?? null,
});
}
// Expired temporary ban — auto-lift so the user can log in again
await user.update({ is_banned: false, ban_expires_at: null });
}
req.user = user;
next();
} catch (err) {
if (err.name === 'TokenExpiredError')
return R.error(res, 'Token expired. Please log in again.', 401);
return R.error(res, 'Invalid token.', 401);
}
};
/**
* Soft authentication for endpoints that are useful both authenticated and unauthenticated.
*
* Behaviour:
* - No Authorization header → guest mode (req.user = null, continues)
* - Valid token → authenticated (req.user = user, continues)
* - Present but invalid/expired token → 401 (a token was attempted; reject it)
*
* The distinction between "no token" and "bad token" matters:
* silently ignoring a malformed token would allow it to be used as a scapegoat
* to probe the endpoint without being flagged as unauthenticated.
*/
const softAuthenticate = async (req, res, next) => {
const authHeader = req.headers.authorization;
// No token at all → guest mode
if (!authHeader?.startsWith('Bearer ')) {
req.user = null;
return next();
}
// Token present → validate it fully (same rules as authenticate)
try {
const token = authHeader.split(' ')[1];
const decoded = verifyAccessToken(token);
const user = await mdl_Users.findByPk(decoded.user_id, {
attributes: { exclude: ['password', 'otp_code', 'otp_expires_at'] },
});
if (!user || !user.is_active) {
req.user = null;
return next();
}
if (user.is_banned) {
const stillBanned = !user.ban_expires_at || new Date() < new Date(user.ban_expires_at);
if (stillBanned) {
req.user = null;
return next();
}
await user.update({ is_banned: false, ban_expires_at: null });
}
req.user = user;
next();
} catch (err) {
if (err.name === 'TokenExpiredError')
return R.error(res, 'Token expired. Please log in again.', 401);
return R.error(res, 'Invalid token.', 401);
}
};
module.exports = { authenticate, softAuthenticate };