Files
novalon-website/docs/plans/2026-03-24-contact-form-security-enhancement.md
T
张翔 498bb3a3c8 refactor: reorganize project structure and improve code quality
- Move CI/CD configs to config/ci/ directory
- Reorganize scripts into categorized directories (deployment, monitoring, testing, utils)
- Consolidate documentation into docs/ directory with proper structure
- Update linting and testing configurations
- Remove obsolete test reports and performance summaries
- Add new documentation for code quality tools and contact form security
- Improve project organization and maintainability
- Fix lint-staged config to only lint JS/TS files
- Disable react/react-in-jsx-scope rule for Next.js compatibility
- Ignore scripts and test config directories in ESLint
2026-03-24 13:38:58 +08:00

59 KiB
Raw Blame History

Contact Form Security Enhancement Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Enhance contact form security with multi-layer rate limiting, improved captcha, and anomaly detection to prevent spam, bot attacks, and DDoS threats.

Architecture: Implement security middleware with Redis-based rate limiting, enhanced captcha generation/validation, input sanitization, and comprehensive security logging. Use existing contact form structure as foundation.

Tech Stack: Next.js 14, TypeScript, Redis (for rate limiting), Zod (validation), existing Resend email service


Task 1: Create Security Configuration Module

Files:

  • Create: src/lib/security/config.ts
  • Test: src/lib/security/config.test.ts

Step 1: Write the failing test

import { getSecurityConfig, validateSecurityConfig } from '../config';

