javascript

examples

examples.js
/**
 * 21.2 Secure Coding Practices - Examples
 *
 * Demonstrating secure coding patterns and best practices
 */

// ================================================
// 1. INPUT VALIDATION
// ================================================

/**
 * Schema-Based Validator
 * Comprehensive input validation using schemas
 */
class SchemaValidator {
  constructor(schema) {
    this.schema = schema;
  }

  validate(data) {
    const errors = [];
    const validated = {};

    for (const [field, rules] of Object.entries(this.schema)) {
      const value = data[field];

      // Check required
      if (
        rules.required &&
        (value === undefined || value === null || value === '')
      ) {
        errors.push({ field, message: `${field} is required` });
        continue;
      }

      // Skip validation if not required and not provided
      if (value === undefined || value === null) {
        if (rules.default !== undefined) {
          validated[field] = rules.default;
        }
        continue;
      }

      // Type validation
      if (rules.type && !this.validateType(value, rules.type)) {
        errors.push({
          field,
          message: `${field} must be of type ${rules.type}`,
        });
        continue;
      }

      // String validations
      if (rules.type === 'string') {
        if (rules.minLength && value.length < rules.minLength) {
          errors.push({
            field,
            message: `${field} must be at least ${rules.minLength} characters`,
          });
        }
        if (rules.maxLength && value.length > rules.maxLength) {
          errors.push({
            field,
            message: `${field} must be at most ${rules.maxLength} characters`,
          });
        }
        if (rules.pattern && !rules.pattern.test(value)) {
          errors.push({ field, message: `${field} format is invalid` });
        }
      }

      // Number validations
      if (rules.type === 'number') {
        if (rules.min !== undefined && value < rules.min) {
          errors.push({
            field,
            message: `${field} must be at least ${rules.min}`,
          });
        }
        if (rules.max !== undefined && value > rules.max) {
          errors.push({
            field,
            message: `${field} must be at most ${rules.max}`,
          });
        }
        if (rules.integer && !Number.isInteger(value)) {
          errors.push({ field, message: `${field} must be an integer` });
        }
      }

      // Array validations
      if (rules.type === 'array') {
        if (rules.minItems && value.length < rules.minItems) {
          errors.push({
            field,
            message: `${field} must have at least ${rules.minItems} items`,
          });
        }
        if (rules.maxItems && value.length > rules.maxItems) {
          errors.push({
            field,
            message: `${field} must have at most ${rules.maxItems} items`,
          });
        }
        if (rules.itemType) {
          for (let i = 0; i < value.length; i++) {
            if (!this.validateType(value[i], rules.itemType)) {
              errors.push({
                field,
                message: `${field}[${i}] must be of type ${rules.itemType}`,
              });
            }
          }
        }
      }

      // Enum validation
      if (rules.enum && !rules.enum.includes(value)) {
        errors.push({
          field,
          message: `${field} must be one of: ${rules.enum.join(', ')}`,
        });
      }

      // Custom validator
      if (rules.validate) {
        const customResult = rules.validate(value, data);
        if (customResult !== true) {
          errors.push({
            field,
            message: customResult || `${field} is invalid`,
          });
        }
      }

      // Sanitize and store
      validated[field] = rules.sanitize ? rules.sanitize(value) : value;
    }

    return {
      valid: errors.length === 0,
      errors,
      data: validated,
    };
  }

  validateType(value, type) {
    switch (type) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number' && !isNaN(value);
      case 'boolean':
        return typeof value === 'boolean';
      case 'array':
        return Array.isArray(value);
      case 'object':
        return (
          typeof value === 'object' && value !== null && !Array.isArray(value)
        );
      case 'email':
        return (
          typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
        );
      case 'url':
        try {
          new URL(value);
          return true;
        } catch {
          return false;
        }
      default:
        return true;
    }
  }
}

// Example usage
const userSchema = {
  username: {
    type: 'string',
    required: true,
    minLength: 3,
    maxLength: 20,
    pattern: /^[a-zA-Z0-9_]+$/,
    sanitize: (v) => v.toLowerCase().trim(),
  },
  email: {
    type: 'email',
    required: true,
    sanitize: (v) => v.toLowerCase().trim(),
  },
  age: {
    type: 'number',
    min: 13,
    max: 120,
    integer: true,
  },
  role: {
    type: 'string',
    enum: ['user', 'admin', 'moderator'],
    default: 'user',
  },
  password: {
    type: 'string',
    required: true,
    minLength: 8,
    validate: (value) => {
      if (!/[A-Z]/.test(value)) return 'Password must contain uppercase letter';
      if (!/[a-z]/.test(value)) return 'Password must contain lowercase letter';
      if (!/[0-9]/.test(value)) return 'Password must contain number';
      return true;
    },
  },
};

