nehonix-uri-processor
Version:
A powerful URI processor for encoding, decoding, and analyzing URI data securely.
410 lines • 16.9 kB
JavaScript
import { NSB } from "../../../services/NehonixSecurityBooster.service";
import { AppLogger } from "../../../common/AppLogger";
import { defaultOptions, getClientIP, rateLimitStore, suspiciousIPs, } from "./EXPRESS.config";
import { activeDatabase, defaultDatabase, } from "./NEHONIX.LocalMemory";
import { combineResults } from "./EXPRESS.combineResults";
import { applySecureHeaders, generateRequestId, generateTimelineData, sanitizeOutputData, } from "./helpers/EXPRESS.helper";
/**
* Enhanced NSB Express middleware for securing incoming requests
* @param options - Middleware options
* @returns Express middleware function
*/
export const nehonixShieldMiddleware = (options = {}) => {
// Merge with default options
const mergedOptions = { ...defaultOptions, ...options };
return async (req, res, next) => {
const clientIP = getClientIP(req);
try {
// Check if IP is blacklisted (either in options or database)
if (mergedOptions.ipBlacklist?.includes(clientIP) ||
(await activeDatabase.isIPBlocked(clientIP))) {
// Log security event
await activeDatabase.saveSecurityEvent({
timestamp: Date.now(),
type: "block",
ip: clientIP,
url: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
method: req.method,
});
return res.status(403).json({
error: "Access denied",
message: "Your IP address has been blacklisted",
requestId: generateRequestId(),
});
}
// IP whitelist bypass
if (mergedOptions.ipWhitelist?.includes(clientIP)) {
return next();
}
// Rate limiting check
if (mergedOptions.enableRateLimit &&
!checkRateLimit(req, res, mergedOptions)) {
return;
}
// Bypass token check
if (mergedOptions.bypassHeader &&
mergedOptions.bypassToken &&
req.get(mergedOptions.bypassHeader) === mergedOptions.bypassToken) {
return next();
}
// Secure headers if enabled
if (mergedOptions.secureHeaders) {
applySecureHeaders(res);
}
// Analyze request components
const result = await scanRequest(req, mergedOptions.scanComponents || ["url", "body"], mergedOptions);
if (mergedOptions.logDetails) {
const fullUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
AppLogger.info(`NSB Analysis for ${fullUrl}:`, result);
}
// Check if request is malicious based on score or detection
const isBlocked = (result.isMalicious && mergedOptions.blockOnMalicious) ||
result.score >= (mergedOptions.scoreThreshold || 80);
if (isBlocked) {
// Create security event
const securityEvent = {
timestamp: Date.now(),
type: "block",
ip: clientIP,
url: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
method: req.method,
score: result.score,
patterns: result.detectedPatterns,
};
// Update suspicious IP tracking with detailed information
await trackSuspiciousIP(clientIP, {
lastBlockReason: result.detectedPatterns
.map((p) => p.type)
.join(", "),
lastBlockedUrl: req.originalUrl,
lastBlockedMethod: req.method,
score: result.score,
});
// Log security event to database
await activeDatabase.saveSecurityEvent(securityEvent);
// If automatic blocking is enabled and score is very high, add to permanent block list
if (mergedOptions.automaticBlocking && result.score >= 150) {
await activeDatabase.blockIP(clientIP, `Automatic block - high threat score: ${result.score}`);
}
// Use custom handler if provided
if (mergedOptions.customBlockHandler) {
return mergedOptions.customBlockHandler(req, res, result);
}
// Default blocking response
return res.status(403).json({
error: "Security violation detected",
details: result.detectedPatterns,
recommendation: result.recommendation,
requestId: generateRequestId(),
});
}
// Add analysis result to request object
req.nehonixShield = result;
// Transform responses if enabled
if (mergedOptions.transformResponse) {
const originalSend = res.send;
res.send = function (body) {
// Apply output transformations to prevent data leakage
const sanitizedBody = typeof body === "string" ? sanitizeOutputData(body) : body;
return originalSend.call(this, sanitizedBody);
};
}
next();
}
catch (error) {
AppLogger.error("Nehonix Shield Middleware Error:", error);
next(error);
}
};
};
/**
* Utility to analyze specific request components
* @param req - Express request object
* @param components - Components to analyze (url, headers, query, body)
* @parascanRequestm options - NSB analysis options
* @returns Analysis result
*/
export const scanRequest = async (req, components = ["url"], options = {}) => {
const results = [];
if (components.includes("url")) {
const fullUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const urlResult = await NSB.analyzeUrl(fullUrl, options);
results.push(urlResult);
}
if (components.includes("headers")) {
const headersToCheck = [
"host",
"user-agent",
"referer",
"origin",
"x-forwarded-for",
"x-forwarded-host",
"x-forwarded-proto",
"x-forwarded-port",
"x-forwarded-ssl",
];
for (const header of headersToCheck) {
const headerValue = req.get(header);
if (headerValue) {
try {
const headerResult = await NSB.analyzeUrl(`http://mock.nehonix.space/${header}/${encodeURIComponent(headerValue)}`, options);
results.push(headerResult);
}
catch (err) {
AppLogger.warn(`Failed to analyze header ${header}:`, err);
}
}
}
}
if (components.includes("query")) {
const queryString = new URLSearchParams(req.query).toString();
if (queryString) {
const queryResult = await NSB.analyzeUrl(`http://mock.nehonix.space?${queryString}`, options);
results.push(queryResult);
}
}
if (components.includes("body") && req.body) {
const bodyString = JSON.stringify(req.body);
if (bodyString) {
const bodyResult = await NSB.analyzeUrl(`http://mock.nehonix.space?data=${encodeURIComponent(bodyString)}`, options);
results.push(bodyResult);
}
}
return combineResults(results);
};
/**
* Tracks suspicious IPs for potential automatic blocking
*/
async function trackSuspiciousIP(ip, details = {}) {
await activeDatabase.trackSuspiciousIP(ip, details);
}
/**
* Cleans up old suspicious IP records
*/
export function cleanupSuspiciousIPs() {
const now = Date.now();
const expirationTime = 24 * 60 * 60 * 1000; // 24 hours
for (const [ip, record] of suspiciousIPs.entries()) {
if (now - record.lastSeen > expirationTime) {
suspiciousIPs.delete(ip);
}
}
}
/**
* Checks rate limiting for a request
*/
function checkRateLimit(req, res, options) {
if (!options.enableRateLimit || !options.rateLimit)
return true;
const ip = getClientIP(req);
const now = Date.now();
const windowMs = options.rateLimit.windowMs || 15 * 60 * 1000;
const maxRequests = options.rateLimit.maxRequests || 100;
// Get or create rate limit record
let record = rateLimitStore.get(ip);
if (!record) {
record = { count: 0, resetTime: now + windowMs };
rateLimitStore.set(ip, record);
}
// Reset if window expired
if (now > record.resetTime) {
record.count = 0;
record.resetTime = now + windowMs;
}
// Increment count
record.count += 1;
// Check if limit exceeded
if (record.count > maxRequests) {
const message = options.rateLimit.message || "Too many requests, please try again later";
const retryAfter = Math.ceil((record.resetTime - now) / 1000);
res.setHeader("Retry-After", retryAfter.toString());
res.status(429).send(message);
return false;
}
return true;
}
/**
*
* This function aggregates all security events from the database and generates a detailed
* security report with threat analysis, recommendations, and visualizable timeline data.
* The report's detail level and included sections are customizable through options.
*
* @param options Report generation options
* @returns Detailed security report
* @example
* ```typescript
* // Generate a basic report for the last 30 days
* const basicReport = await generateSecurityReport({
* days: 30,
* detailLevel: 'basic'
* });
*
* // Generate a comprehensive report with all details
* const fullReport = await generateSecurityReport({
* days: 7,
* detailLevel: 'comprehensive',
* includeIPs: true,
* includePatterns: true,
* includeRecommendations: true
* });
* ```
*/
export async function generateSecurityReport(options = {}) {
const { days = 7, includeIPs = true, includePatterns = true, includeRecommendations = true, detailLevel = "detailed", } = options;
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
// Get security events from the database
const securityEvents = await activeDatabase.getSecurityEvents({
startDate,
endDate,
});
// Calculate statistics
const totalEvents = securityEvents.length;
const eventsByType = new Map();
const patternTypes = new Map();
const blockedIPs = new Set();
const suspiciousIPs = new Set();
const topUrls = new Map();
// Process all events
securityEvents.forEach((event) => {
// Count by event type
const currentTypeCount = eventsByType.get(event.type) || 0;
eventsByType.set(event.type, currentTypeCount + 1);
// Track unique IPs by category
if (event.type === "block") {
blockedIPs.add(event.ip);
}
if (event.type === "suspicious") {
suspiciousIPs.add(event.ip);
}
// Track attack patterns
if (event.patterns) {
event.patterns.forEach((pattern) => {
const patternKey = pattern.type;
const currentPatternCount = patternTypes.get(patternKey) || 0;
patternTypes.set(patternKey, currentPatternCount + 1);
});
}
// Track affected URLs
if (event.url) {
const urlPath = new URL(event.url).pathname;
const currentUrlCount = topUrls.get(urlPath) || 0;
topUrls.set(urlPath, currentUrlCount + 1);
}
});
// Get suspicious IPs data
const suspiciousIPsData = includeIPs
? await activeDatabase.getSuspiciousIPs()
: [];
// Sort pattern types by frequency
const sortedPatternTypes = Array.from(patternTypes.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([pattern, count]) => ({ pattern, count }));
// Sort URLs by frequency
const sortedUrls = Array.from(topUrls.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([url, count]) => ({ url, count }));
// Generate recommendations based on the detected patterns
const recommendations = [];
if (includeRecommendations) {
if (patternTypes.has("sql_injection")) {
recommendations.push("Implement prepared statements for all database queries to prevent SQL injection");
}
if (patternTypes.has("xss")) {
recommendations.push("Use content security policy and output encoding to prevent cross-site scripting");
}
if (patternTypes.has("path_traversal")) {
recommendations.push("Validate file paths against a whitelist and use path normalization to prevent directory traversal");
}
if (eventsByType.get("rate_limit") &&
(eventsByType.get("rate_limit") || 0) > 100) {
recommendations.push("Consider implementing more aggressive rate limiting or adding CAPTCHA for suspicious IPs");
}
if (blockedIPs.size > 10) {
recommendations.push("Consider implementing a web application firewall (WAF) for additional protection");
}
}
// Build the base report
const report = {
generatedAt: new Date().toISOString(),
period: {
start: startDate.toISOString(),
end: endDate.toISOString(),
days,
},
summary: {
totalSecurityEvents: totalEvents,
blockedRequests: eventsByType.get("block") || 0,
warnings: eventsByType.get("warning") || 0,
suspiciousActivities: eventsByType.get("suspicious") || 0,
rateLimitHits: eventsByType.get("rate_limit") || 0,
uniqueBlockedIPs: blockedIPs.size,
uniqueSuspiciousIPs: suspiciousIPs.size,
},
};
// Add detailed information based on detail level
if (detailLevel === "detailed" || detailLevel === "comprehensive") {
report.threatAnalysis = {
topThreats: sortedPatternTypes,
topTargetedEndpoints: sortedUrls,
geographicDistribution: {}, // Would be populated if using a real IP database
};
if (includeRecommendations) {
report.recommendations = recommendations;
}
}
// Add comprehensive details
if (detailLevel === "comprehensive") {
if (includeIPs) {
report.suspiciousIPsDetails = suspiciousIPsData
.sort((a, b) => b.count - a.count)
.slice(0, 100); // Limit to top 100 IPs
}
if (includePatterns) {
// Add sample attack patterns for educational purposes
report.sampleAttackPatterns = securityEvents
.filter((event) => event.patterns && event.patterns.length > 0)
.slice(0, 20)
.map((event) => ({
timestamp: new Date(event.timestamp).toISOString(),
patterns: event.patterns,
score: event.score,
}));
}
// Add timeline data for visualization
const timeline = generateTimelineData(securityEvents, days);
report.timeline = timeline;
}
return report;
}
/**
* Function to block an IP address with a custom reason
*/
export async function blockIP(ip, reason) {
return await activeDatabase.blockIP(ip, reason);
}
/**
* Creates a custom database adapter for specific storage solutions
* @param implementation The implementation of the database adapter
* @returns A configured SecurityDatabaseAdapter
*/
export function createDatabaseAdapter(implementation) {
// Create a wrapper that falls back to the default implementation
return {
trackSuspiciousIP: implementation.trackSuspiciousIP ||
defaultDatabase.trackSuspiciousIP.bind(defaultDatabase),
getSuspiciousIPs: implementation.getSuspiciousIPs ||
defaultDatabase.getSuspiciousIPs.bind(defaultDatabase),
blockIP: implementation.blockIP || defaultDatabase.blockIP.bind(defaultDatabase),
isIPBlocked: implementation.isIPBlocked ||
defaultDatabase.isIPBlocked.bind(defaultDatabase),
saveSecurityEvent: implementation.saveSecurityEvent ||
defaultDatabase.saveSecurityEvent.bind(defaultDatabase),
getSecurityEvents: implementation.getSecurityEvents ||
defaultDatabase.getSecurityEvents.bind(defaultDatabase),
};
}
//# sourceMappingURL=express.middleware.js.map