describe('Security Config', () => {
  test('should return default security configuration', () => {
    const config = getSecurityConfig();
    expect(config).toBeDefined();
    expect(config.rateLimit.ip.maxRequests).toBe(10);
    expect(config.rateLimit.ip.windowMinutes).toBe(60);
    expect(config.captcha.complexity).toBe('medium');
  });

  test('should validate security config structure', () => {
    const config = getSecurityConfig();
    const isValid = validateSecurityConfig(config);
    expect(isValid).toBe(true);
  });

  test('should reject invalid config', () => {
    const invalidConfig = { rateLimit: { ip: { maxRequests: -1 } } };
    const isValid = validateSecurityConfig(invalidConfig);
    expect(isValid).toBe(false);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/config.test.ts

Expected: FAIL with "Cannot find module '../config'"

Step 3: Write minimal implementation

export interface SecurityConfig {
  captcha: {
    complexity: 'simple' | 'medium' | 'complex';
    expiryMinutes: number;
    maxAttempts: number;
  };
  rateLimit: {
    ip: {
      maxRequests: number;
      windowMinutes: number;
    };
    email: {
      maxRequests: number;
      windowHours: number;
    };
    global: {
      maxRequests: number;
      windowMinutes: number;
    };
  };
  protection: {
    enableCSRF: boolean;
    enableInputSanitization: boolean;
    enableBehaviorDetection: boolean;
    blockSuspiciousIPs: boolean;
  };
  logging: {
    enableSecurityLogs: boolean;
    logRetentionDays: number;
    alertThreshold: number;
  };
}

const defaultConfig: SecurityConfig = {
  captcha: {
    complexity: 'medium',
    expiryMinutes: 5,
    maxAttempts: 3,
  },
  rateLimit: {
    ip: {
      maxRequests: 10,
      windowMinutes: 60,
    },
    email: {
      maxRequests: 3,
      windowHours: 24,
    },
    global: {
      maxRequests: 50,
      windowMinutes: 1,
    },
  },
  protection: {
    enableCSRF: true,
    enableInputSanitization: true,
    enableBehaviorDetection: true,
    blockSuspiciousIPs: true,
  },
  logging: {
    enableSecurityLogs: true,
    logRetentionDays: 30,
    alertThreshold: 5,
  },
};

export function getSecurityConfig(): SecurityConfig {
  return { ...defaultConfig };
}

export function validateSecurityConfig(config: any): boolean {
  if (!config || typeof config !== 'object') return false;
  if (!config.rateLimit || !config.captcha || !config.protection) return false;
  if (config.rateLimit.ip?.maxRequests < 0) return false;
  if (config.captcha.expiryMinutes <= 0) return false;
  return true;
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/config.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/config.ts src/lib/security/config.test.ts
git commit -m "feat: add security configuration module"

Task 2: Implement Enhanced Captcha System

Files:

  • Create: src/lib/security/captcha.ts
  • Test: src/lib/security/captcha.test.ts

Step 1: Write the failing test

import { generateCaptcha, validateCaptcha, CaptchaResult } from '../captcha';

describe('Enhanced Captcha System', () => {
  test('should generate captcha with medium complexity', () => {
    const result = generateCaptcha('medium');
    expect(result).toBeDefined();
    expect(result.question).toBeDefined();
    expect(result.answer).toBeDefined();
    expect(result.hash).toBeDefined();
    expect(result.timestamp).toBeDefined();
    expect(typeof result.question).toBe('string');
    expect(typeof result.answer).toBe('number');
  });

  test('should generate different captcha each time', () => {
    const result1 = generateCaptcha('medium');
    const result2 = generateCaptcha('medium');
    expect(result1.hash).not.toBe(result2.hash);
  });

  test('should validate correct captcha answer', () => {
    const captcha = generateCaptcha('medium');
    const isValid = validateCaptcha(captcha.hash, captcha.answer, captcha.timestamp);
    expect(isValid).toBe(true);
  });

  test('should reject incorrect captcha answer', () => {
    const captcha = generateCaptcha('medium');
    const isValid = validateCaptcha(captcha.hash, captcha.answer + 1, captcha.timestamp);
    expect(isValid).toBe(false);
  });

  test('should reject expired captcha', () => {
    const captcha = generateCaptcha('medium');
    const expiredTimestamp = Date.now() - (6 * 60 * 1000); // 6 minutes ago
    const isValid = validateCaptcha(captcha.hash, captcha.answer, expiredTimestamp);
    expect(isValid).toBe(false);
  });

  test('should generate complex captcha', () => {
    const result = generateCaptcha('complex');
    expect(result).toBeDefined();
    const numbers = result.question.match(/\d+/g);
    expect(numbers?.length).toBeGreaterThan(2);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/captcha.test.ts

Expected: FAIL with "Cannot find module '../captcha'"

Step 3: Write minimal implementation

import { getSecurityConfig } from './config';

export interface CaptchaResult {
  question: string;
  answer: number;
  hash: string;
  timestamp: number;
}

function generateSimpleCaptcha(): CaptchaResult {
  const num1 = Math.floor(Math.random() * 10) + 1;
  const num2 = Math.floor(Math.random() * 10) + 1;
  const answer = num1 + num2;
  const question = `${num1} + ${num2} = ?`;

  return {
    question,
    answer,
    hash: '',
    timestamp: Date.now(),
  };
}

function generateMediumCaptcha(): CaptchaResult {
  const operations = ['+', '-', '*'];
  const operation = operations[Math.floor(Math.random() * operations.length)];
  const num1 = Math.floor(Math.random() * 20) + 1;
  const num2 = Math.floor(Math.random() * 10) + 1;

  let answer: number;
  let question: string;

  switch (operation) {
    case '+':
      answer = num1 + num2;
      question = `${num1} + ${num2} = ?`;
      break;
    case '-':
      answer = num1 - num2;
      question = `${num1} - ${num2} = ?`;
      break;
    case '*':
      answer = num1 * num2;
      question = `${num1} × ${num2} = ?`;
      break;
    default:
      answer = num1 + num2;
      question = `${num1} + ${num2} = ?`;
  }

  return {
    question,
    answer,
    hash: '',
    timestamp: Date.now(),
  };
}

function generateComplexCaptcha(): CaptchaResult {
  const num1 = Math.floor(Math.random() * 15) + 1;
  const num2 = Math.floor(Math.random() * 10) + 1;
  const num3 = Math.floor(Math.random() * 5) + 1;

  const operations = ['+', '-', '*'];
  const op1 = operations[Math.floor(Math.random() * operations.length)];
  const op2 = operations[Math.floor(Math.random() * operations.length)];

  let answer: number;
  let question: string;

  const temp1 = op1 === '+' ? num1 + num2 : op1 === '-' ? num1 - num2 : num1 * num2;
  answer = op2 === '+' ? temp1 + num3 : op2 === '-' ? temp1 - num3 : temp1 * num3;

  question = `(${num1} ${op1} ${num2}) ${op2} ${num3} = ?`;

  return {
    question,
    answer,
    hash: '',
    timestamp: Date.now(),
  };
}

function generateHash(answer: number, timestamp: number): string {
  const secret = process.env.CAPTCHA_SECRET || 'default-secret-key';
  const data = `${answer}-${timestamp}-${secret}`;
  return Buffer.from(data).toString('base64');
}

export function generateCaptcha(complexity: 'simple' | 'medium' | 'complex' = 'medium'): CaptchaResult {
  let captcha: CaptchaResult;

  switch (complexity) {
    case 'simple':
      captcha = generateSimpleCaptcha();
      break;
    case 'complex':
      captcha = generateComplexCaptcha();
      break;
    case 'medium':
    default:
      captcha = generateMediumCaptcha();
      break;
  }

  captcha.hash = generateHash(captcha.answer, captcha.timestamp);
  return captcha;
}

export function validateCaptcha(hash: string, answer: number, timestamp: number): boolean {
  const config = getSecurityConfig();
  const now = Date.now();
  const ageMinutes = (now - timestamp) / (1000 * 60);

  if (ageMinutes > config.captcha.expiryMinutes) {
    return false;
  }

  const expectedHash = generateHash(answer, timestamp);
  return hash === expectedHash;
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/captcha.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/captcha.ts src/lib/security/captcha.test.ts
git commit -m "feat: implement enhanced captcha system"

Task 3: Implement Rate Limiting System

Files:

  • Create: src/lib/security/rate-limiter.ts
  • Test: src/lib/security/rate-limiter.test.ts

Step 1: Write the failing test

import { RateLimiter, RateLimitResult } from '../rate-limiter';

describe('Rate Limiting System', () => {
  let rateLimiter: RateLimiter;

  beforeEach(() => {
    rateLimiter = new RateLimiter();
  });

  test('should allow request within limit', async () => {
    const result = await rateLimiter.checkIP('192.168.1.1');
    expect(result.allowed).toBe(true);
    expect(result.remaining).toBeGreaterThan(0);
  });

  test('should block request when limit exceeded', async () => {
    const ip = '192.168.1.2';
    for (let i = 0; i < 10; i++) {
      await rateLimiter.checkIP(ip);
    }
    const result = await rateLimiter.checkIP(ip);
    expect(result.allowed).toBe(false);
  });

  test('should reset limit after window expires', async () => {
    const ip = '192.168.1.3';
    for (let i = 0; i < 10; i++) {
      await rateLimiter.checkIP(ip);
    }
    await rateLimiter.checkIP(ip);

    jest.useFakeTimers();
    jest.advanceTimersByTime(61 * 60 * 1000);

    const result = await rateLimiter.checkIP(ip);
    expect(result.allowed).toBe(true);
    jest.useRealTimers();
  });

  test('should track email rate limits separately', async () => {
    const email = 'test@example.com';
    for (let i = 0; i < 3; i++) {
      await rateLimiter.checkEmail(email);
    }
    const result = await rateLimiter.checkEmail(email);
    expect(result.allowed).toBe(false);
  });

  test('should enforce global rate limit', async () => {
    const promises = [];
    for (let i = 0; i < 51; i++) {
      promises.push(rateLimiter.checkGlobal());
    }
    const results = await Promise.all(promises);
    const blockedCount = results.filter(r => !r.allowed).length;
    expect(blockedCount).toBeGreaterThan(0);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/rate-limiter.test.ts

Expected: FAIL with "Cannot find module '../rate-limiter'"

Step 3: Write minimal implementation

import { getSecurityConfig } from './config';

export interface RateLimitResult {
  allowed: boolean;
  remaining: number;
  resetTime: number;
  limit: number;
}

interface RequestRecord {
  count: number;
  windowStart: number;
}

class InMemoryRateLimiter {
  private ipRequests = new Map<string, RequestRecord>();
  private emailRequests = new Map<string, RequestRecord>();
  private globalRequests: RequestRecord = { count: 0, windowStart: Date.now() };

  async checkIP(ip: string): Promise<RateLimitResult> {
    const config = getSecurityConfig();
    const now = Date.now();
    const windowMs = config.rateLimit.ip.windowMinutes * 60 * 1000;

    let record = this.ipRequests.get(ip);
    if (!record || now - record.windowStart > windowMs) {
      record = { count: 0, windowStart: now };
      this.ipRequests.set(ip, record);
    }

    const allowed = record.count < config.rateLimit.ip.maxRequests;
    if (allowed) {
      record.count++;
    }

    return {
      allowed,
      remaining: Math.max(0, config.rateLimit.ip.maxRequests - record.count),
      resetTime: record.windowStart + windowMs,
      limit: config.rateLimit.ip.maxRequests,
    };
  }

  async checkEmail(email: string): Promise<RateLimitResult> {
    const config = getSecurityConfig();
    const now = Date.now();
    const windowMs = config.rateLimit.email.windowHours * 60 * 60 * 1000;

    let record = this.emailRequests.get(email);
    if (!record || now - record.windowStart > windowMs) {
      record = { count: 0, windowStart: now };
      this.emailRequests.set(email, record);
    }

    const allowed = record.count < config.rateLimit.email.maxRequests;
    if (allowed) {
      record.count++;
    }

    return {
      allowed,
      remaining: Math.max(0, config.rateLimit.email.maxRequests - record.count),
      resetTime: record.windowStart + windowMs,
      limit: config.rateLimit.email.maxRequests,
    };
  }

  async checkGlobal(): Promise<RateLimitResult> {
    const config = getSecurityConfig();
    const now = Date.now();
    const windowMs = config.rateLimit.global.windowMinutes * 60 * 1000;

    if (now - this.globalRequests.windowStart > windowMs) {
      this.globalRequests = { count: 0, windowStart: now };
    }

    const allowed = this.globalRequests.count < config.rateLimit.global.maxRequests;
    if (allowed) {
      this.globalRequests.count++;
    }

    return {
      allowed,
      remaining: Math.max(0, config.rateLimit.global.maxRequests - this.globalRequests.count),
      resetTime: this.globalRequests.windowStart + windowMs,
      limit: config.rateLimit.global.maxRequests,
    };
  }

  clear(): void {
    this.ipRequests.clear();
    this.emailRequests.clear();
    this.globalRequests = { count: 0, windowStart: Date.now() };
  }
}

export class RateLimiter {
  private limiter: InMemoryRateLimiter;

  constructor() {
    this.limiter = new InMemoryRateLimiter();
  }

  async checkIP(ip: string): Promise<RateLimitResult> {
    return this.limiter.checkIP(ip);
  }

  async checkEmail(email: string): Promise<RateLimitResult> {
    return this.limiter.checkEmail(email);
  }

  async checkGlobal(): Promise<RateLimitResult> {
    return this.limiter.checkGlobal();
  }

  clear(): void {
    this.limiter.clear();
  }
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/rate-limiter.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/rate-limiter.ts src/lib/security/rate-limiter.test.ts
git commit -m "feat: implement rate limiting system"

Task 4: Implement Input Sanitization

Files:

  • Create: src/lib/security/sanitizer.ts
  • Test: src/lib/security/sanitizer.test.ts

Step 1: Write the failing test

import { sanitizeInput, sanitizeFormData, detectMaliciousContent } from '../sanitizer';

describe('Input Sanitization', () => {
  test('should remove XSS attempts', () => {
    const malicious = '<script>alert("xss")</script>Hello';
    const sanitized = sanitizeInput(malicious);
    expect(sanitized).not.toContain('<script>');
    expect(sanitized).toContain('Hello');
  });

  test('should remove SQL injection attempts', () => {
    const malicious = "'; DROP TABLE users; --";
    const sanitized = sanitizeInput(malicious);
    expect(sanitized).not.toContain('DROP TABLE');
  });

  test('should detect malicious links', () => {
    const content = 'Visit http://malicious.com for free money';
    const isMalicious = detectMaliciousContent(content);
    expect(isMalicious).toBe(true);
  });

  test('should sanitize form data', () => {
    const formData = {
      name: '<script>alert(1)</script>John',
      email: 'test@example.com',
      message: 'Click http://evil.com',
    };
    const sanitized = sanitizeFormData(formData);
    expect(sanitized.name).not.toContain('<script>');
    expect(sanitized.email).toBe('test@example.com');
  });

  test('should preserve legitimate content', () => {
    const legitimate = 'Hello, I need help with my order #12345';
    const sanitized = sanitizeInput(legitimate);
    expect(sanitized).toBe(legitimate);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/sanitizer.test.ts

Expected: FAIL with "Cannot find module '../sanitizer'"

Step 3: Write minimal implementation

export interface SanitizedFormData {
  name: string;
  email: string;
  phone?: string;
  subject: string;
  message: string;
}

const XSS_PATTERNS = [
  /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
  /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
  /javascript:/gi,
  /on\w+\s*=/gi,
  /<[^>]*>/g,
];

const SQL_INJECTION_PATTERNS = [
  /('|(\\')|(;)|(\-\-)|(\s+(or|and)\s+.*=)|(exec(\s|\+)+(s|x)p\w+)/gi,
  /(union\s+select)|(drop\s+table)|(delete\s+from)|(insert\s+into)/gi,
];

const MALICIOUS_URL_PATTERNS = [
  /(https?:\/\/[^\s]+\.ru\b)/gi,
  /(https?:\/\/[^\s]+\.tk\b)/gi,
  /(https?:\/\/[^\s]+\.ml\b)/gi,
  /(https?:\/\/[^\s]+\.ga\b)/gi,
  /(https?:\/\/bit\.ly\/[^\s]+)/gi,
];

export function sanitizeInput(input: string): string {
  if (!input || typeof input !== 'string') {
    return '';
  }

  let sanitized = input.trim();

  XSS_PATTERNS.forEach(pattern => {
    sanitized = sanitized.replace(pattern, '');
  });

  SQL_INJECTION_PATTERNS.forEach(pattern => {
    sanitized = sanitized.replace(pattern, '');
  });

  return sanitized;
}

export function sanitizeFormData(formData: any): SanitizedFormData {
  return {
    name: sanitizeInput(formData.name || ''),
    email: formData.email || '',
    phone: formData.phone ? sanitizeInput(formData.phone) : undefined,
    subject: sanitizeInput(formData.subject || ''),
    message: sanitizeInput(formData.message || ''),
  };
}

export function detectMaliciousContent(content: string): boolean {
  if (!content || typeof content !== 'string') {
    return false;
  }

  const lowerContent = content.toLowerCase();

  const suspiciousKeywords = [
    'free money',
    'bitcoin',
    'cryptocurrency',
    'lottery',
    'winner',
    'prize',
    'inheritance',
    'nigerian prince',
    'urgent',
    'act now',
    'limited time',
  ];

  for (const keyword of suspiciousKeywords) {
    if (lowerContent.includes(keyword)) {
      return true;
    }
  }

  for (const pattern of MALICIOUS_URL_PATTERNS) {
    if (pattern.test(content)) {
      return true;
    }
  }

  return false;
}

export function validateEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

export function validatePhone(phone: string): boolean {
  const phoneRegex = /^1[3-9]\d{9}$/;
  return phoneRegex.test(phone);
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/sanitizer.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/sanitizer.ts src/lib/security/sanitizer.test.ts
git commit -m "feat: implement input sanitization"

Task 5: Implement Security Logging System

Files:

  • Create: src/lib/security/logger.ts
  • Test: src/lib/security/logger.test.ts

Step 1: Write the failing test

import { SecurityLogger, SecurityEventType } from '../logger';

describe('Security Logging System', () => {
  let logger: SecurityLogger;

  beforeEach(() => {
    logger = new SecurityLogger();
  });

  test('should log security events', () => {
    logger.logEvent({
      type: SecurityEventType.CAPTCHA_FAILED,
      ip: '192.168.1.1',
      email: 'test@example.com',
      details: { reason: 'Invalid answer' },
    });

    const logs = logger.getRecentLogs(10);
    expect(logs.length).toBe(1);
    expect(logs[0].type).toBe(SecurityEventType.CAPTCHA_FAILED);
  });

  test('should detect suspicious activity', () => {
    for (let i = 0; i < 6; i++) {
      logger.logEvent({
        type: SecurityEventType.CAPTCHA_FAILED,
        ip: '192.168.1.2',
        email: 'suspicious@example.com',
        details: {},
      });
    }

    const suspiciousIPs = logger.getSuspiciousIPs();
    expect(suspiciousIPs).toContain('192.168.1.2');
  });

  test('should clear old logs', () => {
    logger.logEvent({
      type: SecurityEventType.RATE_LIMIT_EXCEEDED,
      ip: '192.168.1.3',
      details: {},
    });

    logger.clearOldLogs(31);

    const logs = logger.getRecentLogs(10);
    expect(logs.length).toBe(0);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/logger.test.ts

Expected: FAIL with "Cannot find module '../logger'"

Step 3: Write minimal implementation

export enum SecurityEventType {
  CAPTCHA_FAILED = 'captcha_failed',
  CAPTCHA_EXPIRED = 'captcha_expired',
  RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded',
  MALICIOUS_CONTENT = 'malicious_content',
  XSS_ATTEMPT = 'xss_attempt',
  SQL_INJECTION_ATTEMPT = 'sql_injection_attempt',
  SUSPICIOUS_IP = 'suspicious_ip',
  BLOCKED_REQUEST = 'blocked_request',
}

export interface SecurityEvent {
  type: SecurityEventType;
  timestamp: number;
  ip?: string;
  email?: string;
  userAgent?: string;
  details?: Record<string, any>;
}

export interface SuspiciousActivity {
  ip: string;
  eventCount: number;
  lastSeen: number;
  eventTypes: SecurityEventType[];
}

class InMemorySecurityLogger {
  private events: SecurityEvent[] = [];
  private ipActivity = new Map<string, number>();

  logEvent(event: Omit<SecurityEvent, 'timestamp'>): void {
    const fullEvent: SecurityEvent = {
      ...event,
      timestamp: Date.now(),
    };

    this.events.push(fullEvent);

    if (event.ip) {
      const currentCount = this.ipActivity.get(event.ip) || 0;
      this.ipActivity.set(event.ip, currentCount + 1);
    }

    console.log(`[Security] ${event.type}:`, event);
  }

  getRecentLogs(limit: number = 100): SecurityEvent[] {
    return this.events.slice(-limit);
  }

  getSuspiciousIPs(threshold: number = 5): string[] {
    const suspicious: string[] = [];
    for (const [ip, count] of this.ipActivity.entries()) {
      if (count >= threshold) {
        suspicious.push(ip);
      }
    }
    return suspicious;
  }

  clearOldLogs(daysToKeep: number = 30): void {
    const cutoffTime = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
    this.events = this.events.filter(event => event.timestamp > cutoffTime);
  }

  getActivityByIP(ip: string): number {
    return this.ipActivity.get(ip) || 0;
  }

  clear(): void {
    this.events = [];
    this.ipActivity.clear();
  }
}

export class SecurityLogger {
  private logger: InMemorySecurityLogger;

  constructor() {
    this.logger = new InMemorySecurityLogger();
  }

  logEvent(event: Omit<SecurityEvent, 'timestamp'>): void {
    this.logger.logEvent(event);
  }

  getRecentLogs(limit?: number): SecurityEvent[] {
    return this.logger.getRecentLogs(limit);
  }

  getSuspiciousIPs(threshold?: number): string[] {
    return this.logger.getSuspiciousIPs(threshold);
  }

  clearOldLogs(daysToKeep?: number): void {
    this.logger.clearOldLogs(daysToKeep);
  }

  getActivityByIP(ip: string): number {
    return this.logger.getActivityByIP(ip);
  }

  clear(): void {
    this.logger.clear();
  }
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/logger.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/logger.ts src/lib/security/logger.test.ts
git commit -m "feat: implement security logging system"

Task 6: Create Security Middleware

Files:

  • Create: src/lib/security/middleware.ts
  • Test: src/lib/security/middleware.test.ts

Step 1: Write the failing test

import { SecurityMiddleware, SecurityCheckResult } from '../middleware';

describe('Security Middleware', () => {
  let middleware: SecurityMiddleware;

  beforeEach(() => {
    middleware = new SecurityMiddleware();
  });

  test('should pass valid request', async () => {
    const result = await middleware.checkRequest({
      ip: '192.168.1.1',
      email: 'valid@example.com',
      captchaHash: 'valid-hash',
      captchaAnswer: 5,
      captchaTimestamp: Date.now(),
    });

    expect(result.allowed).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  test('should block request with invalid captcha', async () => {
    const result = await middleware.checkRequest({
      ip: '192.168.1.2',
      email: 'test@example.com',
      captchaHash: 'invalid-hash',
      captchaAnswer: 999,
      captchaTimestamp: Date.now(),
    });

    expect(result.allowed).toBe(false);
    expect(result.errors).toContain('Invalid captcha');
  });

  test('should block request exceeding rate limit', async () => {
    const ip = '192.168.1.3';
    for (let i = 0; i < 11; i++) {
      await middleware.checkRequest({
        ip,
        email: `test${i}@example.com`,
        captchaHash: 'valid-hash',
        captchaAnswer: 5,
        captchaTimestamp: Date.now(),
      });
    }

    const result = await middleware.checkRequest({
      ip,
      email: 'test@example.com',
      captchaHash: 'valid-hash',
      captchaAnswer: 5,
      captchaTimestamp: Date.now(),
    });

    expect(result.allowed).toBe(false);
    expect(result.errors).toContain('Rate limit exceeded');
  });

  test('should block request with malicious content', async () => {
    const result = await middleware.checkRequest({
      ip: '192.168.1.4',
      email: 'test@example.com',
      captchaHash: 'valid-hash',
      captchaAnswer: 5,
      captchaTimestamp: Date.now(),
      message: '<script>alert("xss")</script>',
    });

    expect(result.allowed).toBe(false);
    expect(result.errors.some(e => e.includes('malicious'))).toBe(true);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/lib/security/middleware.test.ts

Expected: FAIL with "Cannot find module '../middleware'"

Step 3: Write minimal implementation

import { RateLimiter } from './rate-limiter';
import { validateCaptcha } from './captcha';
import { sanitizeFormData, detectMaliciousContent, validateEmail } from './sanitizer';
import { SecurityLogger, SecurityEventType } from './logger';
import { getSecurityConfig } from './config';

export interface SecurityCheckRequest {
  ip: string;
  email?: string;
  name?: string;
  phone?: string;
  subject?: string;
  message?: string;
  captchaHash: string;
  captchaAnswer: number;
  captchaTimestamp: number;
  userAgent?: string;
}

export interface SecurityCheckResult {
  allowed: boolean;
  errors: string[];
  warnings: string[];
}

export class SecurityMiddleware {
  private rateLimiter: RateLimiter;
  private logger: SecurityLogger;
  private config: any;

  constructor() {
    this.rateLimiter = new RateLimiter();
    this.logger = new SecurityLogger();
    this.config = getSecurityConfig();
  }

  async checkRequest(request: SecurityCheckRequest): Promise<SecurityCheckResult> {
    const errors: string[] = [];
    const warnings: string[] = [];

    if (this.config.protection.enableInputSanitization) {
      if (request.message && detectMaliciousContent(request.message)) {
        errors.push('Message contains malicious content');
        this.logger.logEvent({
          type: SecurityEventType.MALICIOUS_CONTENT,
          ip: request.ip,
          email: request.email,
          userAgent: request.userAgent,
          details: { message: request.message },
        });
      }
    }

    if (!validateCaptcha(request.captchaHash, request.captchaAnswer, request.captchaTimestamp)) {
      errors.push('Invalid captcha');
      this.logger.logEvent({
        type: SecurityEventType.CAPTCHA_FAILED,
        ip: request.ip,
        email: request.email,
        userAgent: request.userAgent,
        details: { answer: request.captchaAnswer },
      });
    }

    const ipRateLimit = await this.rateLimiter.checkIP(request.ip);
    if (!ipRateLimit.allowed) {
      errors.push('Rate limit exceeded for IP');
      this.logger.logEvent({
        type: SecurityEventType.RATE_LIMIT_EXCEEDED,
        ip: request.ip,
        email: request.email,
        details: { limit: ipRateLimit.limit },
      });
    }

    if (request.email) {
      const emailRateLimit = await this.rateLimiter.checkEmail(request.email);
      if (!emailRateLimit.allowed) {
        errors.push('Rate limit exceeded for email');
        this.logger.logEvent({
          type: SecurityEventType.RATE_LIMIT_EXCEEDED,
          ip: request.ip,
          email: request.email,
          details: { limit: emailRateLimit.limit },
        });
      }
    }

    const globalRateLimit = await this.rateLimiter.checkGlobal();
    if (!globalRateLimit.allowed) {
      warnings.push('Global rate limit nearly exceeded');
    }

    const suspiciousIPs = this.logger.getSuspiciousIPs();
    if (suspiciousIPs.includes(request.ip)) {
      errors.push('Suspicious activity detected from this IP');
      this.logger.logEvent({
        type: SecurityEventType.SUSPICIOUS_IP,
        ip: request.ip,
        email: request.email,
        details: { activityCount: this.logger.getActivityByIP(request.ip) },
      });
    }

    return {
      allowed: errors.length === 0,
      errors,
      warnings,
    };
  }

  sanitizeRequest(request: SecurityCheckRequest): any {
    return sanitizeFormData({
      name: request.name || '',
      email: request.email || '',
      phone: request.phone,
      subject: request.subject || '',
      message: request.message || '',
    });
  }

  getSecurityLogs(limit?: number) {
    return this.logger.getRecentLogs(limit);
  }

  getSuspiciousIPs(threshold?: number) {
    return this.logger.getSuspiciousIPs(threshold);
  }
}

Step 4: Run test to verify it passes

Run: npm test -- src/lib/security/middleware.test.ts

Expected: PASS

Step 5: Commit

git add src/lib/security/middleware.ts src/lib/security/middleware.test.ts
git commit -m "feat: implement security middleware"

Task 7: Update Contact Form API with Security

Files:

  • Modify: src/app/api/contact/route.ts
  • Test: src/app/api/contact/route.test.ts

Step 1: Update existing tests to include security checks

import { POST } from '../route';

describe('Contact API Security', () => {
  test('should reject request without valid captcha', async () => {
    const request = new Request('http://localhost:3000/api/contact', {
      method: 'POST',
      body: JSON.stringify({
        name: 'Test User',
        email: 'test@example.com',
        subject: 'Test',
        message: 'Test message',
        captchaHash: 'invalid',
        captchaAnswer: 999,
        captchaTimestamp: Date.now(),
      }),
    });

    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.success).toBe(false);
    expect(data.error).toContain('captcha');
  });

  test('should reject request with malicious content', async () => {
    const request = new Request('http://localhost:3000/api/contact', {
      method: 'POST',
      body: JSON.stringify({
        name: '<script>alert(1)</script>',
        email: 'test@example.com',
        subject: 'Test',
        message: 'Test message',
        captchaHash: 'valid-hash',
        captchaAnswer: 5,
        captchaTimestamp: Date.now(),
      }),
    });

    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(400);
    expect(data.success).toBe(false);
  });
});

Step 2: Run test to verify it fails

Run: npm test -- src/app/api/contact/route.test.ts

Expected: FAIL with security validation errors

Step 3: Update contact route implementation

import { NextRequest } from 'next/server';
import { Resend } from 'resend';
import { z } from 'zod';
import { SecurityMiddleware, SecurityCheckRequest } from '@/lib/security/middleware';
import { generateCaptcha } from '@/lib/security/captcha';

const resend = new Resend(process.env.RESEND_API_KEY);
const companyEmail = process.env.COMPANY_EMAIL || 'contact@novalon.cn';
const securityMiddleware = new SecurityMiddleware();

export async function GET(request: NextRequest) {
  const captcha = generateCaptcha('medium');
  return Response.json({
    question: captcha.question,
    hash: captcha.hash,
    timestamp: captcha.timestamp,
  });
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();

    const securityRequest: SecurityCheckRequest = {
      ip: request.headers.get('x-forwarded-for') || 'unknown',
      email: body.email,
      name: body.name,
      phone: body.phone,
      subject: body.subject,
      message: body.message,
      captchaHash: body.captchaHash,
      captchaAnswer: body.captchaAnswer,
      captchaTimestamp: body.captchaTimestamp,
      userAgent: request.headers.get('user-agent'),
    };

    const securityResult = await securityMiddleware.checkRequest(securityRequest);

    if (!securityResult.allowed) {
      return Response.json(
        {
          success: false,
          error: securityResult.errors[0] || 'Security validation failed',
          warnings: securityResult.warnings,
        },
        { status: 400 }
      );
    }

    const sanitizedData = securityMiddleware.sanitizeRequest(securityRequest);

    if (sanitizedData.website) {
      return Response.json({ success: true, message: '消息已发送' });
    }

    const emailContent = `
      <html>
        <head>
          <meta charset="utf-8">
          <style>
            body { font-family: sans-serif; line-height: 1.6; color: #1C1C1C; }
            .container { max-width: 600px; margin: 0 auto; padding: 20px; }
            .header { background: #C41E3A; color: white; padding: 20px; text-align: center; }
            .content { padding: 20px; }
            .info-row { margin-bottom: 10px; }
            .info-label { font-weight: bold; }
          </style>
        </head>
        <body>
          <div class="container">
            <div class="header">
              <h1>新的客户咨询</h1>
            </div>
            <div class="content">
              <div class="info-row"><span class="info-label">姓名:</span> ${sanitizedData.name}</div>
              <div class="info-row"><span class="info-label">邮箱:</span> ${sanitizedData.email}</div>
              ${sanitizedData.phone ? `<div class="info-row"><span class="info-label">电话:</span> ${sanitizedData.phone}</div>` : ''}
              <div class="info-row"><span class="info-label">主题:</span> ${sanitizedData.subject}</div>
              <div class="info-row"><span class="info-label">留言:</span> ${sanitizedData.message}</div>
            </div>
          </div>
        </body>
      </html>
    `;

    const result = await resend.emails.send({
      from: '睿新致远官网 <onboarding@resend.dev>',
      to: [companyEmail],
      subject: `${sanitizedData.subject} - ${sanitizedData.name}`,
      html: emailContent,
      replyTo: sanitizedData.email,
    });

    if (result.error) {
      console.error('Resend API error:', result.error);
      return Response.json(
        { success: false, error: '邮件发送失败,请稍后重试' },
        { status: 500 }
      );
    }

    return Response.json({ success: true, message: '消息已发送' });
  } catch (error) {
    console.error('Contact form submission error:', error);
    return Response.json(
      { success: false, error: '提交失败,请重试' },
      { status: 500 }
    );
  }
}

Step 4: Run test to verify it passes

Run: npm test -- src/app/api/contact/route.test.ts

Expected: PASS

Step 5: Commit

git add src/app/api/contact/route.ts src/app/api/contact/route.test.ts
git commit -m "feat: integrate security middleware into contact API"

Task 8: Update Contact Form Client Component

Files:

  • Modify: src/app/(marketing)/contact/page.tsx
  • Modify: src/app/(marketing)/contact/actions.ts

Step 1: Update contact page to use enhanced captcha

'use client';

import { useState, useEffect } from 'react';
import { submitContactForm } from '../actions';

export default function ContactPage() {
  const [captcha, setCaptcha] = useState<any>(null);
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    phone: '',
    subject: '',
    message: '',
    captchaAnswer: '',
  });

  useEffect(() => {
    fetchCaptcha();
  }, []);

  const fetchCaptcha = async () => {
    try {
      const response = await fetch('/api/contact');
      const data = await response.json();
      setCaptcha(data);
    } catch (error) {
      console.error('Failed to fetch captcha:', error);
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!captcha) {
      alert('请等待验证码加载');
      return;
    }

    const result = await submitContactForm({
      ...formData,
      captchaHash: captcha.hash,
      captchaAnswer: parseInt(formData.captchaAnswer),
      captchaTimestamp: captcha.timestamp,
      submitTime: Date.now().toString(),
    });

    if (result.success) {
      alert('消息已发送');
      setFormData({
        name: '',
        email: '',
        phone: '',
        subject: '',
        message: '',
        captchaAnswer: '',
      });
      fetchCaptcha();
    } else {
      alert(result.error || '发送失败');
      if (result.error?.includes('captcha')) {
        fetchCaptcha();
      }
    }
  };

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">联系我们</h1>
      <form onSubmit={handleSubmit} className="max-w-2xl mx-auto space-y-6">
        <div>
          <label className="block mb-2">姓名 *</label>
          <input
            type="text"
            value={formData.name}
            onChange={(e) => setFormData({ ...formData, name: e.target.value })}
            className="w-full p-3 border rounded"
            required
          />
        </div>

        <div>
          <label className="block mb-2">邮箱 *</label>
          <input
            type="email"
            value={formData.email}
            onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            className="w-full p-3 border rounded"
            required
          />
        </div>

        <div>
          <label className="block mb-2">电话</label>
          <input
            type="tel"
            value={formData.phone}
            onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
            className="w-full p-3 border rounded"
          />
        </div>

        <div>
          <label className="block mb-2">主题 *</label>
          <input
            type="text"
            value={formData.subject}
            onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
            className="w-full p-3 border rounded"
            required
          />
        </div>

        <div>
          <label className="block mb-2">留言 *</label>
          <textarea
            value={formData.message}
            onChange={(e) => setFormData({ ...formData, message: e.target.value })}
            className="w-full p-3 border rounded h-32"
            required
          />
        </div>

        {captcha && (
          <div>
            <label className="block mb-2">验证码 *</label>
            <div className="flex items-center gap-4">
              <div className="bg-gray-100 p-4 rounded text-lg font-semibold">
                {captcha.question}
              </div>
              <input
                type="text"
                value={formData.captchaAnswer}
                onChange={(e) => setFormData({ ...formData, captchaAnswer: e.target.value })}
                className="w-32 p-3 border rounded"
                placeholder="答案"
                required
              />
              <button
                type="button"
                onClick={fetchCaptcha}
                className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
              >
                刷新
              </button>
            </div>
          </div>
        )}

        <button
          type="submit"
          className="w-full bg-red-700 text-white py-3 rounded hover:bg-red-800"
        >
          发送消息
        </button>
      </form>
    </div>
  );
}

Step 2: Update actions to work with new security

'use server';

import { Resend } from 'resend';
import { z } from 'zod';
import { SecurityMiddleware } from '@/lib/security/middleware';

const resend = new Resend(process.env.RESEND_API_KEY);
const companyEmail = process.env.COMPANY_EMAIL || 'contact@novalon.cn';
const securityMiddleware = new SecurityMiddleware();

const contactFormSchema = z.object({
  name: z.string().min(2, '姓名至少需要2个字符'),
  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号码').optional(),
  email: z.string().email('请输入有效的邮箱地址'),
  subject: z.string().min(2, '主题至少需要2个字符'),
  message: z.string().min(10, '留言内容至少需要10个字符'),
  captchaHash: z.string(),
  captchaAnswer: z.number(),
  captchaTimestamp: z.number(),
  submitTime: z.string().optional(),
  website: z.string().optional(),
});

export interface ContactFormState {
  success: boolean;
  message?: string;
  error?: string;
  errors?: Record<string, string>;
}

export async function submitContactForm(
  _prevState: ContactFormState | null,
  formData: any
): Promise<ContactFormState> {
  const validationResult = contactFormSchema.safeParse(formData);

  if (!validationResult.success) {
    const errors: Record<string, string> = {};
    validationResult.error.issues.forEach((issue) => {
      const field = issue.path[0] as string;
      errors[field] = issue.message;
    });
    return { success: false, error: '请检查表单字段', errors };
  }

  const data = validationResult.data;

  const securityCheck = await securityMiddleware.checkRequest({
    ip: 'unknown',
    email: data.email,
    name: data.name,
    phone: data.phone,
    subject: data.subject,
    message: data.message,
    captchaHash: data.captchaHash,
    captchaAnswer: data.captchaAnswer,
    captchaTimestamp: data.captchaTimestamp,
  });

  if (!securityCheck.allowed) {
    return {
      success: false,
      error: securityCheck.errors[0] || '安全验证失败',
    };
  }

  const sanitizedData = securityMiddleware.sanitizeRequest({
    ip: 'unknown',
    email: data.email,
    name: data.name,
    phone: data.phone,
    subject: data.subject,
    message: data.message,
    captchaHash: data.captchaHash,
    captchaAnswer: data.captchaAnswer,
    captchaTimestamp: data.captchaTimestamp,
  });

  try {
    const { data: emailData, error } = await resend.emails.send({
      from: '睿新致远官网 <onboarding@resend.dev>',
      to: [companyEmail],
      subject: `📧 ${sanitizedData.subject} - ${sanitizedData.name}`,
      html: generateEmailContent(sanitizedData),
      replyTo: sanitizedData.email,
    });

    if (error) {
      console.error('Resend API error:', error);
      return { success: false, error: '邮件发送失败,请稍后重试' };
    }

    console.log('Email sent successfully:', emailData);
    return { success: true, message: '消息已发送' };
  } catch (error) {
    console.error('Contact form submission error:', error);
    return { success: false, error: '提交失败,请重试' };
  }
}

function generateEmailContent(data: any): string {
  return `
    <html>
      <head>
        <meta charset="utf-8">
        <style>
          body { font-family: sans-serif; line-height: 1.6; color: #1C1C1C; }
          .container { max-width: 600px; margin: 0 auto; padding: 20px; }
          .header { background: #C41E3A; color: white; padding: 20px; text-align: center; }
          .content { padding: 20px; }
          .info-row { margin-bottom: 10px; }
          .info-label { font-weight: bold; }
        </style>
      </head>
      <body>
        <div class="container">
          <div class="header">
            <h1>📬 新的客户咨询</h1>
          </div>
          <div class="content">
            <div class="info-row"><span class="info-label">姓名:</span> ${data.name}</div>
            <div class="info-row"><span class="info-label">邮箱:</span> ${data.email}</div>
            ${data.phone ? `<div class="info-row"><span class="info-label">电话:</span> ${data.phone}</div>` : ''}
            <div class="info-row"><span class="info-label">主题:</span> ${data.subject}</div>
            <div class="info-row"><span class="info-label">留言:</span> ${data.message}</div>
          </div>
        </div>
      </body>
    </html>
  `;
}

Step 3: Run tests

Run: npm test -- src/app/(marketing)/contact/

Expected: PASS

Step 4: Commit

git add src/app/(marketing)/contact/page.tsx src/app/(marketing)/contact/actions.ts
git commit -m "feat: update contact form with enhanced security"

Task 9: Create Security Monitoring Dashboard

Files:

  • Create: src/app/admin/security/page.tsx
  • Create: src/app/api/admin/security/route.ts

Step 1: Create security API endpoint

import { NextRequest, NextResponse } from 'next/server';
import { SecurityMiddleware } from '@/lib/security/middleware';

const securityMiddleware = new SecurityMiddleware();

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const limit = parseInt(searchParams.get('limit') || '100');

    const logs = securityMiddleware.getSecurityLogs(limit);
    const suspiciousIPs = securityMiddleware.getSuspiciousIPs();

    return NextResponse.json({
      success: true,
      data: {
        logs,
        suspiciousIPs,
        summary: {
          totalEvents: logs.length,
          suspiciousIPCount: suspiciousIPs.length,
        },
      },
    });
  } catch (error) {
    console.error('Security API error:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to fetch security data' },
      { status: 500 }
    );
  }
}

Step 2: Create security monitoring page

'use client';

import { useState, useEffect } from 'react';

interface SecurityLog {
  type: string;
  timestamp: number;
  ip?: string;
  email?: string;
  details?: any;
}

interface SecurityData {
  logs: SecurityLog[];
  suspiciousIPs: string[];
  summary: {
    totalEvents: number;
    suspiciousIPCount: number;
  };
}

export default function SecurityMonitoringPage() {
  const [securityData, setSecurityData] = useState<SecurityData | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchSecurityData();
  }, []);

  const fetchSecurityData = async () => {
    try {
      const response = await fetch('/api/admin/security');
      const data = await response.json();
      if (data.success) {
        setSecurityData(data.data);
      }
    } catch (error) {
      console.error('Failed to fetch security data:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <div className="p-8">加载中...</div>;
  }

  if (!securityData) {
    return <div className="p-8">无法加载安全数据</div>;
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">安全监控</h1>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-2">总事件数</h3>
          <p className="text-3xl font-bold text-blue-600">{securityData.summary.totalEvents}</p>
        </div>
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-2">可疑IP数</h3>
          <p className="text-3xl font-bold text-red-600">{securityData.summary.suspiciousIPCount}</p>
        </div>
        <div className="bg-white p-6 rounded-lg shadow">
          <h3 className="text-lg font-semibold mb-2">最近24小时</h3>
          <p className="text-3xl font-bold text-green-600">
            {securityData.logs.filter(log => Date.now() - log.timestamp < 24 * 60 * 60 * 1000).length}
          </p>
        </div>
      </div>

      <div className="bg-white rounded-lg shadow mb-8">
        <h2 className="text-xl font-semibold p-6 border-b">可疑IP列表</h2>
        <div className="p-6">
          {securityData.suspiciousIPs.length === 0 ? (
            <p className="text-gray-500">暂无可疑IP</p>
          ) : (
            <ul className="space-y-2">
              {securityData.suspiciousIPs.map((ip, index) => (
                <li key={index} className="flex items-center justify-between p-3 bg-red-50 rounded">
                  <span className="font-mono">{ip}</span>
                  <span className="text-red-600 text-sm">可疑活动</span>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>

      <div className="bg-white rounded-lg shadow">
        <h2 className="text-xl font-semibold p-6 border-b">安全事件日志</h2>
        <div className="p-6">
          {securityData.logs.length === 0 ? (
            <p className="text-gray-500">暂无安全事件</p>
          ) : (
            <div className="space-y-4">
              {securityData.logs.slice(0, 20).map((log, index) => (
                <div key={index} className="p-4 bg-gray-50 rounded">
                  <div className="flex items-center justify-between mb-2">
                    <span className="font-semibold">{log.type}</span>
                    <span className="text-sm text-gray-500">
                      {new Date(log.timestamp).toLocaleString('zh-CN')}
                    </span>
                  </div>
                  {log.ip && <p className="text-sm text-gray-600">IP: {log.ip}</p>}
                  {log.email && <p className="text-sm text-gray-600">邮箱: {log.email}</p>}
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Step 3: Test the security monitoring

Run: npm run dev and navigate to /admin/security

Expected: Security dashboard loads and displays data

Step 4: Commit

git add src/app/admin/security/page.tsx src/app/api/admin/security/route.ts
git commit -m "feat: add security monitoring dashboard"

Task 10: Add Environment Variables Documentation

Files:

  • Create: .env.example

Step 1: Create environment variables example

# Email Service
RESEND_API_KEY=your_resend_api_key_here
COMPANY_EMAIL=contact@novalon.cn

# Security
CAPTCHA_SECRET=your_secret_key_for_captcha_hashing

# Rate Limiting (optional, defaults are used if not set)
RATE_LIMIT_IP_MAX_REQUESTS=10
RATE_LIMIT_IP_WINDOW_MINUTES=60
RATE_LIMIT_EMAIL_MAX_REQUESTS=3
RATE_LIMIT_EMAIL_WINDOW_HOURS=24
RATE_LIMIT_GLOBAL_MAX_REQUESTS=50
RATE_LIMIT_GLOBAL_WINDOW_MINUTES=1

# Security Logging
SECURITY_LOG_RETENTION_DAYS=30
SECURITY_ALERT_THRESHOLD=5

Step 2: Update README with security section

Add to README.md:

## Security Features

The contact form includes comprehensive security measures:

- **Enhanced Captcha**: Multi-level complexity (simple/medium/complex) with server-side validation
- **Rate Limiting**: Multi-tier protection (IP, email, global limits)
- **Input Sanitization**: XSS and SQL injection prevention
- **Security Logging**: Comprehensive event tracking and monitoring
- **Suspicious Activity Detection**: Automatic IP blocking for repeated violations

### Security Configuration

Configure security settings in `.env.local`:

```bash
CAPTCHA_SECRET=your-secret-key
RATE_LIMIT_IP_MAX_REQUESTS=10

Monitoring

Access security monitoring at /admin/security to view:

  • Recent security events
  • Suspicious IP addresses
  • Rate limit violations
  • Malicious content attempts

**Step 3: Commit**

```bash
git add .env.example README.md
git commit -m "docs: add security configuration documentation"

Task 11: Create Integration Tests

Files:

  • Create: src/lib/security/integration.test.ts

Step 1: Write comprehensive integration tests

import { SecurityMiddleware } from './middleware';
import { generateCaptcha } from './captcha';

describe('Security Integration Tests', () => {
  let middleware: SecurityMiddleware;

  beforeEach(() => {
    middleware = new SecurityMiddleware();
  });

  test('should handle complete contact form submission flow', async () => {
    const captcha = generateCaptcha('medium');

    const result = await middleware.checkRequest({
      ip: '192.168.1.100',
      email: 'integration-test@example.com',
      name: 'Integration Test User',
      subject: 'Test Subject',
      message: 'This is a test message for integration testing.',
      captchaHash: captcha.hash,
      captchaAnswer: captcha.answer,
      captchaTimestamp: captcha.timestamp,
    });

    expect(result.allowed).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  test('should block multiple rapid submissions from same IP', async () => {
    const ip = '192.168.1.200';

    for (let i = 0; i < 10; i++) {
      const captcha = generateCaptcha('medium');
      await middleware.checkRequest({
        ip,
        email: `test${i}@example.com`,
        name: 'Test User',
        subject: 'Test',
        message: 'Test message',
        captchaHash: captcha.hash,
        captchaAnswer: captcha.answer,
        captchaTimestamp: captcha.timestamp,
      });
    }

    const captcha = generateCaptcha('medium');
    const result = await middleware.checkRequest({
      ip,
      email: 'blocked@example.com',
      name: 'Blocked User',
      subject: 'Test',
      message: 'This should be blocked',
      captchaHash: captcha.hash,
      captchaAnswer: captcha.answer,
      captchaTimestamp: captcha.timestamp,
    });

    expect(result.allowed).toBe(false);
    expect(result.errors).toContain('Rate limit exceeded for IP');
  });

  test('should sanitize malicious input while allowing legitimate content', async () => {
    const captcha = generateCaptcha('medium');

    const maliciousRequest = {
      ip: '192.168.1.300',
      email: 'sanitization-test@example.com',
      name: '<script>alert("xss")</script>Test User',
      subject: 'Test Subject',
      message: 'Visit http://malicious.com for free money! But also, I need help with order #12345.',
      captchaHash: captcha.hash,
      captchaAnswer: captcha.answer,
      captchaTimestamp: captcha.timestamp,
    };

    const result = await middleware.checkRequest(maliciousRequest);

    expect(result.allowed).toBe(false);
    expect(result.errors.some(e => e.includes('malicious'))).toBe(true);
  });

  test('should track and report suspicious activity', async () => {
    const ip = '192.168.1.400';

    for (let i = 0; i < 6; i++) {
      const captcha = generateCaptcha('medium');
      await middleware.checkRequest({
        ip,
        email: `suspicious${i}@example.com`,
        name: 'Suspicious User',
        subject: 'Test',
        message: 'Test message',
        captchaHash: 'invalid-hash',
        captchaAnswer: 999,
        captchaTimestamp: Date.now(),
      });
    }

    const suspiciousIPs = middleware.getSuspiciousIPs();
    expect(suspiciousIPs).toContain(ip);

    const logs = middleware.getSecurityLogs(10);
    const failedCaptchaLogs = logs.filter(log => log.type.includes('captcha_failed'));
    expect(failedCaptchaLogs.length).toBeGreaterThan(0);
  });
});

Step 2: Run integration tests

Run: npm test -- src/lib/security/integration.test.ts

Expected: PASS

Step 3: Commit

git add src/lib/security/integration.test.ts
git commit -m "test: add comprehensive security integration tests"

Task 12: Final Testing and Validation

Files:

  • Test: All security modules
  • Manual: Contact form functionality

Step 1: Run all security tests

Run: npm test -- src/lib/security/

Expected: All tests PASS

Step 2: Run contact form tests

Run: npm test -- src/app/api/contact/route.test.ts

Expected: All tests PASS

Step 3: Manual testing checklist

  • Load contact page and verify captcha appears
  • Submit valid form with correct captcha - should succeed
  • Submit form with incorrect captcha - should fail
  • Submit form with XSS content - should be blocked
  • Submit 11 forms rapidly from same IP - should be rate limited
  • Submit 4 forms from same email - should be rate limited
  • Check security monitoring dashboard displays events
  • Verify suspicious IPs are detected and logged

Step 4: Performance testing

Run: npm run dev and test form responsiveness

Expected: Form loads quickly, captcha generation is fast

Step 5: Commit final changes

git add .
git commit -m "test: complete security enhancement testing and validation"

Task 13: Create Deployment Documentation

Files:

  • Create: docs/SECURITY_DEPLOYMENT.md

Step 1: Create deployment guide

# Security Enhancement Deployment Guide

## Prerequisites

- Node.js 18+ installed
- Redis server (optional, for production rate limiting)
- Environment variables configured

## Environment Setup

1. Copy environment variables:
```bash
cp .env.example .env.local
  1. Configure required variables:
RESEND_API_KEY=your_actual_api_key
COMPANY_EMAIL=your@company.com
CAPTCHA_SECRET=generate-a-strong-random-secret

Installation

  1. Install dependencies:
npm install
  1. Run tests:
npm test
  1. Start development server:
npm run dev

Production Deployment

Security Considerations

  1. CAPTCHA_SECRET: Must be unique and unpredictable
  2. Rate Limits: Adjust based on your traffic patterns
  3. Monitoring: Regularly review security logs
  4. IP Blocking: Consider implementing persistent IP blocking

Monitoring Setup

  1. Access security dashboard: /admin/security
  2. Set up alerts for suspicious activity
  3. Review logs regularly
  4. Adjust thresholds based on traffic patterns

Performance Optimization

  1. Consider Redis for distributed rate limiting
  2. Implement log rotation for long-running deployments
  3. Cache captcha generation for high-traffic scenarios
  4. Monitor memory usage of security middleware

Troubleshooting

Captcha Issues

If users report captcha problems:

  • Check CAPTCHA_SECRET is set correctly
  • Verify captcha expiry time is appropriate
  • Review security logs for validation failures

Rate Limit Issues

If legitimate users are blocked:

  • Increase rate limits in configuration
  • Check for shared IP addresses (office networks)
  • Review security logs for false positives

Performance Issues

If form submission is slow:

  • Check rate limiter implementation
  • Review security logging overhead
  • Consider caching strategies

Maintenance

Regular Tasks

  • Weekly: Review security logs
  • Monthly: Adjust rate limits based on traffic
  • Quarterly: Update security patterns and rules
  • Annually: Security audit and penetration testing

Updates

Stay updated with:

  • New security vulnerabilities
  • Improved captcha techniques
  • Enhanced rate limiting algorithms
  • Updated sanitization patterns

**Step 2: Commit documentation**

```bash
git add docs/SECURITY_DEPLOYMENT.md
git commit -m "docs: add security deployment guide"

Summary

This implementation plan provides a comprehensive security enhancement for the contact form with:

Multi-layer rate limiting (IP, email, global) Enhanced captcha system (simple/medium/complex) Input sanitization (XSS, SQL injection prevention) Security logging (comprehensive event tracking) Suspicious activity detection (automatic IP blocking) Security monitoring dashboard (real-time visibility) Comprehensive testing (unit, integration, manual) Production-ready (documentation, monitoring, maintenance)

All tasks follow TDD principles, include proper error handling, and maintain backward compatibility where possible.