UNPKG

thrilled-be-core

Version:

Core Express backend package with middleware, logging, security, and base application setup

373 lines (372 loc) 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidationExports = exports.CoreValidationPlugin = void 0; const Plugin_js_1 = require("./Plugin.js"); /** * Core Validation Plugin - integrates be-validation into be-core * * This plugin provides comprehensive validation and sanitization capabilities * that are automatically available when using be-core. */ class CoreValidationPlugin extends Plugin_js_1.BasePlugin { name = 'validation'; version = '1.0.0'; config = { enableXSSProtection: true, enableSQLInjectionProtection: true, globalValidation: { enabled: true, soft: false, options: { abortEarly: false, allowUnknown: false, stripUnknown: true, }, }, globalSanitization: { body: { html: { enabled: true, stripTags: true }, xss: { enabled: true }, sql: { enabled: true }, }, query: { html: { enabled: true, stripTags: true }, xss: { enabled: true }, sql: { enabled: true }, }, params: { html: { enabled: true, stripTags: true }, xss: { enabled: true }, sql: { enabled: false }, // Usually not needed for URL params }, }, csp: { enabled: true, directives: { 'default-src': ["'self'"], 'script-src': ["'self'", "'unsafe-inline'"], 'style-src': ["'self'", "'unsafe-inline'"], 'img-src': ["'self'", 'data:', 'https:'], 'font-src': ["'self'"], 'connect-src': ["'self'"], 'frame-src': ["'none'"], 'object-src': ["'none'"], 'base-uri': ["'self'"], 'form-action': ["'self'"], }, }, }; // Dynamic imports for validation modules validationModules = {}; constructor(logger, config) { super(logger); if (config) { this.config = { ...this.config, ...config }; } } async setup(config) { if (config && typeof config === 'object') { this.config = { ...this.config, ...config }; } // Load validation modules dynamically try { // Try dynamic import first (for ES modules) this.validationModules = await import('be-validation'); this.logger.info('Validation modules loaded successfully via dynamic import', { context: this.name }); } catch (importError) { try { // Fallback to require (for CommonJS) this.validationModules = require('be-validation'); this.logger.info('Validation modules loaded successfully via require', { context: this.name }); } catch (requireError) { this.logger.warn('Validation modules are not available, continuing with basic security headers only', { context: this.name, importError: importError instanceof Error ? importError.message : String(importError), requireError: requireError instanceof Error ? requireError.message : String(requireError) }); // Continue without validation modules - we can still provide basic security headers } } this.logger.info('Setting up validation plugin with configuration', { context: this.name, config: this.getPublicConfig(), modulesAvailable: Object.keys(this.validationModules).length > 0, }); } registerMiddleware(app) { this.logger.debug('Registering validation middleware', { context: this.name }); // Add security headers app.use((req, res, next) => { // XSS Protection header res.setHeader('X-XSS-Protection', '1; mode=block'); // Content Type Options res.setHeader('X-Content-Type-Options', 'nosniff'); // Frame Options res.setHeader('X-Frame-Options', 'DENY'); // Content Security Policy if (this.config.csp?.enabled && this.config.csp.directives) { const cspString = Object.entries(this.config.csp.directives) .map(([directive, sources]) => `${directive} ${sources.join(' ')}`) .join('; '); res.setHeader('Content-Security-Policy', cspString); } next(); }); // Global sanitization middleware if (this.config.globalSanitization && this.validationModules.Sanitizer) { app.use((req, res, next) => { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const SanitizerClass = this.validationModules.Sanitizer; // Sanitize request body if (req.body && typeof req.body === 'object' && this.config.globalSanitization?.body) { req.body = SanitizerClass.sanitizeObject(req.body, this.config.globalSanitization.body); } // Sanitize query parameters if (req.query && typeof req.query === 'object' && this.config.globalSanitization?.query) { const sanitized = SanitizerClass.sanitizeObject(req.query, this.config.globalSanitization.query); req.query = sanitized; } // Sanitize URL parameters if (req.params && typeof req.params === 'object' && this.config.globalSanitization?.params) { const sanitized = SanitizerClass.sanitizeObject(req.params, this.config.globalSanitization.params); req.params = sanitized; } next(); } catch (error) { this.logger.error('Global sanitization error', { context: this.name, error: error instanceof Error ? error.message : String(error) }); next(error); } }); } // XSS Protection middleware if (this.config.enableXSSProtection && this.validationModules.XSSProtection) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const XSSProtectionClass = this.validationModules.XSSProtection; app.use(XSSProtectionClass.middleware({ enableCSP: this.config.csp?.enabled, cspDirectives: this.config.csp?.directives, })); } // SQL Injection Protection middleware if (this.config.enableSQLInjectionProtection && this.validationModules.SQLInjectionProtection) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const SQLInjectionProtectionClass = this.validationModules.SQLInjectionProtection; app.use(SQLInjectionProtectionClass.middleware()); } } registerErrorHandlers(app) { this.logger.debug('Registering validation error handlers', { context: this.name }); // Custom error handler for validation errors const validationErrorHandler = (err, req, res, next) => { if (this.config.errorHandler) { this.config.errorHandler(err, req, res, next); return; } // Default validation error handling if (err.name === 'ValidationError') { this.logger.warn('Validation error occurred', { context: this.name, error: err.message, details: err.details || err.errors, path: req.path, method: req.method, }); res.status(400).json({ error: 'Validation Error', message: 'Invalid input data', details: err.details || err.errors, }); return; } if (err.name === 'SanitizationError') { this.logger.warn('Sanitization error occurred', { context: this.name, error: err.message, path: req.path, method: req.method, }); res.status(400).json({ error: 'Security Error', message: 'Potentially malicious content detected', }); return; } // Pass other errors to the next error handler next(err); }; app.use(validationErrorHandler); } /** * Get validation middleware for specific routes */ getValidationMiddleware() { return this.validationModules.ValidationMiddleware; } /** * Get sanitization utilities */ getSanitizer() { return this.validationModules.Sanitizer; } /** * Get XSS protection utilities */ getXSSProtection() { return this.validationModules.XSSProtection; } /** * Get SQL injection protection utilities */ getSQLProtection() { return this.validationModules.SQLInjectionProtection; } /** * Get custom validators */ getCustomValidators() { return this.validationModules.CustomValidators; } /** * Get validation utilities */ getValidationUtils() { return this.validationModules.ValidationUtils; } /** * Get Joi validator instance */ getJoiValidator() { // eslint-disable-next-line @typescript-eslint/no-explicit-any const JoiValidatorClass = this.validationModules.JoiValidator; return new JoiValidatorClass(); } /** * Get Zod validator instance */ getZodValidator() { // eslint-disable-next-line @typescript-eslint/no-explicit-any const ZodValidatorClass = this.validationModules.ZodValidator; return new ZodValidatorClass(); } /** * Add custom validator */ addCustomValidator(name, validator) { if (!this.config.customValidators) { this.config.customValidators = {}; } this.config.customValidators[name] = validator; this.logger.debug(`Added custom validator: ${name}`, { context: this.name }); } /** * Get custom validator */ getCustomValidator(name) { return this.config.customValidators?.[name]; } /** * Update plugin configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.logger.info('Updated validation plugin configuration', { context: this.name, config: this.getPublicConfig(), }); } /** * Get current configuration (without sensitive data) */ getPublicConfig() { const { customValidators, errorHandler, ...publicConfig } = this.config; return { ...publicConfig, customValidators: customValidators ? Object.keys(customValidators) : undefined, errorHandler: errorHandler ? 'custom' : undefined, }; } /** * Health check for validation system */ async healthCheck() { const checks = { xssProtection: this.config.enableXSSProtection ?? false, sqlProtection: this.config.enableSQLInjectionProtection ?? false, globalValidation: !!this.config.globalValidation?.enabled, globalSanitization: !!this.config.globalSanitization, customValidators: Object.keys(this.config.customValidators || {}).length > 0, cspEnabled: this.config.csp?.enabled ?? false, modulesLoaded: Object.keys(this.validationModules).length > 0, }; const enabledChecks = Object.values(checks).filter(Boolean).length; const hasModules = checks.modulesLoaded; let status = 'healthy'; let message = 'Validation system is fully operational'; if (!hasModules) { status = 'warning'; message = 'Validation modules not available - basic security headers only'; } else if (enabledChecks === 0) { status = 'error'; message = 'No validation or sanitization features are enabled'; } else if (enabledChecks < 3) { status = 'warning'; message = 'Limited validation features are enabled'; } return { status, checks, message, details: { enabledFeatures: enabledChecks, totalFeatures: Object.keys(checks).length, customValidatorCount: Object.keys(this.config.customValidators || {}).length, availableModules: Object.keys(this.validationModules), modulesAvailable: hasModules, }, }; } } exports.CoreValidationPlugin = CoreValidationPlugin; // Re-export for convenience - these will be available after dynamic import exports.ValidationExports = { async getValidationMiddleware() { const pkg = await import('be-validation'); return pkg.ValidationMiddleware; }, async getXSSProtection() { const pkg = await import('be-validation'); return pkg.XSSProtection; }, async getSQLInjectionProtection() { const pkg = await import('be-validation'); return pkg.SQLInjectionProtection; }, async getSanitizer() { const pkg = await import('be-validation'); return pkg.Sanitizer; }, async getCustomValidators() { const pkg = await import('be-validation'); return pkg.CustomValidators; }, async getValidationUtils() { const pkg = await import('be-validation'); return pkg.ValidationUtils; }, async getJoiValidator() { const pkg = await import('be-validation'); return pkg.JoiValidator; }, async getZodValidator() { const pkg = await import('be-validation'); return pkg.ZodValidator; }, };