const validator = new SchemaValidator(userSchema);

console.log('Schema Validator Examples:');
console.log('-'.repeat(50));

// Valid data
const validResult = validator.validate({
  username: 'JohnDoe',
  email: 'john@example.com',
  age: 25,
  password: 'SecurePass123',
});
console.log('Valid data result:', validResult);

// Invalid data
const invalidResult = validator.validate({
  username: 'ab',
  email: 'invalid-email',
  age: 5,
  password: 'weak',
});
console.log('Invalid data result:', invalidResult);

// ================================================
// 2. OUTPUT ENCODING
// ================================================

/**
 * Context-Aware Output Encoder
 * Encodes output based on the context where it will be used
 */
class OutputEncoder {
  // HTML body context - encode HTML entities
  static html(str) {
    if (typeof str !== 'string') return '';

    const htmlEntities = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      '/': '&#x2F;',
    };

    return str.replace(/[&<>"'/]/g, (char) => htmlEntities[char]);
  }

  // HTML attribute context
  static htmlAttr(str) {
    if (typeof str !== 'string') return '';

    // More aggressive encoding for attributes
    return str.replace(/[&<>"'`=\/]/g, (char) => {
      return '&#x' + char.charCodeAt(0).toString(16) + ';';
    });
  }

  // JavaScript string context
  static js(str) {
    if (typeof str !== 'string') return '';

    const jsEscapes = {
      '\\': '\\\\',
      "'": "\\'",
      '"': '\\"',
      '\n': '\\n',
      '\r': '\\r',
      '\t': '\\t',
      '<': '\\u003c',
      '>': '\\u003e',
      '/': '\\/',
      '\u2028': '\\u2028', // Line separator
      '\u2029': '\\u2029', // Paragraph separator
    };

    return str.replace(
      /[\\'"<>\n\r\t\/\u2028\u2029]/g,
      (char) => jsEscapes[char]
    );
  }

  // CSS context
  static css(str) {
    if (typeof str !== 'string') return '';

    // Escape non-alphanumeric characters
    return str.replace(/[^a-zA-Z0-9]/g, (char) => {
      return '\\' + char.charCodeAt(0).toString(16) + ' ';
    });
  }

  // URL component encoding
  static url(str) {
    if (typeof str !== 'string') return '';
    return encodeURIComponent(str);
  }

  // Full URL encoding (preserves structure)
  static fullUrl(str) {
    if (typeof str !== 'string') return '';
    return encodeURI(str);
  }

  // JSON encoding with extra safety
  static json(data) {
    const jsonStr = JSON.stringify(data);
    // Escape characters that could break out of JSON in HTML
    return jsonStr
      .replace(/</g, '\\u003c')
      .replace(/>/g, '\\u003e')
      .replace(/&/g, '\\u0026');
  }
}

console.log('\n' + '='.repeat(50));
console.log('Output Encoder Examples:');
console.log('-'.repeat(50));

const dangerous = '<script>alert("XSS")</script>';

console.log('Original:', dangerous);
console.log('HTML encoded:', OutputEncoder.html(dangerous));
console.log('HTML attr encoded:', OutputEncoder.htmlAttr(dangerous));
console.log('JS encoded:', OutputEncoder.js(dangerous));
console.log('URL encoded:', OutputEncoder.url(dangerous));

// ================================================
// 3. SECURE PASSWORD HANDLING
// ================================================

/**
 * Password Security Manager
 * Handles password strength checking and secure comparison
 */
class PasswordSecurity {
  static commonPasswords = new Set([
    'password',
    '123456',
    '12345678',
    'qwerty',
    'abc123',
    'password123',
    'admin',
    'letmein',
    'welcome',
    'monkey',
  ]);

  static checkStrength(password) {
    const checks = {
      length: password.length >= 12,
      uppercase: /[A-Z]/.test(password),
      lowercase: /[a-z]/.test(password),
      numbers: /\d/.test(password),
      special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password),
      noCommon: !this.isCommonPassword(password),
      noRepeating: !/(.)\1{2,}/.test(password), // No 3+ repeating chars
      noSequential: !this.hasSequentialChars(password),
    };

    const score = Object.values(checks).filter(Boolean).length;

    let strength = 'weak';
    if (score >= 7) strength = 'strong';
    else if (score >= 5) strength = 'medium';

    return {
      strength,
      score,
      maxScore: 8,
      checks,
      suggestions: this.getSuggestions(checks),
    };
  }

  static isCommonPassword(password) {
    return this.commonPasswords.has(password.toLowerCase());
  }

  static hasSequentialChars(password) {
    const sequences = [
      'abcdefghijklmnopqrstuvwxyz',
      'zyxwvutsrqponmlkjihgfedcba',
      '0123456789',
      '9876543210',
      'qwertyuiop',
      'asdfghjkl',
      'zxcvbnm',
    ];

    const lower = password.toLowerCase();

    for (const seq of sequences) {
      for (let i = 0; i < seq.length - 3; i++) {
        if (lower.includes(seq.substring(i, i + 4))) {
          return true;
        }
      }
    }
    return false;
  }

  static getSuggestions(checks) {
    const suggestions = [];

    if (!checks.length) suggestions.push('Use at least 12 characters');
    if (!checks.uppercase) suggestions.push('Add uppercase letters');
    if (!checks.lowercase) suggestions.push('Add lowercase letters');
    if (!checks.numbers) suggestions.push('Add numbers');
    if (!checks.special) suggestions.push('Add special characters');
    if (!checks.noCommon) suggestions.push('Avoid common passwords');
    if (!checks.noRepeating) suggestions.push('Avoid repeating characters');
    if (!checks.noSequential) suggestions.push('Avoid sequential characters');

    return suggestions;
  }

  // Timing-safe comparison to prevent timing attacks
  static secureCompare(a, b) {
    if (typeof a !== 'string' || typeof b !== 'string') {
      return false;
    }

    // Ensure same length comparison
    const aBytes = Buffer.from(a, 'utf8');
    const bBytes = Buffer.from(b, 'utf8');

    if (aBytes.length !== bBytes.length) {
      // Compare against dummy to maintain constant time
      const dummy = Buffer.alloc(aBytes.length);
      this.timingSafeEqual(aBytes, dummy);
      return false;
    }

    return this.timingSafeEqual(aBytes, bBytes);
  }

  static timingSafeEqual(a, b) {
    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a[i] ^ b[i];
    }
    return result === 0;
  }

