// middleware/fineAuthorize.js /** * Fine-Grained Authorization Helpers * * Provides reusable authorization functions for business logic * Checks if user can perform specific actions on resources */ /** * Authorize action on resource * * @param {Object} params * @param {Object} params.user - User object from req.user * @param {string} params.action - Action being performed (e.g., 'read', 'write', 'delete', 'create') * @param {string} params.resource - Resource type (e.g., 'order', 'user', 'listing') * @param {string} params.resourceOwnerId - ID of the resource owner (optional) * @param {Object} params.resourceData - Additional resource data for context (optional) * @returns {Object} { authorized: boolean, reason?: string } */ export function authorizeAction({ user, action, resource, resourceOwnerId = null, resourceData = {} }) { if (!user || !user.userId) { return { authorized: false, reason: 'User not authenticated' }; } const userRole = (user.role || '').toUpperCase(); const isAdmin = userRole === 'ADMIN'; // Admin can do everything if (isAdmin) { return { authorized: true }; } // Resource-specific authorization rules switch (resource.toLowerCase()) { case 'user': return authorizeUserAction({ user, action, resourceOwnerId }); case 'order': return authorizeOrderAction({ user, action, resourceOwnerId, resourceData }); case 'listing': return authorizeListingAction({ user, action, resourceOwnerId, resourceData }); case 'location': return authorizeLocationAction({ user, action, resourceOwnerId }); default: // Default: Users can access their own resources, admins can access all if (resourceOwnerId && resourceOwnerId === user.userId) { return { authorized: true }; } return { authorized: false, reason: `Access denied for ${resource}` }; } } /** * Authorize user-related actions */ function authorizeUserAction({ user, action, resourceOwnerId }) { // Users can read their own profile if (action === 'read' && resourceOwnerId === user.userId) { return { authorized: true }; } // Users can update their own profile if (action === 'write' && resourceOwnerId === user.userId) { return { authorized: true }; } // Users cannot delete accounts (should go through special endpoint) if (action === 'delete') { return { authorized: false, reason: 'Account deletion requires special process' }; } // Users cannot access other users' data if (resourceOwnerId && resourceOwnerId !== user.userId) { return { authorized: false, reason: 'Cannot access other users\' data' }; } return { authorized: true }; } /** * Authorize order-related actions */ function authorizeOrderAction({ user, action, resourceOwnerId, resourceData }) { // Users can create orders if (action === 'create') { // Verify user can create order for their tenantId if multi-tenant if (resourceData.tenantId && resourceData.tenantId !== user.tenantId) { return { authorized: false, reason: 'Cannot create order for different tenant' }; } return { authorized: true }; } // Users can read/update/delete their own orders if (resourceOwnerId === user.userId) { return { authorized: true }; } // Users cannot access other users' orders return { authorized: false, reason: 'Cannot access other users\' orders' }; } /** * Authorize listing-related actions */ function authorizeListingAction({ user, action, resourceOwnerId, resourceData }) { // Users can create listings if (action === 'create') { return { authorized: true }; } // Users can read all active listings (public) if (action === 'read' && resourceData.isActive !== false) { return { authorized: true }; } // Users can update/delete their own listings if ((action === 'write' || action === 'delete') && resourceOwnerId === user.userId) { return { authorized: true }; } // Users cannot modify other users' listings if (resourceOwnerId && resourceOwnerId !== user.userId) { return { authorized: false, reason: 'Cannot modify other users\' listings' }; } return { authorized: true }; } /** * Authorize location-related actions */ function authorizeLocationAction({ user, action, resourceOwnerId }) { // Users can manage their own locations if (resourceOwnerId === user.userId) { return { authorized: true }; } // Users cannot access other users' locations return { authorized: false, reason: 'Cannot access other users\' locations' }; } /** * Express middleware wrapper for fine-grained authorization * * Usage: * app.get('/orders/:orderId', * fineAuthorize({ action: 'read', resource: 'order', getResourceOwnerId: (req) => req.params.orderId }), * handler * ) */ export function fineAuthorize({ action, resource, getResourceOwnerId, getResourceData }) { return async function(req, res, next) { try { console.log(`[Fine Auth] Starting fine-grained authorization for ${req.method} ${req.path}`); console.log(`[Fine Auth] Action: ${action}, Resource: ${resource}`); console.log(`[Fine Auth] User:`, req.user ? { userId: req.user.userId, role: req.user.role, } : 'NOT SET'); const resourceOwnerId = getResourceOwnerId ? await getResourceOwnerId(req) : null; const resourceData = getResourceData ? await getResourceData(req) : {}; console.log(`[Fine Auth] Resource details:`, { resourceOwnerId, resourceData: Object.keys(resourceData), }); const result = authorizeAction({ user: req.user, action, resource, resourceOwnerId, resourceData, }); console.log(`[Fine Auth] Authorization result:`, { authorized: result.authorized, reason: result.reason || 'N/A', }); if (!result.authorized) { console.log(`[Fine Auth] ❌ FAILED: ${result.reason || 'Access denied'}`); // Log authorization failure if (req.auditLogger) { req.auditLogger.log({ userId: req.user?.userId, action: 'fine_authorization_failed', route: req.path, status: 'forbidden', meta: { action, resource, resourceOwnerId, reason: result.reason, }, }); } return res.status(403).json({ error: 'Forbidden', message: result.reason || 'Access denied', }); } console.log(`[Fine Auth] ✅ Authorization passed`); next(); } catch (err) { console.error('[Fine Auth] ❌ Fine authorization error:', err); return res.status(500).json({ error: 'Internal server error', message: 'Authorization check failed', }); } }; }