From 2f77010222da479b156578cfa929629d73f337e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 02:08:53 +0000 Subject: [PATCH 1/5] Initial plan From 0d299dedef104e6c22b73057614d8134ed31f50e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 02:27:01 +0000 Subject: [PATCH 2/5] Implement comprehensive secure communication layer with documentation and examples Co-authored-by: kmock930 <78272416+kmock930@users.noreply.github.com> --- .babelrc | 3 + README.md | 32 + __test__/security.test.js | 346 +++++++++++ docs/SECURITY.md | 728 +++++++++++++++++++++++ jest.config.js | 15 + jest.setup.js | 1 + next.config.js | 32 +- src/app/content/navMenu.json | 7 + src/app/examples/ErrorBoundary.js | 56 ++ src/app/examples/SecureAuthExample.js | 349 +++++++++++ src/app/examples/SecureContactForm.js | 255 ++++++++ src/app/examples/SecurityExamplesPage.js | 373 ++++++++++++ src/app/security/page.js | 13 + src/lib/apiClient.js | 318 ++++++++++ src/lib/errorHandler.js | 270 +++++++++ src/lib/sanitize.js | 234 ++++++++ 16 files changed, 3031 insertions(+), 1 deletion(-) create mode 100644 .babelrc create mode 100644 __test__/security.test.js create mode 100644 docs/SECURITY.md create mode 100644 jest.config.js create mode 100644 jest.setup.js create mode 100644 src/app/examples/ErrorBoundary.js create mode 100644 src/app/examples/SecureAuthExample.js create mode 100644 src/app/examples/SecureContactForm.js create mode 100644 src/app/examples/SecurityExamplesPage.js create mode 100644 src/app/security/page.js create mode 100644 src/lib/apiClient.js create mode 100644 src/lib/errorHandler.js create mode 100644 src/lib/sanitize.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..e49a7e6 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["next/babel"] +} \ No newline at end of file diff --git a/README.md b/README.md index 38d7c57..f1537d3 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,38 @@ My Personal Website: [kmock930-github-io.vercel.app](https://kmock930-github-io. This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +## 🔐 Security Features + +This project includes a comprehensive secure communication layer with: + +- **HTTPS Implementation**: Force HTTPS in production with security headers +- **Input Validation & Sanitization**: Comprehensive client and server-side validation +- **API Security**: Secure API client with authentication and rate limiting +- **Error Handling**: Secure error responses without information leakage +- **XSS Prevention**: Input sanitization and output encoding +- **CSRF Protection**: Request validation and token management +- **Authentication**: JWT-based authentication with token refresh + +### Security Documentation + +- 📖 [Complete Security Guide](./docs/SECURITY.md) - Comprehensive security documentation +- 🔧 [Security Utilities](./src/lib/) - Reusable security utilities +- 📋 [Example Components](./src/app/examples/) - Secure component implementations +- 🧪 [Security Tests](./tests/security.test.js) - Comprehensive security testing + +### Quick Security Setup + +```bash +# Install dependencies +npm install + +# Run security tests +npm test -- security.test.js + +# Check for vulnerabilities +npm audit +``` + ## Getting Started First, run the development server: diff --git a/__test__/security.test.js b/__test__/security.test.js new file mode 100644 index 0000000..8ac4904 --- /dev/null +++ b/__test__/security.test.js @@ -0,0 +1,346 @@ +/** + * Security Testing Examples + * + * This file contains comprehensive tests for security features + * including input sanitization, validation, and API security. + */ + +const { sanitizeInput, validate, encodeOutput } = require('../src/lib/sanitize') +const { handleClientError, ApiError, ErrorTypes } = require('../src/lib/errorHandler') + +describe('Security Features', () => { + describe('Input Sanitization', () => { + test('should sanitize text input', () => { + const maliciousInput = 'Hello World' + const sanitized = sanitizeInput.text(maliciousInput) + + expect(sanitized).not.toContain('') + expect(sanitized).toContain('Hello World') + }) + + test('should sanitize HTML content', () => { + const htmlInput = '
Safe content
' + const sanitized = sanitizeInput.html(htmlInput) + + expect(sanitized).not.toContain('')).toBeNull() + }) + + test('should sanitize URLs', () => { + expect(sanitizeInput.url('https://example.com')).toBe('https://example.com') + expect(sanitizeInput.url('http://example.com')).toBe('http://example.com') + expect(sanitizeInput.url('javascript:alert("XSS")')).toBeNull() + expect(sanitizeInput.url('data:text/html,')).toBeNull() + }) + + test('should sanitize phone numbers', () => { + expect(sanitizeInput.phone('(123) 456-7890')).toBe('(123) 456-7890') + expect(sanitizeInput.phone('123-456-7890')).toBe('123-456-7890') + expect(sanitizeInput.phone('+1 123 456 7890')).toBe('+1 123 456 7890') + expect(sanitizeInput.phone('invalid-phone')).toBeNull() + expect(sanitizeInput.phone('123')).toBeNull() // Too short + }) + + test('should limit text length', () => { + const longText = 'a'.repeat(2000) + const sanitized = sanitizeInput.text(longText, 100) + + expect(sanitized.length).toBe(100) + }) + + test('should handle non-string inputs', () => { + expect(sanitizeInput.text(null)).toBe('') + expect(sanitizeInput.text(undefined)).toBe('') + expect(sanitizeInput.text(123)).toBe('') + expect(sanitizeInput.email(123)).toBeNull() + expect(sanitizeInput.url({})).toBeNull() + }) + }) + + describe('Input Validation', () => { + test('should validate email format', () => { + expect(validate.email('test@example.com')).toBe(true) + expect(validate.email('invalid-email')).toBe(false) + expect(validate.email('')).toBe(false) + }) + + test('should validate text length', () => { + expect(validate.text('Hello', 3, 10)).toBe(true) + expect(validate.text('Hi', 3, 10)).toBe(false) // Too short + expect(validate.text('This is too long', 3, 10)).toBe(false) // Too long + }) + + test('should validate password strength', () => { + const weak = validate.password('123') + expect(weak.isValid).toBe(false) + expect(weak.errors).toContain('Password must be at least 8 characters long') + + const strong = validate.password('MySecure123!') + expect(strong.isValid).toBe(true) + expect(strong.errors).toHaveLength(0) + + const noNumber = validate.password('MySecurePass!') + expect(noNumber.isValid).toBe(false) + expect(noNumber.errors).toContain('Password must contain at least one number') + }) + + test('should validate URL format', () => { + expect(validate.url('https://example.com')).toBe(true) + expect(validate.url('invalid-url')).toBe(false) + expect(validate.url('javascript:alert("XSS")')).toBe(false) + }) + }) + + describe('Output Encoding', () => { + test('should encode HTML entities', () => { + const input = '' + const encoded = encodeOutput.html(input) + + expect(encoded).toBe('<script>alert("XSS")</script>') + }) + + test('should encode URLs', () => { + const input = 'hello world & special chars' + const encoded = encodeOutput.url(input) + + expect(encoded).toBe('hello%20world%20%26%20special%20chars') + }) + + test('should encode JSON safely', () => { + const input = { message: '' } + const encoded = encodeOutput.json(input) + + expect(encoded).toContain('\\u003c') + expect(encoded).toContain('\\u003e') + expect(encoded).not.toContain('<') + expect(encoded).not.toContain('>') + }) + + test('should handle non-string inputs', () => { + expect(encodeOutput.html(null)).toBe('') + expect(encodeOutput.html(undefined)).toBe('') + expect(encodeOutput.url(123)).toBe('') + }) + }) + + describe('Error Handling', () => { + test('should handle client errors securely', () => { + const apiError = new ApiError('Database connection failed', ErrorTypes.SERVER, 500) + const handled = handleClientError(apiError) + + expect(handled.message).toBe('An unexpected error occurred. Please try again.') + expect(handled.shouldRetry).toBe(false) + }) + + test('should handle network errors with retry suggestion', () => { + const networkError = new Error('fetch failed') + const handled = handleClientError(networkError) + + expect(handled.message).toBe('Network error. Please check your connection.') + expect(handled.shouldRetry).toBe(true) + }) + + test('should handle validation errors', () => { + const validationError = new ApiError('Invalid input', ErrorTypes.VALIDATION, 400) + const handled = handleClientError(validationError) + + expect(handled.message).toBe('Please check your input and try again.') + expect(handled.shouldRetry).toBe(false) + }) + + test('should handle authentication errors', () => { + const authError = new ApiError('Token expired', ErrorTypes.AUTHENTICATION, 401) + const handled = handleClientError(authError) + + expect(handled.message).toBe('Please log in to continue.') + expect(handled.shouldRetry).toBe(false) + }) + + test('should handle rate limiting errors', () => { + const rateLimitError = new ApiError('Too many requests', ErrorTypes.RATE_LIMIT, 429) + const handled = handleClientError(rateLimitError) + + expect(handled.message).toBe('Too many requests. Please wait a moment and try again.') + expect(handled.shouldRetry).toBe(true) + }) + }) + + describe('XSS Prevention', () => { + test('should prevent script injection in text fields', () => { + const maliciousInputs = [ + '', + 'javascript:alert("XSS")', + '', + '', + '">', + "'; alert('XSS'); //", + '' + ] + + maliciousInputs.forEach(input => { + const sanitized = sanitizeInput.text(input) + expect(sanitized).not.toContain('
Loading...
+ } + + if (!user) { + return null + } + + if (requiredRole && user.role !== requiredRole) { + return null + } + + return children +} +``` + +## Error Handling + +### Secure Error Responses + +```javascript +// lib/errorHandler.js +export const handleApiError = (error, req, res) => { + console.error('API Error:', error) + + // Don't expose sensitive information + const isDevelopment = process.env.NODE_ENV === 'development' + + if (error.name === 'ValidationError') { + return res.status(400).json({ + error: 'Invalid request data', + ...(isDevelopment && { details: error.message }) + }) + } + + if (error.name === 'UnauthorizedError') { + return res.status(401).json({ + error: 'Unauthorized access' + }) + } + + if (error.name === 'ForbiddenError') { + return res.status(403).json({ + error: 'Forbidden' + }) + } + + // Generic error for production + return res.status(500).json({ + error: 'Internal server error', + ...(isDevelopment && { details: error.message }) + }) +} +``` + +### Client-side Error Handling + +```javascript +// hooks/useSecureRequest.js +import { useState } from 'react' +import { apiClient } from '../lib/apiClient' + +export const useSecureRequest = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const makeRequest = async (endpoint, options = {}) => { + setLoading(true) + setError(null) + + try { + const response = await apiClient.request(endpoint, options) + return response + } catch (err) { + setError(err.message) + throw err + } finally { + setLoading(false) + } + } + + return { makeRequest, loading, error } +} +``` + +## Testing Security + +### Security Test Examples + +```javascript +// __tests__/security.test.js +import { sanitizeInput } from '../lib/sanitize' +import { validateRequest } from '../lib/validation' + +describe('Security Tests', () => { + describe('Input Sanitization', () => { + test('should sanitize HTML input', () => { + const maliciousInput = '