  // Generate secure random password
  static generatePassword(length = 16) {
    const charset =
      'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
    let password = '';

    // Ensure at least one of each required type
    password += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Math.floor(Math.random() * 26)];
    password += 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)];
    password += '0123456789'[Math.floor(Math.random() * 10)];
    password += '!@#$%^&*'[Math.floor(Math.random() * 8)];

    // Fill remaining length
    for (let i = password.length; i < length; i++) {
      password += charset[Math.floor(Math.random() * charset.length)];
    }

    // Shuffle password
    return password
      .split('')
      .sort(() => Math.random() - 0.5)
      .join('');
  }
}

console.log('\n' + '='.repeat(50));
console.log('Password Security Examples:');
console.log('-'.repeat(50));

console.log('Weak password:', PasswordSecurity.checkStrength('password123'));
console.log(
  'Strong password:',
  PasswordSecurity.checkStrength('MyS3cur3!Pass#2024')
);
console.log('Generated password:', PasswordSecurity.generatePassword(16));

// ================================================
// 4. SECURE ERROR HANDLING
// ================================================

/**
 * Secure Error Handler
 * Logs detailed errors server-side, returns safe messages to client
 */
class SecureErrorHandler {
  constructor(options = {}) {
    this.isProduction =
      options.isProduction ?? process.env.NODE_ENV === 'production';
    this.logFunction = options.logFunction || console.error;
    this.errorIdGenerator =
      options.errorIdGenerator ||
      (() =>
        'ERR-' +
        Date.now().toString(36) +
        '-' +
        Math.random().toString(36).substr(2, 9));
  }

  // Map of safe error messages for different error types
  safeMessages = {
    ValidationError: 'Invalid input provided',
    AuthenticationError: 'Authentication failed',
    AuthorizationError: 'Access denied',
    NotFoundError: 'Resource not found',
    RateLimitError: 'Too many requests',
    DatabaseError: 'Service temporarily unavailable',
    NetworkError: 'Service temporarily unavailable',
    default: 'An unexpected error occurred',
  };

