thrilled-be-core
Version:
Core Express backend package with middleware, logging, security, and base application setup
373 lines (372 loc) • 14.4 kB
JavaScript
"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;
},
};