Merge pull request 'feat: implement admin-only middleware and enhance permission checks with logging' (#5) from middleware-admin-fix into main

Reviewed-on: #5
This commit is contained in:
Seazn 2026-01-18 19:54:38 +00:00
commit 75e8919b0d
8 changed files with 48 additions and 50 deletions

View File

@ -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' });
}

16
middleware/adminOnly.js Normal file
View File

@ -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;

View File

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

View File

@ -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';

View File

@ -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);

View File

@ -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';

View File

@ -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') {

View File

@ -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);