Initial commit: StreamFlow IPTV platform

This commit is contained in:
aiulian25 2025-12-17 00:42:43 +00:00
commit 73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions

View file

@ -0,0 +1,340 @@
/**
* Secure Error Handler Utility
* Prevents CWE-209: Information Exposure Through Error Messages
*
* This utility sanitizes error messages before sending them to clients,
* ensuring that internal system details, file paths, stack traces, and
* other sensitive information are never exposed to end users.
*/
const logger = require('./logger');
/**
* Error types with user-friendly messages
*/
const ERROR_TYPES = {
// Authentication & Authorization
AUTH_FAILED: 'Authentication failed',
AUTH_REQUIRED: 'Authentication required',
AUTH_INVALID_TOKEN: 'Invalid or expired authentication token',
AUTH_INSUFFICIENT_PERMISSIONS: 'Insufficient permissions',
// User Management
USER_NOT_FOUND: 'User not found',
USER_ALREADY_EXISTS: 'User already exists',
USER_CREATION_FAILED: 'Failed to create user',
USER_UPDATE_FAILED: 'Failed to update user',
USER_DELETE_FAILED: 'Failed to delete user',
// Data Validation
VALIDATION_FAILED: 'Validation failed',
INVALID_INPUT: 'Invalid input provided',
INVALID_FILE_TYPE: 'Invalid file type',
FILE_TOO_LARGE: 'File size exceeds limit',
MISSING_REQUIRED_FIELD: 'Required field is missing',
// Database Operations
DATABASE_ERROR: 'Database operation failed',
RECORD_NOT_FOUND: 'Record not found',
DUPLICATE_ENTRY: 'Duplicate entry exists',
// File Operations
FILE_NOT_FOUND: 'File not found',
FILE_UPLOAD_FAILED: 'File upload failed',
FILE_DELETE_FAILED: 'Failed to delete file',
FILE_READ_FAILED: 'Failed to read file',
FILE_WRITE_FAILED: 'Failed to write file',
// Network & External Services
NETWORK_ERROR: 'Network request failed',
EXTERNAL_SERVICE_ERROR: 'External service unavailable',
TIMEOUT_ERROR: 'Request timeout',
// Rate Limiting
RATE_LIMIT_EXCEEDED: 'Too many requests. Please try again later',
// Generic
INTERNAL_ERROR: 'An internal error occurred',
NOT_FOUND: 'Resource not found',
FORBIDDEN: 'Access forbidden',
BAD_REQUEST: 'Bad request',
CONFLICT: 'Resource conflict',
UNPROCESSABLE_ENTITY: 'Unable to process request',
SERVICE_UNAVAILABLE: 'Service temporarily unavailable'
};
/**
* Sanitize error for client response
* Removes sensitive information like stack traces, file paths, and internal details
*
* @param {Error|string} error - The error to sanitize
* @param {string} defaultMessage - Default message if error cannot be parsed
* @returns {Object} Sanitized error object with safe message
*/
function sanitizeError(error, defaultMessage = ERROR_TYPES.INTERNAL_ERROR) {
// If error is a string, return it as is (assuming it's already safe)
if (typeof error === 'string') {
return {
message: error,
code: 'CUSTOM_ERROR'
};
}
// Extract error message
const errorMessage = error?.message || defaultMessage;
// Check for known error patterns and map to safe messages
// Database errors
if (errorMessage.includes('UNIQUE constraint') || errorMessage.includes('UNIQUE')) {
return {
message: ERROR_TYPES.DUPLICATE_ENTRY,
code: 'DUPLICATE_ENTRY'
};
}
if (errorMessage.includes('FOREIGN KEY constraint')) {
return {
message: ERROR_TYPES.CONFLICT,
code: 'FOREIGN_KEY_CONSTRAINT'
};
}
if (errorMessage.includes('NOT NULL constraint')) {
return {
message: ERROR_TYPES.MISSING_REQUIRED_FIELD,
code: 'MISSING_FIELD'
};
}
// File system errors
if (errorMessage.includes('ENOENT') || errorMessage.includes('no such file')) {
return {
message: ERROR_TYPES.FILE_NOT_FOUND,
code: 'FILE_NOT_FOUND'
};
}
if (errorMessage.includes('EACCES') || errorMessage.includes('permission denied')) {
return {
message: ERROR_TYPES.FORBIDDEN,
code: 'PERMISSION_DENIED'
};
}
if (errorMessage.includes('ENOSPC') || errorMessage.includes('no space')) {
return {
message: ERROR_TYPES.SERVICE_UNAVAILABLE,
code: 'DISK_FULL'
};
}
// Network errors
if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('connection refused')) {
return {
message: ERROR_TYPES.EXTERNAL_SERVICE_ERROR,
code: 'CONNECTION_REFUSED'
};
}
if (errorMessage.includes('ETIMEDOUT') || errorMessage.includes('timeout')) {
return {
message: ERROR_TYPES.TIMEOUT_ERROR,
code: 'TIMEOUT'
};
}
if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('getaddrinfo')) {
return {
message: ERROR_TYPES.NETWORK_ERROR,
code: 'DNS_ERROR'
};
}
// Authentication errors
if (errorMessage.toLowerCase().includes('unauthorized') ||
errorMessage.toLowerCase().includes('authentication')) {
return {
message: ERROR_TYPES.AUTH_FAILED,
code: 'AUTH_ERROR'
};
}
if (errorMessage.toLowerCase().includes('forbidden') ||
errorMessage.toLowerCase().includes('permission')) {
return {
message: ERROR_TYPES.AUTH_INSUFFICIENT_PERMISSIONS,
code: 'PERMISSION_ERROR'
};
}
// Validation errors (pass through if they seem safe)
if (errorMessage.toLowerCase().includes('validation') ||
errorMessage.toLowerCase().includes('invalid')) {
// Check if message is reasonably safe (no paths, no system info)
if (!containsSensitiveInfo(errorMessage)) {
return {
message: errorMessage,
code: 'VALIDATION_ERROR'
};
}
return {
message: ERROR_TYPES.VALIDATION_FAILED,
code: 'VALIDATION_ERROR'
};
}
// Default to generic error message
return {
message: defaultMessage,
code: 'INTERNAL_ERROR'
};
}
/**
* Check if error message contains sensitive information
*
* @param {string} message - Error message to check
* @returns {boolean} True if message contains sensitive info
*/
function containsSensitiveInfo(message) {
const sensitivePatterns = [
/\/[a-z0-9_\-\/]+\.(js|json|db|log|conf|env)/i, // File paths
/at\s+[a-zA-Z0-9_]+\s+\(/i, // Stack trace patterns
/line\s+\d+/i, // Line numbers
/column\s+\d+/i, // Column numbers
/Error:\s+SQLITE_/i, // SQLite internal errors
/node_modules/i, // Node modules paths
/\/home\//i, // Unix home directory
/\/usr\//i, // Unix system paths
/\/var\//i, // Unix var paths
/\/tmp\//i, // Temp directory
/C:\\/i, // Windows paths
/\\Users\\/i, // Windows user paths
/password/i, // Password references
/secret/i, // Secret references
/token/i, // Token references
/key/i // Key references (be careful with "keyboard" etc.)
];
return sensitivePatterns.some(pattern => pattern.test(message));
}
/**
* Log error securely (internal logs can contain full details)
*
* @param {Error|string} error - Error to log
* @param {Object} context - Additional context
*/
function logError(error, context = {}) {
const errorInfo = {
message: error?.message || error,
stack: error?.stack,
code: error?.code,
...context
};
logger.error('Application error:', errorInfo);
}
/**
* Express error handler middleware
* Catches all errors and returns sanitized responses
*
* @param {Error} err - Error object
* @param {Object} req - Express request
* @param {Object} res - Express response
* @param {Function} next - Express next function
*/
function errorMiddleware(err, req, res, next) {
// Log full error details internally
logError(err, {
method: req.method,
path: req.path,
userId: req.user?.id,
ip: req.ip
});
// Determine status code
const statusCode = err.statusCode || err.status || 500;
// Sanitize error for client
const sanitized = sanitizeError(err, ERROR_TYPES.INTERNAL_ERROR);
// Send sanitized response
res.status(statusCode).json({
error: sanitized.message,
code: sanitized.code,
timestamp: new Date().toISOString()
});
}
/**
* Async handler wrapper to catch async errors
*
* @param {Function} fn - Async route handler
* @returns {Function} Wrapped function
*/
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
/**
* Create a safe error response object
*
* @param {string} message - Error message (should be user-safe)
* @param {number} statusCode - HTTP status code
* @param {string} code - Error code
* @returns {Object} Error response object
*/
function createError(message, statusCode = 500, code = 'ERROR') {
const error = new Error(message);
error.statusCode = statusCode;
error.code = code;
return error;
}
/**
* Standard error responses
*/
const ErrorResponses = {
badRequest: (message = ERROR_TYPES.BAD_REQUEST) =>
createError(message, 400, 'BAD_REQUEST'),
unauthorized: (message = ERROR_TYPES.AUTH_REQUIRED) =>
createError(message, 401, 'UNAUTHORIZED'),
forbidden: (message = ERROR_TYPES.FORBIDDEN) =>
createError(message, 403, 'FORBIDDEN'),
notFound: (message = ERROR_TYPES.NOT_FOUND) =>
createError(message, 404, 'NOT_FOUND'),
conflict: (message = ERROR_TYPES.CONFLICT) =>
createError(message, 409, 'CONFLICT'),
unprocessable: (message = ERROR_TYPES.UNPROCESSABLE_ENTITY) =>
createError(message, 422, 'UNPROCESSABLE_ENTITY'),
tooManyRequests: (message = ERROR_TYPES.RATE_LIMIT_EXCEEDED) =>
createError(message, 429, 'TOO_MANY_REQUESTS'),
internal: (message = ERROR_TYPES.INTERNAL_ERROR) =>
createError(message, 500, 'INTERNAL_ERROR'),
serviceUnavailable: (message = ERROR_TYPES.SERVICE_UNAVAILABLE) =>
createError(message, 503, 'SERVICE_UNAVAILABLE')
};
module.exports = {
ERROR_TYPES,
sanitizeError,
containsSensitiveInfo,
logError,
errorMiddleware,
asyncHandler,
createError,
ErrorResponses
};