  handle(error, context = {}) {
    const errorId = this.errorIdGenerator();
    const timestamp = new Date().toISOString();

    // Log detailed information server-side
    this.logFunction({
      errorId,
      timestamp,
      error: {
        name: error.name,
        message: error.message,
        stack: error.stack,
      },
      context: {
        userId: context.userId,
        requestId: context.requestId,
        path: context.path,
        method: context.method,
      },
    });

    // Return safe response
    return this.createSafeResponse(error, errorId);
  }

  createSafeResponse(error, errorId) {
    const safeMessage =
      this.safeMessages[error.name] || this.safeMessages.default;

    const response = {
      success: false,
      error: safeMessage,
      errorId,
    };

    // In development, include more details
    if (!this.isProduction) {
      response.debug = {
        name: error.name,
        message: error.message,
      };
    }

    return response;
  }

  // Create custom error types
  static createError(name, statusCode) {
    return class extends Error {
      constructor(message, details = {}) {
        super(message);
        this.name = name;
        this.statusCode = statusCode;
        this.details = details;
      }
    };
  }
}

// Create custom error types
const ValidationError = SecureErrorHandler.createError('ValidationError', 400);
const AuthenticationError = SecureErrorHandler.createError(
  'AuthenticationError',
  401
);
const AuthorizationError = SecureErrorHandler.createError(
  'AuthorizationError',
  403
);
const NotFoundError = SecureErrorHandler.createError('NotFoundError', 404);

console.log('\n' + '='.repeat(50));
console.log('Secure Error Handler Examples:');
console.log('-'.repeat(50));

const errorHandler = new SecureErrorHandler({ isProduction: true });

// Database error - should not expose details
const dbError = new Error(
  'ECONNREFUSED: Connection refused to postgres://user:pass@localhost:5432/db'
);
dbError.name = 'DatabaseError';
console.log(
  'Database error (production):',
  errorHandler.handle(dbError, { userId: 123, path: '/api/users' })
);

// Validation error
const validationError = new ValidationError('Email format is invalid');
console.log('Validation error:', errorHandler.handle(validationError));

// ================================================
// 5. SECURE CONFIGURATION MANAGEMENT
// ================================================

/**
 * Secure Configuration Manager
 * Handles configuration with environment variables and validation
 */
class SecureConfig {
  constructor(schema) {
    this.schema = schema;
    this.config = {};
    this.sensitiveKeys = new Set();
    this.loaded = false;
  }

  load() {
    for (const [key, definition] of Object.entries(this.schema)) {
      const envVar = definition.env || key.toUpperCase().replace(/\./g, '_');
      let value = process.env[envVar] || definition.default;

      // Mark sensitive keys
      if (definition.sensitive) {
        this.sensitiveKeys.add(key);
      }

      // Required check
      if (definition.required && (value === undefined || value === null)) {
        throw new Error(
          `Required configuration missing: ${key} (env: ${envVar})`
        );
      }

      // Type conversion
      if (value !== undefined) {
        value = this.convertType(value, definition.type);
      }

      // Validation
      if (definition.validate && value !== undefined) {
        const validationResult = definition.validate(value);
        if (validationResult !== true) {
          throw new Error(
            `Configuration validation failed for ${key}: ${validationResult}`
          );
        }
      }

      this.config[key] = value;
    }

    this.loaded = true;
    return this;
  }

  convertType(value, type) {
    switch (type) {
      case 'number':
        const num = Number(value);
        if (isNaN(num)) throw new Error(`Cannot convert to number: ${value}`);
        return num;
      case 'boolean':
        return value === 'true' || value === '1' || value === true;
      case 'array':
        return typeof value === 'string'
          ? value.split(',').map((v) => v.trim())
          : value;
      case 'json':
        return typeof value === 'string' ? JSON.parse(value) : value;
      default:
        return value;
    }
  }

  get(key) {
    if (!this.loaded) {
      throw new Error('Configuration not loaded. Call load() first.');
    }
    return this.config[key];
  }

  // Get all config, masking sensitive values
  getAll(maskSensitive = true) {
    if (!this.loaded) {
      throw new Error('Configuration not loaded. Call load() first.');
    }

    const result = { ...this.config };

    if (maskSensitive) {
      for (const key of this.sensitiveKeys) {
        if (result[key]) {
          result[key] = '***MASKED***';
        }
      }
    }

    return result;
  }