Safe content

' + const sanitized = sanitizeInput.html(maliciousInput) + + expect(sanitized).not.toContain('Hello World' - const sanitized = sanitizeInput.text(maliciousInput) - - expect(sanitized).not.toContain('') - expect(sanitized).toContain('Hello World') - }) - - test('should sanitize HTML content', () => { - const htmlInput = '
Safe content
' - const sanitized = sanitizeInput.html(htmlInput) - - expect(sanitized).not.toContain('')).toBeNull() - }) - - test('should sanitize URLs', () => { - expect(sanitizeInput.url('https://example.com')).toBe('https://example.com') - expect(sanitizeInput.url('http://example.com')).toBe('http://example.com') - expect(sanitizeInput.url('javascript:alert("XSS")')).toBeNull() - expect(sanitizeInput.url('data:text/html,')).toBeNull() - }) - - test('should sanitize phone numbers', () => { - expect(sanitizeInput.phone('(123) 456-7890')).toBe('(123) 456-7890') - expect(sanitizeInput.phone('123-456-7890')).toBe('123-456-7890') - expect(sanitizeInput.phone('+1 123 456 7890')).toBe('+1 123 456 7890') - expect(sanitizeInput.phone('invalid-phone')).toBeNull() - expect(sanitizeInput.phone('123')).toBeNull() // Too short - }) - - test('should limit text length', () => { - const longText = 'a'.repeat(2000) - const sanitized = sanitizeInput.text(longText, 100) - - expect(sanitized.length).toBe(100) - }) - - test('should handle non-string inputs', () => { - expect(sanitizeInput.text(null)).toBe('') - expect(sanitizeInput.text(undefined)).toBe('') - expect(sanitizeInput.text(123)).toBe('') - expect(sanitizeInput.email(123)).toBeNull() - expect(sanitizeInput.url({})).toBeNull() - }) - }) - - describe('Input Validation', () => { - test('should validate email format', () => { - expect(validate.email('test@example.com')).toBe(true) - expect(validate.email('invalid-email')).toBe(false) - expect(validate.email('')).toBe(false) - }) - - test('should validate text length', () => { - expect(validate.text('Hello', 3, 10)).toBe(true) - expect(validate.text('Hi', 3, 10)).toBe(false) // Too short - expect(validate.text('This is too long', 3, 10)).toBe(false) // Too long - }) - - test('should validate password strength', () => { - const weak = validate.password('123') - expect(weak.isValid).toBe(false) - expect(weak.errors).toContain('Password must be at least 8 characters long') - - const strong = validate.password('MySecure123!') - expect(strong.isValid).toBe(true) - expect(strong.errors).toHaveLength(0) - - const noNumber = validate.password('MySecurePass!') - expect(noNumber.isValid).toBe(false) - expect(noNumber.errors).toContain('Password must contain at least one number') - }) - - test('should validate URL format', () => { - expect(validate.url('https://example.com')).toBe(true) - expect(validate.url('invalid-url')).toBe(false) - expect(validate.url('javascript:alert("XSS")')).toBe(false) - }) - }) - - describe('Output Encoding', () => { - test('should encode HTML entities', () => { - const input = '' - const encoded = encodeOutput.html(input) - - expect(encoded).toBe('<script>alert("XSS")</script>') - }) - - test('should encode URLs', () => { - const input = 'hello world & special chars' - const encoded = encodeOutput.url(input) - - expect(encoded).toBe('hello%20world%20%26%20special%20chars') - }) - - test('should encode JSON safely', () => { - const input = { message: '' } - const encoded = encodeOutput.json(input) - - expect(encoded).toContain('\\u003c') - expect(encoded).toContain('\\u003e') - expect(encoded).not.toContain('<') - expect(encoded).not.toContain('>') - }) - - test('should handle non-string inputs', () => { - expect(encodeOutput.html(null)).toBe('') - expect(encodeOutput.html(undefined)).toBe('') - expect(encodeOutput.url(123)).toBe('') - }) - }) - - describe('Error Handling', () => { - test('should handle client errors securely', () => { - const apiError = new ApiError('Database connection failed', ErrorTypes.SERVER, 500) - const handled = handleClientError(apiError) - - expect(handled.message).toBe('An unexpected error occurred. Please try again.') - expect(handled.shouldRetry).toBe(false) - }) - - test('should handle network errors with retry suggestion', () => { - const networkError = new Error('fetch failed') - const handled = handleClientError(networkError) - - expect(handled.message).toBe('Network error. Please check your connection.') - expect(handled.shouldRetry).toBe(true) - }) - - test('should handle validation errors', () => { - const validationError = new ApiError('Invalid input', ErrorTypes.VALIDATION, 400) - const handled = handleClientError(validationError) - - expect(handled.message).toBe('Please check your input and try again.') - expect(handled.shouldRetry).toBe(false) - }) - - test('should handle authentication errors', () => { - const authError = new ApiError('Token expired', ErrorTypes.AUTHENTICATION, 401) - const handled = handleClientError(authError) - - expect(handled.message).toBe('Please log in to continue.') - expect(handled.shouldRetry).toBe(false) - }) - - test('should handle rate limiting errors', () => { - const rateLimitError = new ApiError('Too many requests', ErrorTypes.RATE_LIMIT, 429) - const handled = handleClientError(rateLimitError) - - expect(handled.message).toBe('Too many requests. Please wait a moment and try again.') - expect(handled.shouldRetry).toBe(true) - }) - }) - - describe('XSS Prevention', () => { - test('should prevent script injection in text fields', () => { - const maliciousInputs = [ - '', - 'javascript:alert("XSS")', - '', - '', - '">', - "'; alert('XSS'); //", - '' - ] - - maliciousInputs.forEach(input => { - const sanitized = sanitizeInput.text(input) - expect(sanitized).not.toContain('
Loading...
- } - - if (!user) { - return null - } - - if (requiredRole && user.role !== requiredRole) { - return null - } - - return children -} -``` - -## Error Handling - -### Secure Error Responses - -```javascript -// lib/errorHandler.js -export const handleApiError = (error, req, res) => { - console.error('API Error:', error) - - // Don't expose sensitive information - const isDevelopment = process.env.NODE_ENV === 'development' - - if (error.name === 'ValidationError') { - return res.status(400).json({ - error: 'Invalid request data', - ...(isDevelopment && { details: error.message }) - }) - } - - if (error.name === 'UnauthorizedError') { - return res.status(401).json({ - error: 'Unauthorized access' - }) - } - - if (error.name === 'ForbiddenError') { - return res.status(403).json({ - error: 'Forbidden' - }) - } - - // Generic error for production - return res.status(500).json({ - error: 'Internal server error', - ...(isDevelopment && { details: error.message }) - }) -} -``` - -### Client-side Error Handling - -```javascript -// hooks/useSecureRequest.js -import { useState } from 'react' -import { apiClient } from '../lib/apiClient' - -export const useSecureRequest = () => { - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const makeRequest = async (endpoint, options = {}) => { - setLoading(true) - setError(null) - - try { - const response = await apiClient.request(endpoint, options) - return response - } catch (err) { - setError(err.message) - throw err - } finally { - setLoading(false) - } - } - - return { makeRequest, loading, error } -} -``` - -## Testing Security - -### Security Test Examples - -```javascript -// __tests__/security.test.js -import { sanitizeInput } from '../lib/sanitize' -import { validateRequest } from '../lib/validation' - -describe('Security Tests', () => { - describe('Input Sanitization', () => { - test('should sanitize HTML input', () => { - const maliciousInput = '

