From 08e408d68d0289e2365a8c65593937c12145b284 Mon Sep 17 00:00:00 2001 From: jango-blockchained Date: Tue, 4 Feb 2025 04:09:40 +0100 Subject: [PATCH] test: enhance security module with comprehensive token validation and rate limiting tests - Expanded TokenManager test suite with advanced token encryption and decryption scenarios - Added detailed rate limiting tests with IP-based tracking and window-based expiration - Improved test coverage for token validation, tampering detection, and error handling - Implemented mock configurations for faster test execution - Enhanced security test scenarios with unique IP addresses and edge case handling --- .dockerignore | 5 +- .gitignore | 2 +- README.md | 612 +++++++----------------- bunfig.toml | 14 + package.json | 6 +- src/security/__tests__/security.test.ts | 176 ++++--- tsconfig.json | 14 +- 7 files changed, 318 insertions(+), 511 deletions(-) diff --git a/.dockerignore b/.dockerignore index 77ad3c9..e4e4f93 100644 --- a/.dockerignore +++ b/.dockerignore @@ -73,4 +73,7 @@ temp/ .storage/ .cloud/ *.db -*.db-* \ No newline at end of file +*.db-* +.cursor/ +.cursor* +.cursorconfig \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8e1ab22..f44574a 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,4 @@ coverage/ .cursor/* .bun/ -.cursor/ +.cursorconfig diff --git a/README.md b/README.md index fffa6fa..c597d75 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,112 @@ -# Model Context Protocol Server for Home Assistant +# Model Context Protocol (MCP) Server for Home Assistant -The server uses the MCP protocol to share access to a local Home Assistant instance with an LLM application. - -A powerful bridge between your Home Assistant instance and Language Learning Models (LLMs), enabling natural language control and monitoring of your smart home devices through the Model Context Protocol (MCP). This server provides a comprehensive API for managing your entire Home Assistant ecosystem, from device control to system administration. +The Model Context Protocol (MCP) Server is a robust, secure, and high-performance bridge that integrates Home Assistant with Language Learning Models (LLMs), enabling natural language control and real-time monitoring of your smart home devices. Unlock advanced automation, control, and analytics for your Home Assistant ecosystem. ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![Bun](https://img.shields.io/badge/bun-%3E%3D1.0.26-black) -![Docker Compose](https://img.shields.io/badge/docker-compose-%3E%3D1.27.0-blue.svg) ![TypeScript](https://img.shields.io/badge/typescript-%5E5.0.0-blue.svg) ![Test Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen.svg) -## Features +## Table of Contents -- ๐ŸŽฎ **Device Control**: Control any Home Assistant device through natural language -- ๐Ÿ”„ **Real-time Updates**: Get instant updates through Server-Sent Events (SSE) -- ๐Ÿค– **Automation Management**: Create, update, and manage automations -- ๐Ÿ“Š **State Monitoring**: Track and query device states -- ๐Ÿ” **Secure**: Token-based authentication and rate limiting -- ๐Ÿ“ฑ **Mobile Ready**: Works with any HTTP-capable client +- [Overview](#overview) +- [Key Features](#key-features) +- [Architecture & Design](#architecture--design) +- [Installation](#installation) + - [Basic Setup](#basic-setup) + - [Docker Setup (Recommended)](#docker-setup-recommended) +- [Usage](#usage) +- [API & Documentation](#api--documentation) +- [Development](#development) +- [Roadmap & Future Plans](#roadmap--future-plans) +- [Community & Support](#community--support) +- [Contributing](#contributing) +- [Troubleshooting & FAQ](#troubleshooting--faq) +- [License](#license) -## Real-time Updates with SSE +## Overview -The server includes a powerful Server-Sent Events (SSE) system that provides real-time updates from your Home Assistant instance. This allows you to: +The MCP Server bridges Home Assistant with advanced LLM integrations to deliver intuitive control, automation, and state monitoring. Leveraging a high-performance runtime and real-time communication protocols, MCP offers a seamless experience for managing your smart home. -- ๐Ÿ”„ Get instant state changes for any device -- ๐Ÿ“ก Monitor automation triggers and executions -- ๐ŸŽฏ Subscribe to specific domains or entities -- ๐Ÿ“Š Track service calls and script executions +## Key Features -### Quick SSE Example +### Device Control & Monitoring +- **Smart Device Control:** Manage lights, climate, covers, switches, sensors, media players, fans, locks, vacuums, and cameras using natural language commands. +- **Real-time Updates:** Receive instant notifications and updates via Server-Sent Events (SSE). + +### System & Automation Management +- **Automation Engine:** Create, modify, and trigger custom automation rules with ease. +- **Add-on & Package Management:** Integrates with HACS for deploying custom integrations, themes, scripts, and applications. +- **Robust System Management:** Features advanced state monitoring, error handling, and security safeguards. + +## Architecture & Design + +The MCP Server is built with scalability, resilience, and security in mind: + +- **High-Performance Runtime:** Powered by Bun for fast startup, efficient memory utilization, and native TypeScript support. +- **Real-time Communication:** Employs Server-Sent Events (SSE) for continuous, real-time data updates. +- **Modular & Extensible:** Designed to support plugins, add-ons, and custom automation scripts, allowing for easy expansion. +- **Secure API Integration:** Implements token-based authentication, rate limiting, and adherence to best security practices. + +_For a deeper dive into the system architecture, please refer to our [Architecture Documentation](docs/ARCHITECTURE.md) (if available)._ + +## Installation + +### Basic Setup + +1. **Install Bun:** If Bun is not installed: + ```bash + curl -fsSL https://bun.sh/install | bash + ``` + +2. **Clone the Repository:** + ```bash + git clone https://github.com/jango-blockchained/homeassistant-mcp.git + cd homeassistant-mcp + ``` + +3. **Install Dependencies:** + ```bash + bun install + ``` + +4. **Build the Project:** + ```bash + bun run build + ``` + +### Docker Setup (Recommended) + +1. **Clone the Repository:** + ```bash + git clone https://github.com/jango-blockchained/homeassistant-mcp.git + cd homeassistant-mcp + ``` + +2. **Configure Environment:** + ```bash + cp .env.example .env + ``` + Customize the `.env` file with your Home Assistant configuration. + +3. **Deploy with Docker Compose:** + ```bash + docker compose up -d + ``` + - View logs: `docker compose logs -f` + - Stop the server: `docker compose down` + +4. **Update the Application:** + ```bash + git pull && docker compose up -d --build + ``` + +## Usage + +Once the server is running, open your browser at [http://localhost:3000](http://localhost:3000). For real-time device updates, integrate the SSE endpoint in your application: ```javascript -const eventSource = new EventSource( - 'http://localhost:3000/subscribe_events?token=YOUR_TOKEN&domain=light' -); +const eventSource = new EventSource('http://localhost:3000/subscribe_events?token=YOUR_TOKEN&domain=light'); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); @@ -41,468 +114,107 @@ eventSource.onmessage = (event) => { }; ``` -See [SSE_API.md](docs/SSE_API.md) for complete documentation of the SSE system. +## API & Documentation -## Table of Contents +Access comprehensive API details and guides in the docs directory: -- [Key Features](#key-features) -- [Prerequisites](#prerequisites) -- [Installation](#installation) - - [Basic Setup](#basic-setup) - - [Docker Setup (Recommended)](#docker-setup-recommended) -- [Configuration](#configuration) -- [Development](#development) -- [API Reference](#api-reference) -- [OpenAI Integration](#openai-integration) -- [Natural Language Integration](#natural-language-integration) -- [Troubleshooting](#troubleshooting) -- [Project Status](#project-status) -- [Contributing](#contributing) -- [Resources](#resources) -- [License](#license) - -## Key Features - -### Core Functionality ๐ŸŽฎ -- **Smart Device Control** - - ๐Ÿ’ก **Lights**: Brightness, color temperature, RGB color - - ๐ŸŒก๏ธ **Climate**: Temperature, HVAC modes, fan modes, humidity - - ๐Ÿšช **Covers**: Position and tilt control - - ๐Ÿ”Œ **Switches**: On/off control - - ๐Ÿšจ **Sensors & Contacts**: State monitoring - - ๐ŸŽต **Media Players**: Playback control, volume, source selection - - ๐ŸŒช๏ธ **Fans**: Speed, oscillation, direction - - ๐Ÿ”’ **Locks**: Lock/unlock control - - ๐Ÿงน **Vacuums**: Start, stop, return to base - - ๐Ÿ“น **Cameras**: Motion detection, snapshots - -### System Management ๐Ÿ› ๏ธ -- **Add-on Management** - - Browse available add-ons - - Install/uninstall add-ons - - Start/stop/restart add-ons - - Version management - - Configuration access - -- **Package Management (HACS)** - - Integration with Home Assistant Community Store - - Multiple package types support: - - Custom integrations - - Frontend themes - - Python scripts - - AppDaemon apps - - NetDaemon apps - - Version control and updates - - Repository management - -- **Automation Management** - - Create and edit automations - - Advanced configuration options: - - Multiple trigger types - - Complex conditions - - Action sequences - - Execution modes - - Duplicate and modify existing automations - - Enable/disable automation rules - - Trigger automation manually - -### Architecture Features ๐Ÿ—๏ธ -- **Intelligent Organization** - - Area and floor-based device grouping - - State monitoring and querying - - Smart context awareness - - Historical data access - -- **Robust Architecture** - - Comprehensive error handling - - State validation - - Secure API integration - - TypeScript type safety - - Extensive test coverage - -## Prerequisites - -- **Bun** 1.0.26 or higher (Required for optimal performance) -- **Docker Compose** for containerization -- Running **Home Assistant** instance -- Home Assistant long-lived access token ([How to get token](https://community.home-assistant.io/t/how-to-get-long-lived-access-token/162159)) -- **HACS** installed for package management features -- **Supervisor** access for add-on management - -## Installation - -### Basic Setup - -```bash -# Install Bun (if not already installed) -curl -fsSL https://bun.sh/install | bash - -# Clone the repository -git clone https://github.com/jango-blockchained/homeassistant-mcp.git -cd homeassistant-mcp - -# Install dependencies -bun install - -# Build the project -bun run build -``` - -### Docker Setup (Recommended) - -The project includes Docker support with Bun for optimal performance and consistent environments across different platforms. - -1. **Clone the repository:** - ```bash - git clone https://github.com/jango-blockchained/homeassistant-mcp.git - cd homeassistant-mcp - ``` - -2. **Configure environment:** - ```bash - cp .env.example .env - ``` - Edit the `.env` file with your Home Assistant configuration: - ```env - # Home Assistant Configuration - HASS_HOST=http://homeassistant.local:8123 - HASS_TOKEN=your_home_assistant_token - HASS_SOCKET_URL=ws://homeassistant.local:8123/api/websocket - - # Server Configuration - PORT=3000 - BUN_ENV=production - DEBUG=false - ``` - -3. **Build and run with Docker Compose:** - ```bash - # Build and start the containers - docker compose up -d - - # View logs - docker compose logs -f - - # Stop the service - docker compose down - ``` - -4. **Verify the installation:** - The server should now be running at `http://localhost:3000`. You can check the health endpoint at `http://localhost:3000/health`. - -5. **Update the application:** - ```bash - # Pull the latest changes - git pull - - # Rebuild and restart the containers - docker compose up -d --build - ``` - -#### Docker Configuration - -The Docker setup includes: -- Multi-stage build using Bun for optimal performance -- Health checks for container monitoring -- Volume mounting for environment configuration -- Automatic container restart on failure -- Exposed port 3000 for API access - -#### Docker Compose Environment Variables - -All environment variables can be configured in the `.env` file. The following variables are supported: -- `HASS_HOST`: Your Home Assistant instance URL -- `HASS_TOKEN`: Long-lived access token for Home Assistant -- `HASS_SOCKET_URL`: WebSocket URL for Home Assistant -- `PORT`: Server port (default: 3000) -- `BUN_ENV`: Environment (production/development) -- `DEBUG`: Enable debug mode (true/false) - -## Configuration - -### Environment Variables - -```env -# Home Assistant Configuration -HASS_HOST=http://homeassistant.local:8123 # Your Home Assistant instance URL -HASS_TOKEN=your_home_assistant_token # Long-lived access token -HASS_SOCKET_URL=ws://homeassistant.local:8123/api/websocket # WebSocket URL - -# Server Configuration -PORT=3000 # Server port (default: 3000) -BUN_ENV=production # Environment (production/development) -DEBUG=false # Enable debug mode - -# Test Configuration -TEST_HASS_HOST=http://localhost:8123 # Test instance URL -TEST_HASS_TOKEN=test_token # Test token -``` - -### Configuration Files - -1. **Development**: Copy `.env.example` to `.env.development` -2. **Production**: Copy `.env.example` to `.env.production` -3. **Testing**: Copy `.env.example` to `.env.test` +- **API Reference:** [API Documentation](docs/API.md) +- **SSE Documentation:** [SSE API](docs/SSE_API.md) +- **Troubleshooting Guide:** [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) +- **Architecture Details:** [Architecture Documentation](docs/ARCHITECTURE.md) _(if available)_ ## Development +### Running in Development Mode + ```bash -# Development mode with hot reload and TypeScript watch bun run dev +``` -# Run tests with Bun's built-in test runner -bun test +### Running Tests -# Run tests with coverage -bun test --coverage +- Execute all tests: + ```bash + bun test + ``` -# Build the project +- Run tests with coverage: + ```bash + bun test --coverage + ``` + +### Production Build & Start + +```bash bun run build - -# Start the production server bun start ``` -## API Reference +## Roadmap & Future Plans -For detailed API documentation, please refer to: -- [API Documentation](docs/API.md) - Complete API reference -- [SSE API Documentation](docs/SSE_API.md) - Server-Sent Events documentation +The MCP Server is under active development and improvement. Planned enhancements include: -## OpenAI Integration +- **Advanced Automation Capabilities:** Introducing more complex automation rules and conditional logic. +- **Enhanced Security Features:** Additional authentication layers, encryption enhancements, and security monitoring tools. +- **User Interface Improvements:** Development of a more intuitive web dashboard for easier device management. +- **Expanded Integrations:** Support for a wider array of smart home devices and third-party services. +- **Performance Optimizations:** Continued efforts to reduce latency and improve resource efficiency. -The server includes powerful AI analysis capabilities powered by OpenAI's GPT-4 model. This feature provides intelligent analysis of your Home Assistant setup through two main modes: +_For additional details, check out our [Roadmap](docs/ROADMAP.md) (if available)._ -### 1. Standard Analysis +## Community & Support -Performs a comprehensive system analysis including: -- System Overview -- Performance Analysis -- Security Assessment -- Optimization Recommendations -- Maintenance Tasks +Join our community to stay updated, share ideas, and get help: -```bash -# Run standard analysis -bun run test:openai -# Select option 1 when prompted -``` - -### 2. Custom Prompt Analysis - -Allows you to ask specific questions about your Home Assistant setup. The analysis can include: -- Device States -- Configuration Details -- Active Devices -- Device Attributes (brightness, temperature, etc.) - -```bash -# Run custom analysis -bun run test:openai -# Select option 2 when prompted -``` - -### Configuration - -To use the OpenAI integration, you need to set up your OpenAI API key in the `.env` file: -```env -OPENAI_API_KEY=your_openai_api_key -``` - -## Troubleshooting - -### Common Issues - -1. **Connection Issues** - - Verify Home Assistant is running - - Check `HASS_HOST` accessibility - - Validate token permissions - - Ensure WebSocket connection for real-time updates - -2. **Add-on Management Issues** - - Verify Supervisor access - - Check add-on compatibility - - Validate system resources - -3. **HACS Integration Issues** - - Verify HACS installation - - Check HACS integration status - - Validate repository access - -4. **Automation Issues** - - Verify entity availability - - Check trigger conditions - - Validate service calls - - Monitor execution logs - -## Project Status - -### Current Status ๐Ÿš€ - -The project is actively maintained and under continuous development. Recent updates include: - -- โœ… Enhanced Bun runtime optimization -- โœ… Improved WebSocket connection management -- โœ… Advanced type safety and error handling -- โœ… Comprehensive test coverage with Bun's test runner -- โœ… Real-time event handling optimization -- โœ… Enhanced Docker integration with Bun -- โœ… Improved development workflow -- โœ… Advanced security features - -### Upcoming Features ๐Ÿ”œ - -- ๐Ÿ“ฑ Mobile-first UI improvements with modern frameworks -- ๐Ÿ” Advanced security features and authentication methods -- ๐Ÿค– AI-powered automation capabilities -- ๐Ÿ“Š Real-time analytics and reporting dashboard -- ๐ŸŒ Multi-instance support with load balancing -- ๐Ÿ”„ Enhanced state synchronization -- ๐ŸŽฏ Custom automation templates -- ๐Ÿ” Advanced entity search and filtering -- ๐Ÿ“ˆ Performance monitoring tools -- ๐Ÿ› ๏ธ Enhanced debugging capabilities - -### Performance Optimizations - -- โšก Bun's high-performance JavaScript runtime -- ๐Ÿš€ Optimized WebSocket connections -- ๐Ÿ“ฆ Efficient package management with Bun -- ๐Ÿ”„ Enhanced state management -- ๐ŸŽฏ Targeted event subscriptions -- ๐Ÿ“Š Memory usage optimizations -- ๐Ÿ” Query optimization -- ๐Ÿ› ๏ธ Development tools integration - -### Version History - -- **v0.2.0** (Current) - - Enhanced Bun runtime implementation - - Advanced WebSocket management - - Improved error handling and recovery - - Comprehensive test suite with Bun's test runner - - Real-time performance optimizations - - Enhanced security features - - Advanced automation capabilities - - Improved documentation - -- **v0.1.0** - - Initial release with Bun support - - Basic Home Assistant integration - - SSE implementation - - Device control capabilities - - Basic automation support - -## Performance Benefits with Bun - -This project leverages Bun's high-performance runtime for: - -- ๐Ÿš€ **Ultra-Fast Execution**: Bun's JavaScript runtime offers superior performance -- โšก **Quick Development**: Hot reload and TypeScript support out of the box -- ๐Ÿ“ฆ **Efficient Package Management**: Lightning-fast installation and dependency resolution -- ๐Ÿงช **Integrated Testing**: Built-in test runner with superior performance -- ๐Ÿ”„ **Native TypeScript Support**: Zero-config TypeScript support -- ๐ŸŽฏ **Optimized Build Process**: Faster builds and smaller output -- ๐Ÿ› ๏ธ **Development Tools**: Enhanced debugging and profiling -- ๐Ÿ“Š **Performance Monitoring**: Built-in metrics and diagnostics - -## Development Workflow - -### Testing with Bun - -```bash -# Run all tests -bun test - -# Run tests in watch mode -bun test --watch - -# Run tests with coverage -bun test --coverage - -# Run specific test file -bun test path/to/test.test.ts -``` - -### Building with Bun - -```bash -# Build the project -bun run build - -# Clean and rebuild -bun run clean && bun run build -``` - -### Type Checking - -```bash -# Check types -bun run types:check - -# Install type definitions -bun run types:install -``` - -### Linting - -```bash -# Run ESLint -bun run lint - -# Fix linting issues -bun run lint:fix -``` +- **GitHub Issues:** Report bugs or suggest features on our [GitHub Issues Page](https://github.com/jango-blockchained/homeassistant-mcp/issues). +- **Discussion Forums:** Connect with other users and contributors in our community forums. +- **Chat Platforms:** Join our real-time discussions on [Discord](#) or [Slack](#). ## Contributing -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Install dependencies (`bun install`) -4. Make your changes -5. Run tests (`bun test`) -6. Commit your changes (`git commit -m 'Add amazing feature'`) -7. Push to the branch (`git push origin feature/amazing-feature`) -8. Open a Pull Request +We welcome your contributions! To get started: -## Advanced Features +1. Fork the repository. +2. Create your feature branch: + ```bash + git checkout -b feature/your-feature-name + ``` +3. Install dependencies: + ```bash + bun install + ``` +4. Make your changes and run tests: + ```bash + bun test + ``` +5. Commit and push your changes, then open a Pull Request. -### Real-time Monitoring +_For detailed guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md)._ -- ๐Ÿ“Š Live device state tracking -- ๐Ÿ”„ Instant state updates -- ๐Ÿ“ˆ Performance metrics -- ๐ŸŽฏ Event filtering -- ๐Ÿ” Advanced search capabilities +## Troubleshooting & FAQ -### Security Features +### Common Issues -- ๐Ÿ” Token-based authentication -- ๐Ÿ›ก๏ธ Rate limiting -- ๐Ÿ”’ SSL/TLS support -- ๐Ÿ‘ค User management -- ๐Ÿ“ Audit logging +- **Connection Problems:** Ensure that your `HASS_HOST`, authentication token, and WebSocket URL are correctly configured. +- **Docker Deployment:** Confirm that Docker is running and that your `.env` file contains the correct settings. +- **Automation Errors:** Verify entity availability and review your automation configurations for potential issues. -### Automation Capabilities +_For more troubleshooting details, refer to [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)._ -- ๐Ÿค– Complex automation rules -- ๐Ÿ“… Scheduled tasks -- ๐ŸŽฏ Conditional triggers -- ๐Ÿ”„ State-based actions -- ๐Ÿ“Š Automation analytics +### Frequently Asked Questions -### Development Tools +**Q: What platforms does MCP Server support?** -- ๐Ÿ› ๏ธ Built-in debugging -- ๐Ÿ“Š Performance profiling -- ๐Ÿ” Code analysis -- ๐Ÿงช Test coverage reports -- ๐Ÿ“ Documentation generation +A: MCP Server runs on Linux, macOS, and Windows (Docker is recommended for Windows environments). -## Author +**Q: How do I report a bug or request a feature?** -This project was initiated by [Tevon Strand-Brown](https://github.com/tevonsb) and is mainly developed by [Jango Blockchain](https://github.com/jango-blockchained). +A: Please use our [GitHub Issues Page](https://github.com/jango-blockchained/homeassistant-mcp/issues) to report bugs or request new features. + +**Q: Can I contribute to the project?** + +A: Absolutely! We welcome contributions from the community. See the [Contributing](#contributing) section for more details. ## License -MIT License - See [LICENSE](LICENSE) file +This project is licensed under the MIT License. See [LICENSE](LICENSE) for the full license text. diff --git a/bunfig.toml b/bunfig.toml index f48c77a..c353a9c 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,8 +1,22 @@ [test] preload = ["./src/__tests__/setup.ts"] coverage = true +coverageThreshold = { + statements = 80, + branches = 70, + functions = 80, + lines = 80 +} timeout = 30000 testMatch = ["**/__tests__/**/*.test.ts"] +testPathIgnorePatterns = ["/node_modules/", "/dist/"] +collectCoverageFrom = [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/*.test.ts", + "!src/types/**/*", + "!src/mocks/**/*" +] [build] target = "node" diff --git a/package.json b/package.json index 4b1ffd1..7c69371 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,15 @@ "test": "bun test", "test:watch": "bun test --watch", "test:coverage": "bun test --coverage", + "test:ci": "bun test --coverage --bail", + "test:update": "bun test --update-snapshots", + "test:clear": "bun test --clear-cache", + "test:staged": "bun test --findRelatedTests", "lint": "eslint . --ext .ts", "format": "prettier --write \"src/**/*.ts\"", "prepare": "husky install", "profile": "bun --inspect src/index.ts", - "clean": "rm -rf dist .bun", + "clean": "rm -rf dist .bun coverage", "typecheck": "bun x tsc --noEmit", "preinstall": "bun install --frozen-lockfile" }, diff --git a/src/security/__tests__/security.test.ts b/src/security/__tests__/security.test.ts index 956e132..2a97b42 100644 --- a/src/security/__tests__/security.test.ts +++ b/src/security/__tests__/security.test.ts @@ -1,37 +1,50 @@ import { describe, expect, it, beforeEach } from "bun:test"; -import { TokenManager } from "../index.js"; +import { TokenManager } from "../index"; import jwt from "jsonwebtoken"; -const validSecret = "test-secret-key-that-is-at-least-32-chars"; -const validToken = "valid-token-that-is-at-least-32-characters-long"; +const validSecret = "test_secret_that_is_at_least_32_chars_long"; const testIp = "127.0.0.1"; +// Mock the rate limit window for faster tests +const MOCK_RATE_LIMIT_WINDOW = 100; // 100ms instead of 15 minutes + describe("Security Module", () => { beforeEach(() => { process.env.JWT_SECRET = validSecret; - // Clear any existing rate limit data + // Reset failed attempts map (TokenManager as any).failedAttempts = new Map(); + // Mock the security config + (TokenManager as any).SECURITY_CONFIG = { + ...(TokenManager as any).SECURITY_CONFIG, + LOCKOUT_DURATION: MOCK_RATE_LIMIT_WINDOW, + MAX_FAILED_ATTEMPTS: 5, + MAX_TOKEN_AGE: 30 * 24 * 60 * 60 * 1000 // 30 days + }; }); describe("TokenManager", () => { it("should encrypt and decrypt tokens", () => { - const encrypted = TokenManager.encryptToken(validToken, validSecret); - expect(encrypted).toBeDefined(); - expect(typeof encrypted).toBe("string"); - expect(encrypted === validToken).toBe(false); + const originalToken = "test-token"; + const encryptedToken = TokenManager.encryptToken(originalToken, validSecret); + expect(encryptedToken).toBeDefined(); + expect(encryptedToken.includes(originalToken)).toBe(false); - const decrypted = TokenManager.decryptToken(encrypted, validSecret); - expect(decrypted).toBe(validToken); + const decryptedToken = TokenManager.decryptToken(encryptedToken, validSecret); + expect(decryptedToken).toBeDefined(); + expect(decryptedToken).toBe(originalToken); }); it("should validate tokens correctly", () => { const payload = { userId: "123", role: "user" }; const token = jwt.sign(payload, validSecret, { expiresIn: "1h" }); - expect(token).toBeDefined(); - const result = TokenManager.validateToken(token, testIp); expect(result.valid).toBe(true); expect(result.error).toBeUndefined(); + + // Verify payload separately + const decoded = jwt.verify(token, validSecret) as typeof payload; + expect(decoded.userId).toBe(payload.userId); + expect(decoded.role).toBe(payload.role); }); it("should handle empty tokens", () => { @@ -53,66 +66,119 @@ describe("Security Module", () => { expect(result.valid).toBe(false); expect(result.error).toBe("Token has expired"); }); - }); - describe("Request Validation", () => { - it("should validate requests with valid tokens", () => { + it("should handle token tampering", () => { + // Use a different IP for this test to avoid rate limiting + const uniqueIp = "192.168.1.1"; const payload = { userId: "123", role: "user" }; - const token = jwt.sign(payload, validSecret, { expiresIn: "1h" }); - const result = TokenManager.validateToken(token, testIp); - expect(result.valid).toBe(true); - expect(result.error).toBeUndefined(); - }); + const token = jwt.sign(payload, validSecret); + const tamperedToken = token.slice(0, -5) + "xxxxx"; // Tamper with signature - it("should reject invalid tokens", () => { - const result = TokenManager.validateToken("invalid-token", testIp); + const result = TokenManager.validateToken(tamperedToken, uniqueIp); expect(result.valid).toBe(false); - expect(result.error).toBe("Token length below minimum requirement"); + expect(result.error).toBe("Invalid token signature"); }); }); - describe("Error Handling", () => { - it("should handle missing JWT secret", () => { - delete process.env.JWT_SECRET; - const payload = { userId: "123", role: "user" }; - const result = TokenManager.validateToken(jwt.sign(payload, "some-secret"), testIp); - expect(result.valid).toBe(false); - expect(result.error).toBe("JWT secret not configured"); + describe("Token Encryption", () => { + it("should use different IVs for same token", () => { + const token = "test-token"; + const encrypted1 = TokenManager.encryptToken(token, validSecret); + const encrypted2 = TokenManager.encryptToken(token, validSecret); + expect(encrypted1).toBeDefined(); + expect(encrypted2).toBeDefined(); + expect(encrypted1 === encrypted2).toBe(false); }); - it("should handle invalid token format", () => { - const result = TokenManager.validateToken("not-a-jwt-token", testIp); - expect(result.valid).toBe(false); - expect(result.error).toBe("Token length below minimum requirement"); + it("should handle large tokens", () => { + const largeToken = "x".repeat(1024); + const encrypted = TokenManager.encryptToken(largeToken, validSecret); + const decrypted = TokenManager.decryptToken(encrypted, validSecret); + expect(decrypted).toBe(largeToken); }); - it("should handle encryption errors", () => { - expect(() => TokenManager.encryptToken("", validSecret)).toThrow("Invalid token"); - expect(() => TokenManager.encryptToken(validToken, "short-key")).toThrow("Invalid encryption key"); - }); - - it("should handle decryption errors", () => { - expect(() => TokenManager.decryptToken("invalid:format", validSecret)).toThrow(); - expect(() => TokenManager.decryptToken("aes-256-gcm:invalid:base64:data", validSecret)).toThrow(); + it("should fail gracefully with invalid encrypted data", () => { + expect(() => TokenManager.decryptToken("invalid-encrypted-data", validSecret)) + .toThrow("Invalid encrypted token"); }); }); describe("Rate Limiting", () => { - it("should implement rate limiting for failed attempts", () => { - // Create an invalid token that's long enough to pass length check - const invalidToken = "x".repeat(64); // Long enough to pass MIN_TOKEN_LENGTH check + beforeEach(() => { + // Reset failed attempts before each test + (TokenManager as any).failedAttempts = new Map(); + }); - // First attempt should fail with token validation error and record the attempt - const firstResult = TokenManager.validateToken(invalidToken, testIp); - expect(firstResult.valid).toBe(false); - expect(firstResult.error).toBe("Too many failed attempts. Please try again later."); + it("should track failed attempts by IP", () => { + const invalidToken = "x".repeat(64); + const ip1 = "1.1.1.1"; + const ip2 = "2.2.2.2"; - // Verify that even a valid token is blocked during rate limiting - const validPayload = { userId: "123", role: "user" }; - const validToken = jwt.sign(validPayload, validSecret, { expiresIn: "1h" }); - const validResult = TokenManager.validateToken(validToken, testIp); - expect(validResult.valid).toBe(false); - expect(validResult.error).toBe("Too many failed attempts. Please try again later."); + // Make a single failed attempt for each IP + TokenManager.validateToken(invalidToken, ip1); + TokenManager.validateToken(invalidToken, ip2); + + const attempts = (TokenManager as any).failedAttempts; + expect(attempts.has(ip1)).toBe(true); + expect(attempts.has(ip2)).toBe(true); + expect(attempts.get(ip1).count).toBe(1); + expect(attempts.get(ip2).count).toBe(1); + expect(attempts.get(ip1).lastAttempt).toBeGreaterThan(0); + expect(attempts.get(ip2).lastAttempt).toBeGreaterThan(0); + }); + + it("should handle rate limiting for failed attempts", async () => { + const invalidToken = "x".repeat(64); + const uniqueIp = "10.0.0.1"; + + // Make multiple failed attempts + for (let i = 0; i < 5; i++) { + const result = TokenManager.validateToken(invalidToken, uniqueIp); + expect(result.valid).toBe(false); + if (i < 4) { + expect(result.error).toBe("Invalid token signature"); + } else { + expect(result.error).toBe("Too many failed attempts. Please try again later."); + } + } + + // Next attempt should be rate limited + const result = TokenManager.validateToken(invalidToken, uniqueIp); + expect(result.valid).toBe(false); + expect(result.error).toBe("Too many failed attempts. Please try again later."); + + // Wait for rate limit window to expire + await new Promise(resolve => setTimeout(resolve, MOCK_RATE_LIMIT_WINDOW + 50)); + + // After window expires, should get normal error again + const finalResult = TokenManager.validateToken(invalidToken, uniqueIp); + expect(finalResult.valid).toBe(false); + expect(finalResult.error).toBe("Invalid token signature"); + }); + + it("should reset rate limits after window expires", async () => { + const invalidToken = "x".repeat(64); + const uniqueIp = "172.16.0.1"; + + // Make some failed attempts + for (let i = 0; i < 3; i++) { + const result = TokenManager.validateToken(invalidToken, uniqueIp); + expect(result.valid).toBe(false); + expect(result.error).toBe("Invalid token signature"); + } + + // Wait for rate limit window to expire + await new Promise(resolve => setTimeout(resolve, MOCK_RATE_LIMIT_WINDOW + 50)); + + // After window expires, should get normal error + const result = TokenManager.validateToken(invalidToken, uniqueIp); + expect(result.valid).toBe(false); + expect(result.error).toBe("Invalid token signature"); + + // Should have one new attempt recorded + const attempts = (TokenManager as any).failedAttempts; + expect(attempts.has(uniqueIp)).toBe(true); + expect(attempts.get(uniqueIp).count).toBe(1); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index d25259b..ac3cbe5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,16 +21,23 @@ "@types/node", "@types/ws", "@types/jsonwebtoken", - "@types/sanitize-html" + "@types/sanitize-html", + "@types/jest" ], "baseUrl": ".", "paths": { "@/*": [ "./src/*" + ], + "@test/*": [ + "__tests__/*" ] }, "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true }, "include": [ "src/**/*", @@ -39,6 +46,7 @@ ], "exclude": [ "node_modules", - "dist" + "dist", + "coverage" ] } \ No newline at end of file