  // Validate all required configurations
  validate() {
    const issues = [];

    for (const [key, definition] of Object.entries(this.schema)) {
      const value = this.config[key];

      if (
        definition.required &&
        (value === undefined || value === null || value === '')
      ) {
        issues.push(`Missing required: ${key}`);
      }
    }

    return {
      valid: issues.length === 0,
      issues,
    };
  }
}

// Example configuration schema
const configSchema = {
  'app.port': {
    env: 'PORT',
    type: 'number',
    default: 3000,
    validate: (v) => (v > 0 && v < 65536 ? true : 'Port must be 1-65535'),
  },
  'app.environment': {
    env: 'NODE_ENV',
    type: 'string',
    default: 'development',
  },
  'db.host': {
    env: 'DB_HOST',
    type: 'string',
    default: 'localhost',
  },
  'db.password': {
    env: 'DB_PASSWORD',
    type: 'string',
    sensitive: true,
    required: false, // Would be true in production
  },
  'jwt.secret': {
    env: 'JWT_SECRET',
    type: 'string',
    sensitive: true,
    required: false,
  },
  'cors.origins': {
    env: 'CORS_ORIGINS',
    type: 'array',
    default: ['http://localhost:3000'],
  },
};

console.log('\n' + '='.repeat(50));
console.log('Secure Configuration Examples:');
console.log('-'.repeat(50));

// Set test environment variables
process.env.PORT = '8080';
process.env.DB_PASSWORD = 'super-secret-password';

const config = new SecureConfig(configSchema).load();
console.log('All config (masked):', config.getAll(true));
console.log('Port value:', config.get('app.port'));

// ================================================
// 6. SECURE API REQUEST HANDLER
// ================================================

/**
 * Secure API Handler
 * Combines validation, sanitization, and error handling
 */
class SecureAPIHandler {
  constructor(options = {}) {
    this.rateLimiter = new Map();
    this.maxRequests = options.maxRequests || 100;
    this.windowMs = options.windowMs || 60000;
    this.errorHandler = new SecureErrorHandler({
      isProduction: options.isProduction,
    });
  }

  // Rate limiting check
  checkRateLimit(clientId) {
    const now = Date.now();
    const clientData = this.rateLimiter.get(clientId) || {
      count: 0,
      resetTime: now + this.windowMs,
    };

    // Reset if window expired
    if (now > clientData.resetTime) {
      clientData.count = 0;
      clientData.resetTime = now + this.windowMs;
    }

    clientData.count++;
    this.rateLimiter.set(clientId, clientData);

    if (clientData.count > this.maxRequests) {
      const error = new Error('Rate limit exceeded');
      error.name = 'RateLimitError';
      throw error;
    }

    return {
      remaining: this.maxRequests - clientData.count,
      resetTime: clientData.resetTime,
    };
  }

  // Sanitize request data
  sanitizeRequest(data) {
    if (typeof data === 'string') {
      // Remove null bytes
      data = data.replace(/\0/g, '');
      // Trim whitespace
      data = data.trim();
      // Normalize unicode
      data = data.normalize('NFC');
    } else if (typeof data === 'object' && data !== null) {
      for (const key of Object.keys(data)) {
        data[key] = this.sanitizeRequest(data[key]);
      }
    }
    return data;
  }

  // Create handler wrapper
  createHandler(schema, handler) {
    const validator = new SchemaValidator(schema);

    return async (req) => {
      try {
        // Rate limiting
        const rateLimitInfo = this.checkRateLimit(req.clientId || req.ip);

        // Sanitize
        const sanitizedData = this.sanitizeRequest(req.body);

        // Validate
        const validation = validator.validate(sanitizedData);
        if (!validation.valid) {
          const error = new Error(validation.errors[0].message);
          error.name = 'ValidationError';
          throw error;
        }

        // Execute handler
        const result = await handler(validation.data, req);

        return {
          success: true,
          data: result,
          rateLimit: rateLimitInfo,
        };
      } catch (error) {
        return this.errorHandler.handle(error, {
          userId: req.userId,
          path: req.path,
          method: req.method,
        });
      }
    };
  }
}

console.log('\n' + '='.repeat(50));
console.log('Secure API Handler Examples:');
console.log('-'.repeat(50));

const apiHandler = new SecureAPIHandler({ isProduction: false });