Safe content

' - const sanitized = sanitizeInput.html(maliciousInput) - - expect(sanitized).not.toContain(''; + const sanitized = sanitizeInput(maliciousInput); + expect(sanitized).not.toContain('', + 'javascript:alert("xss")', + '' + ]; + + for (const payload of xssPayloads) { + const result = await securityTestSuite.testXSSPrevention(payload); + expect(result.isSecure).toBe(true); + expect(result.sanitizedOutput).not.toContain('', + '', + 'javascript:alert("xss")', + '', + '' + ]; + + xssPayloads.forEach(payload => { + const sanitized = InputSanitizer.sanitizeHTML(payload); + expect(sanitized).not.toContain(''; + + const apiClient = new SecureAPIClient('https://api.example.com'); + const config = apiClient.buildRequestConfig({ + method: 'POST', + body: { test: 'data' } + }); + + expect(config.headers['X-CSRF-Token']).toBe('test-csrf-token'); + }); + }); + + describe('Error Handling Security', () => { + test('Error Information Leakage Prevention', () => { + const errorHandler = new SecureErrorHandler({ isDevelopment: false }); + + // Test with sensitive error + const sensitiveError = new Error('Database connection failed: user=admin, password=secret123'); + const handledError = errorHandler.handleClientError(sensitiveError); + + expect(handledError.message).not.toContain('password'); + expect(handledError.message).not.toContain('secret123'); + expect(handledError.message).not.toContain('admin'); + expect(handledError.errorId).toBeDefined(); + }); + + test('Error Retry Logic', () => { + const errorHandler = new SecureErrorHandler(); + + const retryableError = new Error('Network timeout'); + retryableError.name = 'TimeoutError'; + + const nonRetryableError = new Error('Invalid input'); + nonRetryableError.name = 'ValidationError'; + + const retryableResult = errorHandler.handleClientError(retryableError); + const nonRetryableResult = errorHandler.handleClientError(nonRetryableError); + + expect(retryableResult.canRetry).toBe(true); + expect(nonRetryableResult.canRetry).toBe(false); + }); + }); +}); +``` + +## Configuration Templates + +### Next.js Security Configuration + +```javascript +// next.config.js +/** @type {import('next').NextConfig} */ +const nextConfig = { + // Basic configuration + output: 'export', + trailingSlash: true, + distDir: 'out', + + // Security headers + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on' + }, + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload' + }, + { + key: 'X-XSS-Protection', + value: '1; mode=block' + }, + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, + { + key: 'Referrer-Policy', + value: 'origin-when-cross-origin' + }, + { + key: 'Content-Security-Policy', + value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com" + } + ], + }, + ]; + }, + + // Environment variables validation + env: { + JWT_SECRET: process.env.JWT_SECRET, + API_BASE_URL: process.env.API_BASE_URL, + }, + + // Webpack configuration for security + webpack: (config, { isServer }) => { + if (!isServer) { + config.resolve.fallback = { + ...config.resolve.fallback, + fs: false, + net: false, + tls: false, + }; + } + return config; + }, +}; + +module.exports = nextConfig; +``` + +### Environment Variables Template + +```bash +# .env.local (for development) +# Copy this file and rename to .env.local +# Never commit .env.local to version control + +# JWT Configuration +JWT_SECRET=your-super-secret-jwt-key-here-make-it-long-and-random +JWT_EXPIRY=1h +REFRESH_TOKEN_EXPIRY=7d + +# API Configuration +API_BASE_URL=https://api.yourdomain.com +API_TIMEOUT=30000 +API_RATE_LIMIT_WINDOW=60000 +API_MAX_REQUESTS=100 + +# Database Configuration (if applicable) +DATABASE_URL=your-database-connection-string +DATABASE_SSL=true + +# CORS Configuration +ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com +ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS +ALLOWED_HEADERS=Content-Type,Authorization,X-Requested-With,X-CSRF-Token + +# Security Configuration +BCRYPT_ROUNDS=12 +SESSION_SECRET=another-long-random-secret-for-sessions +CSRF_SECRET=csrf-secret-key-here + +# Development vs Production +NODE_ENV=development +DEBUG=false + +# Rate Limiting +RATE_LIMIT_WINDOW_MS=900000 +RATE_LIMIT_MAX_REQUESTS=100 + +# File Upload Limits +MAX_FILE_SIZE=5242880 +ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,pdf,doc,docx + +# Email Configuration (if applicable) +SMTP_HOST=smtp.yourdomain.com +SMTP_PORT=587 +SMTP_USER=your-email@yourdomain.com +SMTP_PASS=your-email-password +``` + +--- + +**Document Version:** 1.0 +**Last Updated:** July 2025 +**Total Code Examples:** 15 +**Security Patterns Covered:** 8 +**Testing Examples:** 12 \ No newline at end of file diff --git a/documents/security-implementation-checklist.md b/documents/security-implementation-checklist.md new file mode 100644 index 0000000..fdacbba --- /dev/null +++ b/documents/security-implementation-checklist.md @@ -0,0 +1,287 @@ +# Security Implementation Checklist + +**Document Type:** Implementation Guide +**Related Issue:** #19 - Secure Communication Layer +**Target Audience:** Developers with Limited Security Experience + +## Quick Implementation Checklist + +This checklist provides a step-by-step approach to implementing the security recommendations from the main research document. + +### Phase 1: Foundation Security (Required) + +#### HTTP Security Headers +- [ ] **Strict-Transport-Security** - Force HTTPS connections +- [ ] **X-Content-Type-Options** - Prevent MIME type sniffing +- [ ] **X-Frame-Options** - Prevent clickjacking attacks +- [ ] **X-XSS-Protection** - Enable XSS filtering +- [ ] **Referrer-Policy** - Control referrer information +- [ ] **Content-Security-Policy** - Prevent code injection + +#### HTTPS Implementation +- [ ] SSL/TLS certificate installation +- [ ] HTTP to HTTPS redirection +- [ ] Certificate auto-renewal setup +- [ ] TLS 1.2+ enforcement +- [ ] Perfect Forward Secrecy configuration + +#### Basic Input Validation +- [ ] Client-side validation for user experience +- [ ] Server-side validation for security +- [ ] Input sanitization functions +- [ ] Output encoding for XSS prevention + +### Phase 2: Authentication & Authorization (Essential) + +#### JWT Implementation +- [ ] JWT token generation +- [ ] Token validation middleware +- [ ] Token refresh mechanism +- [ ] Secure token storage +- [ ] Token expiration handling + +#### Password Security +- [ ] Password strength validation +- [ ] Secure password hashing (bcrypt/Argon2) +- [ ] Password reset functionality +- [ ] Account lockout protection + +#### Session Management +- [ ] Secure session storage +- [ ] Session timeout configuration +- [ ] Session invalidation on logout +- [ ] Concurrent session limits + +### Phase 3: API Security (Critical) + +#### Request Security +- [ ] Rate limiting implementation +- [ ] Request timeout handling +- [ ] CORS configuration +- [ ] API versioning +- [ ] Request size limits + +#### Response Security +- [ ] Secure error handling +- [ ] Information leakage prevention +- [ ] Response compression security +- [ ] Cache-Control headers + +### Phase 4: Testing & Validation (Mandatory) + +#### Security Tests +- [ ] XSS prevention tests +- [ ] SQL injection protection tests +- [ ] CSRF protection tests +- [ ] Authentication bypass tests +- [ ] Authorization tests +- [ ] Rate limiting tests + +#### Automated Testing +- [ ] Security test suite integration +- [ ] Continuous security testing +- [ ] Vulnerability scanning +- [ ] Dependency security checks + +### Phase 5: Monitoring & Maintenance (Ongoing) + +#### Security Monitoring +- [ ] Security event logging +- [ ] Failed authentication tracking +- [ ] Unusual activity detection +- [ ] Performance monitoring + +#### Maintenance +- [ ] Regular security updates +- [ ] Dependency updates +- [ ] Security configuration reviews +- [ ] Incident response procedures + +## Implementation Priority Matrix + +| Security Feature | Implementation Difficulty | Security Impact | Priority | +|------------------|--------------------------|-----------------|----------| +| HTTPS Enforcement | Low | Critical | **HIGH** | +| Input Validation | Medium | Critical | **HIGH** | +| Security Headers | Low | High | **HIGH** | +| JWT Authentication | Medium | High | **MEDIUM** | +| Rate Limiting | Medium | Medium | **MEDIUM** | +| Security Testing | High | High | **MEDIUM** | +| Monitoring Setup | High | Medium | **LOW** | + +## Common Implementation Mistakes + +### ❌ What NOT to Do + +1. **Client-side Only Validation** - Never rely solely on client-side validation +2. **Plain Text Passwords** - Never store passwords in plain text +3. **Hardcoded Secrets** - Never commit API keys or secrets to version control +4. **Ignoring HTTPS** - Never transmit sensitive data over HTTP +5. **Default Configurations** - Never use default security configurations +6. **Verbose Error Messages** - Never expose system details in error messages + +### ✅ Best Practices + +1. **Defense in Depth** - Implement multiple layers of security +2. **Principle of Least Privilege** - Grant minimum necessary permissions +3. **Fail Securely** - Ensure failures don't compromise security +4. **Regular Updates** - Keep all dependencies and systems updated +5. **Security by Design** - Consider security from the beginning +6. **Comprehensive Testing** - Test all security implementations thoroughly + +## Quick Reference Commands + +### Next.js Security Headers Configuration +```javascript +// next.config.js +const securityHeaders = [ + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN' + } +]; + +module.exports = { + async headers() { + return [ + { + source: '/(.*)', + headers: securityHeaders, + }, + ]; + }, +}; +``` + +### Basic Input Sanitization +```javascript +// Sanitize text input +function sanitizeText(input) { + if (typeof input !== 'string') return ''; + return input.trim().replace(/[<>]/g, ''); +} + +// Sanitize email +function sanitizeEmail(email) { + if (typeof email !== 'string') return ''; + return email.trim().toLowerCase().replace(/[^\w@.-]/g, ''); +} +``` + +### JWT Token Validation +```javascript +// JWT validation middleware +function validateJWT(token) { + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + return { valid: true, payload: decoded }; + } catch (error) { + return { valid: false, error: error.message }; + } +} +``` + +## Security Testing Commands + +### Run Security Test Suite +```bash +# Run all security tests +npm run test:security + +# Run specific security test category +npm run test:security -- --grep "XSS Prevention" + +# Run tests with coverage report +npm run test:security -- --coverage +``` + +### Security Vulnerability Scanning +```bash +# Check for known vulnerabilities +npm audit + +# Fix vulnerabilities automatically +npm audit fix + +# Check for outdated packages +npm outdated +``` + +## Emergency Security Response + +### If Security Breach Detected + +1. **Immediate Actions** + - [ ] Isolate affected systems + - [ ] Change all authentication credentials + - [ ] Revoke compromised tokens + - [ ] Enable enhanced monitoring + +2. **Investigation Steps** + - [ ] Review security logs + - [ ] Identify attack vectors + - [ ] Assess data exposure + - [ ] Document incident details + +3. **Recovery Actions** + - [ ] Patch security vulnerabilities + - [ ] Update security configurations + - [ ] Restore from secure backups + - [ ] Verify system integrity + +4. **Post-Incident Review** + - [ ] Conduct security assessment + - [ ] Update incident response procedures + - [ ] Implement additional security measures + - [ ] Train team on lessons learned + +## Compliance Checklist + +### OWASP Top 10 Protection + +- [ ] **A01:2021 – Broken Access Control** - Implemented proper authorization +- [ ] **A02:2021 – Cryptographic Failures** - Using strong encryption +- [ ] **A03:2021 – Injection** - Input validation and sanitization +- [ ] **A04:2021 – Insecure Design** - Security by design principles +- [ ] **A05:2021 – Security Misconfiguration** - Secure configurations +- [ ] **A06:2021 – Vulnerable Components** - Regular dependency updates +- [ ] **A07:2021 – Authentication Failures** - Strong authentication +- [ ] **A08:2021 – Software Integrity Failures** - Code integrity checks +- [ ] **A09:2021 – Security Logging Failures** - Comprehensive logging +- [ ] **A10:2021 – Server-Side Request Forgery** - SSRF protection + +### Privacy Compliance (GDPR/CCPA) + +- [ ] Data collection consent +- [ ] Data processing transparency +- [ ] Right to data portability +- [ ] Right to data deletion +- [ ] Data breach notification procedures +- [ ] Privacy by design implementation + +## Success Metrics + +### Security KPIs to Track + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Security Test Pass Rate | 100% | Automated testing | +| Vulnerability Count | 0 Critical | Security scanning | +| Authentication Failure Rate | < 5% | Log analysis | +| Security Incident Count | 0 per month | Incident tracking | +| Security Update Time | < 24 hours | Change management | + +--- + +**Document Version:** 1.0 +**Last Updated:** July 2025 +**Review Schedule:** Monthly +**Contact:** Development Team Lead \ No newline at end of file diff --git a/next.config.js b/next.config.js index 05e5105..9acd359 100644 --- a/next.config.js +++ b/next.config.js @@ -3,37 +3,7 @@ const nextConfig = { output: 'export', trailingSlash: true, distDir: `out`, - - // Security headers for production - async headers() { - return [ - { - source: '/(.*)', - headers: [ - { - key: 'X-Content-Type-Options', - value: 'nosniff' - }, - { - key: 'X-Frame-Options', - value: 'DENY' - }, - { - key: 'X-XSS-Protection', - value: '1; mode=block' - }, - { - key: 'Referrer-Policy', - value: 'strict-origin-when-cross-origin' - }, - { - key: 'Permissions-Policy', - value: 'geolocation=(), microphone=(), camera=()' - } - ] - } - ] - } + verbose: true } module.exports = nextConfig diff --git a/src/app/content/navMenu.json b/src/app/content/navMenu.json index 0c971c5..66508de 100644 --- a/src/app/content/navMenu.json +++ b/src/app/content/navMenu.json @@ -14,12 +14,5 @@ {"Life in Canada": ["Work", "Social", "Living"]} ] }, - { - "Security": [ - "Documentation", - "Examples", - "Best Practices" - ] - }, "Contact Me" ] \ No newline at end of file diff --git a/src/app/examples/ErrorBoundary.js b/src/app/examples/ErrorBoundary.js deleted file mode 100644 index 23bf9e8..0000000 --- a/src/app/examples/ErrorBoundary.js +++ /dev/null @@ -1,56 +0,0 @@ -'use client' - -import React from 'react' -import { Alert, Box, Button, Typography } from '@mui/material' - -export class ErrorBoundary extends React.Component { - constructor(props) { - super(props) - this.state = { hasError: false, error: null } - } - - static getDerivedStateFromError(error) { - return { hasError: true, error } - } - - componentDidCatch(error, errorInfo) { - const isDevelopment = process.env.NODE_ENV === 'development' - - if (isDevelopment) { - console.error('Error Boundary caught an error:', error, errorInfo) - } - - // In production, you would send this to an error reporting service - // reportErrorToService(error, errorInfo) - } - - render() { - if (this.state.hasError) { - if (this.props.fallback) { - return this.props.fallback(this.state.error) - } - - return ( - - - - Something went wrong - - - We're sorry, but something unexpected happened. - - - - - ) - } - - return this.props.children - } -} \ No newline at end of file diff --git a/src/app/examples/SecureAuthExample.js b/src/app/examples/SecureAuthExample.js deleted file mode 100644 index a92f198..0000000 --- a/src/app/examples/SecureAuthExample.js +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Secure Authentication Example Component - * - * This component demonstrates secure authentication practices: - * - Password validation - * - Secure token management - * - Session handling - * - Error handling - */ - -'use client' - -import React, { useState } from 'react' -import { - TextField, - Button, - Box, - Alert, - Typography, - CircularProgress, - InputAdornment, - IconButton, - Divider, - Paper -} from '@mui/material' -import { Visibility, VisibilityOff, Security, Info } from '@mui/icons-material' - -// Simplified validation functions for demo -const sanitizeInput = { - text: (input) => { - if (typeof input !== 'string') return '' - return input.replace(/[<>]/g, '').trim().substring(0, 50) - }, - email: (input) => { - if (typeof input !== 'string') return null - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - const sanitized = input.toLowerCase().trim() - return emailRegex.test(sanitized) ? sanitized : null - } -} - -const validate = { - text: (text, minLength = 1, maxLength = 1000) => { - if (typeof text !== 'string') return false - return text.length >= minLength && text.length <= maxLength - }, - email: (email) => { - return sanitizeInput.email(email) !== null - }, - password: (password) => { - const result = { isValid: false, errors: [] } - if (typeof password !== 'string') { - result.errors.push('Password must be a string') - return result - } - if (password.length < 8) result.errors.push('Password must be at least 8 characters long') - if (!/[a-z]/.test(password)) result.errors.push('Password must contain at least one lowercase letter') - if (!/[A-Z]/.test(password)) result.errors.push('Password must contain at least one uppercase letter') - if (!/\d/.test(password)) result.errors.push('Password must contain at least one number') - if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) result.errors.push('Password must contain at least one special character') - result.isValid = result.errors.length === 0 - return result - } -} - -export default function SecureAuthExample() { - const [mode, setMode] = useState('login') // 'login' or 'register' - const [showPassword, setShowPassword] = useState(false) - const [formData, setFormData] = useState({ - email: '', - password: '', - confirmPassword: '', - name: '' - }) - const [errors, setErrors] = useState({}) - const [success, setSuccess] = useState(false) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - // Simplified API request function for demo - const makeRequest = async (endpoint, options) => { - setLoading(true) - setError(null) - - // Simulate API call - return new Promise((resolve, reject) => { - setTimeout(() => { - setLoading(false) - // Simulate successful authentication - resolve({ success: true, token: 'demo-token' }) - }, 1000) - }) - } - - /** - * Validate form data - * @returns {Object} - Validation errors - */ - const validateForm = () => { - const newErrors = {} - - // Validate email - if (!validate.email(formData.email)) { - newErrors.email = 'Please enter a valid email address' - } - - // Validate password - const passwordValidation = validate.password(formData.password) - if (!passwordValidation.isValid) { - newErrors.password = passwordValidation.errors.join(', ') - } - - // For registration mode - if (mode === 'register') { - // Validate name - if (!validate.text(formData.name, 2, 50)) { - newErrors.name = 'Name must be 2-50 characters long' - } - - // Validate password confirmation - if (formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = 'Passwords do not match' - } - } - - return newErrors - } - - /** - * Handle form input changes with sanitization - * @param {Event} e - Input change event - */ - const handleChange = (e) => { - const { name, value } = e.target - - // Sanitize input based on field type - let sanitizedValue = value - - switch (name) { - case 'email': - sanitizedValue = value.toLowerCase().trim() - break - case 'name': - sanitizedValue = sanitizeInput.text(value) - break - case 'password': - case 'confirmPassword': - // Don't sanitize passwords, just validate - sanitizedValue = value - break - default: - break - } - - setFormData(prev => ({ - ...prev, - [name]: sanitizedValue - })) - - // Clear error for this field - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: '' - })) - } - } - - /** - * Handle form submission - * @param {Event} e - Form submit event - */ - const handleSubmit = async (e) => { - e.preventDefault() - setSuccess(false) - - // Validate form - const validationErrors = validateForm() - if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors) - return - } - - // Prepare data for API - const apiData = { - email: sanitizeInput.email(formData.email), - password: formData.password - } - - if (mode === 'register') { - apiData.name = sanitizeInput.text(formData.name) - } - - try { - // Make secure API request (simulated) - const endpoint = mode === 'login' ? '/api/auth/login' : '/api/auth/register' - const response = await makeRequest(endpoint, { - method: 'POST', - body: JSON.stringify(apiData) - }) - - setSuccess(true) - setFormData({ - email: '', - password: '', - confirmPassword: '', - name: '' - }) - setErrors({}) - - // Handle successful authentication - if (response.token) { - console.log('Authentication successful') - } - } catch (err) { - setError('Authentication failed. Please try again.') - console.error('Authentication failed:', err) - } - } - - /** - * Toggle password visibility - */ - const togglePasswordVisibility = () => { - setShowPassword(!showPassword) - } - - /** - * Switch between login and register modes - */ - const switchMode = () => { - setMode(mode === 'login' ? 'register' : 'login') - setFormData({ - email: '', - password: '', - confirmPassword: '', - name: '' - }) - setErrors({}) - setSuccess(false) - } - - return ( - - - - - - Secure Authentication - - - - - This form demonstrates secure authentication with password validation, - token management, and secure communication. - - - {success && ( - - {mode === 'login' ? 'Login successful!' : 'Registration successful!'} - - )} - - {error && ( - - {error} - - )} - - - {mode === 'register' && ( - - )} - - - - - - {showPassword ? : } - - - ) - }} - /> - - {mode === 'register' && ( - - )} - - - - - - - - - - {mode === 'login' ? "Don't have an account?" : 'Already have an account?'} - - - - - - - - - - Security Features: - - - - • Password strength validation -
- • Secure token storage -
- • Input sanitization -
- • Rate limiting protection -
- • Automatic token refresh -
- • Secure session management -
-
-
-
- ) -} \ No newline at end of file diff --git a/src/app/examples/SecureContactForm.js b/src/app/examples/SecureContactForm.js deleted file mode 100644 index b6757cc..0000000 --- a/src/app/examples/SecureContactForm.js +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Secure Contact Form Component - * - * This component demonstrates secure form handling with: - * - Input validation and sanitization - * - Secure API communication - * - Error handling - * - Loading states - */ - -'use client' - -import React, { useState } from 'react' -// Note: In a real application, you would install and import these utilities -// For this demo, we'll create simplified versions -import { TextField, Button, Box, Alert, Typography, CircularProgress } from '@mui/material' - -// Simplified sanitization functions for demo -const sanitizeInput = { - text: (input) => { - if (typeof input !== 'string') return '' - return input.replace(/[<>]/g, '').trim().substring(0, 1000) - }, - email: (input) => { - if (typeof input !== 'string') return null - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - const sanitized = input.toLowerCase().trim() - return emailRegex.test(sanitized) ? sanitized : null - } -} - -const validate = { - text: (text, minLength = 1, maxLength = 1000) => { - if (typeof text !== 'string') return false - return text.length >= minLength && text.length <= maxLength - }, - email: (email) => { - return sanitizeInput.email(email) !== null - } -} - -export default function SecureContactForm() { - const [formData, setFormData] = useState({ - name: '', - email: '', - subject: '', - message: '' - }) - const [errors, setErrors] = useState({}) - const [success, setSuccess] = useState(false) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - // Simplified API request function for demo - const makeRequest = async (endpoint, options) => { - setLoading(true) - setError(null) - - // Simulate API call - return new Promise((resolve, reject) => { - setTimeout(() => { - setLoading(false) - // Simulate successful submission - resolve({ success: true }) - }, 1000) - }) - } - - /** - * Validate form data - * @returns {Object} - Validation errors - */ - const validateForm = () => { - const newErrors = {} - - // Validate name - if (!validate.text(formData.name, 2, 50)) { - newErrors.name = 'Name must be 2-50 characters long' - } - - // Validate email - if (!validate.email(formData.email)) { - newErrors.email = 'Please enter a valid email address' - } - - // Validate subject - if (!validate.text(formData.subject, 5, 100)) { - newErrors.subject = 'Subject must be 5-100 characters long' - } - - // Validate message - if (!validate.text(formData.message, 10, 1000)) { - newErrors.message = 'Message must be 10-1000 characters long' - } - - return newErrors - } - - /** - * Handle form input changes with sanitization - * @param {Event} e - Input change event - */ - const handleChange = (e) => { - const { name, value } = e.target - - // Sanitize input based on field type - let sanitizedValue = value - - switch (name) { - case 'name': - case 'subject': - case 'message': - sanitizedValue = sanitizeInput.text(value) - break - case 'email': - sanitizedValue = value.toLowerCase().trim() - break - default: - break - } - - setFormData(prev => ({ - ...prev, - [name]: sanitizedValue - })) - - // Clear error for this field - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: '' - })) - } - } - - /** - * Handle form submission - * @param {Event} e - Form submit event - */ - const handleSubmit = async (e) => { - e.preventDefault() - setSuccess(false) - - // Validate form - const validationErrors = validateForm() - if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors) - return - } - - // Sanitize all data before sending - const sanitizedData = { - name: sanitizeInput.text(formData.name), - email: sanitizeInput.email(formData.email), - subject: sanitizeInput.text(formData.subject), - message: sanitizeInput.text(formData.message) - } - - try { - // Make secure API request (simulated) - await makeRequest('/api/contact', { - method: 'POST', - body: JSON.stringify(sanitizedData) - }) - - setSuccess(true) - setFormData({ - name: '', - email: '', - subject: '', - message: '' - }) - setErrors({}) - } catch (err) { - setError('Failed to send message. Please try again.') - console.error('Contact form submission failed:', err) - } - } - - return ( - - - Secure Contact Form - - - - This form demonstrates secure communication with input validation, - sanitization, and error handling. - - - {success && ( - - Thank you! Your message has been sent successfully. - - )} - - {error && ( - - {error} - - )} - - - - - - - - - - - - - - - Security Features: -
- • Input validation and sanitization -
- • Secure API communication -
- • Error handling without exposing sensitive data -
- • Rate limiting protection -
- • CSRF protection -
-
- ) -} \ No newline at end of file diff --git a/src/app/examples/SecurityExamplesPage.js b/src/app/examples/SecurityExamplesPage.js deleted file mode 100644 index 91bb651..0000000 --- a/src/app/examples/SecurityExamplesPage.js +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Security Examples Page - * - * This page showcases all security features and best practices - * implemented in the secure communication layer. - */ - -'use client' - -import React, { useState } from 'react' -import { - Box, - Container, - Typography, - Tabs, - Tab, - Paper, - Alert, - Accordion, - AccordionSummary, - AccordionDetails, - Button, - List, - ListItem, - ListItemIcon, - ListItemText, - Divider -} from '@mui/material' -import { - ExpandMore, - Security, - Shield, - Lock, - VerifiedUser, - Https, - BugReport, - Code, - CheckCircle -} from '@mui/icons-material' - -import SecureContactForm from './SecureContactForm' -import SecureAuthExample from './SecureAuthExample' - -function TabPanel({ children, value, index, ...other }) { - return ( - - ) -} - -export default function SecurityExamplesPage() { - const [tabValue, setTabValue] = useState(0) - - const handleTabChange = (event, newValue) => { - setTabValue(newValue) - } - - const securityFeatures = [ - { - icon: , - title: 'HTTPS Implementation', - description: 'Force HTTPS in production with security headers' - }, - { - icon: , - title: 'Input Validation', - description: 'Comprehensive client and server-side validation' - }, - { - icon: , - title: 'Data Sanitization', - description: 'Sanitize all user inputs to prevent XSS attacks' - }, - { - icon: , - title: 'Authentication', - description: 'Secure JWT-based authentication with token refresh' - }, - { - icon: , - title: 'Rate Limiting', - description: 'Protect against abuse and DDoS attacks' - }, - { - icon: , - title: 'Error Handling', - description: 'Secure error responses without information leakage' - } - ] - - const vulnerabilityPrevention = [ - { - vulnerability: 'Cross-Site Scripting (XSS)', - prevention: 'Input sanitization, output encoding, CSP headers' - }, - { - vulnerability: 'Cross-Site Request Forgery (CSRF)', - prevention: 'CSRF tokens, SameSite cookies, origin validation' - }, - { - vulnerability: 'SQL Injection', - prevention: 'Parameterized queries, input validation' - }, - { - vulnerability: 'Man-in-the-Middle', - prevention: 'HTTPS enforcement, HSTS headers' - }, - { - vulnerability: 'Session Hijacking', - prevention: 'Secure cookies, session timeout, token rotation' - }, - { - vulnerability: 'Brute Force Attacks', - prevention: 'Rate limiting, account lockout, strong passwords' - } - ] - - return ( - - - - Secure Communication Layer - - - Complete security implementation guide and examples - - - - - - This page demonstrates a comprehensive secure communication layer implementation - with practical examples and best practices for web applications. - - - - - - - Security Features Overview - - - - {securityFeatures.map((feature, index) => ( - - {feature.icon} - - - ))} - - - - - - - - - - - - - - - - - - - - - - - - - Security Implementation Guide - - - - }> - 1. HTTPS Setup - - - - Always use HTTPS in production to encrypt data in transit: - - - {`// next.config.js -const nextConfig = { - async headers() { - return [ - { - source: '/(.*)', - headers: [ - { - key: 'Strict-Transport-Security', - value: 'max-age=63072000; includeSubDomains; preload' - } - ] - } - ] - } -}`} - - - - - - }> - 2. Input Validation - - - - Always validate and sanitize user input on both client and server: - - - {`// Client-side validation -const sanitizedData = { - name: sanitizeInput.text(formData.name), - email: sanitizeInput.email(formData.email), - message: sanitizeInput.text(formData.message) -}`} - - - - - - }> - 3. Authentication - - - - Implement secure authentication with JWT tokens: - - - {`// JWT implementation -const token = jwt.sign(payload, process.env.JWT_SECRET, { - expiresIn: '1h', - issuer: 'your-app', - audience: 'your-users' -})`} - - - - - - }> - 4. Rate Limiting - - - - Protect your APIs from abuse with rate limiting: - - - {`// Rate limiting middleware -export const rateLimiter = (limit = 10) => { - return (req, res, next) => { - const ip = req.ip - const key = \`\${ip}:\${req.url}\` - // Implementation... - } -}`} - - - - - - - - Vulnerability Prevention - - - - {vulnerabilityPrevention.map((item, index) => ( - - - - - - - - {index < vulnerabilityPrevention.length - 1 && } - - ))} - - - - - Additional Resources - - - - - - - - - - - - - - - - - - - - - - Implementation Checklist - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index 0508a03..85c6519 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -1,14 +1,17 @@ +import { Inter } from 'next/font/google' import './styles/page.module.css' +const inter = Inter({ subsets: ['latin'] }) + export const metadata = { - title: 'Kelvin\'s Personal Website', - description: 'Personal website with secure communication examples', + title: 'Create Next App', + description: 'Generated by create next app', } export default function RootLayout({ children }) { return ( - {children} + {children} ) } diff --git a/src/app/security/page.js b/src/app/security/page.js deleted file mode 100644 index 321fe60..0000000 --- a/src/app/security/page.js +++ /dev/null @@ -1,89 +0,0 @@ -'use client' - -import React from 'react' - -export default function SecurityDemo() { - return ( -
-

- 🔐 Secure Communication Layer -

- -
-

✅ Implementation Complete

-

This project now includes a comprehensive secure communication layer with:

-
- -
-
-

🛡️ Input Validation & Sanitization

-

Comprehensive client and server-side validation utilities

-
- -
-

🔑 API Security

-

Secure API client with authentication and rate limiting

-
- -
-

⚠️ Error Handling

-

Secure error responses without information leakage

-
- -
-

🚫 XSS Prevention

-

Input sanitization and output encoding utilities

-
- -
-

🔒 HTTPS Implementation

-

Security headers and HTTPS enforcement

-
- -
-

👤 Authentication

-

JWT-based authentication patterns and examples

-
-
- -
-

📚 Documentation & Examples

-
    -
  • docs/SECURITY.md - Comprehensive security guide
  • -
  • src/lib/ - Reusable security utilities
  • -
  • src/app/examples/ - Secure component implementations
  • -
  • __test__/security.test.js - 31 passing security tests
  • -
-
- -
-

🧪 Testing Results

-
-
✓ All 31 security tests passing
-
✓ All existing tests still passing
-
✓ Linting clean with no errors
-
-
- -
-

🔐 Security Features Implemented

-
-
• XSS Prevention
-
• SQL Injection Protection
-
• CSRF Protection
-
• Rate Limiting
-
• Input Validation
-
• Output Encoding
-
• Secure Authentication
-
• Error Handling
-
-
- -
-

- This implementation provides a complete secure communication layer suitable for developers with limited security experience. -

-
-
- ) -} \ No newline at end of file diff --git a/src/lib/apiClient.js b/src/lib/apiClient.js deleted file mode 100644 index 6c9e73e..0000000 --- a/src/lib/apiClient.js +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Secure API Client for Frontend-Backend Communication - * - * This module provides a secure wrapper for making API requests - * with built-in security features like token management, - * request validation, and error handling. - */ - -/** - * Configuration for the API client - */ -const getApiConfig = () => { - const isDevelopment = process.env.NODE_ENV === 'development' - - return { - baseURL: process.env.NEXT_PUBLIC_API_URL || - (isDevelopment ? 'http://localhost:3001' : 'https://api.example.com'), - timeout: 30000, // 30 seconds - retryAttempts: 3, - retryDelay: 1000 // 1 second - } -} - -/** - * Secure API Client Class - */ -class SecureApiClient { - constructor() { - this.config = getApiConfig() - this.tokenKey = 'authToken' - this.refreshTokenKey = 'refreshToken' - } - - /** - * Get authentication token from storage - * @returns {string|null} - Auth token or null - */ - getAuthToken() { - if (typeof window !== 'undefined') { - return localStorage.getItem(this.tokenKey) - } - return null - } - - /** - * Set authentication token in storage - * @param {string} token - Auth token to store - */ - setAuthToken(token) { - if (typeof window !== 'undefined' && token) { - localStorage.setItem(this.tokenKey, token) - } - } - - /** - * Remove authentication token from storage - */ - removeAuthToken() { - if (typeof window !== 'undefined') { - localStorage.removeItem(this.tokenKey) - localStorage.removeItem(this.refreshTokenKey) - } - } - - /** - * Generate secure headers for API requests - * @param {Object} additionalHeaders - Additional headers to include - * @returns {Object} - Headers object - */ - generateHeaders(additionalHeaders = {}) { - const headers = { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - ...additionalHeaders - } - - // Add auth token if available - const token = this.getAuthToken() - if (token) { - headers['Authorization'] = `Bearer ${token}` - } - - return headers - } - - /** - * Sleep function for retry delays - * @param {number} ms - Milliseconds to sleep - * @returns {Promise} - Promise that resolves after delay - */ - sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) - } - - /** - * Make a secure API request with automatic retry - * @param {string} endpoint - API endpoint - * @param {Object} options - Request options - * @returns {Promise} - Promise that resolves with response data - */ - async request(endpoint, options = {}) { - const url = `${this.config.baseURL}${endpoint}` - let attempt = 0 - - while (attempt < this.config.retryAttempts) { - try { - const requestOptions = { - ...options, - headers: this.generateHeaders(options.headers), - credentials: 'include', // Include cookies for CSRF protection - } - - // Add timeout using AbortController - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), this.config.timeout) - - const response = await fetch(url, { - ...requestOptions, - signal: controller.signal - }) - - clearTimeout(timeoutId) - - // Handle different response statuses - if (response.status === 401) { - // Token might be expired, try to refresh - const refreshed = await this.refreshToken() - if (refreshed) { - // Retry the request with new token - return this.request(endpoint, options) - } else { - // Refresh failed, redirect to login - this.removeAuthToken() - throw new Error('Authentication required') - } - } - - if (response.status === 429) { - // Rate limited, wait and retry - if (attempt < this.config.retryAttempts - 1) { - await this.sleep(this.config.retryDelay * (attempt + 1)) - attempt++ - continue - } - } - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - throw new Error(errorData.message || `HTTP Error: ${response.status}`) - } - - // Return successful response - return await response.json() - - } catch (error) { - if (error.name === 'AbortError') { - throw new Error('Request timeout') - } - - // If this is the last attempt, throw the error - if (attempt === this.config.retryAttempts - 1) { - throw error - } - - // Wait before retrying - await this.sleep(this.config.retryDelay * (attempt + 1)) - attempt++ - } - } - } - - /** - * Refresh authentication token - * @returns {Promise} - True if refresh successful - */ - async refreshToken() { - try { - const refreshToken = localStorage.getItem(this.refreshTokenKey) - if (!refreshToken) return false - - const response = await fetch(`${this.config.baseURL}/auth/refresh`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ refreshToken }) - }) - - if (response.ok) { - const data = await response.json() - this.setAuthToken(data.accessToken) - return true - } - - return false - } catch (error) { - console.error('Token refresh failed:', error) - return false - } - } - - /** - * Convenient GET request - * @param {string} endpoint - API endpoint - * @param {Object} params - Query parameters - * @returns {Promise} - Promise that resolves with response data - */ - async get(endpoint, params = {}) { - const queryString = new URLSearchParams(params).toString() - const url = queryString ? `${endpoint}?${queryString}` : endpoint - - return this.request(url, { - method: 'GET' - }) - } - - /** - * Convenient POST request - * @param {string} endpoint - API endpoint - * @param {Object} data - Request body data - * @returns {Promise} - Promise that resolves with response data - */ - async post(endpoint, data = {}) { - return this.request(endpoint, { - method: 'POST', - body: JSON.stringify(data) - }) - } - - /** - * Convenient PUT request - * @param {string} endpoint - API endpoint - * @param {Object} data - Request body data - * @returns {Promise} - Promise that resolves with response data - */ - async put(endpoint, data = {}) { - return this.request(endpoint, { - method: 'PUT', - body: JSON.stringify(data) - }) - } - - /** - * Convenient DELETE request - * @param {string} endpoint - API endpoint - * @returns {Promise} - Promise that resolves with response data - */ - async delete(endpoint) { - return this.request(endpoint, { - method: 'DELETE' - }) - } - - /** - * Upload file securely - * @param {string} endpoint - API endpoint - * @param {File} file - File to upload - * @param {Object} additionalData - Additional form data - * @returns {Promise} - Promise that resolves with response data - */ - async uploadFile(endpoint, file, additionalData = {}) { - const formData = new FormData() - formData.append('file', file) - - // Add additional data - Object.entries(additionalData).forEach(([key, value]) => { - formData.append(key, value) - }) - - return this.request(endpoint, { - method: 'POST', - body: formData, - headers: { - // Don't set Content-Type for FormData, let browser set it - 'X-Requested-With': 'XMLHttpRequest' - } - }) - } -} - -// Create and export singleton instance -export const apiClient = new SecureApiClient() - -/** - * React hook for secure API requests - * @returns {Object} - Hook utilities - */ -export const useSecureRequest = () => { - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - const makeRequest = async (endpoint, options = {}) => { - setLoading(true) - setError(null) - - try { - const response = await apiClient.request(endpoint, options) - return response - } catch (err) { - setError(err.message) - throw err - } finally { - setLoading(false) - } - } - - const clearError = () => setError(null) - - return { - makeRequest, - loading, - error, - clearError - } -} - -// Import useState for the hook -import { useState } from 'react' \ No newline at end of file diff --git a/src/lib/errorHandler.js b/src/lib/errorHandler.js deleted file mode 100644 index 3933bc7..0000000 --- a/src/lib/errorHandler.js +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Error Handling Utilities for Secure Communication - * - * This module provides utilities for handling errors securely - * without exposing sensitive information to users. - */ - -/** - * Error types for different scenarios - */ -const ErrorTypes = { - VALIDATION: 'ValidationError', - AUTHENTICATION: 'AuthenticationError', - AUTHORIZATION: 'AuthorizationError', - NETWORK: 'NetworkError', - TIMEOUT: 'TimeoutError', - SERVER: 'ServerError', - RATE_LIMIT: 'RateLimitError', - NOT_FOUND: 'NotFoundError' -} - -/** - * Custom error class for API errors - */ -class ApiError extends Error { - constructor(message, type = ErrorTypes.SERVER, statusCode = 500, details = null) { - super(message) - this.name = 'ApiError' - this.type = type - this.statusCode = statusCode - this.details = details - this.timestamp = new Date().toISOString() - } -} - -/** - * Secure error handler for API routes - * @param {Error} error - The error to handle - * @param {Object} req - Request object - * @param {Object} res - Response object - */ -const handleApiError = (error, req, res) => { - const isDevelopment = process.env.NODE_ENV === 'development' - - // Log the error (in production, this would go to a logging service) - console.error('API Error:', { - message: error.message, - stack: error.stack, - url: req.url, - method: req.method, - timestamp: new Date().toISOString(), - userAgent: req.headers['user-agent'], - ip: req.ip || req.connection.remoteAddress - }) - - // Default error response - let statusCode = 500 - let message = 'Internal server error' - let details = null - - // Handle different error types - if (error instanceof ApiError) { - statusCode = error.statusCode - message = error.message - if (isDevelopment) { - details = error.details - } - } else if (error.name === 'ValidationError') { - statusCode = 400 - message = 'Invalid request data' - if (isDevelopment) { - details = error.message - } - } else if (error.name === 'UnauthorizedError' || error.message.includes('unauthorized')) { - statusCode = 401 - message = 'Authentication required' - } else if (error.name === 'ForbiddenError' || error.message.includes('forbidden')) { - statusCode = 403 - message = 'Access denied' - } else if (error.name === 'NotFoundError' || error.message.includes('not found')) { - statusCode = 404 - message = 'Resource not found' - } else if (error.name === 'TimeoutError') { - statusCode = 408 - message = 'Request timeout' - } else if (error.name === 'RateLimitError') { - statusCode = 429 - message = 'Too many requests' - } - - // Send secure error response - const errorResponse = { - error: message, - timestamp: new Date().toISOString(), - ...(isDevelopment && details && { details }) - } - - res.status(statusCode).json(errorResponse) -} - -/** - * Client-side error handler - * @param {Error} error - The error to handle - * @param {Object} context - Additional context - * @returns {Object} - Formatted error for display - */ -const handleClientError = (error, context = {}) => { - const isDevelopment = process.env.NODE_ENV === 'development' - - // Log error in development - if (isDevelopment) { - console.error('Client Error:', error, context) - } - - // Default user-friendly message - let userMessage = 'An unexpected error occurred. Please try again.' - let shouldRetry = false - - // Handle specific error types - if (error instanceof ApiError) { - switch (error.type) { - case ErrorTypes.VALIDATION: - userMessage = 'Please check your input and try again.' - break - case ErrorTypes.AUTHENTICATION: - userMessage = 'Please log in to continue.' - break - case ErrorTypes.AUTHORIZATION: - userMessage = 'You don\'t have permission to perform this action.' - break - case ErrorTypes.NETWORK: - userMessage = 'Network error. Please check your connection.' - shouldRetry = true - break - case ErrorTypes.TIMEOUT: - userMessage = 'Request timed out. Please try again.' - shouldRetry = true - break - case ErrorTypes.RATE_LIMIT: - userMessage = 'Too many requests. Please wait a moment and try again.' - shouldRetry = true - break - case ErrorTypes.NOT_FOUND: - userMessage = 'The requested resource was not found.' - break - case ErrorTypes.SERVER: - userMessage = 'An unexpected error occurred. Please try again.' - break - default: - userMessage = error.message || userMessage - } - } else if (error.name === 'AbortError') { - userMessage = 'Request was cancelled.' - } else if (error.message.includes('fetch') || error.message.includes('network')) { - userMessage = 'Network error. Please check your connection.' - shouldRetry = true - } - - return { - message: userMessage, - shouldRetry, - originalError: isDevelopment ? error : null, - context - } -} - -/** - * Validation error formatter - * @param {Array} errors - Array of validation errors - * @returns {Object} - Formatted validation error - */ -const formatValidationErrors = (errors) => { - if (!Array.isArray(errors)) { - return { message: 'Validation failed', details: [] } - } - - const details = errors.map(error => ({ - field: error.field || error.path, - message: error.message, - value: error.value - })) - - return { - message: 'Please correct the following errors:', - details - } -} - -/** - * Network error detector - * @param {Error} error - Error to check - * @returns {boolean} - True if it's a network error - */ -const isNetworkError = (error) => { - return ( - error.name === 'NetworkError' || - error.message.includes('fetch') || - error.message.includes('network') || - error.message.includes('timeout') || - error.code === 'ECONNREFUSED' - ) -} - -/** - * Async error handler for React components - * @param {Function} asyncFn - Async function to wrap - * @param {Function} onError - Error handler function - * @returns {Function} - Wrapped async function - */ -const withAsyncErrorHandler = (asyncFn, onError) => { - return async (...args) => { - try { - return await asyncFn(...args) - } catch (error) { - const handledError = handleClientError(error) - if (onError) { - onError(handledError) - } else { - console.error('Unhandled async error:', handledError) - } - throw error - } - } -} - -/** - * Retry mechanism for failed operations - * @param {Function} fn - Function to retry - * @param {Object} options - Retry options - * @returns {Promise} - Promise that resolves when successful or max retries reached - */ -const withRetry = async (fn, options = {}) => { - const { - maxRetries = 3, - delay = 1000, - exponentialBackoff = true, - shouldRetry = (error) => isNetworkError(error) - } = options - - let lastError - - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - return await fn() - } catch (error) { - lastError = error - - if (attempt === maxRetries || !shouldRetry(error)) { - throw error - } - - // Wait before retrying - const waitTime = exponentialBackoff ? delay * Math.pow(2, attempt) : delay - await new Promise(resolve => setTimeout(resolve, waitTime)) - } - } - - throw lastError -} - -module.exports = { - ErrorTypes, - ApiError, - handleApiError, - handleClientError, - formatValidationErrors, - isNetworkError, - withAsyncErrorHandler, - withRetry -} \ No newline at end of file diff --git a/src/lib/sanitize.js b/src/lib/sanitize.js deleted file mode 100644 index 51f0225..0000000 --- a/src/lib/sanitize.js +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Data Sanitization Utilities - * - * This module provides functions to sanitize and validate user input - * to prevent XSS attacks and ensure data integrity. - */ - -const sanitizeInput = { - /** - * Sanitize text input by removing HTML tags and limiting length - * @param {string} input - The input text to sanitize - * @param {number} maxLength - Maximum allowed length (default: 1000) - * @returns {string} - Sanitized text - */ - text: (input, maxLength = 1000) => { - if (typeof input !== 'string') return '' - - return input - .replace(/[<>]/g, '') // Remove potential HTML tags - .replace(/javascript:/gi, '') // Remove javascript: protocol - .replace(/on\w+\s*=/gi, '') // Remove event handlers like onerror, onload - .replace(/DROP\s+TABLE/gi, '') // Remove SQL injection patterns - .replace(/DELETE\s+FROM/gi, '') - .replace(/UNION\s+SELECT/gi, '') - .replace(/--/g, '') // Remove SQL comments - .trim() - .substring(0, maxLength) - }, - - /** - * Validate and sanitize email addresses - * @param {string} input - The email to validate - * @returns {string|null} - Sanitized email or null if invalid - */ - email: (input) => { - if (typeof input !== 'string') return null - - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - const sanitized = input.toLowerCase().trim() - - // Additional security check - if (sanitized.includes('<') || sanitized.includes('>')) { - return null - } - - return emailRegex.test(sanitized) ? sanitized : null - }, - - /** - * Sanitize URL input - * @param {string} input - The URL to sanitize - * @returns {string|null} - Sanitized URL or null if invalid - */ - url: (input) => { - if (typeof input !== 'string') return null - - try { - const url = new URL(input) - // Only allow safe protocols - if (['http:', 'https:'].includes(url.protocol)) { - // Remove trailing slash to match test expectations - return url.toString().replace(/\/$/, '') - } - return null - } catch { - return null - } - }, - - /** - * Sanitize HTML content (basic version without external dependencies) - * @param {string} input - The HTML content to sanitize - * @returns {string} - Sanitized HTML - */ - html: (input) => { - if (typeof input !== 'string') return '' - - // Basic HTML sanitization - remove dangerous tags and attributes - return input - .replace(/)<[^<]*)*<\/script>/gi, '') - .replace(/)<[^<]*)*<\/iframe>/gi, '') - .replace(/)<[^<]*)*<\/object>/gi, '') - .replace(/)<[^<]*)*<\/embed>/gi, '') - .replace(/)<[^<]*)*<\/form>/gi, '') - .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '') // Remove event handlers - .replace(/javascript:/gi, '') // Remove javascript: protocol - .trim() - }, - - /** - * Sanitize phone number input - * @param {string} input - The phone number to sanitize - * @returns {string|null} - Sanitized phone number or null if invalid - */ - phone: (input) => { - if (typeof input !== 'string') return null - - // Remove all non-numeric characters except + and - - const cleaned = input.replace(/[^\d+\-\(\)\s]/g, '') - - // Basic phone number validation (10-15 digits) - const digitCount = cleaned.replace(/\D/g, '').length - if (digitCount >= 10 && digitCount <= 15) { - return cleaned.trim() - } - - return null - } -} - -/** - * Output encoding utilities - */ -const encodeOutput = { - /** - * HTML entity encoding for safe display - * @param {string} text - Text to encode - * @returns {string} - HTML-encoded text - */ - html: (text) => { - if (typeof text !== 'string') return '' - - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - }, - - /** - * URL encoding - * @param {string} text - Text to encode - * @returns {string} - URL-encoded text - */ - url: (text) => { - if (typeof text !== 'string') return '' - return encodeURIComponent(text) - }, - - /** - * JSON encoding with XSS protection - * @param {any} data - Data to encode - * @returns {string} - Safe JSON string - */ - json: (data) => { - return JSON.stringify(data) - .replace(//g, '\\u003e') - .replace(/&/g, '\\u0026') - } -} - -/** - * Validation helpers - */ -const validate = { - /** - * Check if input is a valid email - * @param {string} email - Email to validate - * @returns {boolean} - True if valid - */ - email: (email) => { - return sanitizeInput.email(email) !== null - }, - - /** - * Check if input is a valid URL - * @param {string} url - URL to validate - * @returns {boolean} - True if valid - */ - url: (url) => { - return sanitizeInput.url(url) !== null - }, - - /** - * Check if text meets minimum requirements - * @param {string} text - Text to validate - * @param {number} minLength - Minimum length required - * @param {number} maxLength - Maximum length allowed - * @returns {boolean} - True if valid - */ - text: (text, minLength = 1, maxLength = 1000) => { - if (typeof text !== 'string') return false - const sanitized = sanitizeInput.text(text, maxLength) - return sanitized.length >= minLength && text.length <= maxLength - }, - - /** - * Check if password meets security requirements - * @param {string} password - Password to validate - * @returns {object} - Validation result with details - */ - password: (password) => { - const result = { - isValid: false, - errors: [] - } - - if (typeof password !== 'string') { - result.errors.push('Password must be a string') - return result - } - - if (password.length < 8) { - result.errors.push('Password must be at least 8 characters long') - } - - if (!/[a-z]/.test(password)) { - result.errors.push('Password must contain at least one lowercase letter') - } - - if (!/[A-Z]/.test(password)) { - result.errors.push('Password must contain at least one uppercase letter') - } - - if (!/\d/.test(password)) { - result.errors.push('Password must contain at least one number') - } - - if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { - result.errors.push('Password must contain at least one special character') - } - - result.isValid = result.errors.length === 0 - return result - } -} - -module.exports = { - sanitizeInput, - encodeOutput, - validate -} \ No newline at end of file From 179fcff2a55fada809ef33bc89454d9db72be176 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 02:55:02 +0000 Subject: [PATCH 5/5] Rename README.md to security-documentation-index.md for better clarity Co-authored-by: kmock930 <78272416+kmock930@users.noreply.github.com> --- documents/{README.md => security-documentation-index.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename documents/{README.md => security-documentation-index.md} (100%) diff --git a/documents/README.md b/documents/security-documentation-index.md similarity index 100% rename from documents/README.md rename to documents/security-documentation-index.md