Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions src/__tests__/authorize.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const authorize = require('../middlewares/authorize');

describe('Authorize Middleware', () => {
let req;
let res;
let next;

beforeEach(() => {
req = {
user: null,
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
};
next = jest.fn();
});

it('should call next if user has an allowed role', () => {
req.user = { role: 'admin' };
const middleware = authorize('admin', 'superadmin');

middleware(req, res, next);

expect(next).toHaveBeenCalledWith();
expect(next).not.toHaveBeenCalledWith(expect.any(Error));
});

it('should return 401 if user is not authenticated', () => {
req.user = null;
const middleware = authorize('admin');

middleware(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
statusCode: 401,
message: 'Authentication required'
}));
});

it('should return 403 if user role is not allowed', () => {
req.user = { role: 'user' };
const middleware = authorize('admin');

middleware(req, res, next);

expect(next).toHaveBeenCalledWith(expect.objectContaining({
statusCode: 403,
message: 'Access forbidden: insufficient permissions'
}));
});

it('should work with multiple allowed roles', () => {
req.user = { role: 'editor' };
const middleware = authorize('admin', 'editor');

middleware(req, res, next);

expect(next).toHaveBeenCalledWith();
});
});
4 changes: 4 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { getLoggerStream } = require('./utils/logger');
const { globalLimiter, authLimiter } = require('./middlewares/rateLimiter');
const authRoutes = require('./routes/auth.routes');
const protectedRoutes = require('./routes/protected.routes');
const adminRoutes = require('./routes/admin.routes');

const app = express();

Expand Down Expand Up @@ -40,6 +41,9 @@ app.use('/api/auth', authLimiter, authRoutes);
// Protected routes
app.use('/api/protected', protectedRoutes);

// Admin routes
app.use('/api/admin', adminRoutes);

// Global error handling middleware - must be registered last
const errorHandler = require('./middlewares/errorHandler');
app.use(errorHandler);
Expand Down
29 changes: 29 additions & 0 deletions src/middlewares/authorize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Middleware to restrict access by user role
* @param {...string} allowedRoles - Roles allowed to access the route
* @returns {Function} Middleware function
*/
const authorize = (...allowedRoles) => {
return (req, res, next) => {
// Check if user is authenticated (req.user must be populated by auth middleware)
if (!req.user) {
const error = new Error('Authentication required');
error.statusCode = 401;
error.isOperational = true;
return next(error);
}

// Check if user's role is in the list of allowed roles
if (!allowedRoles.includes(req.user.role)) {
const error = new Error('Access forbidden: insufficient permissions');
error.statusCode = 403;
error.isOperational = true;
return next(error);
}

// User is authorized
next();
};
};

module.exports = authorize;
41 changes: 41 additions & 0 deletions src/routes/admin.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const express = require('express');
const authenticate = require('../middlewares/auth');
const authorize = require('../middlewares/authorize');
const { sendSuccess } = require('../utils/response');

const router = express.Router();

/**
* Admin Routes
* All routes in this router require authentication and 'admin' role
*/

// Global middleware for this router
router.use(authenticate);
router.use(authorize('admin'));

// GET /api/admin/dashboard - Admin dashboard data
router.get('/dashboard', (req, res) => {
sendSuccess(res, {
admin: {
id: req.user._id,
fullName: req.user.fullName,
role: req.user.role
},
stats: {
totalUsers: 0,
activeCampaigns: 0,
pendingVerifications: 0
}
}, 200, 'Admin dashboard statistics retrieved');
});

// GET /api/admin/users - List all users
router.get('/users', (req, res) => {
sendSuccess(res, {
users: [],
message: 'User management system placeholder'
}, 200, 'Users retrieved successfully');
});

module.exports = router;