const createUserHandler = apiHandler.createHandler(
  {
    username: { type: 'string', required: true, minLength: 3 },
    email: { type: 'email', required: true },
  },
  async (data) => {
    // Simulated user creation
    return { id: 1, ...data, created: new Date().toISOString() };
  }
);

// Test the handler
(async () => {
  const validRequest = {
    clientId: 'client-1',
    body: { username: 'john_doe', email: 'john@example.com' },
    path: '/api/users',
    method: 'POST',
  };

  console.log('Valid request result:', await createUserHandler(validRequest));

  const invalidRequest = {
    clientId: 'client-2',
    body: { username: 'jo', email: 'invalid' },
    path: '/api/users',
    method: 'POST',
  };

  console.log(
    'Invalid request result:',
    await createUserHandler(invalidRequest)
  );
})();

// ================================================
// 7. CONTENT SECURITY POLICY BUILDER
// ================================================

/**
 * CSP Builder
 * Creates Content Security Policy headers
 */
class CSPBuilder {
  constructor() {
    this.directives = {};
  }

  // Add directive
  directive(name, ...values) {
    if (!this.directives[name]) {
      this.directives[name] = new Set();
    }
    values.forEach((v) => this.directives[name].add(v));
    return this;
  }

  // Common directive helpers
  defaultSrc(...sources) {
    return this.directive('default-src', ...sources);
  }
  scriptSrc(...sources) {
    return this.directive('script-src', ...sources);
  }
  styleSrc(...sources) {
    return this.directive('style-src', ...sources);
  }
  imgSrc(...sources) {
    return this.directive('img-src', ...sources);
  }
  connectSrc(...sources) {
    return this.directive('connect-src', ...sources);
  }
  fontSrc(...sources) {
    return this.directive('font-src', ...sources);
  }
  objectSrc(...sources) {
    return this.directive('object-src', ...sources);
  }
  mediaSrc(...sources) {
    return this.directive('media-src', ...sources);
  }
  frameSrc(...sources) {
    return this.directive('frame-src', ...sources);
  }

  // Special directives
  upgradeInsecureRequests() {
    return this.directive('upgrade-insecure-requests');
  }
  blockAllMixedContent() {
    return this.directive('block-all-mixed-content');
  }

  // Generate nonce
  static generateNonce() {
    return Buffer.from(Math.random().toString() + Date.now().toString())
      .toString('base64')
      .slice(0, 16);
  }

  // Build the CSP string
  build() {
    return Object.entries(this.directives)
      .map(([directive, values]) => {
        const valueStr = Array.from(values).join(' ');
        return valueStr ? `${directive} ${valueStr}` : directive;
      })
      .join('; ');
  }

  // Common presets
  static strict() {
    return new CSPBuilder()
      .defaultSrc("'self'")
      .scriptSrc("'self'")
      .styleSrc("'self'")
      .imgSrc("'self'", 'data:', 'https:')
      .connectSrc("'self'")
      .fontSrc("'self'")
      .objectSrc("'none'")
      .frameSrc("'none'")
      .upgradeInsecureRequests();
  }

  static moderate() {
    return new CSPBuilder()
      .defaultSrc("'self'")
      .scriptSrc("'self'", "'unsafe-inline'")
      .styleSrc("'self'", "'unsafe-inline'")
      .imgSrc("'self'", 'data:', 'https:')
      .connectSrc("'self'", 'https:')
      .fontSrc("'self'", 'https:')
      .objectSrc("'none'");
  }
}

console.log('\n' + '='.repeat(50));
console.log('CSP Builder Examples:');
console.log('-'.repeat(50));

const strictCSP = CSPBuilder.strict();
console.log('Strict CSP:', strictCSP.build());

const customCSP = new CSPBuilder()
  .defaultSrc("'self'")
  .scriptSrc("'self'", 'https://cdn.example.com')
  .styleSrc("'self'", "'unsafe-inline'")
  .imgSrc("'self'", 'https:', 'data:')
  .connectSrc("'self'", 'https://api.example.com')
  .objectSrc("'none'")
  .upgradeInsecureRequests();

console.log('Custom CSP:', customCSP.build());

// ================================================
// 8. SECURE FILE UPLOAD HANDLER
// ================================================

/**
 * Secure File Upload Validator
 * Validates file uploads for security
 */
