javascript

exercises

exercises.js
/**
 * 21.2 Secure Coding Practices - Exercises
 *
 * Practice implementing secure coding patterns
 */

/**
 * Exercise 1: Advanced Input Validator
 *
 * Build a comprehensive input validation system that supports:
 * - Nested object validation
 * - Array item validation
 * - Cross-field validation
 * - Custom sanitization
 * - Internationalization support
 *
 * Requirements:
 * 1. Validate nested objects recursively
 * 2. Support conditional required fields
 * 3. Support cross-field validation rules
 * 4. Provide detailed error paths
 * 5. Support async validators
 */

class AdvancedValidator {
  constructor() {
    // TODO: Initialize validator state
    this.schemas = new Map();
  }

  // Register a schema
  registerSchema(name, schema) {
    // TODO: Store schema for reuse
  }

  // Validate data against schema
  async validate(data, schema) {
    // TODO: Implement validation logic
    // Return { valid, errors, sanitized }
  }

  // Validate a single field
  async validateField(value, rules, path, context) {
    // TODO: Validate individual field
    // Handle all rule types
  }

  // Cross-field validation
  validateCrossFields(data, crossRules) {
    // TODO: Validate relationships between fields
  }
}

// Test your implementation
function testAdvancedValidator() {
  const validator = new AdvancedValidator();

  const addressSchema = {
    street: { type: 'string', required: true },
    city: { type: 'string', required: true },
    zip: { type: 'string', pattern: /^\d{5}(-\d{4})?$/ },
  };

  const orderSchema = {
    customer: {
      type: 'object',
      required: true,
      properties: {
        name: { type: 'string', required: true },
        email: { type: 'email', required: true },
      },
    },
    items: {
      type: 'array',
      required: true,
      minItems: 1,
      items: {
        productId: { type: 'string', required: true },
        quantity: { type: 'number', required: true, min: 1 },
      },
    },
    shippingAddress: {
      type: 'object',
      required: true,
      schema: addressSchema,
    },
    billingAddress: {
      type: 'object',
      requiredIf: (data) => !data.sameAsShipping,
    },
    sameAsShipping: { type: 'boolean', default: false },
  };

  // Test cases
  console.log('Testing Advanced Validator...');

  // Add your tests here
}

// Uncomment to test
// testAdvancedValidator();

/**
 * Exercise 2: Secure Token Manager
 *
 * Implement a secure token management system for:
 * - JWT generation and validation
 * - Refresh token rotation
 * - Token revocation
 * - Rate limiting by token
 *
 * Requirements:
 * 1. Generate cryptographically secure tokens
 * 2. Implement token expiration
 * 3. Support token refresh with rotation
 * 4. Maintain a revocation list
 * 5. Implement sliding expiration
 */

class SecureTokenManager {
  constructor(options = {}) {
    // TODO: Initialize with secret and options
    this.secret = options.secret;
    this.accessTokenTTL = options.accessTokenTTL || 15 * 60 * 1000; // 15 minutes
    this.refreshTokenTTL = options.refreshTokenTTL || 7 * 24 * 60 * 60 * 1000; // 7 days
    this.revokedTokens = new Set();
    this.refreshTokens = new Map();
  }

  // Generate access token
  generateAccessToken(payload) {
    // TODO: Create JWT-like token structure
    // Include: sub, iat, exp, jti (token ID)
  }

  // Generate refresh token
  generateRefreshToken(userId) {
    // TODO: Create secure refresh token
    // Store mapping to user
  }

  // Verify and decode token
  verifyAccessToken(token) {
    // TODO: Verify signature and expiration
    // Check revocation list
  }

  // Refresh access token
  refreshAccessToken(refreshToken) {
    // TODO: Validate refresh token
    // Rotate refresh token (issue new, revoke old)
    // Return new access + refresh tokens
  }

  // Revoke token
  revokeToken(token) {
    // TODO: Add to revocation list
  }

  // Revoke all tokens for user
  revokeAllUserTokens(userId) {
    // TODO: Revoke all refresh tokens for user
  }

  // Create HMAC signature
  sign(data) {
    // TODO: Create HMAC-SHA256 signature
  }

  // Verify HMAC signature
  verifySignature(data, signature) {
    // TODO: Timing-safe signature verification
  }
}

// Test your implementation
function testSecureTokenManager() {
  const tokenManager = new SecureTokenManager({
    secret: 'test-secret-key-32-bytes-long!!',
  });

  console.log('Testing Secure Token Manager...');

  // Generate tokens
  const accessToken = tokenManager.generateAccessToken({
    sub: 'user123',
    role: 'admin',
  });
  console.log('Access token:', accessToken);

  const refreshToken = tokenManager.generateRefreshToken('user123');
  console.log('Refresh token:', refreshToken);

  // Verify token
  const verified = tokenManager.verifyAccessToken(accessToken);
  console.log('Verified:', verified);

  // Refresh token
  const newTokens = tokenManager.refreshAccessToken(refreshToken);
  console.log('New tokens:', newTokens);
}

