From 2a69b83d842e20ac0527504abd219445a1db9222 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Sun, 18 Jan 2026 20:50:31 +0100 Subject: [PATCH 1/2] feat: implement admin-only middleware and enhance permission checks with logging --- .../permissions/PermissionController.js | 10 ++++++- middleware/adminOnly.js | 16 ++++++++++ middleware/authMiddleware.js | 10 +++++++ routes/deleteRoutes.js | 7 +---- routes/getRoutes.js | 29 ++++++------------- routes/patchRoutes.js | 7 +---- routes/postRoutes.js | 10 +------ routes/putRoutes.js | 9 +----- 8 files changed, 48 insertions(+), 50 deletions(-) create mode 100644 middleware/adminOnly.js diff --git a/controller/permissions/PermissionController.js b/controller/permissions/PermissionController.js index 409dcf9..51a0424 100644 --- a/controller/permissions/PermissionController.js +++ b/controller/permissions/PermissionController.js @@ -1,6 +1,7 @@ const UnitOfWork = require('../../database/UnitOfWork'); const PermissionService = require('../../services/permissions/PermissionService'); const PermissionRepository = require('../../repositories/permissions/PermissionRepository'); +const { logger } = require('../../middleware/logger'); class PermissionController { static async list(req, res) { @@ -37,10 +38,17 @@ class PermissionController { static async getUserPermissions(req, res) { // Access control: only self or admin/super_admin can view const requestedUserId = Number(req.params.id); - const requesterUserId = req.user.userId; + const requesterUserId = Number(req.user.userId ?? req.user.id ?? req.user.sub); const requesterRole = req.user.role; if (requestedUserId !== requesterUserId && requesterRole !== 'admin' && requesterRole !== 'super_admin') { + const requesterIdLog = Number.isNaN(requesterUserId) ? (req.user.userId ?? req.user.id ?? req.user.sub) : requesterUserId; + logger.warn('PermissionController.getUserPermissions:forbidden', { + requestedUserId, + requesterUserId: requesterIdLog, + requesterRole, + route: req.originalUrl + }); return res.status(403).json({ success: false, message: 'Forbidden' }); } diff --git a/middleware/adminOnly.js b/middleware/adminOnly.js new file mode 100644 index 0000000..efcb1a3 --- /dev/null +++ b/middleware/adminOnly.js @@ -0,0 +1,16 @@ +const { logger } = require('./logger'); + +function adminOnly(req, res, next) { + const role = req.user?.role; + if (!role || !['admin', 'super_admin'].includes(role)) { + logger.warn('adminOnly:forbidden', { + role, + route: req.originalUrl, + method: req.method + }); + return res.status(403).json({ success: false, message: 'Admin role required' }); + } + next(); +} + +module.exports = adminOnly; diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js index 4f0ce47..9af2b9e 100644 --- a/middleware/authMiddleware.js +++ b/middleware/authMiddleware.js @@ -4,6 +4,10 @@ const { logger } = require('./logger'); function authMiddleware(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { + logger.warn('authMiddleware:missingToken', { + method: req.method, + route: req.originalUrl + }); return res.status(401).json({ success: false, message: 'No access token provided' }); } @@ -33,9 +37,15 @@ function authMiddleware(req, res, next) { // console fallback for local dev console.log('[authMiddleware] verified', authDebug); + // Normalize common user fields for downstream checks + const normalizedUserId = payload.userId ?? payload.id ?? payload.sub; + const normalizedRole = payload.role ?? payload.userRole ?? payload.user_role ?? payload.type; + // Attach user info to request (with normalized userType for downstream checks) req.user = { ...payload, + userId: normalizedUserId, + role: normalizedRole ?? payload.role, userType: payload.userType ?? payload.user_type, user_type: payload.user_type ?? payload.userType }; diff --git a/routes/deleteRoutes.js b/routes/deleteRoutes.js index bce2ff9..dca35e9 100644 --- a/routes/deleteRoutes.js +++ b/routes/deleteRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const authMiddleware = require('../middleware/authMiddleware'); +const adminOnly = require('../middleware/adminOnly'); const AdminUserController = require('../controller/admin/AdminUserController'); const DocumentTemplateController = require('../controller/documentTemplate/DocumentTemplateController'); const CompanyStampController = require('../controller/companyStamp/CompanyStampController'); @@ -10,12 +11,6 @@ const AffiliateController = require('../controller/affiliate/AffiliateController const NewsController = require('../controller/news/NewsController'); // Helper middlewares for company-stamp -function adminOnly(req, res, next) { - if (!req.user || !['admin','super_admin'].includes(req.user.role)) { - return res.status(403).json({ error: 'Admin role required' }); - } - next(); -} function forceCompanyForAdmin(req, res, next) { if (req.user && ['admin','super_admin'].includes(req.user.role) && req.user.user_type !== 'company') { req.user.user_type = 'company'; diff --git a/routes/getRoutes.js b/routes/getRoutes.js index faee98e..5942ff7 100644 --- a/routes/getRoutes.js +++ b/routes/getRoutes.js @@ -3,6 +3,7 @@ const path = require('path'); const router = express.Router(); const authMiddleware = require('../middleware/authMiddleware'); +const adminOnly = require('../middleware/adminOnly'); const UserSettingsController = require('../controller/auth/UserSettingsController'); const ReferralTokenController = require('../controller/referral/ReferralTokenController'); const ReferralRegistrationController = require('../controller/referral/ReferralRegistrationController'); @@ -24,18 +25,6 @@ const NewsController = require('../controller/news/NewsController'); const InvoiceController = require('../controller/invoice/InvoiceController'); // NEW // small helpers copied from original files -function adminOnly(req, res, next) { - if (!req.user || !['admin', 'super_admin'].includes(req.user.role)) { - return res.status(403).json({ error: 'Forbidden: Admins only' }); - } - next(); -} -function requireAdmin(req, res, next) { - if (!req.user || req.user.role !== 'admin') { - return res.status(403).json({ success: false, message: 'Forbidden: Admins only.' }); - } - next(); -} // NEW helper used by company-stamp routes function forceCompanyForAdmin(req, res, next) { @@ -55,19 +44,19 @@ router.get('/users/:id/full', authMiddleware, UserController.getFullUserData); router.get('/user/settings', authMiddleware, UserSettingsController.getSettings); router.get('/users/:id/permissions', authMiddleware, PermissionController.getUserPermissions); router.get('/admin/users/:id/full', authMiddleware, adminOnly, AdminUserController.getFullUserAccountDetails); -router.get('/admin/users/:id/detailed', authMiddleware, requireAdmin, AdminUserController.getDetailedUserInfo); +router.get('/admin/users/:id/detailed', authMiddleware, adminOnly, AdminUserController.getDetailedUserInfo); router.get('/users/:id/documents', authMiddleware, UserController.getUserDocumentsAndContracts); router.get('/verify-password-reset', (req, res) => { /* Note: was moved from PasswordResetController.verifyPasswordResetToken */ res.status(204).end(); }); // keep placeholder if controller already registered via other verb // admin.js GETs -router.get('/admin/user-stats', authMiddleware, requireAdmin, AdminUserController.getUserStats); -router.get('/admin/user-list', authMiddleware, requireAdmin, AdminUserController.getUserList); -router.get('/admin/verification-pending-users', authMiddleware, requireAdmin, AdminUserController.getVerificationPendingUsers); -router.get('/admin/unverified-users', authMiddleware, requireAdmin, AdminUserController.getUnverifiedUsers); -router.get('/admin/user/:id/documents', authMiddleware, requireAdmin, UserDocumentController.getAllDocumentsForUser); -router.get('/admin/server-status', authMiddleware, requireAdmin, ServerStatusController.getStatus); +router.get('/admin/user-stats', authMiddleware, adminOnly, AdminUserController.getUserStats); +router.get('/admin/user-list', authMiddleware, adminOnly, AdminUserController.getUserList); +router.get('/admin/verification-pending-users', authMiddleware, adminOnly, AdminUserController.getVerificationPendingUsers); +router.get('/admin/unverified-users', authMiddleware, adminOnly, AdminUserController.getUnverifiedUsers); +router.get('/admin/user/:id/documents', authMiddleware, adminOnly, UserDocumentController.getAllDocumentsForUser); +router.get('/admin/server-status', authMiddleware, adminOnly, ServerStatusController.getStatus); // Contract preview for admin: latest active by user type -router.get('/admin/contracts/:id/preview', authMiddleware, requireAdmin, DocumentTemplateController.previewLatestForUser); +router.get('/admin/contracts/:id/preview', authMiddleware, adminOnly, DocumentTemplateController.previewLatestForUser); // permissions.js GETs router.get('/permissions', authMiddleware, PermissionController.list); diff --git a/routes/patchRoutes.js b/routes/patchRoutes.js index b75250b..32f7bf1 100644 --- a/routes/patchRoutes.js +++ b/routes/patchRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const authMiddleware = require('../middleware/authMiddleware'); +const adminOnly = require('../middleware/adminOnly'); const DocumentTemplateController = require('../controller/documentTemplate/DocumentTemplateController'); const CompanyStampController = require('../controller/companyStamp/CompanyStampController'); // <-- added const CoffeeController = require('../controller/admin/CoffeeController'); @@ -16,12 +17,6 @@ const multer = require('multer'); const upload = multer({ storage: multer.memoryStorage() }); // Helper middlewares for company-stamp -function adminOnly(req, res, next) { - if (!req.user || !['admin','super_admin'].includes(req.user.role)) { - return res.status(403).json({ error: 'Admin role required' }); - } - next(); -} function forceCompanyForAdmin(req, res, next) { if (req.user && ['admin','super_admin'].includes(req.user.role) && req.user.user_type !== 'company') { req.user.user_type = 'company'; diff --git a/routes/postRoutes.js b/routes/postRoutes.js index 1325377..4bd7f55 100644 --- a/routes/postRoutes.js +++ b/routes/postRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const authMiddleware = require('../middleware/authMiddleware'); +const adminOnly = require('../middleware/adminOnly'); // Controllers used by POST routes const LoginController = require('../controller/login/LoginController'); @@ -107,15 +108,6 @@ router.post('/admin/send-password-reset/:userId', authMiddleware, adminOnly, asy } }); -// Helper middleware for company-stamp routes -function adminOnly(req, res, next) { - if (!req.user || !['admin','super_admin'].includes(req.user.role)) { - return res.status(403).json({ error: 'Admin role required' }); - } - next(); -} - - // NEW: ensure service sees a "company" user_type for admin users function forceCompanyForAdmin(req, res, next) { if (req.user && ['admin','super_admin'].includes(req.user.role) && req.user.user_type !== 'company') { diff --git a/routes/putRoutes.js b/routes/putRoutes.js index c25b4d6..d219cc1 100644 --- a/routes/putRoutes.js +++ b/routes/putRoutes.js @@ -2,20 +2,13 @@ const express = require('express'); const router = express.Router(); const authMiddleware = require('../middleware/authMiddleware'); +const adminOnly = require('../middleware/adminOnly'); const AdminUserController = require('../controller/admin/AdminUserController'); const DocumentTemplateController = require('../controller/documentTemplate/DocumentTemplateController'); const CoffeeController = require('../controller/admin/CoffeeController'); const multer = require('multer'); const upload = multer({ storage: multer.memoryStorage() }); -// Helper middleware for admin-only routes (keeps consistency with other route files) -function adminOnly(req, res, next) { - if (!req.user || !['admin','super_admin'].includes(req.user.role)) { - return res.status(403).json({ error: 'Admin role required' }); - } - next(); -} - // PUT /admin/users/:id/permissions (moved from routes/admin.js) router.put('/admin/users/:id/permissions', authMiddleware, adminOnly, AdminUserController.updateUserPermissions); From 2d1557d5ec2353d26fef967dde7ba05adc575ab6 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Sun, 18 Jan 2026 21:09:18 +0100 Subject: [PATCH 2/2] feat: add caching prevention headers to getUserPermissions method --- controller/permissions/PermissionController.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/controller/permissions/PermissionController.js b/controller/permissions/PermissionController.js index 51a0424..02d50c7 100644 --- a/controller/permissions/PermissionController.js +++ b/controller/permissions/PermissionController.js @@ -36,6 +36,11 @@ class PermissionController { } static async getUserPermissions(req, res) { + // Prevent caching of permission responses + res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private'); + res.set('Pragma', 'no-cache'); + res.set('Vary', 'Authorization'); + // Access control: only self or admin/super_admin can view const requestedUserId = Number(req.params.id); const requesterUserId = Number(req.user.userId ?? req.user.id ?? req.user.sub);