class SecureFileUpload {
  constructor(options = {}) {
    this.allowedTypes = options.allowedTypes || [
      'image/jpeg',
      'image/png',
      'image/gif',
    ];
    this.maxSize = options.maxSize || 5 * 1024 * 1024; // 5MB
    this.allowedExtensions = options.allowedExtensions || [
      '.jpg',
      '.jpeg',
      '.png',
      '.gif',
    ];
  }

  // Magic bytes for file type detection
  static magicBytes = {
    'image/jpeg': [[0xff, 0xd8, 0xff]],
    'image/png': [[0x89, 0x50, 0x4e, 0x47]],
    'image/gif': [[0x47, 0x49, 0x46, 0x38]],
    'application/pdf': [[0x25, 0x50, 0x44, 0x46]],
  };

  validate(file) {
    const errors = [];

    // Check file exists
    if (!file || !file.buffer) {
      return { valid: false, errors: ['No file provided'] };
    }

    // Check size
    if (file.size > this.maxSize) {
      errors.push(
        `File size exceeds maximum of ${this.maxSize / 1024 / 1024}MB`
      );
    }

    // Check extension
    const ext = this.getExtension(file.name);
    if (!this.allowedExtensions.includes(ext.toLowerCase())) {
      errors.push(`File extension not allowed: ${ext}`);
    }

    // Check MIME type
    if (!this.allowedTypes.includes(file.mimeType)) {
      errors.push(`File type not allowed: ${file.mimeType}`);
    }

    // Verify magic bytes match declared type
    const detectedType = this.detectType(file.buffer);
    if (detectedType && detectedType !== file.mimeType) {
      errors.push(`File content doesn't match declared type`);
    }

    // Check for dangerous content
    if (this.hasExecutableContent(file.buffer)) {
      errors.push('File contains potentially dangerous content');
    }

    return {
      valid: errors.length === 0,
      errors,
      sanitizedName: this.sanitizeFilename(file.name),
    };
  }

  getExtension(filename) {
    const match = filename.match(/\.[^.]+$/);
    return match ? match[0] : '';
  }

  detectType(buffer) {
    for (const [type, signatures] of Object.entries(
      SecureFileUpload.magicBytes
    )) {
      for (const signature of signatures) {
        if (this.matchesSignature(buffer, signature)) {
          return type;
        }
      }
    }
    return null;
  }

  matchesSignature(buffer, signature) {
    for (let i = 0; i < signature.length; i++) {
      if (buffer[i] !== signature[i]) {
        return false;
      }
    }
    return true;
  }

  hasExecutableContent(buffer) {
    const content = buffer.toString('utf8', 0, Math.min(buffer.length, 1000));

    // Check for script tags, PHP code, etc.
    const dangerousPatterns = [/<script/i, /<\?php/i, /<%/, /javascript:/i];

    return dangerousPatterns.some((pattern) => pattern.test(content));
  }

  sanitizeFilename(filename) {
    // Remove path components
    filename = filename.replace(/^.*[\\\/]/, '');

    // Remove dangerous characters
    filename = filename.replace(/[^a-zA-Z0-9._-]/g, '_');

    // Limit length
    if (filename.length > 100) {
      const ext = this.getExtension(filename);
      filename = filename.slice(0, 100 - ext.length) + ext;
    }

    return filename;
  }
}

console.log('\n' + '='.repeat(50));
console.log('Secure File Upload Examples:');
console.log('-'.repeat(50));

const uploader = new SecureFileUpload();

// Simulate file validation
const validFile = {
  name: 'photo.jpg',
  mimeType: 'image/jpeg',
  size: 1024 * 100, // 100KB
  buffer: Buffer.from([0xff, 0xd8, 0xff, 0xe0]), // JPEG magic bytes
};

console.log('Valid file:', uploader.validate(validFile));

const invalidFile = {
  name: '../../../etc/passwd.jpg',
  mimeType: 'image/jpeg',
  size: 10 * 1024 * 1024, // 10MB
  buffer: Buffer.from([0x25, 0x50, 0x44, 0x46]), // PDF magic bytes
};

console.log('Invalid file:', uploader.validate(invalidFile));

// ================================================
// EXPORTS
// ================================================

module.exports = {
  SchemaValidator,
  OutputEncoder,
  PasswordSecurity,
  SecureErrorHandler,
  SecureConfig,
  SecureAPIHandler,
  CSPBuilder,
  SecureFileUpload,
  // Custom errors
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
};
Examples - JavaScript Tutorial | DeepML