// Uncomment to test
// testSecureTokenManager();

/**
 * Exercise 3: Secure Form Handler
 *
 * Create a comprehensive form security handler that:
 * - Validates CSRF tokens
 * - Sanitizes all inputs
 * - Prevents double submission
 * - Implements honeypot fields
 * - Logs security events
 *
 * Requirements:
 * 1. Generate and validate CSRF tokens
 * 2. Detect and block bot submissions
 * 3. Implement submission rate limiting
 * 4. Track and prevent replay attacks
 */

class SecureFormHandler {
  constructor(options = {}) {
    // TODO: Initialize form handler
    this.csrfTokens = new Map();
    this.submissionLog = new Map();
    this.honeypotField = options.honeypotField || '_hp_field';
  }

  // Generate form with CSRF token
  generateForm(formId, userId) {
    // TODO: Create CSRF token
    // Return token for inclusion in form
  }

  // Validate form submission
  validateSubmission(formId, data, userId, meta = {}) {
    // TODO: Validate CSRF token
    // Check honeypot
    // Check for double submission
    // Check rate limiting
    // Return validation result
  }

  // Check honeypot field
  checkHoneypot(data) {
    // TODO: Verify honeypot is empty
    // Filled honeypot = bot
  }

  // Check submission timing
  checkSubmissionTiming(meta) {
    // TODO: Check if form was submitted too quickly
    // Very fast submission = likely bot
  }

  // Rate limit submissions
  checkRateLimit(userId, formId) {
    // TODO: Limit submissions per user per time window
  }

  // Log security event
  logSecurityEvent(event, details) {
    // TODO: Log for security monitoring
  }
}

// Test your implementation
function testSecureFormHandler() {
  const formHandler = new SecureFormHandler();

  console.log('Testing Secure Form Handler...');

  // Generate form token
  const token = formHandler.generateForm('contact-form', 'user123');
  console.log('Form token:', token);

  // Valid submission
  const validResult = formHandler.validateSubmission(
    'contact-form',
    { name: 'John', email: 'john@example.com', csrfToken: token },
    'user123',
    { submitTime: Date.now() - 5000, loadTime: Date.now() - 10000 }
  );
  console.log('Valid submission:', validResult);

  // Bot submission (honeypot filled)
  const botResult = formHandler.validateSubmission(
    'contact-form',
    {
      name: 'Bot',
      email: 'bot@spam.com',
      _hp_field: 'spam content',
      csrfToken: token,
    },
    'bot-user',
    { submitTime: Date.now() - 100 }
  );
  console.log('Bot submission:', botResult);
}

// Uncomment to test
// testSecureFormHandler();

/**
 * Exercise 4: Secure Data Store
 *
 * Implement a secure in-memory data store that:
 * - Encrypts sensitive data at rest
 * - Implements access control
 * - Audit logs all access
 * - Supports data expiration
 * - Prevents information leakage
 *
 * Requirements:
 * 1. Field-level encryption for sensitive data
 * 2. Role-based access control
 * 3. Complete audit trail
 * 4. Secure data deletion
 */

class SecureDataStore {
  constructor(options = {}) {
    // TODO: Initialize secure store
    this.data = new Map();
    this.encryptionKey = options.encryptionKey;
    this.accessControl = new Map();
    this.auditLog = [];
  }

  // Define access rules
  defineAccess(resource, rules) {
    // TODO: Set up access control rules
    // { read: ['admin', 'user'], write: ['admin'] }
  }

  // Store data securely
  set(key, value, options = {}) {
    // TODO: Encrypt sensitive fields
    // Set TTL if provided
    // Log the operation
  }

  // Retrieve data
  get(key, accessor) {
    // TODO: Check access permissions
    // Decrypt if needed
    // Log the access
  }

  // Delete data securely
  delete(key, accessor) {
    // TODO: Check permissions
    // Securely delete (overwrite before removal)
    // Log the deletion
  }

  // Encrypt value
  encrypt(value) {
    // TODO: Encrypt using AES-256-GCM or similar
  }

  // Decrypt value
  decrypt(encrypted) {
    // TODO: Decrypt and verify integrity
  }

  // Check access permission
  checkAccess(key, accessor, operation) {
    // TODO: Verify accessor has permission
  }

  // Get audit log
  getAuditLog(filter = {}) {
    // TODO: Return filtered audit log
  }
}

