UNPKG

@dax-crafta/auth

Version:

A powerful, flexible, and secure authentication plugin for the Crafta framework. Supports JWT, social login, 2FA, RBAC, audit logging, and enterprise-grade security features.

129 lines (111 loc) 4.39 kB
// packages/auth/src/middlewares/auth.middleware.js const rateLimit = require('express-rate-limit'); const jwt = require('jsonwebtoken'); const createAuthMiddleware = (config) => { const noOpLimiter = (_req, _res, next) => next(); const rateLimitEnabled = config?.features?.rateLimit !== false; // Default generic limiter (used for non-sensitive endpoints) const genericLimiter = rateLimitEnabled ? rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, standardHeaders: true, legacyHeaders: false }) : noOpLimiter; // Sensitive endpoint limiters - tuned for production const loginLimiter = rateLimitEnabled ? rateLimit({ windowMs: 15 * 60 * 1000, max: config?.limits?.loginMax || 10, // default 10 attempts per 15m message: { success: false, error: 'Too many login attempts, please try later' }, standardHeaders: true, legacyHeaders: false }) : noOpLimiter; const twoFALimiter = rateLimitEnabled ? rateLimit({ windowMs: 15 * 60 * 1000, max: config?.limits?.twoFAMax || 20, // 20 per 15m message: { success: false, error: 'Too many 2FA attempts, please try later' }, standardHeaders: true, legacyHeaders: false }) : noOpLimiter; const forgotPasswordLimiter = rateLimitEnabled ? rateLimit({ windowMs: 60 * 60 * 1000, max: config?.limits?.forgotPasswordMax || 5, // 5 per hour message: { success: false, error: 'Too many password reset requests, please try later' }, standardHeaders: true, legacyHeaders: false }) : noOpLimiter; // Refresh token limiter const refreshLimiter = rateLimitEnabled ? rateLimit({ windowMs: 15 * 60 * 1000, max: config?.limits?.refreshMax || 30, message: { success: false, error: 'Too many refresh attempts' } }) : noOpLimiter; // Expose function to choose limiter per-route const limiterFor = (routeName) => { switch (routeName) { case 'login': return loginLimiter; case '2fa': return twoFALimiter; case 'forgotPassword': return forgotPasswordLimiter; case 'refreshToken': return refreshLimiter; case 'refresh': return refreshLimiter; default: return genericLimiter; } }; const extractToken = (req) => { const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { return authHeader.split(' ')[1]; } if (req.headers['x-access-token']) { return req.headers['x-access-token']; } return null; }; const verifyToken = (req, res, next) => { const token = extractToken(req); if (!token) { return res.status(401).json({ success: false, error: 'No token provided' }); } try { const decoded = jwt.verify(token, config.env.JWT_SECRET); // keep minimal user data on req.user req.user = { id: decoded.id, role: decoded.role, email: decoded.email }; next(); } catch (err) { return res.status(401).json({ success: false, error: 'Invalid or expired token' }); } }; const checkRole = (roles = []) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ success: false, error: 'Unauthorized' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, error: 'Insufficient permissions' }); } next(); }; }; // Ownership check middleware for IDOR protection: allow if owner or admin const checkOwnershipOrAdmin = (getTargetUserId) => { return async (req, res, next) => { try { const targetId = typeof getTargetUserId === 'function' ? getTargetUserId(req) : req.params.id; if (!req.user) return res.status(401).json({ success: false, error: 'Unauthorized' }); if (req.user.role === 'admin' || String(req.user.id) === String(targetId)) { return next(); } return res.status(403).json({ success: false, error: 'Insufficient permissions to modify this resource' }); } catch (err) { return res.status(500).json({ success: false, error: 'Server error' }); } }; }; return { rateLimiter: genericLimiter, limiterFor, verifyToken, checkRole, checkOwnershipOrAdmin }; }; module.exports = createAuthMiddleware;