UNPKG

@sehirapp/core-microservice

Version:

Modern mikroservis core paketi - MongoDB 6.7, Express API, Mongoose, PM2 cluster desteği

525 lines (458 loc) 16.2 kB
/** * Authentication Middleware Suite - Core mikroservisler için kimlik doğrulama * Multi-tenant, city-based, user/admin rol desteği */ import { logger } from '../logger.js'; /** * Multi-tenant şehir anahtarı doğrulaması * Header'dan city-key'i alır ve doğrular */ export const requireCity = (req, res, next) => { try { const cityKey = req.headers['x-city-key'] || req.headers['city-key'] || req.headers['x-city']; if (!cityKey) { logger.warn('City key missing in request', { path: req.path, method: req.method, ip: req.ip, userAgent: req.get('User-Agent') }); return res.status(400).json({ success: false, error: 'City key is required', code: 'MISSING_CITY_KEY', timestamp: Date.now() }); } // City key formatını doğrula if (typeof cityKey !== 'string' || cityKey.length < 3) { logger.warn('Invalid city key format', { cityKey: cityKey.substring(0, 10) + '...', // Güvenlik için kısalt path: req.path, method: req.method, ip: req.ip }); return res.status(400).json({ success: false, error: 'Invalid city key format', code: 'INVALID_CITY_KEY', timestamp: Date.now() }); } // City key'i request'e ekle req.cityKey = cityKey; req.city = { key: cityKey, id: cityKey, // Alias for compatibility validated: true }; logger.debug('City key validated', { cityKey: cityKey.substring(0, 10) + '...', path: req.path, method: req.method }); next(); } catch (error) { logger.error('City key validation error', { error: error.message, path: req.path, method: req.method, stack: error.stack }); return res.status(500).json({ success: false, error: 'Internal server error during city validation', code: 'CITY_VALIDATION_ERROR', timestamp: Date.now() }); } }; /** * City key'i request'e atar (zorunlu değil) */ export const setCityKey = (req, res, next) => { const cityKey = req.headers['x-city-key'] || req.headers['city-key']; if (cityKey) { req.cityKey = cityKey; req.city = { key: cityKey, id: cityKey, validated: false // Optional olduğu için validated false }; } next(); }; /** * Kullanıcı kimlik doğrulaması * API Gateway'in token doğrulamış olduğunu varsayar */ export const requireUser = (req, res, next) => { try { // API Gateway'den gelen user bilgisi const userId = req.headers['x-user-id']; const userRole = req.headers['x-user-role']; const userEmail = req.headers['x-user-email']; const userToken = req.headers['authorization']; if (!userId) { logger.warn('User ID missing in request', { path: req.path, method: req.method, ip: req.ip, hasToken: !!userToken }); return res.status(401).json({ success: false, error: 'User authentication required', code: 'USER_AUTH_REQUIRED', timestamp: Date.now() }); } // User bilgilerini request'e ekle req.user = { id: userId, role: userRole || 'user', email: userEmail, authenticated: true, token: userToken }; logger.debug('User authenticated', { userId, userRole, path: req.path, method: req.method }); next(); } catch (error) { logger.error('User authentication error', { error: error.message, path: req.path, method: req.method, stack: error.stack }); return res.status(500).json({ success: false, error: 'Internal server error during user authentication', code: 'USER_AUTH_ERROR', timestamp: Date.now() }); } }; /** * Admin rol doğrulaması */ export const requireAdmin = (req, res, next) => { try { // Önce user doğrulamasını yap requireUser(req, res, (err) => { if (err) return next(err); const userRole = req.user?.role; const allowedRoles = ['admin', 'super_admin', 'system_admin']; if (!allowedRoles.includes(userRole)) { logger.warn('Admin access denied', { userId: req.user?.id, userRole, requiredRoles: allowedRoles, path: req.path, method: req.method, ip: req.ip }); return res.status(403).json({ success: false, error: 'Admin access required', code: 'ADMIN_ACCESS_REQUIRED', timestamp: Date.now() }); } // Admin bilgilerini ekle req.admin = { id: req.user.id, role: req.user.role, level: userRole === 'super_admin' ? 'super' : 'standard' }; logger.debug('Admin access granted', { adminId: req.admin.id, adminRole: req.admin.role, path: req.path, method: req.method }); next(); }); } catch (error) { logger.error('Admin authentication error', { error: error.message, path: req.path, method: req.method, stack: error.stack }); return res.status(500).json({ success: false, error: 'Internal server error during admin authentication', code: 'ADMIN_AUTH_ERROR', timestamp: Date.now() }); } }; /** * Resource ownership doğrulaması factory * @param {CoreClass} Model - Model sınıfı * @param {string} idField - ID alanının adı (default: 'id') * @param {string} ownerField - Sahiplik alanının adı (default: 'userId') */ export const requireOwnership = (Model, idField = 'id', ownerField = 'userId') => { return async (req, res, next) => { try { // User doğrulaması gerekli if (!req.user?.id) { return res.status(401).json({ success: false, error: 'User authentication required for ownership check', code: 'USER_AUTH_REQUIRED', timestamp: Date.now() }); } const resourceId = req.params[idField]; if (!resourceId) { return res.status(400).json({ success: false, error: `Resource ${idField} parameter is required`, code: 'MISSING_RESOURCE_ID', timestamp: Date.now() }); } // Resource'u bul ve sahiplik kontrol et const modelInstance = new Model(); const found = await modelInstance.find({ [idField]: resourceId }); if (!found) { logger.warn('Resource not found for ownership check', { resourceId, userId: req.user.id, model: Model.name, path: req.path }); return res.status(404).json({ success: false, error: 'Resource not found', code: 'RESOURCE_NOT_FOUND', timestamp: Date.now() }); } // Sahiplik kontrolü - Admin bypass if (req.admin || req.user.role === 'admin') { logger.debug('Ownership check bypassed for admin', { resourceId, adminId: req.user.id, model: Model.name }); } else if (modelInstance[ownerField] !== req.user.id) { logger.warn('Ownership validation failed', { resourceId, userId: req.user.id, ownerId: modelInstance[ownerField], model: Model.name, path: req.path }); return res.status(403).json({ success: false, error: 'You do not have access to this resource', code: 'OWNERSHIP_REQUIRED', timestamp: Date.now() }); } // Resource'u request'e ekle req.resource = modelInstance; req.resourceId = resourceId; logger.debug('Ownership validated', { resourceId, userId: req.user.id, model: Model.name }); next(); } catch (error) { logger.error('Ownership validation error', { error: error.message, resourceId: req.params[idField], userId: req.user?.id, model: Model?.name, stack: error.stack }); return res.status(500).json({ success: false, error: 'Internal server error during ownership validation', code: 'OWNERSHIP_VALIDATION_ERROR', timestamp: Date.now() }); } }; }; /** * Request alanları doğrulama middleware'i * @param {Array<string>} requiredFields - Zorunlu alanlar * @param {string} source - Kaynak ('body', 'query', 'params') */ export const validateRequired = (requiredFields = [], source = 'body') => { return (req, res, next) => { try { const data = req[source]; const missing = []; if (!data || typeof data !== 'object') { return res.status(400).json({ success: false, error: `Request ${source} is required`, code: 'MISSING_REQUEST_DATA', timestamp: Date.now() }); } requiredFields.forEach(field => { if (data[field] === undefined || data[field] === null || data[field] === '') { missing.push(field); } }); if (missing.length > 0) { logger.warn('Required fields missing', { missing, source, path: req.path, method: req.method, userId: req.user?.id }); return res.status(400).json({ success: false, error: 'Required fields are missing', code: 'MISSING_REQUIRED_FIELDS', details: { missing, source }, timestamp: Date.now() }); } logger.debug('Required fields validated', { fields: requiredFields, source, path: req.path }); next(); } catch (error) { logger.error('Field validation error', { error: error.message, requiredFields, source, path: req.path, stack: error.stack }); return res.status(500).json({ success: false, error: 'Internal server error during field validation', code: 'FIELD_VALIDATION_ERROR', timestamp: Date.now() }); } }; }; /** * Request loglama middleware'i */ export const logRequest = (req, res, next) => { const startTime = Date.now(); // Response'u intercept et const originalSend = res.send; res.send = function(body) { const duration = Date.now() - startTime; logger.info('Request completed', { method: req.method, path: req.path, statusCode: res.statusCode, duration: `${duration}ms`, userId: req.user?.id, cityKey: req.cityKey ? req.cityKey.substring(0, 10) + '...' : null, ip: req.ip, userAgent: req.get('User-Agent'), contentLength: body ? Buffer.byteLength(body, 'utf8') : 0 }); return originalSend.call(this, body); }; logger.debug('Request started', { method: req.method, path: req.path, query: req.query, userId: req.user?.id, cityKey: req.cityKey ? req.cityKey.substring(0, 10) + '...' : null, ip: req.ip }); next(); }; /** * Rate limiting ve güvenlik kontrolleri * @param {Object} options - Rate limiting seçenekleri */ export const rateLimitByUser = (options = {}) => { const { windowMs = 15 * 60 * 1000, // 15 dakika maxRequests = 100, skipSuccessfulRequests = false, skipFailedRequests = false } = options; const userLimits = new Map(); return (req, res, next) => { const userId = req.user?.id || req.ip; const now = Date.now(); const windowStart = now - windowMs; // Kullanıcı verilerini al veya oluştur if (!userLimits.has(userId)) { userLimits.set(userId, { requests: [], firstRequest: now }); } const userLimit = userLimits.get(userId); // Eski istekleri temizle userLimit.requests = userLimit.requests.filter(time => time > windowStart); // Limit kontrolü if (userLimit.requests.length >= maxRequests) { logger.warn('Rate limit exceeded', { userId, requestCount: userLimit.requests.length, maxRequests, windowMs, path: req.path, ip: req.ip }); return res.status(429).json({ success: false, error: 'Too many requests', code: 'RATE_LIMIT_EXCEEDED', details: { maxRequests, windowMs, retryAfter: Math.ceil(windowMs / 1000) }, timestamp: Date.now() }); } // İsteği kaydet userLimit.requests.push(now); next(); }; }; // Kombine middleware setleri export const userAuth = [requireUser, requireCity, logRequest]; export const adminAuth = [requireAdmin, setCityKey, logRequest]; export const publicWithCity = [requireCity, logRequest]; export const publicOptional = [setCityKey, logRequest]; // Tüm auth middleware'leri dışa aktar export const authMiddleware = { requireCity, setCityKey, requireUser, requireAdmin, requireOwnership, validateRequired, logRequest, rateLimitByUser, // Kombine setler userAuth, adminAuth, publicWithCity, publicOptional }; export default authMiddleware;