// Test your implementation
function testSecureDataStore() {
  const store = new SecureDataStore({
    encryptionKey: 'test-encryption-key-32-bytes!!',
  });

  console.log('Testing Secure Data Store...');

  // Define access rules
  store.defineAccess('user-data', {
    read: ['admin', 'owner'],
    write: ['owner'],
  });

  // Store sensitive data
  store.set(
    'user:123',
    {
      name: 'John Doe',
      ssn: '123-45-6789',
      creditCard: '4111111111111111',
    },
    {
      sensitiveFields: ['ssn', 'creditCard'],
      owner: 'user:123',
      ttl: 3600000,
    }
  );

  // Access as admin
  const adminView = store.get('user:123', { role: 'admin', id: 'admin:1' });
  console.log('Admin view:', adminView);

  // Access as different user (should fail)
  const otherView = store.get('user:123', { role: 'user', id: 'user:456' });
  console.log('Other user view:', otherView);
}

// Uncomment to test
// testSecureDataStore();

/**
 * Exercise 5: Security Middleware Pipeline
 *
 * Build a modular security middleware system that:
 * - Applies multiple security checks in order
 * - Allows custom middleware addition
 * - Handles errors gracefully
 * - Provides detailed security reports
 *
 * Requirements:
 * 1. Composable middleware architecture
 * 2. Built-in common security middleware
 * 3. Async middleware support
 * 4. Early termination on security failures
 */

class SecurityPipeline {
  constructor() {
    // TODO: Initialize middleware stack
    this.middleware = [];
  }

  // Add middleware to pipeline
  use(name, handler, options = {}) {
    // TODO: Add middleware with priority support
  }

  // Execute pipeline
  async execute(request, response) {
    // TODO: Run all middleware in order
    // Stop on first failure
    // Return combined result
  }

  // Built-in: Rate Limiter middleware
  static rateLimiter(options = {}) {
    // TODO: Return rate limiting middleware
  }

  // Built-in: Input Sanitizer middleware
  static inputSanitizer(options = {}) {
    // TODO: Return input sanitization middleware
  }

  // Built-in: Authentication checker
  static authChecker(options = {}) {
    // TODO: Return auth checking middleware
  }

  // Built-in: CORS validator
  static corsValidator(options = {}) {
    // TODO: Return CORS validation middleware
  }

  // Built-in: Content Security Policy setter
  static cspSetter(options = {}) {
    // TODO: Return CSP header setting middleware
  }

  // Generate security report
  generateReport(results) {
    // TODO: Create detailed security report
  }
}

// Test your implementation
function testSecurityPipeline() {
  const pipeline = new SecurityPipeline();

  console.log('Testing Security Pipeline...');

  // Add middleware
  pipeline.use(
    'rate-limiter',
    SecurityPipeline.rateLimiter({ maxRequests: 100 })
  );
  pipeline.use('sanitizer', SecurityPipeline.inputSanitizer());
  pipeline.use('auth', SecurityPipeline.authChecker({ required: true }));
  pipeline.use(
    'cors',
    SecurityPipeline.corsValidator({ origins: ['http://localhost:3000'] })
  );
  pipeline.use('csp', SecurityPipeline.cspSetter());

  // Custom middleware
  pipeline.use('custom-check', async (req, res, next) => {
    if (req.headers['x-custom-token'] !== 'valid') {
      return { passed: false, reason: 'Invalid custom token' };
    }
    return next();
  });

  // Test request
  const request = {
    ip: '192.168.1.1',
    headers: {
      authorization: 'Bearer valid-token',
      origin: 'http://localhost:3000',
      'x-custom-token': 'valid',
    },
    body: { name: '<script>alert("xss")</script>' },
  };

  const response = { headers: {} };

  pipeline.execute(request, response).then((result) => {
    console.log('Pipeline result:', result);
    console.log('Response headers:', response.headers);
  });
}

// Uncomment to test
// testSecurityPipeline();

// ============================================================
// SOLUTIONS (Uncomment to reveal)
// ============================================================

/*
// SOLUTION 1: Advanced Input Validator

class AdvancedValidatorSolution {
    constructor() {
        this.schemas = new Map();
        this.customValidators = new Map();
    }
    
    registerSchema(name, schema) {
        this.schemas.set(name, schema);
    }
    
    registerValidator(name, validator) {
        this.customValidators.set(name, validator);
    }
    
    async validate(data, schema) {
        const errors = [];
        const sanitized = {};
        
        for (const [field, rules] of Object.entries(schema)) {
            const result = await this.validateField(
                data[field],
                rules,
                field,
                data
            );
            
            if (result.errors.length > 0) {
                errors.push(...result.errors);
            } else {
                sanitized[field] = result.value;
            }
        }
        
        // Cross-field validation
        if (schema._crossValidation) {
            const crossErrors = this.validateCrossFields(sanitized, schema._crossValidation);
            errors.push(...crossErrors);
        }
        
        return { valid: errors.length === 0, errors, sanitized };
    }
    
    async validateField(value, rules, path, context) {
        const errors = [];
        let processedValue = value;
        
        // Check required
        const isRequired = typeof rules.required === 'function'
            ? rules.required(context)
            : rules.required;
        
        if (isRequired && (value === undefined || value === null || value === '')) {
            return { errors: [{ path, message: `${path} is required` }], value };
        }
        
        if (value === undefined || value === null) {
            return { errors: [], value: rules.default };
        }
        
        // Type validation
        if (rules.type) {
            const typeValid = this.checkType(value, rules.type);
            if (!typeValid) {
                errors.push({ path, message: `${path} must be of type ${rules.type}` });
                return { errors, value };
            }
        }
        
        // Nested object validation
        if (rules.type === 'object' && rules.properties) {
            const nestedResult = await this.validate(value, rules.properties);
            if (!nestedResult.valid) {
                for (const error of nestedResult.errors) {
                    errors.push({ path: `${path}.${error.path}`, message: error.message });
                }
            }
            processedValue = nestedResult.sanitized;
        }
        
        // Array validation
        if (rules.type === 'array') {
            if (rules.minItems && value.length < rules.minItems) {
                errors.push({ path, message: `${path} must have at least ${rules.minItems} items` });
            }
            if (rules.maxItems && value.length > rules.maxItems) {
                errors.push({ path, message: `${path} must have at most ${rules.maxItems} items` });
            }
            if (rules.items) {
                const validatedItems = [];
                for (let i = 0; i < value.length; i++) {
                    const itemResult = await this.validate(value[i], rules.items);
                    if (!itemResult.valid) {
                        for (const error of itemResult.errors) {
                            errors.push({ path: `${path}[${i}].${error.path}`, message: error.message });
                        }
                    }
                    validatedItems.push(itemResult.sanitized);
                }
                processedValue = validatedItems;
            }
        }
        
        // String validations
        if (rules.type === 'string') {
            if (rules.minLength && value.length < rules.minLength) {
                errors.push({ path, message: `${path} must be at least ${rules.minLength} characters` });
            }
            if (rules.maxLength && value.length > rules.maxLength) {
                errors.push({ path, message: `${path} must be at most ${rules.maxLength} characters` });
            }
            if (rules.pattern && !rules.pattern.test(value)) {
                errors.push({ path, message: `${path} format is invalid` });
            }
        }
        
        // Custom async validator
        if (rules.validate) {
            const customResult = await rules.validate(value, context);
            if (customResult !== true) {
                errors.push({ path, message: customResult || `${path} is invalid` });
            }
        }
        
        // Sanitization
        if (rules.sanitize && errors.length === 0) {
            processedValue = rules.sanitize(processedValue);
        }
        
        return { errors, value: processedValue };
    }
    
    checkType(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 /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
            default: return true;
        }
    }
    
    validateCrossFields(data, rules) {
        const errors = [];
        for (const rule of rules) {
            const result = rule.validate(data);
            if (result !== true) {
                errors.push({ path: rule.fields.join(', '), message: result });
            }
        }
        return errors;
    }
}


// SOLUTION 2: Secure Token Manager

class SecureTokenManagerSolution {
    constructor(options = {}) {
        this.secret = options.secret || this.generateSecret();
        this.accessTokenTTL = options.accessTokenTTL || 15 * 60 * 1000;
        this.refreshTokenTTL = options.refreshTokenTTL || 7 * 24 * 60 * 60 * 1000;
        this.revokedTokens = new Set();
        this.refreshTokens = new Map();
    }
    
    generateSecret() {
        return Array.from({ length: 32 }, () =>
            Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
        ).join('');
    }
    
    generateTokenId() {
        return Date.now().toString(36) + Math.random().toString(36).substr(2);
    }
    
    generateAccessToken(payload) {
        const header = { alg: 'HS256', typ: 'JWT' };
        const now = Date.now();
        
        const tokenPayload = {
            ...payload,
            iat: now,
            exp: now + this.accessTokenTTL,
            jti: this.generateTokenId()
        };
        
        const headerB64 = this.base64UrlEncode(JSON.stringify(header));
        const payloadB64 = this.base64UrlEncode(JSON.stringify(tokenPayload));
        const signature = this.sign(`${headerB64}.${payloadB64}`);
        
        return `${headerB64}.${payloadB64}.${signature}`;
    }
    
    generateRefreshToken(userId) {
        const tokenId = this.generateTokenId();
        const token = {
            id: tokenId,
            userId,
            createdAt: Date.now(),
            expiresAt: Date.now() + this.refreshTokenTTL
        };
        
        this.refreshTokens.set(tokenId, token);
        
        const tokenData = this.base64UrlEncode(JSON.stringify({ id: tokenId, userId }));
        const signature = this.sign(tokenData);
        
        return `${tokenData}.${signature}`;
    }
    
    verifyAccessToken(token) {
        try {
            const [headerB64, payloadB64, signature] = token.split('.');
            
            // Verify signature
            if (!this.verifySignature(`${headerB64}.${payloadB64}`, signature)) {
                return { valid: false, error: 'Invalid signature' };
            }
            
            const payload = JSON.parse(this.base64UrlDecode(payloadB64));
            
            // Check expiration
            if (payload.exp < Date.now()) {
                return { valid: false, error: 'Token expired' };
            }
            
            // Check revocation
            if (this.revokedTokens.has(payload.jti)) {
                return { valid: false, error: 'Token revoked' };
            }
            
            return { valid: true, payload };
            
        } catch (error) {
            return { valid: false, error: 'Invalid token format' };
        }
    }
    
    refreshAccessToken(refreshToken) {
        try {
            const [tokenData, signature] = refreshToken.split('.');
            
            if (!this.verifySignature(tokenData, signature)) {
                return { success: false, error: 'Invalid refresh token' };
            }
            
            const { id, userId } = JSON.parse(this.base64UrlDecode(tokenData));
            const storedToken = this.refreshTokens.get(id);
            
            if (!storedToken) {
                return { success: false, error: 'Refresh token not found' };
            }
            
            if (storedToken.expiresAt < Date.now()) {
                this.refreshTokens.delete(id);
                return { success: false, error: 'Refresh token expired' };
            }
            
            // Rotate: delete old, create new
            this.refreshTokens.delete(id);
            
            const newAccessToken = this.generateAccessToken({ sub: userId });
            const newRefreshToken = this.generateRefreshToken(userId);
            
            return {
                success: true,
                accessToken: newAccessToken,
                refreshToken: newRefreshToken
            };
            
        } catch (error) {
            return { success: false, error: 'Invalid refresh token format' };
        }
    }
    
    revokeToken(token) {
        const verified = this.verifyAccessToken(token);
        if (verified.valid) {
            this.revokedTokens.add(verified.payload.jti);
        }
    }
    
    revokeAllUserTokens(userId) {
        for (const [id, token] of this.refreshTokens.entries()) {
            if (token.userId === userId) {
                this.refreshTokens.delete(id);
            }
        }
    }
    
    sign(data) {
        // Simple HMAC simulation (use crypto in production)
        let hash = 0;
        const combined = data + this.secret;
        for (let i = 0; i < combined.length; i++) {
            hash = ((hash << 5) - hash) + combined.charCodeAt(i);
            hash = hash & hash;
        }
        return Math.abs(hash).toString(36);
    }
    
    verifySignature(data, signature) {
        return this.sign(data) === signature;
    }
    
    base64UrlEncode(str) {
        return Buffer.from(str).toString('base64')
            .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    }
    
    base64UrlDecode(str) {
        str = str.replace(/-/g, '+').replace(/_/g, '/');
        while (str.length % 4) str += '=';
        return Buffer.from(str, 'base64').toString();
    }
}


// SOLUTION 3: Secure Form Handler

class SecureFormHandlerSolution {
    constructor(options = {}) {
        this.csrfTokens = new Map();
        this.submissionLog = new Map();
        this.honeypotField = options.honeypotField || '_hp_field';
        this.minSubmitTime = options.minSubmitTime || 2000; // 2 seconds minimum
        this.maxSubmissionsPerWindow = options.maxSubmissions || 5;
        this.windowMs = options.windowMs || 60000;
        this.tokenTTL = options.tokenTTL || 3600000; // 1 hour
        this.securityLog = [];
    }
    
    generateForm(formId, userId) {
        const token = this.generateToken();
        const key = `${formId}:${userId}`;
        
        this.csrfTokens.set(key, {
            token,
            createdAt: Date.now(),
            expiresAt: Date.now() + this.tokenTTL,
            used: false
        });
        
        return token;
    }
    
    generateToken() {
        return Array.from({ length: 32 }, () =>
            Math.floor(Math.random() * 16).toString(16)
        ).join('');
    }
    
    validateSubmission(formId, data, userId, meta = {}) {
        const errors = [];
        const key = `${formId}:${userId}`;
        
        // 1. CSRF Token validation
        const csrfResult = this.validateCsrfToken(key, data.csrfToken);
        if (!csrfResult.valid) {
            this.logSecurityEvent('CSRF_FAILURE', { formId, userId, reason: csrfResult.error });
            errors.push({ type: 'csrf', message: csrfResult.error });
        }
        
        // 2. Honeypot check
        if (!this.checkHoneypot(data)) {
            this.logSecurityEvent('HONEYPOT_TRIGGERED', { formId, userId });
            errors.push({ type: 'honeypot', message: 'Bot detected' });
        }
        
        // 3. Submission timing check
        if (!this.checkSubmissionTiming(meta)) {
            this.logSecurityEvent('SUSPICIOUS_TIMING', { formId, userId, meta });
            errors.push({ type: 'timing', message: 'Submission too fast' });
        }
        
        // 4. Rate limiting
        const rateLimitResult = this.checkRateLimit(userId, formId);
        if (!rateLimitResult.allowed) {
            this.logSecurityEvent('RATE_LIMIT_EXCEEDED', { formId, userId });
            errors.push({ type: 'rateLimit', message: 'Too many submissions' });
        }
        
        if (errors.length === 0) {
            // Mark token as used
            const tokenData = this.csrfTokens.get(key);
            if (tokenData) {
                tokenData.used = true;
            }
            
            // Log successful submission
            this.recordSubmission(userId, formId);
        }
        
        return {
            valid: errors.length === 0,
            errors,
            rateLimitInfo: rateLimitResult
        };
    }
    
    validateCsrfToken(key, token) {
        const stored = this.csrfTokens.get(key);
        
        if (!stored) {
            return { valid: false, error: 'No CSRF token found' };
        }
        
        if (stored.used) {
            return { valid: false, error: 'CSRF token already used' };
        }
        
        if (stored.expiresAt < Date.now()) {
            this.csrfTokens.delete(key);
            return { valid: false, error: 'CSRF token expired' };
        }
        
        if (stored.token !== token) {
            return { valid: false, error: 'Invalid CSRF token' };
        }
        
        return { valid: true };
    }
    
    checkHoneypot(data) {
        const honeypotValue = data[this.honeypotField];
        return honeypotValue === undefined || honeypotValue === '' || honeypotValue === null;
    }
    
    checkSubmissionTiming(meta) {
        const { submitTime, loadTime } = meta;
        if (!submitTime || !loadTime) return true;
        
        const timeTaken = submitTime - loadTime;
        return timeTaken >= this.minSubmitTime;
    }
    
    checkRateLimit(userId, formId) {
        const key = `rate:${userId}:${formId}`;
        const now = Date.now();
        
        let record = this.submissionLog.get(key);
        
        if (!record || record.windowStart + this.windowMs < now) {
            record = { windowStart: now, count: 0 };
        }
        
        record.count++;
        this.submissionLog.set(key, record);
        
        return {
            allowed: record.count <= this.maxSubmissionsPerWindow,
            remaining: Math.max(0, this.maxSubmissionsPerWindow - record.count),
            resetAt: record.windowStart + this.windowMs
        };
    }
    
    recordSubmission(userId, formId) {
        const key = `rate:${userId}:${formId}`;
        const record = this.submissionLog.get(key) || { windowStart: Date.now(), count: 0 };
        record.count++;
        this.submissionLog.set(key, record);
    }
    
    logSecurityEvent(event, details) {
        this.securityLog.push({
            timestamp: new Date().toISOString(),
            event,
            details
        });
    }
    
    getSecurityLog() {
        return this.securityLog;
    }
}


// SOLUTION 4: Secure Data Store

class SecureDataStoreSolution {
    constructor(options = {}) {
        this.data = new Map();
        this.encryptionKey = options.encryptionKey || this.generateKey();
        this.accessControl = new Map();
        this.auditLog = [];
        this.ttlTimers = new Map();
    }
    
    generateKey() {
        return Array.from({ length: 32 }, () =>
            Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
        ).join('');
    }
    
    defineAccess(resource, rules) {
        this.accessControl.set(resource, rules);
    }
    
    set(key, value, options = {}) {
        const { sensitiveFields = [], owner, ttl } = options;
        
        // Deep clone to avoid reference issues
        let storedValue = JSON.parse(JSON.stringify(value));
        
        // Encrypt sensitive fields
        for (const field of sensitiveFields) {
            if (storedValue[field] !== undefined) {
                storedValue[field] = this.encrypt(storedValue[field]);
            }
        }
        
        const record = {
            value: storedValue,
            sensitiveFields,
            owner,
            createdAt: Date.now(),
            updatedAt: Date.now()
        };
        
        // Set TTL
        if (ttl) {
            record.expiresAt = Date.now() + ttl;
            this.setTTLTimer(key, ttl);
        }
        
        this.data.set(key, record);
        
        this.log('SET', key, { owner }, 'success');
    }
    
    get(key, accessor) {
        const record = this.data.get(key);
        
        if (!record) {
            this.log('GET', key, accessor, 'not_found');
            return null;
        }
        
        // Check expiration
        if (record.expiresAt && record.expiresAt < Date.now()) {
            this.secureDelete(key);
            this.log('GET', key, accessor, 'expired');
            return null;
        }
        
        // Check access
        if (!this.checkAccess(key, record, accessor, 'read')) {
            this.log('GET', key, accessor, 'access_denied');
            return { error: 'Access denied' };
        }
        
        // Decrypt sensitive fields
        const result = JSON.parse(JSON.stringify(record.value));
        for (const field of record.sensitiveFields) {
            if (result[field]) {
                result[field] = this.decrypt(result[field]);
            }
        }
        
        this.log('GET', key, accessor, 'success');
        return result;
    }
    
    delete(key, accessor) {
        const record = this.data.get(key);
        
        if (!record) {
            return { success: false, error: 'Not found' };
        }
        
        if (!this.checkAccess(key, record, accessor, 'write')) {
            this.log('DELETE', key, accessor, 'access_denied');
            return { success: false, error: 'Access denied' };
        }
        
        this.secureDelete(key);
        this.log('DELETE', key, accessor, 'success');
        
        return { success: true };
    }
    
    secureDelete(key) {
        const record = this.data.get(key);
        if (record) {
            // Overwrite sensitive data before deletion
            for (const field in record.value) {
                record.value[field] = null;
            }
        }
        this.data.delete(key);
        
        // Clear TTL timer
        const timer = this.ttlTimers.get(key);
        if (timer) {
            clearTimeout(timer);
            this.ttlTimers.delete(key);
        }
    }
    
    setTTLTimer(key, ttl) {
        const existing = this.ttlTimers.get(key);
        if (existing) clearTimeout(existing);
        
        const timer = setTimeout(() => {
            this.secureDelete(key);
            this.log('TTL_EXPIRE', key, { system: true }, 'expired');
        }, ttl);
        
        this.ttlTimers.set(key, timer);
    }
    
    encrypt(value) {
        // Simple XOR encryption simulation (use real crypto in production)
        const str = typeof value === 'string' ? value : JSON.stringify(value);
        let encrypted = '';
        for (let i = 0; i < str.length; i++) {
            encrypted += String.fromCharCode(
                str.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
            );
        }
        return { encrypted: Buffer.from(encrypted).toString('base64'), isEncrypted: true };
    }
    
    decrypt(encrypted) {
        if (!encrypted || !encrypted.isEncrypted) return encrypted;
        
        const str = Buffer.from(encrypted.encrypted, 'base64').toString();
        let decrypted = '';
        for (let i = 0; i < str.length; i++) {
            decrypted += String.fromCharCode(
                str.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
            );
        }
        
        try {
            return JSON.parse(decrypted);
        } catch {
            return decrypted;
        }
    }
    
    checkAccess(key, record, accessor, operation) {
        // Owner always has access
        if (accessor.id === record.owner) {
            return true;
        }
        
        // Check role-based access
        const resourceType = key.split(':')[0];
        const rules = this.accessControl.get(resourceType);
        
        if (!rules) return false;
        
        const allowedRoles = rules[operation] || [];
        return allowedRoles.includes(accessor.role);
    }
    
    log(operation, key, accessor, result) {
        this.auditLog.push({
            timestamp: new Date().toISOString(),
            operation,
            key,
            accessor: accessor?.id || 'unknown',
            accessorRole: accessor?.role || 'unknown',
            result
        });
    }
    
    getAuditLog(filter = {}) {
        let logs = [...this.auditLog];
        
        if (filter.key) {
            logs = logs.filter(l => l.key === filter.key);
        }
        if (filter.operation) {
            logs = logs.filter(l => l.operation === filter.operation);
        }
        if (filter.accessor) {
            logs = logs.filter(l => l.accessor === filter.accessor);
        }
        if (filter.since) {
            logs = logs.filter(l => new Date(l.timestamp) >= new Date(filter.since));
        }
        
        return logs;
    }
}


// SOLUTION 5: Security Pipeline

class SecurityPipelineSolution {
    constructor() {
        this.middleware = [];
    }
    
    use(name, handler, options = {}) {
        this.middleware.push({
            name,
            handler,
            priority: options.priority || this.middleware.length,
            enabled: options.enabled !== false
        });
        
        // Sort by priority
        this.middleware.sort((a, b) => a.priority - b.priority);
    }
    
    async execute(request, response) {
        const results = [];
        const context = { startTime: Date.now() };
        
        for (const mw of this.middleware) {
            if (!mw.enabled) continue;
            
            const startTime = Date.now();
            
            try {
                const result = await mw.handler(request, response, () => ({ passed: true }));
                const duration = Date.now() - startTime;
                
                results.push({
                    name: mw.name,
                    passed: result.passed,
                    duration,
                    details: result
                });
                
                if (!result.passed) {
                    return {
                        success: false,
                        failedAt: mw.name,
                        reason: result.reason,
                        results,
                        report: this.generateReport(results)
                    };
                }
            } catch (error) {
                results.push({
                    name: mw.name,
                    passed: false,
                    error: error.message
                });
                
                return {
                    success: false,
                    failedAt: mw.name,
                    reason: error.message,
                    results,
                    report: this.generateReport(results)
                };
            }
        }
        
        return {
            success: true,
            results,
            totalTime: Date.now() - context.startTime,
            report: this.generateReport(results)
        };
    }
    
    static rateLimiter(options = {}) {
        const store = new Map();
        const { maxRequests = 100, windowMs = 60000 } = options;
        
        return async (req, res, next) => {
            const key = req.ip || 'unknown';
            const now = Date.now();
            
            let record = store.get(key);
            if (!record || record.resetTime < now) {
                record = { count: 0, resetTime: now + windowMs };
            }
            
            record.count++;
            store.set(key, record);
            
            res.headers['X-RateLimit-Limit'] = maxRequests;
            res.headers['X-RateLimit-Remaining'] = Math.max(0, maxRequests - record.count);
            
            if (record.count > maxRequests) {
                return { passed: false, reason: 'Rate limit exceeded' };
            }
            
            return next();
        };
    }
    
    static inputSanitizer(options = {}) {
        return async (req, res, next) => {
            if (req.body) {
                req.body = sanitizeObject(req.body);
            }
            return next();
            
            function sanitizeObject(obj) {
                if (typeof obj === 'string') {
                    return obj
                        .replace(/</g, '&lt;')
                        .replace(/>/g, '&gt;')
                        .replace(/"/g, '&quot;')
                        .trim();
                }
                if (typeof obj === 'object' && obj !== null) {
                    for (const key of Object.keys(obj)) {
                        obj[key] = sanitizeObject(obj[key]);
                    }
                }
                return obj;
            }
        };
    }
    
    static authChecker(options = {}) {
        const { required = true, headerName = 'authorization' } = options;
        
        return async (req, res, next) => {
            const authHeader = req.headers[headerName];
            
            if (!authHeader && required) {
                return { passed: false, reason: 'Authentication required' };
            }
            
            if (authHeader) {
                const [scheme, token] = authHeader.split(' ');
                if (scheme?.toLowerCase() !== 'bearer' || !token) {
                    return { passed: false, reason: 'Invalid authentication format' };
                }
                req.authToken = token;
            }
            
            return next();
        };
    }
    
    static corsValidator(options = {}) {
        const { origins = [], methods = ['GET', 'POST', 'PUT', 'DELETE'] } = options;
        
        return async (req, res, next) => {
            const origin = req.headers.origin;
            
            if (origin && !origins.includes(origin) && !origins.includes('*')) {
                return { passed: false, reason: 'Origin not allowed' };
            }
            
            res.headers['Access-Control-Allow-Origin'] = origin || '*';
            res.headers['Access-Control-Allow-Methods'] = methods.join(', ');
            res.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization';
            
            return next();
        };
    }
    
    static cspSetter(options = {}) {
        const defaultPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'";
        const policy = options.policy || defaultPolicy;
        
        return async (req, res, next) => {
            res.headers['Content-Security-Policy'] = policy;
            res.headers['X-Content-Type-Options'] = 'nosniff';
            res.headers['X-Frame-Options'] = 'DENY';
            res.headers['X-XSS-Protection'] = '1; mode=block';
            
            return next();
        };
    }
    
    generateReport(results) {
        const passed = results.filter(r => r.passed).length;
        const failed = results.filter(r => !r.passed).length;
        const totalDuration = results.reduce((sum, r) => sum + (r.duration || 0), 0);
        
        return {
            summary: {
                total: results.length,
                passed,
                failed,
                passRate: `${((passed / results.length) * 100).toFixed(1)}%`,
                totalDuration: `${totalDuration}ms`
            },
            details: results.map(r => ({
                name: r.name,
                status: r.passed ? 'PASS' : 'FAIL',
                duration: r.duration ? `${r.duration}ms` : 'N/A',
                reason: r.details?.reason || r.error || null
            }))
        };
    }
}

console.log('Solutions loaded. Uncomment test functions to verify implementations.');
*/

module.exports = {
  AdvancedValidator,
  SecureTokenManager,
  SecureFormHandler,
  SecureDataStore,
  SecurityPipeline,
};
Exercises - JavaScript Tutorial | DeepML