feat(security): Implement comprehensive security fixes and enhancements
- Added Security Handoff Report detailing resolved issues and current configurations. - Implemented CSRF protection using Flask-WTF, including token management in templates and JavaScript. - Created standardized error handling module to log detailed errors while returning generic messages. - Developed phone number validation module to ensure compliance with E.164 standards. - Added CSV injection prevention measures during file uploads. - Updated installation guide for clarity and completeness. - Created script to update API keys from Android device, ensuring secure key management. - Enhanced Docker security configurations to remove privileged mode and host networking. - Implemented logging and sanitization for error messages to prevent information disclosure. - Added verification script to test security setup flow and validate configurations.
This commit is contained in:
parent
498e1ab6ca
commit
30c2cfeba5
865
README.md
865
README.md
@ -1,818 +1,115 @@
|
|||||||
# SMS Campaign Manager 📱
|
# SMS Campaign Manager
|
||||||
*Secure, Dockerized SMS automation system with Android integration*
|
|
||||||
|
|
||||||
## 🔐 Now with User Authentication!
|
Dockerized SMS automation system with Android device integration via Termux API.
|
||||||
|
|
||||||
**No more ModHeader!** Access the web dashboard with username and password.
|
## Features
|
||||||
|
|
||||||
- ✅ **User Login** - Simple username/password authentication
|
- **Campaign Management**: Create, schedule, and monitor SMS campaigns
|
||||||
- ✅ **API Keys** - Secure API access for scripts and automation
|
- **Contact Import**: Upload contacts from CSV files with template variables
|
||||||
- ✅ **24-Hour Sessions** - Stay logged in without re-entering credentials
|
- **Android Integration**: Send SMS through Termux API with ADB fallback
|
||||||
- ✅ **Role-Based Access** - Admin and User roles with different permissions
|
- **User Authentication**: Web login and API key access
|
||||||
|
- **Real-time Analytics**: Track delivery status and responses
|
||||||
|
|
||||||
**Quick Start**: Open `http://localhost:5000/` → Login with `admin` / `@thebunker`
|
## Architecture
|
||||||
|
|
||||||
[](./docker/docker-compose.yml)
|
```
|
||||||
[](./src/requirements.txt)
|
Ubuntu Server (Docker) Android Device (Termux)
|
||||||
[](./config/.env)
|
┌─────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ Flask Web App │◄──────►│ Termux SMS API │
|
||||||
|
│ Port 5000 │ │ Port 5001 │
|
||||||
|
└─────────────────────┘ └─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### One-Command Deployment (Recommended - Tailscale)
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Configure environment
|
# 1. Configure environment
|
||||||
# Edit .env with your Android device's Tailscale IP
|
cp .env.example .env
|
||||||
nano .env
|
nano .env # Set your Android device IP and credentials
|
||||||
|
|
||||||
# 2. Deploy to Android using automated script
|
# 2. Generate API keys
|
||||||
|
python3 src/core/auth.py
|
||||||
|
|
||||||
|
# 3. Deploy to Android
|
||||||
./scripts/deploy-android.sh
|
./scripts/deploy-android.sh
|
||||||
|
|
||||||
# 3. Start Ubuntu homelab
|
# 4. Start the application
|
||||||
./run.sh start
|
|
||||||
# OR with docker-compose
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 4. Verify everything is running
|
# 5. Open web dashboard
|
||||||
curl http://YOUR_TAILSCALE_IP:5001/health # Android SMS API
|
open http://localhost:5000
|
||||||
curl http://YOUR_TAILSCALE_IP:5000/ # Android Monitor
|
|
||||||
curl http://localhost:5000/ # Ubuntu Dashboard
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Prerequisites
|
## Requirements
|
||||||
- Docker & Docker Compose installed
|
|
||||||
- **Tailscale** installed on both Ubuntu homelab and Android device (Recommended)
|
|
||||||
- Or local network access to Android device
|
|
||||||
- Android device with Termux and Termux:API installed
|
|
||||||
- SSH access to Android device (port 8022)
|
|
||||||
|
|
||||||
### 1. Clone and Configure
|
**Ubuntu Server**
|
||||||
```bash
|
- Docker and Docker Compose
|
||||||
# Clone or navigate to project directory
|
- Tailscale (recommended) or local network access
|
||||||
cd campaign_connector
|
|
||||||
|
|
||||||
# Create environment configuration
|
**Android Device**
|
||||||
cat > .env << 'EOF'
|
- Termux (from F-Droid)
|
||||||
# Android Device Configuration (use Tailscale IP for best reliability)
|
- Termux:API (from F-Droid)
|
||||||
PHONE_IP=YOUR_TAILSCALE_IP_HERE
|
- SSH server enabled
|
||||||
ADB_PORT=5555
|
|
||||||
TERMUX_API_PORT=5001
|
|
||||||
|
|
||||||
# Flask Application
|
## Documentation
|
||||||
FLASK_ENV=production
|
|
||||||
SECRET_KEY=your-secure-secret-key-here
|
|
||||||
DEFAULT_DELAY_SECONDS=3
|
|
||||||
|
|
||||||
# SMS Retry Configuration
|
Full documentation is available in the [docs/](docs/) directory:
|
||||||
SMS_MAX_RETRIES=3
|
|
||||||
SMS_RETRY_BASE_DELAY=2
|
|
||||||
SMS_MAX_RETRY_DELAY=8
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Edit with your actual Tailscale IP
|
### Getting Started
|
||||||
nano .env
|
- [Installation Guide](docs/setup/installation.md) - Complete setup instructions
|
||||||
|
- [Quick Start](docs/setup/quick-start.md) - Rapid deployment
|
||||||
|
- [Authentication](docs/setup/authentication.md) - User login configuration
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- [Security Setup](docs/security/security-setup.md) - API keys and Docker security
|
||||||
|
- [Environment Variables](docs/reference/environment-variables.md) - All configuration options
|
||||||
|
|
||||||
|
### User Guides
|
||||||
|
- [User Management](docs/guides/user-management.md) - Managing users and roles
|
||||||
|
- [Testing Guide](docs/guides/testing.md) - Verification procedures
|
||||||
|
- [Troubleshooting](docs/guides/troubleshooting.md) - Common issues
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
- [API Endpoints](docs/api/endpoints.md) - Complete API reference
|
||||||
|
- [Deployment Guide](docs/deployment/deployment-guide.md) - Production deployment
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
campaign_connector/
|
||||||
|
├── src/ # Flask application
|
||||||
|
├── android/ # Termux server scripts
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── scripts/ # Deployment scripts
|
||||||
|
├── docker/ # Docker configuration
|
||||||
|
├── data/ # SQLite database (runtime)
|
||||||
|
├── uploads/ # CSV uploads (runtime)
|
||||||
|
└── logs/ # Application logs (runtime)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Launch Ubuntu Homelab
|
## Common Commands
|
||||||
```bash
|
|
||||||
# Quick start using convenience script
|
|
||||||
./run.sh start
|
|
||||||
|
|
||||||
# Or manually with docker compose
|
```bash
|
||||||
|
# Start services
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
docker compose logs -f sms-campaign
|
docker compose logs -f sms-campaign
|
||||||
|
|
||||||
# Access web interface
|
# Stop services
|
||||||
open http://localhost:5000
|
docker compose down
|
||||||
|
|
||||||
|
# Manage users
|
||||||
|
python3 manage_users.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Deploy to Android Device
|
## Support
|
||||||
```bash
|
|
||||||
# Use the automated deployment script
|
|
||||||
./scripts/deploy-android.sh
|
|
||||||
|
|
||||||
# The script will:
|
For issues:
|
||||||
# - Test connectivity to your Android device
|
1. Check [Troubleshooting](docs/guides/troubleshooting.md)
|
||||||
# - Deploy shell scripts to ~/bin/
|
2. Review logs: `docker compose logs`
|
||||||
# - Deploy Python apps to ~/projects/sms-campaign-manager/
|
3. Verify configuration in `.env`
|
||||||
# - Start all services automatically
|
|
||||||
# - Verify deployment success
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Verify Full System
|
## License
|
||||||
```bash
|
|
||||||
# Test Ubuntu homelab → Android connection
|
|
||||||
curl http://localhost:5000/api/phone/status
|
|
||||||
|
|
||||||
# Test Android SMS API (replace with your Tailscale IP)
|
Copyright 2025 Campaign Connector Team
|
||||||
curl http://YOUR_TAILSCALE_IP:5001/health
|
|
||||||
|
|
||||||
# Access web interfaces
|
|
||||||
open http://localhost:5000 # Main campaign manager
|
|
||||||
open http://YOUR_TAILSCALE_IP:5000 # Android monitoring dashboard
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deployed Services:**
|
|
||||||
- 🖥️ **Ubuntu Homelab**: http://localhost:5000 (Campaign manager)
|
|
||||||
- 🚀 **SMS API Server**: http://YOUR_TAILSCALE_IP:5001 (REST API for sending SMS)
|
|
||||||
- 📊 **Android Monitor**: http://YOUR_TAILSCALE_IP:5000 (Device monitoring dashboard)
|
|
||||||
|
|
||||||
**Available Service Scripts on Android:**
|
|
||||||
- `start-all-services.sh` - Start both services with health checks
|
|
||||||
- `sms-service.sh` - Service management (start/stop/status)
|
|
||||||
- `start-sms-api.sh` - SMS API Server only
|
|
||||||
- `start-monitoring.sh` - Monitoring interface only
|
|
||||||
- `network-monitor.sh` - Network connectivity monitoring
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## <20> Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/ # Main application code (Python, templates, static files)
|
|
||||||
scripts/ # Shell scripts and utilities
|
|
||||||
docs/ # Documentation files
|
|
||||||
config/ # Configuration templates
|
|
||||||
tests/ # Test scripts
|
|
||||||
docker/ # Docker configuration
|
|
||||||
samples/ # Sample CSV files
|
|
||||||
data/ # Database files (runtime)
|
|
||||||
logs/ # Application logs (runtime)
|
|
||||||
uploads/ # CSV uploads (runtime)
|
|
||||||
```
|
|
||||||
|
|
||||||
See [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) for detailed information.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## <20>📋 System Overview
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
```
|
|
||||||
Ubuntu Homelab ──→ Docker Container ──→ Android Device
|
|
||||||
↓ ↓ ↓
|
|
||||||
Web Interface Flask App SMS Sending
|
|
||||||
↓ ↓ ↓
|
|
||||||
Campaign Mgmt SQLite Database Dual Connection:
|
|
||||||
↓ ↓ • Termux API (fast)
|
|
||||||
CSV Upload Contact Storage • ADB Automation (reliable)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
- **🎯 Campaign Management**: Create, schedule, and monitor SMS campaigns
|
|
||||||
- **📊 Real-time Analytics**: Response tracking and delivery reports
|
|
||||||
- **🔄 Dual Connection**: Termux API + ADB automation for maximum reliability
|
|
||||||
- **📋 CSV Processing**: Smart column detection for contact imports
|
|
||||||
- **🎨 Template System**: Message templates with variable substitution
|
|
||||||
- **🐳 Docker Deployment**: One-command deployment with data persistence
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ Architecture Overview
|
|
||||||
|
|
||||||
### Dual SMS Connection System
|
|
||||||
```
|
|
||||||
Ubuntu Homelab → Tailscale VPN → Android Device
|
|
||||||
↓ ↓
|
|
||||||
Flask App Termux API Server
|
|
||||||
↓ ↓
|
|
||||||
SMS Manager → {
|
|
||||||
├── Termux API (Primary) → Native Android SMS [Faster, Preferred]
|
|
||||||
└── ADB Automation (Fallback) → UI Touch Events [Backup only]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Benefits:**
|
|
||||||
- **Tailscale Connectivity**: Secure, encrypted connection that works anywhere
|
|
||||||
- **Termux API Primary**: Direct SMS sending without ADB overhead
|
|
||||||
- **Intelligent Failover**: Automatically skips ADB when Termux API is working
|
|
||||||
- **Real-time Monitoring**: Phone status, battery, connectivity tracking
|
|
||||||
- **SSH Remote Development**: Full development environment on Android
|
|
||||||
- **No WiFi Required**: Works over cellular via Tailscale
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ Service Architecture
|
|
||||||
|
|
||||||
### Three-Tier System
|
|
||||||
```
|
|
||||||
Ubuntu Homelab (Port 5000)
|
|
||||||
↓ HTTP API calls
|
|
||||||
Android SMS API Server (Port 5001) ← Primary SMS gateway
|
|
||||||
↓ Termux API
|
|
||||||
Android Monitor Dashboard (Port 5000) ← Device monitoring
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Responsibilities
|
|
||||||
|
|
||||||
**Ubuntu Homelab (`src/app.py`)**
|
|
||||||
- Campaign management and web interface
|
|
||||||
- Contact CSV processing and storage
|
|
||||||
- Campaign scheduling and analytics
|
|
||||||
- Coordinates with Android services via HTTP
|
|
||||||
|
|
||||||
**Android SMS API Server (`android/termux-sms-api-server.py`)**
|
|
||||||
- Receives SMS requests from homelab
|
|
||||||
- Native Android SMS sending via Termux API
|
|
||||||
- Rate limiting and message queuing
|
|
||||||
- Battery and device status reporting
|
|
||||||
|
|
||||||
**Android Monitor Dashboard (`android/app.py`)**
|
|
||||||
- Device health monitoring interface
|
|
||||||
- Termux API testing and diagnostics
|
|
||||||
- Real-time system status display
|
|
||||||
- Direct Android device interaction
|
|
||||||
|
|
||||||
### Android Directory Structure
|
|
||||||
```
|
|
||||||
~/bin/ # Executable scripts (in PATH)
|
|
||||||
├── start-all-services.sh # Master startup script
|
|
||||||
├── sms-service.sh # Service daemon management
|
|
||||||
├── start-sms-api.sh # SMS API launcher
|
|
||||||
├── start-monitoring.sh # Monitor launcher
|
|
||||||
└── network-monitor.sh # Network watchdog
|
|
||||||
|
|
||||||
~/projects/sms-campaign-manager/ # Python applications
|
|
||||||
├── app.py # Monitoring dashboard
|
|
||||||
├── termux-sms-api-server.py # SMS API server
|
|
||||||
└── app.py.backup # Backup files
|
|
||||||
|
|
||||||
~/logs/ # Service logs
|
|
||||||
├── sms-api.log # SMS API server logs
|
|
||||||
├── monitoring.log # Monitor dashboard logs
|
|
||||||
└── network-monitor.log # Network connectivity logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Files Explained
|
|
||||||
- `start-all-services.sh` - Master startup script with health checks
|
|
||||||
- `sms-service.sh` - Daemon-style service management (start/stop/status)
|
|
||||||
- `start-sms-api.sh` - SMS API server launcher
|
|
||||||
- `start-monitoring.sh` - Monitoring dashboard launcher
|
|
||||||
- `network-monitor.sh` - Network connectivity watchdog
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation
|
|
||||||
|
|
||||||
For complete documentation, see the [docs/](docs/) directory or visit the documentation site.
|
|
||||||
|
|
||||||
### Quick Links
|
|
||||||
- [Quick Start Guide](docs/setup/quick-start.md) - Get started in minutes
|
|
||||||
- [Deployment Guide](docs/deployment/deployment-guide.md) - Production deployment
|
|
||||||
- [User Management](docs/guides/user-management.md) - Managing users and permissions
|
|
||||||
- [Security Setup](docs/security/security-setup.md) - Securing your installation
|
|
||||||
- [API Security](docs/security/api-security.md) - API authentication guide
|
|
||||||
- [Android Development](docs/development/android-dev-setup.md) - Android setup details
|
|
||||||
- [File Structure](docs/reference/files.md) - Project file documentation
|
|
||||||
- [Project Instructions](docs/reference/project-instructions.md) - Development guidelines
|
|
||||||
|
|
||||||
### Building Documentation
|
|
||||||
```bash
|
|
||||||
# Install mkdocs and material theme
|
|
||||||
pip install mkdocs mkdocs-material
|
|
||||||
|
|
||||||
# Serve documentation locally
|
|
||||||
mkdocs serve
|
|
||||||
|
|
||||||
# Build static documentation
|
|
||||||
mkdocs build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Development Workflow
|
|
||||||
|
|
||||||
### Docker Development (Recommended)
|
|
||||||
```bash
|
|
||||||
# Build and run with Docker Compose
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# View real-time logs
|
|
||||||
docker-compose logs -f sms-campaign
|
|
||||||
|
|
||||||
# Access container shell
|
|
||||||
docker-compose exec sms-campaign bash
|
|
||||||
|
|
||||||
# Rebuild after code changes
|
|
||||||
docker-compose build && docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
```bash
|
|
||||||
# Install Python dependencies
|
|
||||||
cd src
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Set up environment (from project root)
|
|
||||||
cd ..
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
# Run Flask development server
|
|
||||||
cd src
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing SMS Integration
|
|
||||||
```bash
|
|
||||||
# Test ADB connection
|
|
||||||
./scripts/auto.sh
|
|
||||||
|
|
||||||
# Manual SMS test via UI script
|
|
||||||
./scripts/ui.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## <20>️ Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
ABD Texting Testing/
|
|
||||||
├── 📱 Core Application
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── app.py # Flask web application
|
|
||||||
│ │ ├── sms_connection_manager.py # Dual SMS connection handler
|
|
||||||
│ │ ├── requirements.txt # Python dependencies
|
|
||||||
│ │ ├── templates/dashboard.html # Web UI
|
|
||||||
│ │ └── static/js/ # JavaScript files
|
|
||||||
│
|
|
||||||
├── 🐳 Docker Deployment
|
|
||||||
│ ├── dockerfile # Container definition
|
|
||||||
│ ├── docker-compose.yml # Production orchestration
|
|
||||||
│ └── .env # Environment configuration
|
|
||||||
│
|
|
||||||
├── 📊 Data & Storage
|
|
||||||
│ ├── data/campaign.db # SQLite database
|
|
||||||
│ ├── uploads/contacts_cleaned.csv # Contact imports
|
|
||||||
│ └── logs/ # Application logs
|
|
||||||
│
|
|
||||||
├── 🔧 Scripts & Automation
|
|
||||||
│ ├── auto.sh # Android auto-connect
|
|
||||||
│ ├── ui.sh # Manual SMS sender
|
|
||||||
│ ├── deploy.sh # Deployment automation
|
|
||||||
│ └── setup-termux-integration.sh # Termux setup
|
|
||||||
│
|
|
||||||
├── 📱 Android Integration
|
|
||||||
│ ├── termux-sms-api-server.py # Termux API server
|
|
||||||
│ ├── app-integration-patch.py # Integration helpers
|
|
||||||
│ └── termux_integration_simple.py # Simple integration
|
|
||||||
│
|
|
||||||
└── 📚 Documentation
|
|
||||||
├── README.md # This file
|
|
||||||
├── files.md # Project documentation
|
|
||||||
├── android-dev-setup.md # Android setup guide
|
|
||||||
└── workplan.md # Development roadmap
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Deployment Script
|
|
||||||
|
|
||||||
### Using `deploy-android.sh`
|
|
||||||
|
|
||||||
The automated deployment script simplifies Android service deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/deploy-android.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**What it does:**
|
|
||||||
1. ✅ Tests Tailscale connectivity to Android device
|
|
||||||
2. ✅ Verifies SSH connection
|
|
||||||
3. ✅ Creates necessary directories on Android
|
|
||||||
4. ✅ Deploys shell scripts to `~/bin/`
|
|
||||||
5. ✅ Deploys Python apps to `~/projects/sms-campaign-manager/`
|
|
||||||
6. ✅ Sets proper permissions
|
|
||||||
7. ✅ Starts all services automatically
|
|
||||||
8. ✅ Verifies services are running and responsive
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Interactive confirmation before deployment
|
|
||||||
- Automatic service health checks
|
|
||||||
- Clear progress indicators
|
|
||||||
- Helpful error messages
|
|
||||||
- Post-deployment verification
|
|
||||||
|
|
||||||
**Requirements:**
|
|
||||||
- SSH password authentication to Android device
|
|
||||||
- Termux and Termux:API installed on Android
|
|
||||||
- Tailscale running on both devices (or local network access)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Service Management
|
|
||||||
|
|
||||||
### Android Service Control
|
|
||||||
```bash
|
|
||||||
# Start all services
|
|
||||||
ssh android-dev "~/bin/start-all-services.sh"
|
|
||||||
|
|
||||||
# Individual service management
|
|
||||||
ssh android-dev "~/bin/sms-service.sh start" # Start SMS API
|
|
||||||
ssh android-dev "~/bin/sms-service.sh stop" # Stop SMS API
|
|
||||||
ssh android-dev "~/bin/sms-service.sh status" # Check SMS API status
|
|
||||||
|
|
||||||
# Restart monitoring interface
|
|
||||||
ssh android-dev "~/bin/start-monitoring.sh"
|
|
||||||
|
|
||||||
# View service logs
|
|
||||||
ssh android-dev "tail -f ~/logs/sms-api.log"
|
|
||||||
ssh android-dev "tail -f ~/logs/monitoring.log"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto-start on Boot (Optional)
|
|
||||||
```bash
|
|
||||||
# Add to Termux ~/.bashrc for auto-start
|
|
||||||
ssh android-dev
|
|
||||||
echo '~/bin/start-all-services.sh' >> ~/.bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Health Monitoring
|
|
||||||
```bash
|
|
||||||
# Network connectivity monitoring
|
|
||||||
ssh android-dev "~/bin/network-monitor.sh"
|
|
||||||
|
|
||||||
# Complete system health check
|
|
||||||
curl http://10.0.0.193:5001/health && echo "SMS API: ✅" || echo "SMS API: ❌"
|
|
||||||
curl http://10.0.0.193:5000/ > /dev/null && echo "Monitor: ✅" || echo "Monitor: ❌"
|
|
||||||
curl http://localhost:5000/api/phone/status && echo "Homelab: ✅" || echo "Homelab: ❌"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ Configuration
|
|
||||||
|
|
||||||
### Environment Variables (`.env`)
|
|
||||||
```bash
|
|
||||||
# Android Device Configuration (Tailscale Recommended)
|
|
||||||
PHONE_IP=100.107.173.66 # Your Android Tailscale IP (or local IP)
|
|
||||||
ADB_PORT=5555 # ADB wireless debugging port (optional)
|
|
||||||
TERMUX_API_PORT=5001 # Termux API server port
|
|
||||||
|
|
||||||
# Flask Application
|
|
||||||
FLASK_ENV=production # Environment mode
|
|
||||||
SECRET_KEY=your-secret-key-here # Flask secret key (change in production!)
|
|
||||||
DEFAULT_DELAY_SECONDS=3 # SMS sending delay
|
|
||||||
|
|
||||||
# SMS Retry Configuration
|
|
||||||
SMS_MAX_RETRIES=3 # Number of retry attempts
|
|
||||||
SMS_RETRY_BASE_DELAY=2 # Base delay for exponential backoff
|
|
||||||
SMS_MAX_RETRY_DELAY=8 # Maximum retry delay
|
|
||||||
|
|
||||||
# SMS Automation (ADB coordinates - only for fallback)
|
|
||||||
SEND_BUTTON_X=1300 # Send button X coordinate
|
|
||||||
SEND_BUTTON_Y=2900 # Send button Y coordinate
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** With Tailscale connectivity, the system primarily uses Termux API and only falls back to ADB if needed. ADB wireless debugging is optional but provides redundancy.
|
|
||||||
|
|
||||||
### Docker Volumes
|
|
||||||
- `./data:/app/data` - SQLite database persistence
|
|
||||||
- `./uploads:/app/uploads` - CSV contact file storage
|
|
||||||
- `./logs:/app/logs` - Application logs
|
|
||||||
- `/dev/bus/usb:/dev/bus/usb` - USB device access for ADB
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Android Setup
|
|
||||||
|
|
||||||
### Method 1: Tailscale + Termux API (Recommended)
|
|
||||||
1. **Install Tailscale on Android** from Google Play Store
|
|
||||||
- Sign in and connect to your Tailnet
|
|
||||||
- Note your Android device's Tailscale IP (e.g., 100.x.x.x)
|
|
||||||
|
|
||||||
2. **Install Termux and Termux:API** from F-Droid (not Google Play)
|
|
||||||
- [Download Termux](https://f-droid.org/packages/com.termux/)
|
|
||||||
- [Download Termux:API](https://f-droid.org/packages/com.termux.api/)
|
|
||||||
|
|
||||||
3. **Setup SSH and Termux API**
|
|
||||||
```bash
|
|
||||||
# In Termux on Android:
|
|
||||||
pkg update && pkg upgrade
|
|
||||||
pkg install openssh termux-api python
|
|
||||||
|
|
||||||
# Start SSH server
|
|
||||||
sshd
|
|
||||||
|
|
||||||
# Set password for SSH access
|
|
||||||
passwd
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Grant SMS Permissions**
|
|
||||||
- Open Android Settings → Apps → Termux:API
|
|
||||||
- Grant SMS and Phone permissions
|
|
||||||
|
|
||||||
5. **Deploy Services**
|
|
||||||
```bash
|
|
||||||
# On your Ubuntu homelab:
|
|
||||||
./deploy-android.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Local Network (WiFi only)
|
|
||||||
1. Enable Developer Options on Android
|
|
||||||
2. Enable Wireless Debugging
|
|
||||||
3. Connect to same WiFi network as your homelab
|
|
||||||
4. Use local IP instead of Tailscale IP in `.env`
|
|
||||||
5. Follow Termux setup steps above
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Usage Examples
|
|
||||||
|
|
||||||
### Campaign Management
|
|
||||||
1. **Access Web Interface**: `http://localhost:5000`
|
|
||||||
2. **Upload Contacts**: CSV with `phone`, `name`, `message` columns
|
|
||||||
3. **Create Campaign**: Set message template with `{name}` variables
|
|
||||||
4. **Monitor Progress**: Real-time dashboard with analytics
|
|
||||||
|
|
||||||
### CSV Format
|
|
||||||
```csv
|
|
||||||
phone,name,message
|
|
||||||
7802921731,Reed,Hi {name}! Your custom message here
|
|
||||||
7809101334,Ken,Hello {name}, different message per contact
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Variables
|
|
||||||
- `{name}` - Contact name from CSV
|
|
||||||
- `{phone}` - Contact phone number
|
|
||||||
- Custom fields from your CSV columns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing & Troubleshooting
|
|
||||||
|
|
||||||
### Health Checks
|
|
||||||
```bash
|
|
||||||
# Check Docker containers
|
|
||||||
docker-compose ps
|
|
||||||
|
|
||||||
# Test phone connectivity
|
|
||||||
curl http://localhost:5000/api/phone/status
|
|
||||||
|
|
||||||
# View application logs
|
|
||||||
docker-compose logs sms-campaign
|
|
||||||
|
|
||||||
# Test SMS functionality
|
|
||||||
curl -X POST http://localhost:5000/api/sms/test \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"phone":"YOUR_NUMBER","message":"Test message"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Phone not connecting:**
|
|
||||||
- Verify Tailscale is running on both devices
|
|
||||||
- Check PHONE_IP in `.env` matches Android's Tailscale IP
|
|
||||||
- Test connectivity: `ping YOUR_TAILSCALE_IP`
|
|
||||||
- For local network: Ensure both devices on same WiFi
|
|
||||||
|
|
||||||
**SMS not sending:**
|
|
||||||
- Check Termux API health: `curl http://YOUR_TAILSCALE_IP:5001/health`
|
|
||||||
- Verify SMS permissions granted to Termux:API app
|
|
||||||
- Check Android service logs: `ssh -p 8022 YOUR_TAILSCALE_IP "tail ~/logs/sms-api.log"`
|
|
||||||
|
|
||||||
**Termux API not responding:**
|
|
||||||
- Restart services: `./deploy-android.sh` or SSH and run `~/bin/start-all-services.sh`
|
|
||||||
- Check if Python is installed in Termux: `ssh -p 8022 YOUR_TAILSCALE_IP "which python"`
|
|
||||||
- Verify Termux:API app is installed (from F-Droid, not Google Play)
|
|
||||||
|
|
||||||
**Permission denied (Docker):**
|
|
||||||
- Ensure Docker has USB device access (only needed for ADB fallback)
|
|
||||||
- Check volume mounts and file permissions
|
|
||||||
|
|
||||||
**Database errors:**
|
|
||||||
- Check volume mounts in [docker-compose.yml](docker-compose.yml)
|
|
||||||
- Verify data directory permissions: `ls -la data/`
|
|
||||||
|
|
||||||
**Connection timeout errors in logs:**
|
|
||||||
- Normal if using Termux API over Tailscale (ADB is skipped)
|
|
||||||
- System automatically uses Termux API when available
|
|
||||||
- Only an issue if Termux API is also failing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
This project follows Test-Driven Development (TDD) principles:
|
|
||||||
1. Write tests first to define expected behavior
|
|
||||||
2. Implement features to pass tests
|
|
||||||
3. Refactor for performance and maintainability
|
|
||||||
|
|
||||||
See [`instruct.md`](instruct.md) for detailed development guidelines and coding standards.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
This project is developed for personal/educational use. Please ensure compliance with local SMS and privacy regulations when deploying.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Links
|
|
||||||
|
|
||||||
- [Docker Installation](https://docs.docker.com/get-docker/)
|
|
||||||
- [Android ADB Setup](https://developer.android.com/studio/command-line/adb)
|
|
||||||
- [Termux Documentation](https://wiki.termux.com/)
|
|
||||||
- [Flask Documentation](https://flask.palletsprojects.com/)
|
|
||||||
- ✅ **Pause/Resume**: Full campaign control with state persistence
|
|
||||||
- ✅ **Analytics**: Response tracking and success rates
|
|
||||||
|
|
||||||
### SMS Delivery
|
|
||||||
- ✅ **Dual Connection**: Termux API (primary) + ADB automation (fallback)
|
|
||||||
- ✅ **50% Faster**: Native Android SMS vs UI automation
|
|
||||||
- ✅ **90% More Reliable**: Automatic connection switching
|
|
||||||
- ✅ **Rate Limiting**: Configurable delays between messages
|
|
||||||
- ✅ **Error Handling**: Comprehensive retry and logging
|
|
||||||
|
|
||||||
### Device Integration
|
|
||||||
- ✅ **Real-time Status**: Phone connectivity, battery, location
|
|
||||||
- ✅ **Auto-Discovery**: Automatic phone detection and connection
|
|
||||||
- ✅ **SSH Development**: Remote coding directly on Android
|
|
||||||
- ✅ **Background Monitoring**: Continuous device health checks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Development Setup
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
cd src
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Configure phone IP
|
|
||||||
cd ..
|
|
||||||
echo "PHONE_IP=10.0.0.193" >> .env
|
|
||||||
|
|
||||||
# Run development server
|
|
||||||
cd src
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Android Termux Setup
|
|
||||||
```bash
|
|
||||||
# Install Termux + Termux:API from F-Droid
|
|
||||||
# Run the automated setup
|
|
||||||
./setup-termux-integration.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
```bash
|
|
||||||
# Test phone connection
|
|
||||||
./test-termux-integration.sh
|
|
||||||
|
|
||||||
# Test CSV parsing
|
|
||||||
./test_column_detection.sh
|
|
||||||
|
|
||||||
# Test campaign sending
|
|
||||||
python -c "from app import *; init_db(); print('Database initialized')"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔌 API Endpoints
|
|
||||||
|
|
||||||
### Campaign Management
|
|
||||||
```http
|
|
||||||
POST /api/campaign/create # Create new campaign
|
|
||||||
POST /api/campaign/start # Start SMS campaign
|
|
||||||
POST /api/campaign/pause # Pause running campaign
|
|
||||||
GET /api/campaign/status # Real-time progress
|
|
||||||
GET /api/campaign/list # List all campaigns
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection & Device
|
|
||||||
```http
|
|
||||||
GET /api/phone/status # Phone connectivity
|
|
||||||
POST /api/phone/connect # Manual reconnection
|
|
||||||
GET /api/connections/status # Dual connection status
|
|
||||||
GET /api/device/status # Battery, location, etc.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Management
|
|
||||||
```http
|
|
||||||
POST /api/csv/upload # Upload contact CSV
|
|
||||||
GET /api/templates # Message templates
|
|
||||||
POST /api/responses/sync # Sync SMS replies
|
|
||||||
GET /api/analytics # Campaign statistics
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
├── src/ # Main application code
|
|
||||||
│ ├── app.py # Main Flask application
|
|
||||||
│ ├── sms_connection_manager.py # Dual SMS connection handler
|
|
||||||
│ ├── templates/dashboard.html # Web interface
|
|
||||||
│ └── static/js/ # JavaScript files
|
|
||||||
├── android/
|
|
||||||
│ └── termux-sms-api-server.py # Android-side API server
|
|
||||||
├── data/campaign.db # SQLite database
|
|
||||||
├── uploads/ # CSV contact files
|
|
||||||
├── logs/ # Application logs
|
|
||||||
├── docker-compose.yml # Production deployment
|
|
||||||
└── *.sh # Automation scripts
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚨 Troubleshooting
|
|
||||||
|
|
||||||
### Android Service Issues
|
|
||||||
```bash
|
|
||||||
# Check if services are running
|
|
||||||
ssh android-dev "ps aux | grep -E '(termux-sms-api|python.*app.py)'"
|
|
||||||
|
|
||||||
# Restart all services
|
|
||||||
ssh android-dev "pkill -f 'termux-sms-api-server.py'; pkill -f 'python app.py'"
|
|
||||||
ssh android-dev "~/bin/start-all-services.sh"
|
|
||||||
|
|
||||||
# Check service logs for errors
|
|
||||||
ssh android-dev "tail -20 ~/logs/sms-api.log"
|
|
||||||
ssh android-dev "tail -20 ~/logs/monitoring.log"
|
|
||||||
|
|
||||||
# Test Termux API permissions
|
|
||||||
ssh android-dev "termux-sms-list -l 1" # Should list recent SMS
|
|
||||||
|
|
||||||
### Reinstall if services are corrupted
|
|
||||||
```bash
|
|
||||||
# Use the deployment script to redeploy everything
|
|
||||||
./scripts/deploy-android.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Port Conflicts
|
|
||||||
```bash
|
|
||||||
# Check what's using ports 5000/5001
|
|
||||||
ssh android-dev "netstat -tlnp | grep -E ':500[01]'"
|
|
||||||
|
|
||||||
# Kill processes on specific ports
|
|
||||||
ssh android-dev "lsof -ti:5001 | xargs kill -9"
|
|
||||||
ssh android-dev "lsof -ti:5000 | xargs kill -9"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phone Not Connecting
|
|
||||||
```bash
|
|
||||||
# Auto-reconnect your phone
|
|
||||||
./scripts/auto.sh
|
|
||||||
|
|
||||||
# Check ADB devices
|
|
||||||
adb devices
|
|
||||||
```
|
|
||||||
|
|
||||||
### Termux API Issues
|
|
||||||
```bash
|
|
||||||
# Test Termux API health
|
|
||||||
curl http://10.0.0.193:5001/health
|
|
||||||
|
|
||||||
# Restart Termux API server
|
|
||||||
ssh android-dev "cd ~/projects/sms-campaign-manager && python termux-sms-api-server.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Deployment Issues
|
|
||||||
```bash
|
|
||||||
# Rebuild containers
|
|
||||||
docker-compose down && docker-compose up --build
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker-compose logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Success Metrics
|
|
||||||
|
|
||||||
### Technical Performance
|
|
||||||
- **50% faster** SMS sending via native Termux API
|
|
||||||
- **90% more reliable** with dual connection failover
|
|
||||||
- **Real-time** device monitoring and status updates
|
|
||||||
- **Zero dependency** on UI automation for primary SMS path
|
|
||||||
|
|
||||||
### Developer Experience
|
|
||||||
- **One-command deployment** via Docker Compose
|
|
||||||
- **SSH-based development** workflow on Android
|
|
||||||
- **Comprehensive logging** and error handling
|
|
||||||
- **Web interface** for testing and monitoring
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support & Development
|
|
||||||
|
|
||||||
### Development Philosophy
|
|
||||||
Following [JavaScript-first, TDD methodology](instruct.md) with:
|
|
||||||
- **Modern ES6+** and vanilla JS preferred
|
|
||||||
- **Test-driven development** with comprehensive unit tests
|
|
||||||
- **Lightweight solutions** optimized for mobile performance
|
|
||||||
- **Battery-conscious** background services
|
|
||||||
|
|
||||||
### Tech Stack
|
|
||||||
- **Backend**: Python 3.11 + Flask + SQLite
|
|
||||||
- **Frontend**: Alpine.js + Tailwind CSS + Vanilla JavaScript
|
|
||||||
- **Android**: Termux + Termux:API + SSH server
|
|
||||||
- **Deployment**: Docker + Docker Compose
|
|
||||||
- **Connectivity**: ADB wireless debugging + HTTP API
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Built with ❤️ for reliable, scalable SMS campaign management.
|
|
||||||
|
|||||||
151
android/setup-api-key.sh
Executable file
151
android/setup-api-key.sh
Executable file
@ -0,0 +1,151 @@
|
|||||||
|
#!/data/data/com.termux/files/usr/bin/bash
|
||||||
|
#
|
||||||
|
# Termux SMS API Server - Security Setup Script
|
||||||
|
# Generates and configures the required SMS_API_SECRET environment variable
|
||||||
|
#
|
||||||
|
|
||||||
|
# Color codes for pretty output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Banner
|
||||||
|
clear
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}🔐 Termux SMS API Server - Security Setup${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if Python is available
|
||||||
|
if ! command -v python &> /dev/null; then
|
||||||
|
echo -e "${RED}❌ ERROR: Python is not installed${NC}"
|
||||||
|
echo -e "${YELLOW}Please install Python: pkg install python${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}📝 Step 1: Generating secure API key...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Generate the API key
|
||||||
|
API_KEY=$(python -c "import secrets; print(secrets.token_hex(32))")
|
||||||
|
|
||||||
|
if [ -z "$API_KEY" ]; then
|
||||||
|
echo -e "${RED}❌ Failed to generate API key${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Secure API key generated successfully!${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Determine shell config file
|
||||||
|
SHELL_RC=""
|
||||||
|
if [ -f "$HOME/.bashrc" ]; then
|
||||||
|
SHELL_RC="$HOME/.bashrc"
|
||||||
|
elif [ -f "$HOME/.zshrc" ]; then
|
||||||
|
SHELL_RC="$HOME/.zshrc"
|
||||||
|
else
|
||||||
|
SHELL_RC="$HOME/.bashrc"
|
||||||
|
touch "$SHELL_RC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}📝 Step 2: Saving to ${SHELL_RC}...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if SMS_API_SECRET already exists in config
|
||||||
|
if grep -q "SMS_API_SECRET" "$SHELL_RC" 2>/dev/null; then
|
||||||
|
echo -e "${YELLOW}⚠️ SMS_API_SECRET already exists in ${SHELL_RC}${NC}"
|
||||||
|
echo -e "${YELLOW}Do you want to replace it? (y/n)${NC}"
|
||||||
|
read -r response
|
||||||
|
if [[ "$response" =~ ^[Yy]$ ]]; then
|
||||||
|
# Remove old entry
|
||||||
|
sed -i '/export SMS_API_SECRET=/d' "$SHELL_RC"
|
||||||
|
echo "export SMS_API_SECRET=\"$API_KEY\"" >> "$SHELL_RC"
|
||||||
|
echo -e "${GREEN}✅ Updated existing API key${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⏭️ Skipping - keeping existing key${NC}"
|
||||||
|
# Use existing key for display
|
||||||
|
API_KEY=$(grep "SMS_API_SECRET" "$SHELL_RC" | cut -d'"' -f2)
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Add new entry
|
||||||
|
echo "" >> "$SHELL_RC"
|
||||||
|
echo "# Termux SMS API Server Authentication" >> "$SHELL_RC"
|
||||||
|
echo "export SMS_API_SECRET=\"$API_KEY\"" >> "$SHELL_RC"
|
||||||
|
echo -e "${GREEN}✅ API key saved to ${SHELL_RC}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}📝 Step 3: Activating in current session...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Export for current session
|
||||||
|
export SMS_API_SECRET="$API_KEY"
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ API key activated${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Display summary box
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}🎉 Setup Complete!${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╠════════════════════════════════════════════════════════════════════════╣${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}Your API Key:${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}${API_KEY}${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}Saved to:${NC} ${YELLOW}${SHELL_RC}${NC}"
|
||||||
|
# Pad the line to align with box
|
||||||
|
printf "${CYAN}║${NC}\n"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}Status:${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}✅ Active in current session${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}✅ Will persist across restarts${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Next steps
|
||||||
|
echo -e "${BOLD}📋 Next Steps:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}1.${NC} ${BOLD}Copy this key to your Ubuntu homelab .env file:${NC}"
|
||||||
|
echo -e " ${CYAN}TERMUX_API_KEY=${API_KEY}${NC}"
|
||||||
|
echo -e " ${CYAN}SMS_API_SECRET=${API_KEY}${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}2.${NC} ${BOLD}Start the SMS API server:${NC}"
|
||||||
|
echo -e " ${CYAN}python ~/projects/sms-campaign-manager/android/termux-sms-api-server.py${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}3.${NC} ${BOLD}Or use the service manager:${NC}"
|
||||||
|
echo -e " ${CYAN}~/bin/sms-service.sh start${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verification section
|
||||||
|
echo -e "${BOLD}🔍 Verification:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Check if key is set: ${CYAN}echo \$SMS_API_SECRET${NC}"
|
||||||
|
echo -e " Expected output: ${GREEN}${API_KEY}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test the environment variable
|
||||||
|
CURRENT_VALUE="${SMS_API_SECRET}"
|
||||||
|
if [ "$CURRENT_VALUE" == "$API_KEY" ]; then
|
||||||
|
echo -e "${GREEN}✅ Verification passed - API key is correctly set!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Note: New terminal sessions will have the key automatically loaded${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${GREEN}${BOLD} Setup complete! Your SMS API server is now secure. 🔒${NC}"
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Optional: Save key to a file for easy copying (secured with permissions)
|
||||||
|
KEY_FILE="$HOME/.sms-api-key"
|
||||||
|
echo "$API_KEY" > "$KEY_FILE"
|
||||||
|
chmod 600 "$KEY_FILE"
|
||||||
|
|
||||||
|
echo -e "${BLUE}💾 API key also saved to: ${KEY_FILE} (readable only by you)${NC}"
|
||||||
|
echo ""
|
||||||
@ -34,7 +34,7 @@ app = Flask(__name__)
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
'SECRET_KEY': os.environ.get('SMS_API_SECRET', 'termux-sms-campaign-2025'),
|
'SECRET_KEY': os.environ.get('SMS_API_SECRET') or os.environ.get('TERMUX_API_KEY', ''),
|
||||||
'MAX_MESSAGE_LENGTH': 1600, # Increased from 160 to support longer messages (SMS can be up to 1600 chars)
|
'MAX_MESSAGE_LENGTH': 1600, # Increased from 160 to support longer messages (SMS can be up to 1600 chars)
|
||||||
'RATE_LIMIT_DELAY': 1.0, # Reduced from 2.0 to 1.0 seconds between messages for faster campaigns
|
'RATE_LIMIT_DELAY': 1.0, # Reduced from 2.0 to 1.0 seconds between messages for faster campaigns
|
||||||
'ALLOWED_COMMANDS': [
|
'ALLOWED_COMMANDS': [
|
||||||
@ -768,18 +768,38 @@ def list_contacts():
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Create logs directory
|
# Create logs directory
|
||||||
os.makedirs('/data/data/com.termux/files/home/logs', exist_ok=True)
|
os.makedirs('/data/data/com.termux/files/home/logs', exist_ok=True)
|
||||||
|
|
||||||
|
# Validate required configuration
|
||||||
|
if not CONFIG['SECRET_KEY']:
|
||||||
|
logger.critical("❌ SECURITY ERROR: SMS_API_SECRET or TERMUX_API_KEY environment variable is required!")
|
||||||
|
logger.critical("Set SMS_API_SECRET environment variable before starting the server")
|
||||||
|
logger.critical("Generate a secure key with: python -c \"import secrets; print(secrets.token_hex(32))\"")
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("FATAL ERROR: Missing required security configuration")
|
||||||
|
print("="*80)
|
||||||
|
print("SMS_API_SECRET environment variable must be set for authentication.")
|
||||||
|
print("This server cannot start without proper API key configuration.")
|
||||||
|
print("\nTo fix this:")
|
||||||
|
print("1. Generate a secure key: python -c \"import secrets; print(secrets.token_hex(32))\"")
|
||||||
|
print("2. Set environment variable: export SMS_API_SECRET='your-generated-key'")
|
||||||
|
print("3. Add to your shell profile or systemd service file")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
logger.info("Starting Termux SMS API Server...")
|
logger.info("Starting Termux SMS API Server...")
|
||||||
|
logger.info(f"✅ API authentication configured")
|
||||||
logger.info(f"Available commands: {CONFIG['ALLOWED_COMMANDS']}")
|
logger.info(f"Available commands: {CONFIG['ALLOWED_COMMANDS']}")
|
||||||
|
|
||||||
# Get local IP for display
|
# Get local IP for display (secure method without shell=True)
|
||||||
try:
|
try:
|
||||||
ip_result = subprocess.run([
|
import socket
|
||||||
'ifconfig', '2>/dev/null', '|', 'grep', '-A1', 'wlan0', '|',
|
# Get IP by connecting to external host (doesn't actually send data)
|
||||||
'grep', 'inet', '|', 'awk', '{print $2}', '|', 'cut', '-d:', '-f2'
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
], shell=True, capture_output=True, text=True)
|
s.connect(('8.8.8.8', 80))
|
||||||
local_ip = ip_result.stdout.strip() or '10.0.0.193'
|
local_ip = s.getsockname()[0]
|
||||||
except:
|
s.close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not determine IP address: {e}")
|
||||||
local_ip = '10.0.0.193'
|
local_ip = '10.0.0.193'
|
||||||
|
|
||||||
print(f"""
|
print(f"""
|
||||||
|
|||||||
@ -14,7 +14,13 @@ services:
|
|||||||
- ./src:/app/src # Live source for development
|
- ./src:/app/src # Live source for development
|
||||||
- ./src/static:/app/src/static # Static assets
|
- ./src/static:/app/src/static # Static assets
|
||||||
- ./src/templates:/app/src/templates # Templates
|
- ./src/templates:/app/src/templates # Templates
|
||||||
- /dev/bus/usb:/dev/bus/usb # USB devices (for direct USB connection)
|
# USB device access (if using USB ADB connection)
|
||||||
|
# Uncomment the following lines if you need USB ADB:
|
||||||
|
# devices:
|
||||||
|
# - /dev/bus/usb:/dev/bus/usb
|
||||||
|
# For specific device access (more secure), identify your device with 'lsusb' and use:
|
||||||
|
# devices:
|
||||||
|
# - /dev/bus/usb/001/002:/dev/bus/usb/001/002
|
||||||
environment:
|
environment:
|
||||||
PHONE_IP: ${PHONE_IP:-10.0.0.193}
|
PHONE_IP: ${PHONE_IP:-10.0.0.193}
|
||||||
ADB_PORT: ${ADB_PORT:-5555}
|
ADB_PORT: ${ADB_PORT:-5555}
|
||||||
@ -32,8 +38,10 @@ services:
|
|||||||
RATE_LIMIT_SMS: ${RATE_LIMIT_SMS:-10 per minute, 100 per hour, 500 per day}
|
RATE_LIMIT_SMS: ${RATE_LIMIT_SMS:-10 per minute, 100 per hour, 500 per day}
|
||||||
RATE_LIMIT_UPLOAD: ${RATE_LIMIT_UPLOAD:-10 per hour, 50 per day}
|
RATE_LIMIT_UPLOAD: ${RATE_LIMIT_UPLOAD:-10 per hour, 50 per day}
|
||||||
RATE_LIMIT_DATABASE_RESET: ${RATE_LIMIT_DATABASE_RESET:-2 per hour}
|
RATE_LIMIT_DATABASE_RESET: ${RATE_LIMIT_DATABASE_RESET:-2 per hour}
|
||||||
network_mode: host # Required for ADB network connection (host mode needed for ADB)
|
# SECURITY: Removed privileged mode and host networking
|
||||||
privileged: true # Required for USB access
|
# - Network isolation via bridge network (default)
|
||||||
|
# - ADB network connection works fine with regular networking and port mappings
|
||||||
|
# - USB access can be granted via specific device mapping (see above)
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
stop_grace_period: 30s # Give container 30 seconds to gracefully shutdown
|
stop_grace_period: 30s # Give container 30 seconds to gracefully shutdown
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@ -233,6 +233,13 @@ After deployment:
|
|||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
- Check logs: `docker compose logs -f`
|
- Check logs: `docker compose logs -f`
|
||||||
- Review [README.md](README.md) for detailed documentation
|
|
||||||
- Verify all prerequisites are met
|
- Verify all prerequisites are met
|
||||||
- Ensure Tailscale is running on both devices
|
- Ensure Tailscale is running on both devices
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Installation Guide](../setup/installation.md) - Complete setup instructions
|
||||||
|
- [Quick Start](../setup/quick-start.md) - Rapid deployment
|
||||||
|
- [Security Setup](../security/security-setup.md) - Security configuration
|
||||||
|
- [Troubleshooting](../guides/troubleshooting.md) - Common issues
|
||||||
|
- [Testing Guide](../guides/testing.md) - Verification procedures
|
||||||
|
|||||||
301
docs/guides/testing.md
Normal file
301
docs/guides/testing.md
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
# Testing Guide
|
||||||
|
|
||||||
|
This guide covers testing procedures for SMS Campaign Manager, including verification of security configuration, service health, and end-to-end functionality.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before testing:
|
||||||
|
|
||||||
|
- Docker container running on Ubuntu server
|
||||||
|
- Android device accessible via Tailscale or local network
|
||||||
|
- API keys configured in `.env` file
|
||||||
|
- Termux SMS API server running on Android
|
||||||
|
|
||||||
|
## Quick Health Check
|
||||||
|
|
||||||
|
Verify all services are operational:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test Ubuntu web application
|
||||||
|
curl http://localhost:5000/health
|
||||||
|
|
||||||
|
# Test Android Termux API
|
||||||
|
curl http://YOUR_ANDROID_IP:5001/health
|
||||||
|
|
||||||
|
# Both should return healthy status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Verification
|
||||||
|
|
||||||
|
### Docker Container Security
|
||||||
|
|
||||||
|
Verify the container is properly isolated:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check privileged mode (should be false)
|
||||||
|
docker inspect sms-campaign-manager | grep -i "privileged"
|
||||||
|
|
||||||
|
# Check network mode (should NOT be "host")
|
||||||
|
docker inspect sms-campaign-manager | grep -i "networkmode"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
```
|
||||||
|
"Privileged": false,
|
||||||
|
"NetworkMode": "bridge" (or "campaign_connector_default")
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Authentication
|
||||||
|
|
||||||
|
Test that authentication is enforced:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should fail with 401 (no API key)
|
||||||
|
curl http://localhost:5000/api/campaign/list
|
||||||
|
|
||||||
|
# Should succeed (with valid API key)
|
||||||
|
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Termux API Authentication
|
||||||
|
|
||||||
|
Test Android server authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get API key from .env
|
||||||
|
API_KEY=$(grep "^TERMUX_API_KEY=" .env | cut -d'=' -f2)
|
||||||
|
|
||||||
|
# Test health endpoint
|
||||||
|
curl http://YOUR_ANDROID_IP:5001/health
|
||||||
|
|
||||||
|
# Test authenticated endpoint
|
||||||
|
curl -H "X-API-Key: $API_KEY" http://YOUR_ANDROID_IP:5001/api/device/battery
|
||||||
|
|
||||||
|
# Test with wrong key (should fail)
|
||||||
|
curl -H "X-API-Key: wrong_key" http://YOUR_ANDROID_IP:5001/api/device/battery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Dashboard Testing
|
||||||
|
|
||||||
|
### Login Flow
|
||||||
|
|
||||||
|
1. Open browser: `http://localhost:5000/`
|
||||||
|
2. Should redirect to `/login`
|
||||||
|
3. Enter credentials:
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: (from `.env` ADMIN_PASSWORD)
|
||||||
|
4. Should redirect to dashboard after login
|
||||||
|
|
||||||
|
### API Access
|
||||||
|
|
||||||
|
Test API endpoints with session authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login via API
|
||||||
|
curl -X POST http://localhost:5000/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"YOUR_PASSWORD"}'
|
||||||
|
|
||||||
|
# Check auth status
|
||||||
|
curl http://localhost:5000/api/auth/status
|
||||||
|
```
|
||||||
|
|
||||||
|
## SMS Functionality Testing
|
||||||
|
|
||||||
|
### Test SMS Sending
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test via API (replace with your number)
|
||||||
|
curl -X POST http://localhost:5000/api/sms/test/real \
|
||||||
|
-H "X-API-Key: YOUR_USER_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"phone":"YOUR_PHONE_NUMBER","message":"Test from SMS Campaign Manager"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Termux Permissions
|
||||||
|
|
||||||
|
SSH into Android device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Test Termux API directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List recent SMS (should work if permissions granted)
|
||||||
|
termux-sms-list -l 1
|
||||||
|
```
|
||||||
|
|
||||||
|
If this fails, grant SMS permissions:
|
||||||
|
1. Open Android Settings
|
||||||
|
2. Apps → Termux:API
|
||||||
|
3. Permissions → SMS → Allow
|
||||||
|
|
||||||
|
## User Management Testing
|
||||||
|
|
||||||
|
### Create Test User
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 manage_users.py
|
||||||
|
# Select option 1 (Create new user)
|
||||||
|
# Enter: testuser / TestPass123! / Role: User
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Users
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 manage_users.py
|
||||||
|
# Select option 2 (List all users)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Login with New User
|
||||||
|
|
||||||
|
1. Log out of current session
|
||||||
|
2. Log in as new user
|
||||||
|
3. Verify access to dashboard
|
||||||
|
|
||||||
|
## Container Logs
|
||||||
|
|
||||||
|
Monitor application behavior:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View real-time logs
|
||||||
|
docker compose logs -f sms-campaign
|
||||||
|
|
||||||
|
# Filter for authentication events
|
||||||
|
docker compose logs sms-campaign | grep -i "auth"
|
||||||
|
|
||||||
|
# Filter for errors
|
||||||
|
docker compose logs sms-campaign | grep -i "error"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Android Service Logs
|
||||||
|
|
||||||
|
Check Termux server logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
|
||||||
|
# View SMS API logs
|
||||||
|
tail -f ~/logs/sms-api.log
|
||||||
|
|
||||||
|
# View monitoring logs
|
||||||
|
tail -f ~/logs/monitoring.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## End-to-End Test Checklist
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
- [ ] Docker container running and healthy
|
||||||
|
- [ ] Container NOT in privileged mode
|
||||||
|
- [ ] Container NOT using host networking
|
||||||
|
- [ ] Android device reachable via SSH
|
||||||
|
- [ ] Termux API server running on Android
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
- [ ] API calls without key return 401
|
||||||
|
- [ ] API calls with valid key succeed
|
||||||
|
- [ ] Wrong API key returns authentication error
|
||||||
|
- [ ] Web login with username/password works
|
||||||
|
- [ ] Session persists across page loads
|
||||||
|
|
||||||
|
### SMS Functionality
|
||||||
|
|
||||||
|
- [ ] Termux health check returns healthy
|
||||||
|
- [ ] SMS permissions granted in Android settings
|
||||||
|
- [ ] Test SMS sends successfully
|
||||||
|
- [ ] SMS delivery confirmed on receiving device
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
|
||||||
|
- [ ] Admin user exists and can login
|
||||||
|
- [ ] Can create new users via CLI
|
||||||
|
- [ ] User roles enforced correctly
|
||||||
|
- [ ] Password change works
|
||||||
|
|
||||||
|
## Troubleshooting Test Failures
|
||||||
|
|
||||||
|
### Container Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs for errors
|
||||||
|
docker compose logs sms-campaign
|
||||||
|
|
||||||
|
# Verify environment variables
|
||||||
|
docker compose exec sms-campaign env | grep -E "(API_KEY|SECRET_KEY)"
|
||||||
|
|
||||||
|
# Rebuild container
|
||||||
|
docker compose down
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Can't Reach Android Device
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Tailscale status
|
||||||
|
tailscale status
|
||||||
|
|
||||||
|
# Ping device
|
||||||
|
ping YOUR_ANDROID_IP
|
||||||
|
|
||||||
|
# Test SSH connection
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "whoami"
|
||||||
|
```
|
||||||
|
|
||||||
|
### SMS Not Sending
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Termux server is running
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "pgrep -f termux-sms-api-server"
|
||||||
|
|
||||||
|
# View server logs
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "tail -20 ~/logs/sms-api.log"
|
||||||
|
|
||||||
|
# Check SMS_API_SECRET is set
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "echo \$SMS_API_SECRET"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Errors in Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check API keys match
|
||||||
|
grep API_KEY .env
|
||||||
|
docker compose exec sms-campaign env | grep API_KEY
|
||||||
|
|
||||||
|
# Restart to reload configuration
|
||||||
|
docker compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automated Test Script
|
||||||
|
|
||||||
|
Create a quick verification script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# test-all.sh
|
||||||
|
|
||||||
|
echo "Testing Ubuntu health..."
|
||||||
|
curl -s http://localhost:5000/health | grep -q "ok" && echo "PASS" || echo "FAIL"
|
||||||
|
|
||||||
|
echo "Testing Android health..."
|
||||||
|
curl -s http://YOUR_ANDROID_IP:5001/health | grep -q "healthy" && echo "PASS" || echo "FAIL"
|
||||||
|
|
||||||
|
echo "Testing auth enforcement..."
|
||||||
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/api/campaign/list)
|
||||||
|
[ "$STATUS" = "401" ] && echo "PASS" || echo "FAIL"
|
||||||
|
|
||||||
|
echo "Testing Docker security..."
|
||||||
|
docker inspect sms-campaign-manager | grep -q '"Privileged": false' && echo "PASS" || echo "FAIL"
|
||||||
|
|
||||||
|
echo "All tests complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Deployment Guide](../deployment/deployment-guide.md) - Production deployment
|
||||||
|
- [Security Setup](../security/security-setup.md) - Security configuration
|
||||||
|
- [Troubleshooting](troubleshooting.md) - Common issues and solutions
|
||||||
@ -330,3 +330,11 @@ When reporting issues, include:
|
|||||||
- Steps to reproduce
|
- Steps to reproduce
|
||||||
- Environment details (OS, Docker version)
|
- Environment details (OS, Docker version)
|
||||||
- Configuration (sanitized `.env` without secrets)
|
- Configuration (sanitized `.env` without secrets)
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Installation Guide](../setup/installation.md) - Setup instructions
|
||||||
|
- [Quick Start](../setup/quick-start.md) - Deployment steps
|
||||||
|
- [Testing Guide](testing.md) - Verification procedures
|
||||||
|
- [Security Setup](../security/security-setup.md) - Security configuration
|
||||||
|
- [Deployment Guide](../deployment/deployment-guide.md) - Production deployment
|
||||||
|
|||||||
@ -557,10 +557,9 @@ The "Remember me" checkbox (currently cosmetic) can be enhanced to extend sessio
|
|||||||
- Review user list periodically
|
- Review user list periodically
|
||||||
- Disable inactive users
|
- Disable inactive users
|
||||||
|
|
||||||
---
|
## Related Documentation
|
||||||
|
|
||||||
**Created**: 2025-12-30
|
- [Authentication Setup](../setup/authentication.md) - Login configuration
|
||||||
**Version**: 2.0
|
- [API Security](../security/api-security.md) - API key authentication
|
||||||
**Last Updated**: 2025-12-30
|
- [Security Setup](../security/security-setup.md) - Security configuration
|
||||||
|
- [Quick Start](../setup/quick-start.md) - Getting started
|
||||||
For API security documentation, see: [API Security](../security/api-security.md)
|
|
||||||
|
|||||||
201
docs/index.md
201
docs/index.md
@ -1,140 +1,135 @@
|
|||||||
# SMS Campaign Manager
|
# SMS Campaign Manager Documentation
|
||||||
|
|
||||||
**Dockerized SMS automation system with Android integration**
|
SMS Campaign Manager is a Dockerized SMS automation system with Android device integration via Termux API. It provides a web interface for managing campaigns, tracking responses, and viewing analytics.
|
||||||
|
|
||||||
## Overview
|
## Getting Started
|
||||||
|
|
||||||
SMS Campaign Manager is a Flask-based web application that automates sending SMS messages through an Android device. It uses Termux API for Android integration and runs in Docker for easy deployment.
|
New to SMS Campaign Manager? Follow these guides in order:
|
||||||
|
|
||||||
## Key Features
|
1. **[Installation Guide](setup/installation.md)** - Complete production setup
|
||||||
|
2. **[Quick Start](setup/quick-start.md)** - Deploy and verify your installation
|
||||||
|
3. **[Authentication Setup](setup/authentication.md)** - Configure user login
|
||||||
|
|
||||||
### User Management
|
## Documentation Overview
|
||||||
|
|
||||||
- Web dashboard login with username and password
|
### Setup
|
||||||
- API keys for programmatic access
|
|
||||||
- Admin and user roles
|
|
||||||
- 24-hour login sessions
|
|
||||||
|
|
||||||
### Android SMS Integration
|
| Guide | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| [Installation Guide](setup/installation.md) | Step-by-step production installation |
|
||||||
|
| [Quick Start](setup/quick-start.md) | Rapid deployment and testing |
|
||||||
|
| [Authentication Setup](setup/authentication.md) | User login and session configuration |
|
||||||
|
|
||||||
- Send SMS through Android device via Termux API
|
### Security
|
||||||
- Automatic fallback to ADB if Termux unavailable
|
|
||||||
- Track message delivery status
|
|
||||||
- Monitor device battery and connectivity
|
|
||||||
- Auto-retry failed messages
|
|
||||||
|
|
||||||
### Campaign Features
|
| Guide | Description |
|
||||||
|
|-------|-------------|
|
||||||
- Import contacts from CSV files
|
| [Security Setup](security/security-setup.md) | API keys, Docker security, best practices |
|
||||||
- Personalize messages with template variables
|
| [API Security](security/api-security.md) | API authentication implementation |
|
||||||
- Schedule message batches
|
|
||||||
- Real-time analytics dashboard
|
|
||||||
- Track SMS replies
|
|
||||||
|
|
||||||
### Deployment
|
### Deployment
|
||||||
|
|
||||||
- Docker Compose setup
|
| Guide | Description |
|
||||||
- Environment-based configuration
|
|-------|-------------|
|
||||||
- Automatic health monitoring
|
| [Deployment Guide](deployment/deployment-guide.md) | Production deployment with Tailscale |
|
||||||
- One-command deployment scripts
|
|
||||||
|
|
||||||
## Architecture
|
### User Guides
|
||||||
|
|
||||||
The system has three main components:
|
| Guide | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| [User Management](guides/user-management.md) | Creating and managing users |
|
||||||
|
| [Testing](guides/testing.md) | Verification and testing procedures |
|
||||||
|
| [Troubleshooting](guides/troubleshooting.md) | Common issues and solutions |
|
||||||
|
|
||||||
1. **Flask Web Application** (Ubuntu server, port 5000)
|
### Development
|
||||||
- Web dashboard for campaign management
|
|
||||||
- REST API for external integrations
|
|
||||||
- SQLite database for data storage
|
|
||||||
|
|
||||||
2. **Termux API Server** (Android device, port 5001)
|
| Guide | Description |
|
||||||
- Communicates with Android SMS system
|
|-------|-------------|
|
||||||
- Provides device status information
|
| [Android Development](development/android-dev-setup.md) | Android device configuration |
|
||||||
- Handles message sending
|
| [Termux Flask Setup](development/termux-flask-setup.md) | Termux server configuration |
|
||||||
|
|
||||||
3. **Android Monitor** (Android device, port 5000)
|
### Reference
|
||||||
- Dashboard running on Android
|
|
||||||
- Device health monitoring
|
|
||||||
- Service status tracking
|
|
||||||
|
|
||||||
## Quick Start
|
| Guide | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| [API Endpoints](api/endpoints.md) | Complete API reference |
|
||||||
|
| [Environment Variables](reference/environment-variables.md) | Configuration options |
|
||||||
|
| [File Structure](reference/files.md) | Project organization |
|
||||||
|
| [Project Instructions](reference/project-instructions.md) | Development guidelines |
|
||||||
|
|
||||||
```bash
|
## System Architecture
|
||||||
# 1. Configure environment
|
|
||||||
cp .env.example .env
|
|
||||||
nano .env # Set your Android device IP
|
|
||||||
|
|
||||||
# 2. Deploy to Android device
|
```
|
||||||
./scripts/deploy-android.sh
|
Ubuntu Server (Docker) Android Device (Termux)
|
||||||
|
┌─────────────────────┐ ┌─────────────────────┐
|
||||||
# 3. Start the Flask application
|
│ Flask Web App │ │ Termux SMS API │
|
||||||
docker compose up -d
|
│ Port 5000 │◄──────►│ Port 5001 │
|
||||||
|
│ │ │ │
|
||||||
# 4. Open web dashboard
|
│ - Campaign Mgmt │ │ - SMS Sending │
|
||||||
open http://localhost:5000
|
│ - Contact Upload │ │ - Device Status │
|
||||||
# Default login: admin / @thebunker
|
│ - Analytics │ │ - Response Sync │
|
||||||
|
│ - User Auth │ │ │
|
||||||
|
└─────────────────────┘ └─────────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
SQLite Database Android SMS System
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [Quick Start Guide](setup/quick-start.md) for detailed setup instructions.
|
## Key Features
|
||||||
|
|
||||||
## Common Use Cases
|
**Campaign Management**
|
||||||
|
- Create and schedule SMS campaigns
|
||||||
|
- Import contacts from CSV files
|
||||||
|
- Personalize messages with template variables
|
||||||
|
- Track delivery and responses
|
||||||
|
|
||||||
- Marketing campaigns to customer lists
|
**User Management**
|
||||||
- Automated appointment reminders
|
- Web-based login (no browser extensions needed)
|
||||||
- System notifications and alerts
|
- Role-based access control (Admin/User)
|
||||||
- Testing SMS integrations
|
- API key authentication for automation
|
||||||
- Personal message automation
|
- 24-hour session persistence
|
||||||
|
|
||||||
|
**Android Integration**
|
||||||
|
- Send SMS via Termux API
|
||||||
|
- Automatic device status monitoring
|
||||||
|
- Battery and connectivity tracking
|
||||||
|
- Fallback to ADB if needed
|
||||||
|
|
||||||
|
**Security**
|
||||||
|
- API key authentication
|
||||||
|
- Session-based web authentication
|
||||||
|
- Docker container isolation
|
||||||
|
- Encrypted Tailscale connectivity
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Docker and Docker Compose installed
|
**Ubuntu Server**
|
||||||
- Android device with Termux and Termux:API apps
|
- Docker and Docker Compose
|
||||||
- SSH access to Android device (typically port 8022)
|
- Tailscale (recommended) or local network access
|
||||||
- Network connection between server and Android
|
|
||||||
- Tailscale recommended for reliable connectivity
|
**Android Device**
|
||||||
- Local network also works
|
- Termux (from F-Droid, not Google Play)
|
||||||
|
- Termux:API (from F-Droid)
|
||||||
|
- Tailscale app
|
||||||
|
- SSH server enabled in Termux
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
|
||||||
|
- **Start Here**: [Installation Guide](setup/installation.md)
|
||||||
|
- **Having Issues?**: [Troubleshooting](guides/troubleshooting.md)
|
||||||
|
- **API Reference**: [API Endpoints](api/endpoints.md)
|
||||||
|
- **Configuration**: [Environment Variables](reference/environment-variables.md)
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
campaign_connector/
|
campaign_connector/
|
||||||
├── src/ # Flask application code
|
├── src/ # Flask application code
|
||||||
├── android/ # Android-side Python servers
|
├── android/ # Android Termux servers
|
||||||
├── docs/ # Documentation (this site)
|
├── docs/ # Documentation (this site)
|
||||||
├── scripts/ # Deployment and utility scripts
|
├── scripts/ # Deployment and utility scripts
|
||||||
├── docker/ # Docker configuration
|
├── docker/ # Docker configuration
|
||||||
└── data/ # SQLite database (created at runtime)
|
├── data/ # SQLite database (runtime)
|
||||||
|
├── uploads/ # CSV uploads (runtime)
|
||||||
|
└── logs/ # Application logs (runtime)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation Navigation
|
|
||||||
|
|
||||||
- **Getting Started**
|
|
||||||
- [Quick Start](setup/quick-start.md) - Installation and first run
|
|
||||||
- [Authentication Setup](setup/authentication.md) - User and API key setup
|
|
||||||
|
|
||||||
- **Deployment**
|
|
||||||
- [Deployment Guide](deployment/deployment-guide.md) - Production deployment
|
|
||||||
|
|
||||||
- **User Guides**
|
|
||||||
- [User Management](guides/user-management.md) - Adding and managing users
|
|
||||||
|
|
||||||
- **Development**
|
|
||||||
- [Android Development](development/android-dev-setup.md) - Android setup details
|
|
||||||
- [Termux Flask Setup](development/termux-flask-setup.md) - Termux configuration
|
|
||||||
|
|
||||||
- **Reference**
|
|
||||||
- [File Structure](reference/files.md) - Detailed file organization
|
|
||||||
- [Project Instructions](reference/project-instructions.md) - Development guidelines
|
|
||||||
|
|
||||||
## Technology Stack
|
|
||||||
|
|
||||||
- **Backend**: Flask 3.0.0 (Python web framework)
|
|
||||||
- **Database**: SQLite (embedded database)
|
|
||||||
- **Frontend**: HTML, Tailwind CSS, vanilla JavaScript
|
|
||||||
- **Android**: Termux, Termux:API, ADB
|
|
||||||
- **Deployment**: Docker, Docker Compose
|
|
||||||
- **Networking**: Tailscale (optional but recommended)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Copyright © 2025 Campaign Connector Team
|
|
||||||
|
|||||||
@ -1,355 +1,225 @@
|
|||||||
# API Security Implementation Summary
|
# API Security
|
||||||
|
|
||||||
## ✅ What Was Implemented
|
This document covers the API authentication implementation in SMS Campaign Manager.
|
||||||
|
|
||||||
Your SMS Campaign Manager application has been secured with comprehensive API key authentication.
|
## Authentication System
|
||||||
|
|
||||||
### 1. Authentication System
|
All API endpoints require authentication via one of:
|
||||||
**File**: [src/core/auth.py](src/core/auth.py)
|
|
||||||
|
|
||||||
- ✅ API key-based authentication system
|
- **X-API-Key header**: `X-API-Key: YOUR_KEY`
|
||||||
- ✅ Three-tier access control (Admin, User, Termux)
|
- **Bearer token**: `Authorization: Bearer YOUR_KEY`
|
||||||
- ✅ Constant-time comparison to prevent timing attacks
|
- **Session cookie**: From web login
|
||||||
- ✅ SHA-256 key hashing for secure storage
|
|
||||||
- ✅ Cryptographically secure key generation
|
|
||||||
|
|
||||||
### 2. Protected Endpoints
|
## API Key Types
|
||||||
|
|
||||||
All API endpoints now require authentication:
|
| Key | Variable | Access Level |
|
||||||
|
|-----|----------|--------------|
|
||||||
|
| Admin | `ADMIN_API_KEY` | Full access including database reset |
|
||||||
|
| User | `USER_API_KEY` | Standard operations |
|
||||||
|
| Termux | `TERMUX_API_KEY` | Android device communication |
|
||||||
|
|
||||||
#### Flask Application Routes
|
### Admin Key
|
||||||
**Protected with `@require_auth()` decorator**:
|
|
||||||
|
|
||||||
- **Campaign Routes** ([src/routes/api/campaign_routes.py](src/routes/api/campaign_routes.py))
|
Use for administrative operations:
|
||||||
- `/api/campaign/create` - User role required
|
|
||||||
- `/api/campaign/start` - User role required
|
|
||||||
- `/api/campaign/pause` - User role required
|
|
||||||
- `/api/campaign/resume` - User role required
|
|
||||||
- `/api/campaign/status` - User role required
|
|
||||||
- `/api/campaign/list` - User role required
|
|
||||||
- `/api/campaign/recent` - User role required
|
|
||||||
|
|
||||||
- **SMS Routes** ([src/routes/api/sms_routes.py](src/routes/api/sms_routes.py))
|
- All user permissions
|
||||||
- `/api/sms/test/real` - User role required
|
- `/api/database/reset` - Database reset
|
||||||
- `/api/sms/test` - User role required
|
- System configuration changes
|
||||||
- `/api/sms/send/enhanced` - User role required
|
|
||||||
- `/api/sms/status` - User role required
|
|
||||||
|
|
||||||
- **Upload Routes** ([src/routes/api/upload_routes.py](src/routes/api/upload_routes.py))
|
### User Key
|
||||||
- `/api/csv/upload` - User role required
|
|
||||||
- `/api/campaign/upload` - User role required
|
|
||||||
|
|
||||||
- **Database Routes** ([src/routes/api/database_routes.py](src/routes/api/database_routes.py))
|
Use for standard operations:
|
||||||
- `/api/database/reset` - **Admin role required** ⚠️
|
|
||||||
- `/api/database/stats` - User role required
|
|
||||||
|
|
||||||
#### Termux API Server (Android)
|
- Campaign management
|
||||||
**Protected with `verify_api_key()` function**:
|
- SMS sending
|
||||||
|
- CSV upload
|
||||||
|
- Analytics viewing
|
||||||
|
|
||||||
- `/api/sms/send` - Authentication required
|
### Termux Key
|
||||||
- `/api/sms/send-reply` - Authentication required
|
|
||||||
|
|
||||||
### 3. Configuration Updates
|
Use for Android communication:
|
||||||
|
|
||||||
- ✅ Updated [.gitignore](.gitignore) to prevent secret leaks
|
- Internal server-to-device requests
|
||||||
- ✅ Created [.env.example](.env.example) template
|
- SMS sending via Termux API
|
||||||
- ✅ Updated [src/app.py](src/app.py) to initialize auth manager
|
- Device status queries
|
||||||
- ✅ Updated [android/termux-sms-api-server.py](android/termux-sms-api-server.py) with auth
|
|
||||||
- ✅ Updated [src/services/sms/connection_manager.py](src/services/sms/connection_manager.py) to pass API keys
|
|
||||||
|
|
||||||
### 4. Documentation Created
|
## Protected Endpoints
|
||||||
|
|
||||||
- 📄 [SECURITY_SETUP.md](SECURITY_SETUP.md) - Complete setup guide
|
### Campaign Routes
|
||||||
- 📄 [API_SECURITY_SUMMARY.md](API_SECURITY_SUMMARY.md) - This file
|
|
||||||
- 📄 [.env.example](.env.example) - Environment template
|
|
||||||
- 🔧 [generate-api-keys.sh](generate-api-keys.sh) - Key generation script
|
|
||||||
|
|
||||||
---
|
All require User role minimum:
|
||||||
|
|
||||||
## 🚀 Quick Start Guide
|
- `POST /api/campaign/create`
|
||||||
|
- `POST /api/campaign/start`
|
||||||
|
- `POST /api/campaign/pause`
|
||||||
|
- `POST /api/campaign/resume`
|
||||||
|
- `GET /api/campaign/status`
|
||||||
|
- `GET /api/campaign/list`
|
||||||
|
|
||||||
### Step 1: Generate API Keys
|
### SMS Routes
|
||||||
|
|
||||||
```bash
|
All require User role minimum:
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
|
||||||
./generate-api-keys.sh
|
- `POST /api/sms/send/enhanced`
|
||||||
|
- `POST /api/sms/test`
|
||||||
|
- `GET /api/sms/status`
|
||||||
|
|
||||||
|
### Upload Routes
|
||||||
|
|
||||||
|
All require User role minimum:
|
||||||
|
|
||||||
|
- `POST /api/csv/upload`
|
||||||
|
- `POST /api/campaign/upload`
|
||||||
|
|
||||||
|
### Database Routes
|
||||||
|
|
||||||
|
- `GET /api/database/stats` - User role
|
||||||
|
- `POST /api/database/reset` - Admin role only
|
||||||
|
|
||||||
|
### Android Termux API
|
||||||
|
|
||||||
|
Protected endpoints on Android:
|
||||||
|
|
||||||
|
- `POST /api/sms/send`
|
||||||
|
- `POST /api/sms/send-reply`
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
### Key Hashing
|
||||||
|
|
||||||
|
API keys are hashed with SHA-256 before comparison. Keys are never stored in plaintext.
|
||||||
|
|
||||||
|
### Constant-Time Comparison
|
||||||
|
|
||||||
|
All key comparisons use constant-time algorithms to prevent timing attacks.
|
||||||
|
|
||||||
|
### Role-Based Access
|
||||||
|
|
||||||
|
Each endpoint declares required role level:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@require_auth(role='admin') # Admin only
|
||||||
|
@require_auth(role='user') # User or Admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run directly:
|
## Generating Keys
|
||||||
|
|
||||||
|
Generate new API keys:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 src/core/auth.py
|
python3 src/core/auth.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Update .env File
|
This generates cryptographically secure 64-character hex strings.
|
||||||
|
|
||||||
Copy the generated keys to your `.env` file:
|
## Using API Keys
|
||||||
|
|
||||||
```env
|
### curl Examples
|
||||||
ADMIN_API_KEY=<generated_admin_key>
|
|
||||||
USER_API_KEY=<generated_user_key>
|
|
||||||
TERMUX_API_KEY=<generated_termux_key>
|
|
||||||
SECRET_KEY=<generated_secret_key>
|
|
||||||
TERMUX_API_SECRET=<same_as_termux_api_key>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Update Android Termux
|
|
||||||
|
|
||||||
SSH to Android and set the API key:
|
|
||||||
```bash
|
|
||||||
ssh android-dev@100.107.173.66 -p 8022
|
|
||||||
echo "SMS_API_SECRET=<your_termux_api_key>" >> ~/projects/sms-campaign-manager/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Restart Services
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Restart Docker container
|
# X-API-Key header
|
||||||
docker-compose restart
|
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/campaign/list
|
||||||
|
|
||||||
# Restart Termux API server (on Android)
|
# Bearer token
|
||||||
ssh android-dev@100.107.173.66 -p 8022 "~/bin/sms-service.sh restart"
|
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:5000/api/campaign/list
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Test Authentication
|
# POST with data
|
||||||
|
curl -X POST \
|
||||||
```bash
|
-H "X-API-Key: YOUR_KEY" \
|
||||||
# Should FAIL without API key
|
|
||||||
curl http://localhost:5000/api/campaign/list
|
|
||||||
|
|
||||||
# Should SUCCEED with API key
|
|
||||||
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 API Key Roles
|
|
||||||
|
|
||||||
### Admin API Key
|
|
||||||
**Full system access including destructive operations**
|
|
||||||
|
|
||||||
Permissions:
|
|
||||||
- ✅ All User permissions
|
|
||||||
- ✅ Database reset
|
|
||||||
- ✅ System configuration
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
```bash
|
|
||||||
curl -H "X-API-Key: ADMIN_API_KEY" http://localhost:5000/api/database/reset
|
|
||||||
```
|
|
||||||
|
|
||||||
### User API Key
|
|
||||||
**Regular application access for daily operations**
|
|
||||||
|
|
||||||
Permissions:
|
|
||||||
- ✅ Create/manage campaigns
|
|
||||||
- ✅ Send SMS messages
|
|
||||||
- ✅ Upload CSV files
|
|
||||||
- ✅ View analytics
|
|
||||||
- ❌ Database reset
|
|
||||||
- ❌ System config changes
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
```bash
|
|
||||||
curl -H "X-API-Key: USER_API_KEY" http://localhost:5000/api/campaign/create \
|
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"name":"Test Campaign","message":"Hello {name}"}'
|
-d '{"phone":"+1234567890","message":"Hello"}' \
|
||||||
|
http://localhost:5000/api/sms/send/enhanced
|
||||||
```
|
```
|
||||||
|
|
||||||
### Termux API Key
|
### Python Example
|
||||||
**Android device communication**
|
|
||||||
|
|
||||||
Permissions:
|
```python
|
||||||
- ✅ Send SMS via Termux
|
import requests
|
||||||
- ✅ Query SMS history
|
|
||||||
- ✅ Device status
|
|
||||||
|
|
||||||
Usage:
|
API_KEY = "your-api-key-here"
|
||||||
```bash
|
BASE_URL = "http://localhost:5000"
|
||||||
curl -H "X-API-Key: TERMUX_API_KEY" http://100.107.173.66:5001/api/sms/send \
|
|
||||||
-H "Content-Type: application/json" \
|
headers = {
|
||||||
-d '{"phone":"1234567890","message":"Test"}'
|
"X-API-Key": API_KEY,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List campaigns
|
||||||
|
response = requests.get(f"{BASE_URL}/api/campaign/list", headers=headers)
|
||||||
|
print(response.json())
|
||||||
|
|
||||||
|
# Send SMS
|
||||||
|
data = {"phone": "+1234567890", "message": "Test"}
|
||||||
|
response = requests.post(f"{BASE_URL}/api/sms/send/enhanced", headers=headers, json=data)
|
||||||
|
print(response.json())
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Error Responses
|
||||||
|
|
||||||
## 🔐 How Authentication Works
|
### 401 Unauthorized
|
||||||
|
|
||||||
### Request Flow
|
No API key or invalid key:
|
||||||
|
|
||||||
1. **Client makes request** with API key in header
|
```json
|
||||||
```
|
{
|
||||||
X-API-Key: abc123...xyz
|
"error": "Authentication required",
|
||||||
```
|
"message": "Please provide valid API key"
|
||||||
|
}
|
||||||
2. **AuthManager validates key**
|
|
||||||
- Extracts key from `X-API-Key` or `Authorization: Bearer` header
|
|
||||||
- Hashes provided key with SHA-256
|
|
||||||
- Compares with stored key hashes using constant-time comparison
|
|
||||||
- Determines user role (admin/user/termux)
|
|
||||||
|
|
||||||
3. **Authorization check**
|
|
||||||
- Compares user role against endpoint requirements
|
|
||||||
- Admin (level 3) > User (level 2) > Termux (level 1)
|
|
||||||
- Allows access if user level >= required level
|
|
||||||
|
|
||||||
4. **Request processing**
|
|
||||||
- If authorized: Request proceeds to endpoint
|
|
||||||
- If unauthorized: Returns 401 or 403 error
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
|
|
||||||
✅ **Constant-time comparison** - Prevents timing attacks
|
|
||||||
✅ **SHA-256 hashing** - Keys never stored in plaintext
|
|
||||||
✅ **Role-based access** - Principle of least privilege
|
|
||||||
✅ **Environment variables** - Secrets never in code
|
|
||||||
✅ **Comprehensive logging** - All auth attempts logged
|
|
||||||
✅ **Multiple auth methods** - Header or Bearer token
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Using with Web Dashboard
|
|
||||||
|
|
||||||
### Option 1: Browser Extension (Recommended)
|
|
||||||
|
|
||||||
Install **ModHeader** (Chrome/Firefox):
|
|
||||||
1. Add header: `X-API-Key`
|
|
||||||
2. Value: Your `USER_API_KEY`
|
|
||||||
3. Filter: `http://localhost:5000/*`
|
|
||||||
|
|
||||||
### Option 2: Modify JavaScript
|
|
||||||
|
|
||||||
Edit [src/static/js/dashboard.js](src/static/js/dashboard.js):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Add to all fetch() calls
|
|
||||||
const headers = {
|
|
||||||
'X-API-Key': 'YOUR_USER_API_KEY',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch('/api/campaign/list', { headers })
|
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ **WARNING**: Don't commit API keys to JavaScript files!
|
### 403 Forbidden
|
||||||
|
|
||||||
---
|
Valid key but insufficient permissions:
|
||||||
|
|
||||||
## 🛡️ Security Best Practices
|
```json
|
||||||
|
{
|
||||||
|
"error": "Insufficient permissions",
|
||||||
|
"message": "Admin role required"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- Generate unique random keys
|
||||||
|
- Store keys in `.env` file only
|
||||||
|
- Set file permissions: `chmod 600 .env`
|
||||||
|
- Never commit keys to version control
|
||||||
|
|
||||||
|
### Recommended
|
||||||
|
|
||||||
### ✅ DO:
|
|
||||||
- Store keys in environment variables only
|
|
||||||
- Use different keys for different roles
|
|
||||||
- Rotate keys every 90 days
|
- Rotate keys every 90 days
|
||||||
- Monitor logs for unauthorized attempts
|
- Use different keys per environment
|
||||||
|
- Monitor failed authentication logs
|
||||||
- Use HTTPS in production (Tailscale provides this)
|
- Use HTTPS in production (Tailscale provides this)
|
||||||
- Keep `.env` file permissions at 600
|
|
||||||
- Back up keys securely (encrypted)
|
|
||||||
|
|
||||||
### ❌ DON'T:
|
## Troubleshooting
|
||||||
- Commit `.env` to git
|
|
||||||
- Share keys in plain text
|
|
||||||
- Use same key across environments
|
|
||||||
- Store keys in JavaScript/HTML
|
|
||||||
- Log API keys in application logs
|
|
||||||
- Reuse compromised keys
|
|
||||||
- Use weak or predictable keys
|
|
||||||
|
|
||||||
---
|
### Invalid API Key
|
||||||
|
|
||||||
## 🔄 Key Rotation
|
|
||||||
|
|
||||||
Rotate keys every 90 days:
|
|
||||||
|
|
||||||
1. Generate new keys: `./generate-api-keys.sh`
|
|
||||||
2. Update `.env` files (server + Android)
|
|
||||||
3. Restart all services
|
|
||||||
4. Update any scripts/apps using old keys
|
|
||||||
5. Test all functionality
|
|
||||||
6. Securely delete old keys
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
|
||||||
|
|
||||||
### "Authentication required" error
|
|
||||||
**Solution**: Add API key to request headers
|
|
||||||
```bash
|
```bash
|
||||||
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/endpoint
|
# Verify key is set in container
|
||||||
```
|
docker compose exec sms-campaign env | grep API_KEY
|
||||||
|
|
||||||
### "Invalid API key" error
|
# Check .env format (no spaces around =)
|
||||||
**Causes**:
|
|
||||||
- Wrong key copied (check for extra spaces)
|
|
||||||
- Key not in .env file
|
|
||||||
- Services not restarted after .env update
|
|
||||||
- Using wrong role key (e.g., user key for admin endpoint)
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
```bash
|
|
||||||
# Verify .env has correct keys
|
|
||||||
cat .env | grep API_KEY
|
cat .env | grep API_KEY
|
||||||
|
|
||||||
# Restart services
|
# Restart to reload
|
||||||
docker-compose restart
|
docker compose restart
|
||||||
```
|
```
|
||||||
|
|
||||||
### Application won't start
|
### Permission Denied
|
||||||
**Error**: "API keys must be configured"
|
|
||||||
|
|
||||||
**Solution**:
|
Using wrong key type for endpoint:
|
||||||
```bash
|
|
||||||
# Generate keys
|
|
||||||
python3 src/core/auth.py
|
|
||||||
|
|
||||||
# Add to .env
|
- User key for admin endpoints returns 403
|
||||||
nano .env
|
- Check endpoint requires admin role
|
||||||
|
- Use admin key for privileged operations
|
||||||
|
|
||||||
# Restart
|
## Related Documentation
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Web dashboard not working
|
- [Security Setup](security-setup.md) - Complete security configuration
|
||||||
**Solution**: Use browser extension or update JavaScript (see above)
|
- [Authentication Setup](../setup/authentication.md) - User login
|
||||||
|
- [API Endpoints](../api/endpoints.md) - Full endpoint reference
|
||||||
---
|
- [Environment Variables](../reference/environment-variables.md) - Configuration
|
||||||
|
|
||||||
## 📊 Security Improvements Made
|
|
||||||
|
|
||||||
| Issue | Severity | Status |
|
|
||||||
|-------|----------|--------|
|
|
||||||
| No authentication | 🔴 Critical | ✅ Fixed |
|
|
||||||
| Database reset unprotected | 🔴 Critical | ✅ Fixed |
|
|
||||||
| Hardcoded secrets in git | 🔴 Critical | ✅ Fixed |
|
|
||||||
| Termux API unprotected | 🟠 High | ✅ Fixed |
|
|
||||||
| .env in git history | 🟠 High | ⚠️ Action required |
|
|
||||||
| No role-based access | 🟡 Medium | ✅ Fixed |
|
|
||||||
| Weak secret keys | 🟡 Medium | ✅ Fixed |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps (Optional Enhancements)
|
|
||||||
|
|
||||||
1. **Rate limiting** - Add Flask-Limiter to prevent API abuse
|
|
||||||
2. **Request logging** - Log all API calls for audit trail
|
|
||||||
3. **IP whitelisting** - Restrict access by IP address
|
|
||||||
4. **Tailscale ACLs** - Use Tailscale's access controls
|
|
||||||
5. **Session tokens** - Implement JWT for web dashboard
|
|
||||||
6. **2FA** - Add two-factor authentication for admin operations
|
|
||||||
7. **API versioning** - Version your API endpoints
|
|
||||||
8. **Monitoring** - Set up security alerts and dashboards
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
For issues or questions:
|
|
||||||
1. Check [SECURITY_SETUP.md](SECURITY_SETUP.md) for detailed guide
|
|
||||||
2. Review logs: `docker-compose logs -f sms-campaign`
|
|
||||||
3. Test connectivity: `curl http://localhost:5000/health`
|
|
||||||
4. Verify environment: `docker-compose exec sms-campaign env | grep API_KEY`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created**: 2025-12-30
|
|
||||||
**Version**: 2.0 (Secured)
|
|
||||||
**Last Updated**: 2025-12-30
|
|
||||||
|
|||||||
214
docs/security/security-handoff.md
Normal file
214
docs/security/security-handoff.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# Security Handoff Report
|
||||||
|
|
||||||
|
**Date:** 2026-01-01
|
||||||
|
**Project:** SMS Campaign Manager
|
||||||
|
**Status:** All critical, high, and medium priority issues resolved
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Completed Security Fixes
|
||||||
|
|
||||||
|
| Issue | Severity | Status |
|
||||||
|
|-------|----------|--------|
|
||||||
|
| Docker privileged mode | Critical | Fixed |
|
||||||
|
| Docker host networking | Critical | Fixed |
|
||||||
|
| Hardcoded API secret | Critical | Fixed |
|
||||||
|
| Command injection (IP detection) | Critical | Fixed |
|
||||||
|
| API keys in query parameters | High | Fixed |
|
||||||
|
| No security headers | High | Fixed |
|
||||||
|
| SameSite cookie too permissive | Medium | Fixed |
|
||||||
|
| Login redirect error | Medium | Fixed |
|
||||||
|
| No CSRF protection | Medium | Fixed |
|
||||||
|
| Error messages expose internal details | Medium | Fixed |
|
||||||
|
| No input validation on phone numbers | Medium | Fixed |
|
||||||
|
| CSV injection not prevented | Medium | Fixed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Headers Now Active
|
||||||
|
|
||||||
|
All responses now include:
|
||||||
|
```
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
X-XSS-Protection: 1; mode=block
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSRF Protection
|
||||||
|
|
||||||
|
Flask-WTF CSRF protection is now enabled:
|
||||||
|
- All state-changing requests require CSRF token
|
||||||
|
- Token available via meta tag in templates: `<meta name="csrf-token">`
|
||||||
|
- JavaScript helper `secureFetch()` in common.js automatically includes token
|
||||||
|
- Login endpoint exempt (no session before login)
|
||||||
|
- API key authenticated endpoints exempt (separate auth mechanism)
|
||||||
|
|
||||||
|
**Files modified:**
|
||||||
|
- `src/app.py` - Added CSRFProtect initialization
|
||||||
|
- `src/requirements.txt` - Added Flask-WTF==1.2.1
|
||||||
|
- `src/templates/base.html` - Added CSRF meta tag
|
||||||
|
- `src/static/js/common.js` - Added `getCSRFToken()` and `secureFetch()` helpers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Message Sanitization
|
||||||
|
|
||||||
|
Created standardized error handler module:
|
||||||
|
- `src/core/error_handler.py` - Centralized error handling
|
||||||
|
- Logs full error details server-side
|
||||||
|
- Returns generic safe messages to clients
|
||||||
|
- Prevents internal path/stack trace disclosure
|
||||||
|
|
||||||
|
**Files updated:**
|
||||||
|
- `src/routes/lists.py` - 6 locations
|
||||||
|
- `src/routes/conversations.py` - 8 locations
|
||||||
|
- `src/routes/api/upload_routes.py` - 4 locations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phone Number Validation
|
||||||
|
|
||||||
|
Created phone validation module:
|
||||||
|
- `src/core/phone_validation.py` - Validation and normalization
|
||||||
|
- Validates minimum/maximum length (7-15 digits per E.164)
|
||||||
|
- Normalizes to consistent format
|
||||||
|
- Filters invalid numbers during CSV upload
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- `validate_phone_number()` - Returns (is_valid, normalized, error)
|
||||||
|
- `normalize_phone_number()` - Strips formatting, keeps digits
|
||||||
|
- `format_phone_display()` - Formats for UI display
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSV Injection Prevention
|
||||||
|
|
||||||
|
Added sanitization for uploaded CSV files:
|
||||||
|
- Prefixes dangerous characters with single quote
|
||||||
|
- Prevents formula execution: `=`, `+`, `-`, `@`, `\t`, `\r`, `\n`
|
||||||
|
- Applied to all CSV upload endpoints
|
||||||
|
|
||||||
|
**Functions added to upload_routes.py:**
|
||||||
|
- `sanitize_csv_value()` - Sanitizes individual values
|
||||||
|
- `sanitize_csv_row()` - Sanitizes entire row dict
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Low Priority / Best Practices (Pending)
|
||||||
|
|
||||||
|
| Issue | Recommendation |
|
||||||
|
|-------|----------------|
|
||||||
|
| Database timeout (30s) | Reduce to 5-10 seconds |
|
||||||
|
| Rate limiting | Add per-user limiting for authenticated endpoints |
|
||||||
|
| Dependency scanning | Add `safety check` to CI/CD |
|
||||||
|
| Audit logging | Log failed logins, API usage, admin actions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk Summary
|
||||||
|
|
||||||
|
| Severity | Count | Status |
|
||||||
|
|----------|-------|--------|
|
||||||
|
| Critical | 4 | All Fixed |
|
||||||
|
| High | 2 | All Fixed |
|
||||||
|
| Medium | 8 | All Fixed |
|
||||||
|
| Low | 4 | Pending |
|
||||||
|
|
||||||
|
**Overall Risk Level:** Very Low (all critical/high/medium resolved)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Modified During Security Review
|
||||||
|
|
||||||
|
### Phase 1 - Critical/High Fixes:
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `docker-compose.yml` | Removed privileged mode and host networking |
|
||||||
|
| `android/termux-sms-api-server.py` | Removed default secret, added validation, fixed command injection |
|
||||||
|
| `src/routes/auth_routes.py` | Fixed dashboard redirect to campaigns |
|
||||||
|
| `src/core/auth.py` | Removed API key query parameter support |
|
||||||
|
| `src/app.py` | Added security headers, changed SameSite to Strict |
|
||||||
|
|
||||||
|
### Phase 2 - Medium Priority Fixes:
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `src/app.py` | Added CSRF protection with Flask-WTF |
|
||||||
|
| `src/requirements.txt` | Added Flask-WTF dependency |
|
||||||
|
| `src/templates/base.html` | Added CSRF meta tag |
|
||||||
|
| `src/static/js/common.js` | Added CSRF token helpers |
|
||||||
|
| `src/routes/lists.py` | Sanitized error messages |
|
||||||
|
| `src/routes/conversations.py` | Sanitized error messages |
|
||||||
|
| `src/routes/api/upload_routes.py` | Error sanitization, CSV injection prevention, phone validation |
|
||||||
|
|
||||||
|
### New Files Created:
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `android/setup-api-key.sh` | Android API key setup script |
|
||||||
|
| `scripts/update-api-keys.sh` | Ubuntu API key update script |
|
||||||
|
| `src/core/error_handler.py` | Standardized error handling module |
|
||||||
|
| `src/core/phone_validation.py` | Phone number validation utilities |
|
||||||
|
| `SECURITY_FIXES.md` | Documentation of critical fixes |
|
||||||
|
| `test-setup-flow.sh` | Automated validation script |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Security Configuration
|
||||||
|
|
||||||
|
**API Authentication:**
|
||||||
|
- Android (Termux): `SMS_API_SECRET` in `~/.bashrc`
|
||||||
|
- Ubuntu (.env): `TERMUX_API_KEY`, `SMS_API_SECRET`
|
||||||
|
- Headers only (query parameters disabled)
|
||||||
|
|
||||||
|
**Docker Security:**
|
||||||
|
- Privileged: `false`
|
||||||
|
- NetworkMode: `bridge` (isolated)
|
||||||
|
|
||||||
|
**Session Security:**
|
||||||
|
- Cookie: HttpOnly, Secure (when HTTPS), SameSite=Strict
|
||||||
|
- Remember Cookie: SameSite=Strict
|
||||||
|
- CSRF: Enabled via Flask-WTF
|
||||||
|
- Timeout: 24 hours
|
||||||
|
|
||||||
|
**Response Headers:**
|
||||||
|
- X-Frame-Options: DENY
|
||||||
|
- X-Content-Type-Options: nosniff
|
||||||
|
- X-XSS-Protection: 1; mode=block
|
||||||
|
- Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
- Permissions-Policy: restricted
|
||||||
|
|
||||||
|
**Input Validation:**
|
||||||
|
- Phone numbers: Validated and normalized on upload
|
||||||
|
- CSV files: Sanitized to prevent formula injection
|
||||||
|
- Error messages: Generic responses, full details logged server-side
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify Docker security
|
||||||
|
docker inspect sms-campaign-manager | grep -E "Privileged|NetworkMode"
|
||||||
|
|
||||||
|
# Verify security headers
|
||||||
|
curl -I http://localhost:5000/health | grep -E "X-Frame|X-Content|X-XSS"
|
||||||
|
|
||||||
|
# Verify CSRF token endpoint
|
||||||
|
curl http://localhost:5000/api/csrf-token
|
||||||
|
|
||||||
|
# Verify API authentication
|
||||||
|
curl -H "X-API-Key: $(grep TERMUX_API_KEY .env | cut -d= -f2)" \
|
||||||
|
http://100.107.173.66:5001/health
|
||||||
|
|
||||||
|
# Run dependency security check
|
||||||
|
pip install safety && safety check
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Report generated:** 2026-01-01
|
||||||
|
**Last updated:** 2026-01-01 (medium priority fixes implemented)
|
||||||
|
**Status:** Ready for production
|
||||||
@ -1,357 +1,312 @@
|
|||||||
# Security Setup Guide - SMS Campaign Manager
|
# Security Setup Guide
|
||||||
|
|
||||||
## 🔒 Immediate Security Setup Required
|
This guide covers security configuration for SMS Campaign Manager, including API key generation, Docker security, and best practices.
|
||||||
|
|
||||||
This application now requires API key authentication for all endpoints. Follow these steps to complete the security setup.
|
## Prerequisites
|
||||||
|
|
||||||
---
|
Before configuring security:
|
||||||
|
|
||||||
## Step 1: Generate Secure API Keys
|
- Docker and Docker Compose installed
|
||||||
|
- Access to Android device (Termux)
|
||||||
|
- `.env` file created from `.env.example`
|
||||||
|
|
||||||
Run the key generation script to create cryptographically secure API keys:
|
## Quick Setup
|
||||||
|
|
||||||
|
### Step 1: Generate API Keys
|
||||||
|
|
||||||
|
Run the key generation script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
cd /mnt/storagessd1tb/campaign_connector
|
||||||
python3 src/core/auth.py
|
python3 src/core/auth.py
|
||||||
```
|
```
|
||||||
|
|
||||||
This will generate and display new API keys. **Copy these keys immediately** - they will only be shown once.
|
This generates cryptographically secure keys for:
|
||||||
|
|
||||||
---
|
- `ADMIN_API_KEY` - Full system access
|
||||||
|
- `USER_API_KEY` - Regular operations
|
||||||
|
- `TERMUX_API_KEY` - Android device communication
|
||||||
|
- `SECRET_KEY` - Session encryption
|
||||||
|
|
||||||
## Step 2: Update Environment Configuration
|
Copy the generated keys immediately as they are only shown once.
|
||||||
|
|
||||||
### On Ubuntu Homelab Server
|
### Step 2: Configure Environment
|
||||||
|
|
||||||
1. **IMPORTANT**: Back up your current .env file (if it exists):
|
Back up and edit your `.env` file:
|
||||||
```bash
|
|
||||||
cp .env .env.backup
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Edit your `.env` file:
|
|
||||||
```bash
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Replace the old keys with the newly generated ones:
|
|
||||||
```env
|
|
||||||
# Android Device Configuration (Tailscale IP)
|
|
||||||
PHONE_IP=100.107.173.66
|
|
||||||
ADB_PORT=5555
|
|
||||||
TERMUX_API_PORT=5001
|
|
||||||
|
|
||||||
# Flask Application
|
|
||||||
FLASK_ENV=production
|
|
||||||
DEFAULT_DELAY_SECONDS=3
|
|
||||||
|
|
||||||
# SMS Automation (ADB coordinates for S24 Ultra)
|
|
||||||
SEND_BUTTON_X=1300
|
|
||||||
SEND_BUTTON_Y=2900
|
|
||||||
|
|
||||||
# SMS Retry Configuration
|
|
||||||
SMS_MAX_RETRIES=3
|
|
||||||
SMS_RETRY_BASE_DELAY=2
|
|
||||||
SMS_MAX_RETRY_DELAY=8
|
|
||||||
|
|
||||||
# SECURITY - API KEYS (GENERATED KEYS GO HERE)
|
|
||||||
ADMIN_API_KEY=<paste your generated ADMIN_API_KEY here>
|
|
||||||
USER_API_KEY=<paste your generated USER_API_KEY here>
|
|
||||||
TERMUX_API_KEY=<paste your generated TERMUX_API_KEY here>
|
|
||||||
SECRET_KEY=<paste your generated SECRET_KEY here>
|
|
||||||
TERMUX_API_SECRET=<paste your generated TERMUX_API_SECRET here>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Save the file** (Ctrl+O, Enter, Ctrl+X in nano)
|
|
||||||
|
|
||||||
5. **Secure the file permissions**:
|
|
||||||
```bash
|
|
||||||
chmod 600 .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### On Android Device (Termux)
|
|
||||||
|
|
||||||
1. SSH into your Android device:
|
|
||||||
```bash
|
|
||||||
ssh android-dev@100.107.173.66 -p 8022
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Edit the Termux environment file:
|
|
||||||
```bash
|
|
||||||
nano ~/projects/sms-campaign-manager/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Add the TERMUX_API_SECRET (use the same value as TERMUX_API_KEY from above):
|
|
||||||
```env
|
|
||||||
SMS_API_SECRET=<paste your generated TERMUX_API_KEY here>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Save and secure:
|
|
||||||
```bash
|
|
||||||
chmod 600 ~/projects/sms-campaign-manager/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Remove Old Secrets from Git History
|
|
||||||
|
|
||||||
⚠️ **CRITICAL**: Your old secrets are in git history. Follow these steps:
|
|
||||||
|
|
||||||
1. **Backup your repository**:
|
|
||||||
```bash
|
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
|
||||||
tar -czf ../campaign_connector_backup_$(date +%Y%m%d).tar.gz .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Remove .env from git tracking** (if it's currently tracked):
|
|
||||||
```bash
|
|
||||||
git rm --cached .env
|
|
||||||
git commit -m "Remove .env from version control"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Rewrite git history** to remove secrets (OPTIONAL but RECOMMENDED):
|
|
||||||
```bash
|
|
||||||
# Install git-filter-repo if not installed
|
|
||||||
pip install git-filter-repo
|
|
||||||
|
|
||||||
# Remove .env from all commits
|
|
||||||
git filter-repo --path .env --invert-paths
|
|
||||||
|
|
||||||
# Force push to remote (if you use a remote)
|
|
||||||
git push origin --force --all
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Verify .env is in .gitignore**:
|
|
||||||
```bash
|
|
||||||
grep "^.env$" .gitignore
|
|
||||||
```
|
|
||||||
Should return: `.env`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Restart Services
|
|
||||||
|
|
||||||
### Restart Docker Container
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
cp .env .env.backup
|
||||||
docker-compose down
|
nano .env
|
||||||
docker-compose build
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Restart Termux SMS API Server
|
Add the security configuration:
|
||||||
|
|
||||||
```bash
|
```env
|
||||||
ssh android-dev@100.107.173.66 -p 8022
|
# API Keys (generated above)
|
||||||
~/bin/sms-service.sh restart
|
ADMIN_API_KEY=<your-admin-key>
|
||||||
|
USER_API_KEY=<your-user-key>
|
||||||
|
TERMUX_API_KEY=<your-termux-key>
|
||||||
|
SECRET_KEY=<your-secret-key>
|
||||||
|
TERMUX_API_SECRET=<same-as-termux-api-key>
|
||||||
|
|
||||||
|
# User Management
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=YourSecurePassword123!
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
Secure the file:
|
||||||
|
|
||||||
## Step 5: Test Authentication
|
|
||||||
|
|
||||||
### Test Flask API
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# This should FAIL (no API key)
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Configure Android Device
|
||||||
|
|
||||||
|
SSH into your Android device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Option A - Use the automated setup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/projects/sms-campaign-manager
|
||||||
|
./android/setup-api-key.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Option B - Set manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'export SMS_API_SECRET="<your-termux-api-key>"' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Restart Services
|
||||||
|
|
||||||
|
On Ubuntu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
On Android (Termux):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pkill -f termux-sms-api-server.py
|
||||||
|
python ~/projects/sms-campaign-manager/android/termux-sms-api-server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Verify Security
|
||||||
|
|
||||||
|
Test authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should fail (no API key)
|
||||||
curl http://localhost:5000/api/campaign/list
|
curl http://localhost:5000/api/campaign/list
|
||||||
|
|
||||||
# This should SUCCEED (with your USER_API_KEY)
|
# Should succeed (with API key)
|
||||||
curl -H "X-API-Key: YOUR_USER_API_KEY_HERE" http://localhost:5000/api/campaign/list
|
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Termux API
|
Verify Docker security:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# This should FAIL (no API key)
|
docker inspect sms-campaign-manager | grep -E "Privileged|NetworkMode"
|
||||||
curl http://100.107.173.66:5001/api/sms/send \
|
# Expected: "Privileged": false, "NetworkMode": "bridge" or "default"
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"phone":"1234567890","message":"test"}'
|
|
||||||
|
|
||||||
# This should SUCCEED (with your TERMUX_API_KEY)
|
|
||||||
curl http://100.107.173.66:5001/api/sms/send \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "X-API-Key: YOUR_TERMUX_API_KEY_HERE" \
|
|
||||||
-d '{"phone":"1234567890","message":"test"}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Docker Security Configuration
|
||||||
|
|
||||||
## API Key Roles and Permissions
|
The application runs with these security measures:
|
||||||
|
|
||||||
### Admin API Key (`ADMIN_API_KEY`)
|
### Container Isolation
|
||||||
**Permissions**: Full access to all endpoints including:
|
|
||||||
- ✅ All user permissions
|
|
||||||
- ✅ Database reset (`/api/database/reset`)
|
|
||||||
- ✅ System configuration changes
|
|
||||||
|
|
||||||
**Use**: Personal admin access, automated admin scripts
|
The container runs without privileged mode:
|
||||||
|
|
||||||
### User API Key (`USER_API_KEY`)
|
```yaml
|
||||||
**Permissions**: Regular application access:
|
services:
|
||||||
- ✅ Create and manage campaigns
|
sms-campaign:
|
||||||
- ✅ Send SMS messages
|
# Runs with standard permissions
|
||||||
- ✅ Upload CSV files
|
ports:
|
||||||
- ✅ View analytics and reports
|
- "5000:5000"
|
||||||
- ❌ Cannot reset database
|
- "5037:5037"
|
||||||
- ❌ Cannot modify system configuration
|
|
||||||
|
|
||||||
**Use**: Web dashboard, regular API access, automated campaigns
|
|
||||||
|
|
||||||
### Termux API Key (`TERMUX_API_KEY`)
|
|
||||||
**Permissions**: Android device communication:
|
|
||||||
- ✅ Send SMS via Termux API
|
|
||||||
- ✅ Query SMS history
|
|
||||||
- ✅ Device status endpoints
|
|
||||||
|
|
||||||
**Use**: Communication between Flask server and Android device
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Using API Keys in the Web Dashboard
|
|
||||||
|
|
||||||
### Option 1: Browser Extension (Recommended)
|
|
||||||
|
|
||||||
Install "ModHeader" Chrome/Firefox extension:
|
|
||||||
1. Add header: `X-API-Key`
|
|
||||||
2. Value: Your `USER_API_KEY`
|
|
||||||
3. Filter: `http://localhost:5000/*` or your Tailscale IP
|
|
||||||
|
|
||||||
### Option 2: Update Dashboard JavaScript
|
|
||||||
|
|
||||||
Edit `src/static/js/dashboard.js` and add the API key to all fetch requests:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Find all fetch() calls and add headers
|
|
||||||
fetch('/api/campaign/list', {
|
|
||||||
headers: {
|
|
||||||
'X-API-Key': 'YOUR_USER_API_KEY_HERE'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ WARNING**: Only do this for local development. Never commit API keys to JavaScript files.
|
### Network Isolation
|
||||||
|
|
||||||
### Option 3: Nginx Proxy with Authentication
|
Uses Docker bridge networking instead of host mode, providing proper network isolation while allowing necessary port access.
|
||||||
|
|
||||||
Set up nginx reverse proxy that adds the API key header automatically (advanced).
|
### Secure Volumes
|
||||||
|
|
||||||
---
|
Only required directories are mounted:
|
||||||
|
|
||||||
## Security Best Practices
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
- ./uploads:/app/uploads
|
||||||
|
- ./logs:/app/logs
|
||||||
|
```
|
||||||
|
|
||||||
### ✅ DO:
|
## API Key Roles
|
||||||
- Store API keys in environment variables only
|
|
||||||
- Use different keys for admin, user, and Termux access
|
|
||||||
- Rotate keys every 90 days
|
|
||||||
- Use HTTPS/TLS in production (Tailscale provides this)
|
|
||||||
- Monitor logs for unauthorized access attempts
|
|
||||||
- Back up your .env file securely (encrypted)
|
|
||||||
- Use strong, randomly generated keys (64+ characters)
|
|
||||||
|
|
||||||
### ❌ DON'T:
|
### Admin API Key
|
||||||
- Commit .env files to git
|
|
||||||
- Share API keys in plain text (email, chat, etc.)
|
|
||||||
- Use the same key across multiple environments
|
|
||||||
- Store keys in JavaScript files
|
|
||||||
- Log API keys in application logs
|
|
||||||
- Reuse old compromised keys
|
|
||||||
|
|
||||||
---
|
Full system access:
|
||||||
|
|
||||||
## Rotating API Keys
|
- All user permissions
|
||||||
|
- Database reset operations
|
||||||
|
- System configuration changes
|
||||||
|
|
||||||
To rotate keys (recommended every 90 days):
|
Use for administrative tasks and automation scripts requiring full access.
|
||||||
|
|
||||||
1. Generate new keys:
|
### User API Key
|
||||||
```bash
|
|
||||||
python3 src/core/auth.py
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update .env with new keys
|
Regular application access:
|
||||||
|
|
||||||
|
- Create and manage campaigns
|
||||||
|
- Send SMS messages
|
||||||
|
- Upload CSV files
|
||||||
|
- View analytics
|
||||||
|
|
||||||
|
Use for web dashboard access and team member API calls.
|
||||||
|
|
||||||
|
### Termux API Key
|
||||||
|
|
||||||
|
Android device communication:
|
||||||
|
|
||||||
|
- Send SMS via Termux API
|
||||||
|
- Query SMS history
|
||||||
|
- Device status endpoints
|
||||||
|
|
||||||
|
Used internally for server-to-device communication.
|
||||||
|
|
||||||
|
## Using API Keys
|
||||||
|
|
||||||
|
### HTTP Headers
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# X-API-Key header
|
||||||
|
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
|
|
||||||
|
# Bearer token
|
||||||
|
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Dashboard
|
||||||
|
|
||||||
|
The web dashboard uses session-based authentication. Log in with username/password at `/login`. API keys are not needed for browser access after login.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- Generate unique, random API keys
|
||||||
|
- Store keys only in `.env` file
|
||||||
|
- Set file permissions: `chmod 600 .env`
|
||||||
|
- Use HTTPS in production (Tailscale provides this)
|
||||||
|
- Never commit `.env` to version control
|
||||||
|
|
||||||
|
### Recommended
|
||||||
|
|
||||||
|
- Rotate API keys every 90 days
|
||||||
|
- Use different keys per environment
|
||||||
|
- Monitor logs for failed authentication
|
||||||
|
- Keep Docker and dependencies updated
|
||||||
|
- Back up `.env` securely (encrypted)
|
||||||
|
|
||||||
|
## Key Rotation
|
||||||
|
|
||||||
|
To rotate keys:
|
||||||
|
|
||||||
|
1. Generate new keys: `python3 src/core/auth.py`
|
||||||
|
2. Update `.env` on server and Android device
|
||||||
3. Restart all services
|
3. Restart all services
|
||||||
4. Update any scripts/applications using the old keys
|
4. Update external scripts using old keys
|
||||||
5. Test all functionality
|
5. Test all functionality
|
||||||
6. Securely delete old keys
|
6. Securely delete old keys
|
||||||
|
|
||||||
---
|
## Git Security
|
||||||
|
|
||||||
|
Remove secrets from version control:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove .env from git tracking
|
||||||
|
git rm --cached .env
|
||||||
|
git commit -m "Remove .env from version control"
|
||||||
|
|
||||||
|
# Verify .env is in .gitignore
|
||||||
|
grep "^.env$" .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Issue: "Authentication required" error
|
### Authentication Required Error
|
||||||
|
|
||||||
|
Missing or invalid API key:
|
||||||
|
|
||||||
**Solution**: Ensure you're passing the API key in the request:
|
|
||||||
```bash
|
```bash
|
||||||
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:5000/api/endpoint
|
# Check if key is loaded in container
|
||||||
|
docker compose exec sms-campaign env | grep API_KEY
|
||||||
|
|
||||||
|
# Test with correct key
|
||||||
|
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/campaign/list
|
||||||
```
|
```
|
||||||
|
|
||||||
### Issue: "Invalid API key" error
|
### Invalid API Key Error
|
||||||
|
|
||||||
**Solutions**:
|
Key mismatch or formatting issue:
|
||||||
1. Verify you copied the key correctly (no extra spaces)
|
|
||||||
2. Check .env file has the correct key
|
|
||||||
3. Restart Docker container to reload environment
|
|
||||||
4. Verify key hasn't been rotated
|
|
||||||
|
|
||||||
### Issue: Application won't start - "API keys must be configured"
|
```bash
|
||||||
|
# Verify .env format (no spaces around =)
|
||||||
|
cat .env | grep API_KEY
|
||||||
|
|
||||||
**Solution**:
|
# Restart to reload environment
|
||||||
1. Generate keys: `python3 src/core/auth.py`
|
docker compose restart
|
||||||
2. Add keys to .env file
|
```
|
||||||
3. Restart: `docker-compose restart`
|
|
||||||
|
|
||||||
### Issue: Web dashboard not working
|
### Android Server Won't Start
|
||||||
|
|
||||||
**Solution**: Add API key to dashboard JavaScript or use browser extension (see "Using API Keys in the Web Dashboard" above)
|
Missing `SMS_API_SECRET`:
|
||||||
|
|
||||||
---
|
```bash
|
||||||
|
# Check if set
|
||||||
|
echo $SMS_API_SECRET
|
||||||
|
|
||||||
## Emergency Access
|
# If empty, run setup
|
||||||
|
./android/setup-api-key.sh
|
||||||
|
```
|
||||||
|
|
||||||
If you lose your API keys:
|
### Container Security Check
|
||||||
|
|
||||||
1. **Stop the application**:
|
Verify Docker configuration:
|
||||||
```bash
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Generate new keys**:
|
```bash
|
||||||
```bash
|
# Should show "Privileged": false
|
||||||
python3 src/core/auth.py
|
docker inspect sms-campaign-manager | grep Privileged
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update .env file** with new keys
|
# Should NOT show "NetworkMode": "host"
|
||||||
|
docker inspect sms-campaign-manager | grep NetworkMode
|
||||||
|
```
|
||||||
|
|
||||||
4. **Restart application**:
|
## Environment Variables
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
| Variable | Description | Required |
|
||||||
|
|----------|-------------|----------|
|
||||||
|
| `ADMIN_API_KEY` | Admin access key | Yes |
|
||||||
|
| `USER_API_KEY` | User access key | Yes |
|
||||||
|
| `TERMUX_API_KEY` | Android communication key | Yes |
|
||||||
|
| `SECRET_KEY` | Session encryption key | Yes |
|
||||||
|
| `TERMUX_API_SECRET` | Android server auth | Yes |
|
||||||
|
| `ADMIN_USERNAME` | Initial admin username | Yes |
|
||||||
|
| `ADMIN_PASSWORD` | Initial admin password | Yes |
|
||||||
|
|
||||||
## Support
|
See [Environment Variables Reference](../reference/environment-variables.md) for the complete list.
|
||||||
|
|
||||||
If you encounter issues:
|
## Related Documentation
|
||||||
1. Check logs: `docker-compose logs -f sms-campaign`
|
|
||||||
2. Verify environment: `docker-compose exec sms-campaign env | grep API_KEY`
|
|
||||||
3. Test connectivity: `curl http://localhost:5000/health`
|
|
||||||
|
|
||||||
---
|
- [API Security](api-security.md) - Detailed API authentication guide
|
||||||
|
- [Authentication Setup](../setup/authentication.md) - User login configuration
|
||||||
## Next Steps (Additional Security Hardening)
|
- [Quick Start](../setup/quick-start.md) - Getting started guide
|
||||||
|
- [Deployment Guide](../deployment/deployment-guide.md) - Production deployment
|
||||||
After completing this setup, consider:
|
|
||||||
|
|
||||||
1. **Add rate limiting** to prevent API abuse
|
|
||||||
2. **Implement request logging** for audit trails
|
|
||||||
3. **Set up HTTPS** with proper TLS certificates
|
|
||||||
4. **Enable Tailscale ACLs** to restrict access by device
|
|
||||||
5. **Add IP whitelisting** for additional security
|
|
||||||
6. **Implement session tokens** for web dashboard
|
|
||||||
7. **Set up security monitoring** and alerts
|
|
||||||
8. **Regular security audits** of logs and access patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated**: 2025-12-30
|
|
||||||
**Version**: 2.0 (Secured)
|
|
||||||
|
|||||||
@ -1,223 +1,193 @@
|
|||||||
# Quick Setup: User Authentication System
|
# Authentication Setup
|
||||||
|
|
||||||
## ✅ What's Been Added
|
This guide covers user authentication configuration for the web dashboard and API access.
|
||||||
|
|
||||||
Your SMS Campaign Manager now has **complete user management** with web-based login. No more ModHeader or API keys in headers for the web dashboard!
|
## Overview
|
||||||
|
|
||||||
---
|
SMS Campaign Manager supports two authentication methods:
|
||||||
|
|
||||||
## 🚀 Quick Setup (5 Minutes)
|
- **Session-based**: Username/password login for web dashboard
|
||||||
|
- **API key-based**: Header authentication for scripts and automation
|
||||||
|
|
||||||
### Step 1: Update Your .env File
|
Both methods work simultaneously.
|
||||||
|
|
||||||
Add these lines to your existing `.env` file:
|
## Web Dashboard Authentication
|
||||||
|
|
||||||
```bash
|
### Configure Admin User
|
||||||
# Add to /mnt/storagessd1tb/campaign_connector/.env
|
|
||||||
|
|
||||||
# User Management (create initial admin)
|
Add these lines to your `.env` file:
|
||||||
|
|
||||||
|
```env
|
||||||
ADMIN_USERNAME=admin
|
ADMIN_USERNAME=admin
|
||||||
ADMIN_PASSWORD=ChangeThisSecurePassword123!
|
ADMIN_PASSWORD=YourSecurePassword123!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Restart Docker
|
Restart the application:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
docker compose restart
|
||||||
docker-compose restart
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The system will automatically create the admin user on startup.
|
The admin user is created automatically on startup.
|
||||||
|
|
||||||
### Step 3: Access the Login Page
|
### Login Process
|
||||||
|
|
||||||
Open your browser:
|
1. Open `http://localhost:5000/`
|
||||||
```
|
2. You'll be redirected to `/login`
|
||||||
http://localhost:5000/login
|
3. Enter your credentials
|
||||||
```
|
4. After login, sessions last 24 hours
|
||||||
|
|
||||||
Or via Tailscale:
|
### Session Features
|
||||||
```
|
|
||||||
http://your-tailscale-ip:5000/login
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Log In
|
- 24-hour session duration
|
||||||
|
- HTTP-only cookies for security
|
||||||
|
- Automatic session cleanup
|
||||||
|
- Login tracking and auditing
|
||||||
|
|
||||||
- **Username**: `admin`
|
## API Key Authentication
|
||||||
- **Password**: Whatever you set in `.env`
|
|
||||||
|
|
||||||
You're done! You'll stay logged in for 24 hours.
|
API keys are used for programmatic access and automation scripts.
|
||||||
|
|
||||||
---
|
### Key Types
|
||||||
|
|
||||||
## 🎯 What Changed
|
| Key | Variable | Purpose |
|
||||||
|
|-----|----------|---------|
|
||||||
|
| Admin | `ADMIN_API_KEY` | Full access including database reset |
|
||||||
|
| User | `USER_API_KEY` | Standard operations |
|
||||||
|
| Termux | `TERMUX_API_KEY` | Android device communication |
|
||||||
|
|
||||||
### Before (API Keys Only)
|
### Usage
|
||||||
```
|
|
||||||
❌ Install ModHeader extension
|
|
||||||
❌ Add X-API-Key header manually
|
|
||||||
❌ Remember to enable it for localhost
|
|
||||||
❌ Different keys for different roles
|
|
||||||
```
|
|
||||||
|
|
||||||
### After (User Login)
|
Include the key in request headers:
|
||||||
```
|
|
||||||
✅ Visit /login
|
```bash
|
||||||
✅ Enter username/password
|
# X-API-Key header
|
||||||
✅ Click "Sign In"
|
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
✅ Stay logged in for 24 hours
|
|
||||||
✅ No browser extensions needed!
|
# Bearer token
|
||||||
```
|
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
|
```
|
||||||
---
|
|
||||||
|
## User Roles
|
||||||
## 👥 Managing Users
|
|
||||||
|
### Admin Role
|
||||||
### Create Additional Users
|
|
||||||
|
Full system access:
|
||||||
|
|
||||||
|
- All user permissions
|
||||||
|
- Create and delete users
|
||||||
|
- Database reset
|
||||||
|
- System configuration
|
||||||
|
|
||||||
|
### User Role
|
||||||
|
|
||||||
|
Standard operations:
|
||||||
|
|
||||||
|
- Create and manage campaigns
|
||||||
|
- Send SMS messages
|
||||||
|
- Upload CSV files
|
||||||
|
- View analytics
|
||||||
|
- Change own password
|
||||||
|
|
||||||
|
## Managing Users
|
||||||
|
|
||||||
|
Use the CLI tool to manage users:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
|
||||||
python3 manage_users.py
|
python3 manage_users.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Interactive menu will guide you through:
|
Available options:
|
||||||
- Creating new users
|
|
||||||
- Listing existing users
|
|
||||||
- Deleting users
|
|
||||||
- Changing passwords
|
|
||||||
|
|
||||||
### User Roles
|
1. Create new user
|
||||||
|
2. List all users
|
||||||
|
3. Delete user
|
||||||
|
4. Change password
|
||||||
|
|
||||||
**Admin**: Full access (you should be admin)
|
### Create User via CLI
|
||||||
**User**: Regular access (for team members)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Both Authentication Methods Work
|
|
||||||
|
|
||||||
### Session-Based (Web Dashboard)
|
|
||||||
- Log in with username/password
|
|
||||||
- Stay logged in for 24 hours
|
|
||||||
- Automatic session management
|
|
||||||
- **Use this for the web interface**
|
|
||||||
|
|
||||||
### API Key-Based (External Scripts)
|
|
||||||
- Still works for automation
|
|
||||||
- Use `X-API-Key` header
|
|
||||||
- Three keys: ADMIN_API_KEY, USER_API_KEY, TERMUX_API_KEY
|
|
||||||
- **Use this for scripts and integrations**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Files Created
|
|
||||||
|
|
||||||
1. **src/core/user_auth.py** - User authentication system
|
|
||||||
2. **src/routes/auth_routes.py** - Login/logout routes
|
|
||||||
3. **src/templates/login.html** - Beautiful login page
|
|
||||||
4. **manage_users.py** - CLI tool for user management (in project root)
|
|
||||||
5. **[User Management Guide](../guides/user-management.md)** - Complete guide
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing
|
|
||||||
|
|
||||||
### Test Login
|
|
||||||
```bash
|
```bash
|
||||||
# Should redirect to login page
|
python3 manage_users.py
|
||||||
curl -i http://localhost:5000/
|
# Select option 1
|
||||||
|
# Enter username, password, role
|
||||||
# Should show login page
|
|
||||||
curl http://localhost:5000/login
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Authentication
|
### Create User via API (Admin Only)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
curl -X POST http://localhost:5000/api/admin/users/create \
|
||||||
|
-H "Cookie: session=YOUR_SESSION" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"newuser","password":"SecurePass123!","role":"user"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Authentication
|
||||||
|
|
||||||
|
### Test Web Login
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should redirect to login
|
||||||
|
curl -i http://localhost:5000/
|
||||||
|
|
||||||
# Login via API
|
# Login via API
|
||||||
curl -X POST http://localhost:5000/api/auth/login \
|
curl -X POST http://localhost:5000/api/auth/login \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"username":"admin","password":"YourPassword"}'
|
-d '{"username":"admin","password":"YourPassword"}'
|
||||||
|
|
||||||
# Should return:
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Login successful",
|
|
||||||
"user": {
|
|
||||||
"username": "admin",
|
|
||||||
"role": "admin"
|
|
||||||
},
|
|
||||||
"redirect": "/"
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Session
|
### Test API Authentication
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check auth status
|
# Should fail (no key)
|
||||||
curl http://localhost:5000/api/auth/status
|
curl http://localhost:5000/api/campaign/list
|
||||||
|
|
||||||
|
# Should succeed
|
||||||
|
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Security Features
|
||||||
|
|
||||||
## 🔄 Migration Path
|
- PBKDF2 password hashing (100,000 iterations)
|
||||||
|
- HTTP-only session cookies
|
||||||
|
- Secure session tokens
|
||||||
|
- Constant-time password comparison
|
||||||
|
- Failed login tracking
|
||||||
|
|
||||||
If you were using ModHeader before:
|
## Troubleshooting
|
||||||
|
|
||||||
1. **Keep your API keys** - still work for automation
|
### Can't Log In
|
||||||
2. **Add user login** - new feature for web dashboard
|
|
||||||
3. **Choose your preference**:
|
|
||||||
- Web browsing: Use username/password login
|
|
||||||
- Scripts/automation: Use API keys
|
|
||||||
|
|
||||||
Both work simultaneously!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Full Documentation
|
|
||||||
|
|
||||||
For complete details, see:
|
|
||||||
- **[User Management Guide](../guides/user-management.md)** - Comprehensive user guide
|
|
||||||
- **[API Security](../security/api-security.md)** - API key documentation
|
|
||||||
- **[Security Setup](../security/security-setup.md)** - Security setup guide
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 Quick Troubleshooting
|
|
||||||
|
|
||||||
**Can't log in?**
|
|
||||||
```bash
|
```bash
|
||||||
# List users to verify admin exists
|
# Verify user exists
|
||||||
python3 manage_users.py
|
python3 manage_users.py
|
||||||
# Choose option 2 to list users
|
# Select option 2
|
||||||
|
|
||||||
|
# Reset password via .env
|
||||||
|
nano .env
|
||||||
|
# Update ADMIN_PASSWORD
|
||||||
|
docker compose restart
|
||||||
```
|
```
|
||||||
|
|
||||||
**Forgot password?**
|
### Session Expires Too Quickly
|
||||||
|
|
||||||
|
Session duration is configured in `src/app.py`. Default is 24 hours.
|
||||||
|
|
||||||
|
### Forgot Password
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Change it via .env
|
# Via CLI
|
||||||
echo "ADMIN_PASSWORD=NewPassword123!" >> .env
|
python3 manage_users.py
|
||||||
docker-compose restart
|
# Select option 4 (Change password)
|
||||||
|
|
||||||
|
# Or reset via .env
|
||||||
|
nano .env
|
||||||
|
# Update ADMIN_PASSWORD
|
||||||
|
docker compose restart
|
||||||
```
|
```
|
||||||
|
|
||||||
**Database error?**
|
## Related Documentation
|
||||||
```bash
|
|
||||||
docker-compose logs -f sms-campaign
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
- [Installation Guide](installation.md) - Initial setup
|
||||||
|
- [Security Setup](../security/security-setup.md) - API key configuration
|
||||||
## ✨ Features
|
- [User Management](../guides/user-management.md) - Detailed user guide
|
||||||
|
- [API Endpoints](../api/endpoints.md) - Authentication endpoints
|
||||||
- ✅ Secure password hashing (PBKDF2, 100k iterations)
|
|
||||||
- ✅ Session management (24-hour sessions)
|
|
||||||
- ✅ HTTP-only cookies (XSS protection)
|
|
||||||
- ✅ Role-based access control
|
|
||||||
- ✅ User administration (admin only)
|
|
||||||
- ✅ Password change functionality
|
|
||||||
- ✅ Login tracking and auditing
|
|
||||||
- ✅ Beautiful, responsive login page
|
|
||||||
- ✅ CLI management tool
|
|
||||||
- ✅ Database-backed user storage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Ready to use!** Just add the env variables and restart. 🎉
|
|
||||||
|
|
||||||
**Questions?** Check the full guides or the troubleshooting sections.
|
|
||||||
|
|||||||
398
docs/setup/installation.md
Normal file
398
docs/setup/installation.md
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
# Installation Guide
|
||||||
|
|
||||||
|
This guide walks through the complete installation of SMS Campaign Manager for production use.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The installation process involves:
|
||||||
|
|
||||||
|
1. Setting up the Ubuntu server with Docker
|
||||||
|
2. Configuring the Android device with Termux
|
||||||
|
3. Establishing connectivity between devices
|
||||||
|
4. Deploying the application
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Ubuntu Server
|
||||||
|
|
||||||
|
- Ubuntu 20.04 or later
|
||||||
|
- Docker and Docker Compose installed
|
||||||
|
- At least 1GB RAM and 10GB disk space
|
||||||
|
- Network access to Android device
|
||||||
|
|
||||||
|
### Android Device
|
||||||
|
|
||||||
|
- Android 7.0 or later
|
||||||
|
- Termux app (from F-Droid)
|
||||||
|
- Termux:API app (from F-Droid)
|
||||||
|
- Active SIM card for SMS
|
||||||
|
|
||||||
|
### Network
|
||||||
|
|
||||||
|
Choose one of:
|
||||||
|
- **Tailscale (Recommended)**: Secure connectivity anywhere
|
||||||
|
- **Local Network**: Both devices on same WiFi
|
||||||
|
|
||||||
|
## Step 1: Install Docker on Ubuntu
|
||||||
|
|
||||||
|
If Docker is not installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update package index
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# Install prerequisites
|
||||||
|
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
|
||||||
|
|
||||||
|
# Add Docker's GPG key
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
|
||||||
|
# Add Docker repository
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
|
# Install Docker
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
|
||||||
|
# Add user to docker group
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
|
||||||
|
# Apply group changes (or log out and back in)
|
||||||
|
newgrp docker
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
docker --version
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Install Tailscale (Recommended)
|
||||||
|
|
||||||
|
On Ubuntu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Tailscale
|
||||||
|
curl -fsSL https://tailscale.com/install.sh | sh
|
||||||
|
|
||||||
|
# Connect to your Tailnet
|
||||||
|
sudo tailscale up
|
||||||
|
|
||||||
|
# Note your Tailscale IP
|
||||||
|
tailscale ip -4
|
||||||
|
```
|
||||||
|
|
||||||
|
On Android:
|
||||||
|
1. Install Tailscale from Google Play Store
|
||||||
|
2. Open Tailscale and sign in with the same account
|
||||||
|
3. Enable the VPN connection
|
||||||
|
4. Note your Android's Tailscale IP (Settings → Connected)
|
||||||
|
|
||||||
|
## Step 3: Set Up Android Device
|
||||||
|
|
||||||
|
### Install Termux
|
||||||
|
|
||||||
|
Download from F-Droid (not Google Play):
|
||||||
|
- [Termux](https://f-droid.org/packages/com.termux/)
|
||||||
|
- [Termux:API](https://f-droid.org/packages/com.termux.api/)
|
||||||
|
|
||||||
|
### Configure Termux
|
||||||
|
|
||||||
|
Open Termux and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update packages
|
||||||
|
pkg update && pkg upgrade -y
|
||||||
|
|
||||||
|
# Install required packages
|
||||||
|
pkg install -y openssh python termux-api
|
||||||
|
|
||||||
|
# Start SSH server
|
||||||
|
sshd
|
||||||
|
|
||||||
|
# Set SSH password
|
||||||
|
passwd
|
||||||
|
# Enter a secure password when prompted
|
||||||
|
|
||||||
|
# Verify SSH is running
|
||||||
|
pgrep sshd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grant Permissions
|
||||||
|
|
||||||
|
On Android:
|
||||||
|
1. Open Settings → Apps → Termux:API
|
||||||
|
2. Tap Permissions
|
||||||
|
3. Enable SMS permission
|
||||||
|
4. Enable Phone permission (optional, for call info)
|
||||||
|
|
||||||
|
### Test Termux API
|
||||||
|
|
||||||
|
In Termux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test SMS access
|
||||||
|
termux-sms-list -l 1
|
||||||
|
|
||||||
|
# Should display recent SMS (if any)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Clone the Repository
|
||||||
|
|
||||||
|
On Ubuntu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or download the project
|
||||||
|
cd /path/to/your/projects
|
||||||
|
git clone <repository-url> campaign_connector
|
||||||
|
cd campaign_connector
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you have the files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/storagessd1tb/campaign_connector
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Configure Environment
|
||||||
|
|
||||||
|
Create your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit configuration
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Set these required values:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Android Device (use your Tailscale IP)
|
||||||
|
PHONE_IP=100.x.x.x
|
||||||
|
ADB_PORT=5555
|
||||||
|
TERMUX_API_PORT=5001
|
||||||
|
|
||||||
|
# Flask Application
|
||||||
|
FLASK_ENV=production
|
||||||
|
DEFAULT_DELAY_SECONDS=3
|
||||||
|
|
||||||
|
# SMS Configuration
|
||||||
|
SMS_MAX_RETRIES=3
|
||||||
|
SMS_RETRY_BASE_DELAY=2
|
||||||
|
SMS_MAX_RETRY_DELAY=8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 6: Generate API Keys
|
||||||
|
|
||||||
|
Generate secure API keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 src/core/auth.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the generated keys to your `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# API Keys (paste generated values)
|
||||||
|
ADMIN_API_KEY=<generated-admin-key>
|
||||||
|
USER_API_KEY=<generated-user-key>
|
||||||
|
TERMUX_API_KEY=<generated-termux-key>
|
||||||
|
SECRET_KEY=<generated-secret-key>
|
||||||
|
TERMUX_API_SECRET=<same-as-termux-api-key>
|
||||||
|
|
||||||
|
# User Management
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=YourSecurePassword123!
|
||||||
|
```
|
||||||
|
|
||||||
|
Secure the file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 7: Deploy to Android
|
||||||
|
|
||||||
|
Test connectivity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test SSH connection
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "echo 'Connection successful'"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the deployment script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/deploy-android.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script:
|
||||||
|
- Tests connectivity
|
||||||
|
- Creates required directories on Android
|
||||||
|
- Deploys Python scripts and services
|
||||||
|
- Starts the Termux API server
|
||||||
|
- Verifies deployment
|
||||||
|
|
||||||
|
If the script fails, you can deploy manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy files to Android
|
||||||
|
scp -P 8022 android/*.py android-dev@YOUR_ANDROID_IP:~/projects/sms-campaign-manager/
|
||||||
|
scp -P 8022 android/*.sh android-dev@YOUR_ANDROID_IP:~/bin/
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "chmod +x ~/bin/*.sh"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 8: Configure Android API Key
|
||||||
|
|
||||||
|
SSH into Android:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the API key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set API key (use the same TERMUX_API_KEY from Step 6)
|
||||||
|
echo 'export SMS_API_SECRET="<your-termux-api-key>"' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
|
||||||
|
# Verify it's set
|
||||||
|
echo $SMS_API_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 9: Start Services
|
||||||
|
|
||||||
|
### Start Android Services
|
||||||
|
|
||||||
|
On Android (via SSH or Termux directly):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/bin/start-all-services.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or start individually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start SMS API server
|
||||||
|
python ~/projects/sms-campaign-manager/android/termux-sms-api-server.py &
|
||||||
|
|
||||||
|
# Check if running
|
||||||
|
curl http://localhost:5001/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start Ubuntu Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and start Docker container
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose logs -f sms-campaign
|
||||||
|
|
||||||
|
# Press Ctrl+C to stop viewing logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 10: Verify Installation
|
||||||
|
|
||||||
|
### Check Service Health
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test Ubuntu application
|
||||||
|
curl http://localhost:5000/health
|
||||||
|
# Expected: {"status": "ok", "version": "2.0"}
|
||||||
|
|
||||||
|
# Test Android Termux API
|
||||||
|
curl http://YOUR_ANDROID_IP:5001/health
|
||||||
|
# Expected: {"status": "healthy", ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Authentication
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should fail (no API key)
|
||||||
|
curl http://localhost:5000/api/campaign/list
|
||||||
|
|
||||||
|
# Should succeed
|
||||||
|
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Web Dashboard
|
||||||
|
|
||||||
|
Open browser: `http://localhost:5000`
|
||||||
|
|
||||||
|
Log in with:
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: (from ADMIN_PASSWORD in .env)
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
After installation, verify:
|
||||||
|
|
||||||
|
- [ ] Docker container running: `docker compose ps`
|
||||||
|
- [ ] Ubuntu health check passes: `curl http://localhost:5000/health`
|
||||||
|
- [ ] Android health check passes: `curl http://YOUR_ANDROID_IP:5001/health`
|
||||||
|
- [ ] Web login works at `/login`
|
||||||
|
- [ ] API authentication enforced
|
||||||
|
- [ ] Container not in privileged mode: `docker inspect sms-campaign-manager | grep Privileged`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Can't Connect to Android
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Tailscale is running
|
||||||
|
tailscale status
|
||||||
|
|
||||||
|
# Ping Android device
|
||||||
|
ping YOUR_ANDROID_IP
|
||||||
|
|
||||||
|
# Test SSH
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "whoami"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker compose logs sms-campaign
|
||||||
|
|
||||||
|
# Rebuild container
|
||||||
|
docker compose down
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android Server Not Responding
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH to Android
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
|
||||||
|
# Check if server is running
|
||||||
|
ps aux | grep termux-sms-api-server
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
tail -20 ~/logs/sms-api.log
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
pkill -f termux-sms-api-server.py
|
||||||
|
~/bin/start-sms-api.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After successful installation:
|
||||||
|
|
||||||
|
1. **[Quick Start](quick-start.md)** - Test your installation
|
||||||
|
2. **[Security Setup](../security/security-setup.md)** - Review security configuration
|
||||||
|
3. **[User Management](../guides/user-management.md)** - Add team members
|
||||||
|
4. **[Testing Guide](../guides/testing.md)** - Verify all functionality
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Environment Variables](../reference/environment-variables.md) - All configuration options
|
||||||
|
- [Deployment Guide](../deployment/deployment-guide.md) - Advanced deployment topics
|
||||||
|
- [Troubleshooting](../guides/troubleshooting.md) - Common issues
|
||||||
@ -1,205 +1,219 @@
|
|||||||
# 🚀 Quick Start - Deployment & Testing
|
# Quick Start Guide
|
||||||
|
|
||||||
|
This guide covers rapid deployment and testing after you've completed the [Installation Guide](installation.md).
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
✅ `.env` file updated with API keys and admin credentials
|
|
||||||
✅ Android device accessible via Tailscale
|
|
||||||
✅ Docker installed and running
|
|
||||||
|
|
||||||
---
|
Before starting, ensure you have:
|
||||||
|
|
||||||
## 📦 Deploy Everything (3 Commands)
|
- `.env` file configured with API keys and admin credentials
|
||||||
|
- Docker installed on Ubuntu server
|
||||||
|
- Android device accessible via SSH
|
||||||
|
- Termux API server deployed
|
||||||
|
|
||||||
|
If not, complete the [Installation Guide](installation.md) first.
|
||||||
|
|
||||||
|
## Deploy in 3 Steps
|
||||||
|
|
||||||
|
### Step 1: Deploy to Android
|
||||||
|
|
||||||
### 1. Deploy to Android
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/storagessd1tb/campaign_connector
|
cd /mnt/storagessd1tb/campaign_connector
|
||||||
./scripts/deploy-android.sh
|
./scripts/deploy-android.sh
|
||||||
```
|
```
|
||||||
**Wait for:** `🎉 Deployment Complete!`
|
|
||||||
|
|
||||||
### 2. Restart Docker
|
Wait for: `Deployment Complete!`
|
||||||
|
|
||||||
|
### Step 2: Start Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose down && docker-compose build && docker-compose up -d
|
docker compose down && docker compose build && docker compose up -d
|
||||||
```
|
|
||||||
**Wait for:** Container to be `healthy`
|
|
||||||
|
|
||||||
### 3. Verify Health
|
|
||||||
```bash
|
|
||||||
curl http://localhost:5000/health && \
|
|
||||||
curl http://100.107.173.66:5001/health
|
|
||||||
```
|
|
||||||
**Expected:** Both return healthy status
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Quick Tests (5 Minutes)
|
|
||||||
|
|
||||||
### Test 1: Web Login (Browser)
|
|
||||||
1. Open: **http://localhost:5000/**
|
|
||||||
2. Should redirect to login page
|
|
||||||
3. Login with:
|
|
||||||
- Username: `admin`
|
|
||||||
- Password: `Campaign2025!Secure`
|
|
||||||
4. Should access dashboard **without ModHeader!** ✅
|
|
||||||
|
|
||||||
### Test 2: API Authentication (Terminal)
|
|
||||||
```bash
|
|
||||||
# Should FAIL (no key)
|
|
||||||
curl http://localhost:5000/api/campaign/list
|
|
||||||
|
|
||||||
# Should SUCCEED (with key)
|
|
||||||
curl http://localhost:5000/api/campaign/list \
|
|
||||||
-H "X-API-Key: 2dd80622e868a9365bc037106fd5b2bda8c520805faaf3aa2267269c0b9303f8"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test 3: Create User (Terminal)
|
Wait for container to be healthy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 manage_users.py
|
docker compose ps
|
||||||
# Select: 1 (Create new user)
|
# STATUS should show "healthy"
|
||||||
# Username: testuser
|
|
||||||
# Password: TestPass123!
|
|
||||||
# Role: 2 (User)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test 4: Send Test SMS (Terminal)
|
### Step 3: Verify Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:5000/api/sms/test/real \
|
# Test Ubuntu server
|
||||||
-H "X-API-Key: 2dd80622e868a9365bc037106fd5b2bda8c520805faaf3aa2267269c0b9303f8" \
|
curl http://localhost:5000/health
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"phone":"YOUR_NUMBER","message":"Test from secured API!"}'
|
# Test Android server (replace with your IP)
|
||||||
|
curl http://YOUR_ANDROID_IP:5001/health
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
Both should return healthy status.
|
||||||
|
|
||||||
## 🔑 Your Credentials
|
## Quick Tests
|
||||||
|
|
||||||
### Web Dashboard Login
|
### Web Dashboard Login
|
||||||
- URL: `http://localhost:5000/login`
|
|
||||||
- Username: `admin`
|
|
||||||
- Password: `Campaign2025!Secure`
|
|
||||||
|
|
||||||
### API Keys (for scripts)
|
1. Open browser: `http://localhost:5000/`
|
||||||
|
2. Should redirect to login page
|
||||||
|
3. Log in with your admin credentials (from `.env`)
|
||||||
|
4. Dashboard should load without errors
|
||||||
|
|
||||||
|
### API Authentication
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# User operations (most common)
|
# Should FAIL (no API key)
|
||||||
USER_API_KEY="2dd80622e868a9365bc037106fd5b2bda8c520805faaf3aa2267269c0b9303f8"
|
curl http://localhost:5000/api/campaign/list
|
||||||
|
# Expected: 401 Unauthorized
|
||||||
|
|
||||||
# Admin operations (database reset, user management)
|
# Should SUCCEED (with API key from .env)
|
||||||
ADMIN_API_KEY="208da9821e9f945355cd4c65e22a0570d8cf367483cfaef42cfd858cefacb7dd"
|
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||||
|
# Expected: JSON response with campaigns
|
||||||
# Android communication
|
|
||||||
TERMUX_API_KEY="aee141babda29fb0e68b5eb462c7feb5885f29b12c735d29ea337c360b00d351"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage Example
|
### Send Test SMS
|
||||||
```bash
|
|
||||||
# With API key
|
|
||||||
curl http://localhost:5000/api/endpoint \
|
|
||||||
-H "X-API-Key: YOUR_API_KEY_HERE"
|
|
||||||
|
|
||||||
# Or with Bearer token
|
```bash
|
||||||
curl http://localhost:5000/api/endpoint \
|
curl -X POST http://localhost:5000/api/sms/test/real \
|
||||||
-H "Authorization: Bearer YOUR_API_KEY_HERE"
|
-H "X-API-Key: YOUR_USER_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"phone":"YOUR_PHONE_NUMBER","message":"Test from SMS Campaign Manager"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Common Commands
|
||||||
|
|
||||||
## 📱 User Management
|
### Service Management
|
||||||
|
|
||||||
### Create New User
|
|
||||||
```bash
|
```bash
|
||||||
python3 manage_users.py
|
# Start services
|
||||||
# Option 1: Create new user
|
docker compose up -d
|
||||||
```
|
|
||||||
|
|
||||||
### List All Users
|
# Stop services
|
||||||
```bash
|
docker compose down
|
||||||
python3 manage_users.py
|
|
||||||
# Option 2: List all users
|
|
||||||
```
|
|
||||||
|
|
||||||
### Change Password
|
|
||||||
```bash
|
|
||||||
python3 manage_users.py
|
|
||||||
# Option 4: Change password
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Can't Login?
|
|
||||||
```bash
|
|
||||||
# Check if admin was created
|
|
||||||
docker-compose logs | grep "Created admin"
|
|
||||||
|
|
||||||
# Or create manually
|
|
||||||
docker-compose exec sms-campaign python3 manage_users.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Key Not Working?
|
|
||||||
```bash
|
|
||||||
# Verify keys loaded
|
|
||||||
docker-compose exec sms-campaign env | grep API_KEY
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker-compose logs -f | grep "Authentication"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Termux API Error?
|
|
||||||
```bash
|
|
||||||
# Check Android service
|
|
||||||
ssh android-dev@100.107.173.66 -p 8022
|
|
||||||
pgrep -f termux-sms-api-server.py
|
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
tail -f ~/projects/sms-campaign-manager/logs/sms-api.log
|
docker compose logs -f sms-campaign
|
||||||
|
|
||||||
|
# Restart container
|
||||||
|
docker compose restart
|
||||||
```
|
```
|
||||||
|
|
||||||
### Need to Restart?
|
### Android Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Restart Docker
|
# SSH to Android
|
||||||
docker-compose restart
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||||
|
|
||||||
# Restart Android service
|
# Start all services
|
||||||
./deploy-to-android.sh
|
~/bin/start-all-services.sh
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
~/bin/sms-service.sh status
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
tail -f ~/logs/sms-api.log
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### User Management
|
||||||
|
|
||||||
## 📚 Full Documentation
|
```bash
|
||||||
|
# Create new user
|
||||||
|
python3 manage_users.py
|
||||||
|
# Select option 1, follow prompts
|
||||||
|
|
||||||
| Document | Purpose |
|
# List users
|
||||||
|----------|---------|
|
python3 manage_users.py
|
||||||
| **[Deployment Guide](../deployment/deployment-guide.md)** | Complete deployment instructions |
|
# Select option 2
|
||||||
| **[User Management](../guides/user-management.md)** | User system guide |
|
```
|
||||||
| **[Authentication Setup](authentication.md)** | Quick auth setup |
|
|
||||||
| **[API Security](../security/api-security.md)** | API key documentation |
|
|
||||||
| **[Security Setup](../security/security-setup.md)** | Security configuration |
|
|
||||||
|
|
||||||
---
|
## Credentials Reference
|
||||||
|
|
||||||
## ✅ Success Checklist
|
After setup, your credentials are stored in `.env`:
|
||||||
|
|
||||||
|
| Credential | Variable | Purpose |
|
||||||
|
|------------|----------|---------|
|
||||||
|
| Admin username | `ADMIN_USERNAME` | Web dashboard login |
|
||||||
|
| Admin password | `ADMIN_PASSWORD` | Web dashboard login |
|
||||||
|
| User API key | `USER_API_KEY` | API access for scripts |
|
||||||
|
| Admin API key | `ADMIN_API_KEY` | Admin operations |
|
||||||
|
| Termux API key | `TERMUX_API_KEY` | Android communication |
|
||||||
|
|
||||||
|
### API Key Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Header method
|
||||||
|
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
|
|
||||||
|
# Bearer token method
|
||||||
|
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:5000/api/endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service URLs
|
||||||
|
|
||||||
|
| Service | URL |
|
||||||
|
|---------|-----|
|
||||||
|
| Web Dashboard | `http://localhost:5000` |
|
||||||
|
| Login Page | `http://localhost:5000/login` |
|
||||||
|
| Health Check | `http://localhost:5000/health` |
|
||||||
|
| Android API | `http://YOUR_ANDROID_IP:5001/health` |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Can't Login
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if admin was created
|
||||||
|
docker compose logs | grep "Created admin"
|
||||||
|
|
||||||
|
# Create user manually
|
||||||
|
python3 manage_users.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Key Not Working
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify keys are loaded
|
||||||
|
docker compose exec sms-campaign env | grep API_KEY
|
||||||
|
|
||||||
|
# Restart to reload
|
||||||
|
docker compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android Not Responding
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check connectivity
|
||||||
|
ping YOUR_ANDROID_IP
|
||||||
|
|
||||||
|
# View Android logs
|
||||||
|
ssh -p 8022 android-dev@YOUR_ANDROID_IP "tail -20 ~/logs/sms-api.log"
|
||||||
|
|
||||||
|
# Redeploy
|
||||||
|
./scripts/deploy-android.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Checklist
|
||||||
|
|
||||||
|
After deployment, verify:
|
||||||
|
|
||||||
- [ ] Deployed to Android successfully
|
|
||||||
- [ ] Docker container running and healthy
|
- [ ] Docker container running and healthy
|
||||||
- [ ] Can access login page at `/login`
|
- [ ] Can access login page at `/login`
|
||||||
- [ ] Can log in as admin
|
- [ ] Can log in as admin
|
||||||
- [ ] Dashboard works without ModHeader
|
- [ ] Dashboard loads without errors
|
||||||
- [ ] API calls require authentication
|
- [ ] API calls require authentication
|
||||||
- [ ] Can create new users via CLI
|
- [ ] Android health check passes
|
||||||
- [ ] SMS sending works with authentication
|
- [ ] Test SMS sends successfully
|
||||||
|
|
||||||
---
|
## Next Steps
|
||||||
|
|
||||||
## 🎯 Next Steps
|
1. **Create team users**: Use `manage_users.py`
|
||||||
|
2. **Import contacts**: Upload CSV via web dashboard
|
||||||
|
3. **Create campaign**: Set up your first SMS campaign
|
||||||
|
4. **Review security**: See [Security Setup](../security/security-setup.md)
|
||||||
|
|
||||||
1. **Test the web dashboard** - Login and explore
|
## Related Documentation
|
||||||
2. **Create team users** - Use `manage_users.py`
|
|
||||||
3. **Try API calls** - Test with and without keys
|
|
||||||
4. **Send test SMS** - Verify end-to-end flow
|
|
||||||
5. **Review logs** - Monitor authentication attempts
|
|
||||||
|
|
||||||
---
|
- [Installation Guide](installation.md) - Complete setup instructions
|
||||||
|
- [Authentication Setup](authentication.md) - User login details
|
||||||
**Everything is configured and ready to test!** 🚀
|
- [User Management](../guides/user-management.md) - Managing users
|
||||||
|
- [Testing Guide](../guides/testing.md) - Comprehensive testing
|
||||||
**Support:** See [Deployment Guide](../deployment/deployment-guide.md) for detailed deployment instructions.
|
- [Troubleshooting](../guides/troubleshooting.md) - Common issues
|
||||||
|
|||||||
226
scripts/update-api-keys.sh
Executable file
226
scripts/update-api-keys.sh
Executable file
@ -0,0 +1,226 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Ubuntu Homelab - Update API Keys from Android Device
|
||||||
|
# Syncs the SMS_API_SECRET from Android Termux to the local .env file
|
||||||
|
#
|
||||||
|
|
||||||
|
# Color codes
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
ENV_FILE="$PROJECT_DIR/.env"
|
||||||
|
ENV_EXAMPLE="$PROJECT_DIR/.env.example"
|
||||||
|
|
||||||
|
# Banner
|
||||||
|
clear
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}🔐 SMS Campaign Manager - API Key Configuration${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if .env exists
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ .env file not found${NC}"
|
||||||
|
if [ -f "$ENV_EXAMPLE" ]; then
|
||||||
|
echo -e "${BLUE}Creating .env from .env.example...${NC}"
|
||||||
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||||
|
echo -e "${GREEN}✅ Created .env file${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ ERROR: .env.example not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to update or add key in .env
|
||||||
|
update_env_key() {
|
||||||
|
local key=$1
|
||||||
|
local value=$2
|
||||||
|
|
||||||
|
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
|
# Update existing key
|
||||||
|
sed -i.bak "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
echo -e "${GREEN}✅ Updated ${key}${NC}"
|
||||||
|
elif grep -q "^#${key}=" "$ENV_FILE"; then
|
||||||
|
# Uncomment and update
|
||||||
|
sed -i.bak "s|^#${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
echo -e "${GREEN}✅ Enabled and updated ${key}${NC}"
|
||||||
|
else
|
||||||
|
# Add new key
|
||||||
|
echo "${key}=${value}" >> "$ENV_FILE"
|
||||||
|
echo -e "${GREEN}✅ Added ${key}${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -e "${BOLD}Choose an option:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${CYAN}1.${NC} Enter API key manually"
|
||||||
|
echo -e " ${CYAN}2.${NC} Fetch from Android device via SSH"
|
||||||
|
echo -e " ${CYAN}3.${NC} Generate new keys for both systems"
|
||||||
|
echo ""
|
||||||
|
echo -n "Enter choice (1-3): "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
# Manual entry
|
||||||
|
echo -e "${BLUE}📝 Manual API Key Entry${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Enter the API key from your Android device:${NC}"
|
||||||
|
echo -e "${CYAN}(It should be a 64-character hex string)${NC}"
|
||||||
|
echo ""
|
||||||
|
read -r API_KEY
|
||||||
|
|
||||||
|
if [ -z "$API_KEY" ]; then
|
||||||
|
echo -e "${RED}❌ No API key provided${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate format (should be 64 hex characters)
|
||||||
|
if [[ ! "$API_KEY" =~ ^[a-f0-9]{64}$ ]]; then
|
||||||
|
echo -e "${YELLOW}⚠️ Warning: API key format looks unusual${NC}"
|
||||||
|
echo -e "${YELLOW}Expected: 64 hexadecimal characters (0-9, a-f)${NC}"
|
||||||
|
echo -e "${YELLOW}Got: ${#API_KEY} characters${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -n "Continue anyway? (y/n): "
|
||||||
|
read -r confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${YELLOW}Cancelled${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
2)
|
||||||
|
# Fetch via SSH
|
||||||
|
echo -e "${BLUE}📡 Fetching from Android Device${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Try to get PHONE_IP from .env
|
||||||
|
PHONE_IP=$(grep "^PHONE_IP=" "$ENV_FILE" | cut -d'=' -f2)
|
||||||
|
|
||||||
|
if [ -z "$PHONE_IP" ]; then
|
||||||
|
echo -n "Enter Android device IP address: "
|
||||||
|
read -r PHONE_IP
|
||||||
|
else
|
||||||
|
echo -e "Using IP from .env: ${CYAN}${PHONE_IP}${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Attempting to fetch API key from $PHONE_IP...${NC}"
|
||||||
|
echo -e "${CYAN}(You may be prompted for SSH password)${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Try to fetch the key file or environment variable
|
||||||
|
API_KEY=$(ssh -p 8022 android-dev@"$PHONE_IP" "cat ~/.sms-api-key 2>/dev/null || echo \$SMS_API_SECRET" 2>/dev/null | tr -d '\n\r')
|
||||||
|
|
||||||
|
if [ -z "$API_KEY" ]; then
|
||||||
|
echo -e "${RED}❌ Could not fetch API key from device${NC}"
|
||||||
|
echo -e "${YELLOW}Please make sure:${NC}"
|
||||||
|
echo -e " 1. SSH is enabled on Android device"
|
||||||
|
echo -e " 2. You've run setup-api-key.sh on the device"
|
||||||
|
echo -e " 3. The device IP address is correct"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Try option 1 (manual entry) instead${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Successfully fetched API key from device${NC}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
3)
|
||||||
|
# Generate new keys
|
||||||
|
echo -e "${BLUE}🎲 Generating New API Keys${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}This will generate a NEW set of keys.${NC}"
|
||||||
|
echo -e "${YELLOW}You'll need to update the Android device with the new key.${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -n "Continue? (y/n): "
|
||||||
|
read -r confirm
|
||||||
|
|
||||||
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${YELLOW}Cancelled${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
API_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
||||||
|
echo -e "${GREEN}✅ Generated new API key${NC}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo -e "${RED}❌ Invalid choice${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Update .env file
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}📝 Updating .env file...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
update_env_key "TERMUX_API_KEY" "$API_KEY"
|
||||||
|
update_env_key "SMS_API_SECRET" "$API_KEY"
|
||||||
|
|
||||||
|
# Cleanup backup
|
||||||
|
rm -f "$ENV_FILE.bak"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}✅ Configuration Updated${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╠════════════════════════════════════════════════════════════════════════╣${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}API Key:${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}${API_KEY}${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}Updated in:${NC} ${YELLOW}${ENV_FILE}${NC}"
|
||||||
|
printf "${CYAN}║${NC}\n"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}✅ TERMUX_API_KEY${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${GREEN}✅ SMS_API_SECRET${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show next steps
|
||||||
|
echo -e "${BOLD}📋 Next Steps:${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$choice" == "3" ]; then
|
||||||
|
echo -e " ${YELLOW}1.${NC} ${BOLD}Update Android device with new key:${NC}"
|
||||||
|
echo -e " ${CYAN}ssh -p 8022 android-dev@${PHONE_IP:-PHONE_IP}${NC}"
|
||||||
|
echo -e " ${CYAN}echo 'export SMS_API_SECRET=\"${API_KEY}\"' >> ~/.bashrc${NC}"
|
||||||
|
echo -e " ${CYAN}source ~/.bashrc${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}2.${NC} ${BOLD}Restart SMS API server on Android${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}3.${NC} ${BOLD}Restart Docker container:${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}1.${NC} ${BOLD}Restart Docker container:${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e " ${CYAN}docker-compose down${NC}"
|
||||||
|
echo -e " ${CYAN}docker-compose up -d --build${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}🔍 Verify:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${CYAN}# Test Termux API${NC}"
|
||||||
|
echo -e " ${CYAN}curl -H \"X-API-Key: ${API_KEY}\" http://${PHONE_IP:-PHONE_IP}:5001/health${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${CYAN}# Test web interface${NC}"
|
||||||
|
echo -e " ${CYAN}curl http://localhost:5000/health${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${GREEN}${BOLD} Configuration complete! 🎉${NC}"
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
53
src/app.py
53
src/app.py
@ -12,6 +12,7 @@ from datetime import timedelta
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
|
from flask_wtf.csrf import CSRFProtect, generate_csrf
|
||||||
|
|
||||||
# Generate cache version at startup (changes on each container restart)
|
# Generate cache version at startup (changes on each container restart)
|
||||||
CACHE_VERSION = str(int(time.time()))
|
CACHE_VERSION = str(int(time.time()))
|
||||||
@ -85,11 +86,12 @@ def create_app():
|
|||||||
app.config.update({
|
app.config.update({
|
||||||
'SESSION_COOKIE_SECURE': use_https,
|
'SESSION_COOKIE_SECURE': use_https,
|
||||||
'SESSION_COOKIE_HTTPONLY': True,
|
'SESSION_COOKIE_HTTPONLY': True,
|
||||||
'SESSION_COOKIE_SAMESITE': 'Lax',
|
'SESSION_COOKIE_SAMESITE': 'Strict', # SECURITY: Strict for better CSRF protection
|
||||||
'PERMANENT_SESSION_LIFETIME': timedelta(hours=24),
|
'PERMANENT_SESSION_LIFETIME': timedelta(hours=24),
|
||||||
'REMEMBER_COOKIE_DURATION': timedelta(days=14),
|
'REMEMBER_COOKIE_DURATION': timedelta(days=14),
|
||||||
'REMEMBER_COOKIE_SECURE': use_https,
|
'REMEMBER_COOKIE_SECURE': use_https,
|
||||||
'REMEMBER_COOKIE_HTTPONLY': True,
|
'REMEMBER_COOKIE_HTTPONLY': True,
|
||||||
|
'REMEMBER_COOKIE_SAMESITE': 'Strict', # SECURITY: Match session cookie
|
||||||
})
|
})
|
||||||
|
|
||||||
# Initialize Flask-Login
|
# Initialize Flask-Login
|
||||||
@ -100,10 +102,34 @@ def create_app():
|
|||||||
login_manager.login_message_category = 'warning'
|
login_manager.login_message_category = 'warning'
|
||||||
app.login_manager = login_manager
|
app.login_manager = login_manager
|
||||||
|
|
||||||
# Inject cache version into all templates (auto-updates on container restart)
|
# Initialize CSRF protection
|
||||||
|
csrf = CSRFProtect(app)
|
||||||
|
app.csrf = csrf
|
||||||
|
logger.info("✅ CSRF protection initialized")
|
||||||
|
|
||||||
|
# Inject cache version and CSRF token into all templates
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_cache_version():
|
def inject_globals():
|
||||||
return {'cache_version': CACHE_VERSION}
|
return {
|
||||||
|
'cache_version': CACHE_VERSION,
|
||||||
|
'csrf_token': generate_csrf
|
||||||
|
}
|
||||||
|
|
||||||
|
# Endpoint to get CSRF token for JavaScript fetch requests
|
||||||
|
@app.route('/api/csrf-token')
|
||||||
|
def get_csrf_token():
|
||||||
|
"""Return CSRF token for AJAX requests"""
|
||||||
|
return jsonify({'csrf_token': generate_csrf()})
|
||||||
|
|
||||||
|
# Security headers middleware
|
||||||
|
@app.after_request
|
||||||
|
def set_security_headers(response):
|
||||||
|
response.headers['X-Content-Type-Options'] = 'nosniff'
|
||||||
|
response.headers['X-Frame-Options'] = 'DENY'
|
||||||
|
response.headers['X-XSS-Protection'] = '1; mode=block'
|
||||||
|
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
||||||
|
response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()'
|
||||||
|
return response
|
||||||
|
|
||||||
# Initialize authentication manager (for API keys)
|
# Initialize authentication manager (for API keys)
|
||||||
try:
|
try:
|
||||||
@ -249,6 +275,25 @@ def create_app():
|
|||||||
limiter.exempt(app.view_functions['connection_routes.device_status'])
|
limiter.exempt(app.view_functions['connection_routes.device_status'])
|
||||||
limiter.exempt(app.view_functions['connection_routes.termux_status'])
|
limiter.exempt(app.view_functions['connection_routes.termux_status'])
|
||||||
|
|
||||||
|
# CSRF exemptions for API blueprints
|
||||||
|
# API endpoints are protected by session auth + SameSite=Strict cookies
|
||||||
|
# This is safe because:
|
||||||
|
# 1. All API endpoints require login (session authentication)
|
||||||
|
# 2. SameSite=Strict prevents cookies from being sent cross-origin
|
||||||
|
# 3. API endpoints return JSON, not HTML (no clickjacking risk)
|
||||||
|
csrf.exempt(conversations_bp)
|
||||||
|
csrf.exempt(conversations_enhanced_bp)
|
||||||
|
csrf.exempt(lists_bp)
|
||||||
|
csrf.exempt(auth_bp)
|
||||||
|
csrf.exempt(campaign_routes)
|
||||||
|
csrf.exempt(template_routes)
|
||||||
|
csrf.exempt(sms_routes)
|
||||||
|
csrf.exempt(connection_routes)
|
||||||
|
csrf.exempt(analytics_routes)
|
||||||
|
csrf.exempt(upload_routes)
|
||||||
|
csrf.exempt(test_routes)
|
||||||
|
csrf.exempt(database_routes)
|
||||||
|
|
||||||
# Basic routes
|
# Basic routes
|
||||||
@app.route('/health')
|
@app.route('/health')
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
|
|||||||
@ -86,12 +86,11 @@ class AuthManager:
|
|||||||
|
|
||||||
def get_api_key_from_request(self) -> Optional[str]:
|
def get_api_key_from_request(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Extract API key from request headers or query params
|
Extract API key from request headers
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
- Header: X-API-Key
|
- Header: X-API-Key
|
||||||
- Header: Authorization: Bearer <key>
|
- Header: Authorization: Bearer <key>
|
||||||
- Query param: api_key
|
|
||||||
"""
|
"""
|
||||||
# Check X-API-Key header
|
# Check X-API-Key header
|
||||||
api_key = request.headers.get('X-API-Key')
|
api_key = request.headers.get('X-API-Key')
|
||||||
@ -103,12 +102,7 @@ class AuthManager:
|
|||||||
if auth_header and auth_header.startswith('Bearer '):
|
if auth_header and auth_header.startswith('Bearer '):
|
||||||
return auth_header[7:] # Remove 'Bearer ' prefix
|
return auth_header[7:] # Remove 'Bearer ' prefix
|
||||||
|
|
||||||
# Check query parameter (less secure, for testing only)
|
# SECURITY: Query parameter support removed - headers only
|
||||||
api_key = request.args.get('api_key')
|
|
||||||
if api_key:
|
|
||||||
logger.warning("⚠️ API key passed in query string - use headers instead")
|
|
||||||
return api_key
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def require_auth(self, min_role: str = 'user') -> Callable:
|
def require_auth(self, min_role: str = 'user') -> Callable:
|
||||||
|
|||||||
116
src/core/error_handler.py
Normal file
116
src/core/error_handler.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""
|
||||||
|
Standardized Error Handling Module
|
||||||
|
Logs detailed errors but returns generic messages to prevent information disclosure
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
from functools import wraps
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Safe error messages for different error types
|
||||||
|
SAFE_ERROR_MESSAGES = {
|
||||||
|
'database': 'A database error occurred. Please try again.',
|
||||||
|
'validation': 'Invalid input provided.',
|
||||||
|
'not_found': 'The requested resource was not found.',
|
||||||
|
'permission': 'You do not have permission to perform this action.',
|
||||||
|
'connection': 'A connection error occurred. Please try again.',
|
||||||
|
'timeout': 'The request timed out. Please try again.',
|
||||||
|
'file': 'A file operation error occurred.',
|
||||||
|
'default': 'An unexpected error occurred. Please try again.'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def classify_error(error: Exception) -> str:
|
||||||
|
"""
|
||||||
|
Classify an exception into a category for safe error messaging
|
||||||
|
"""
|
||||||
|
error_str = str(type(error).__name__).lower()
|
||||||
|
error_msg = str(error).lower()
|
||||||
|
|
||||||
|
if 'database' in error_str or 'sqlite' in error_str or 'sql' in error_msg:
|
||||||
|
return 'database'
|
||||||
|
elif 'validation' in error_str or 'value' in error_str:
|
||||||
|
return 'validation'
|
||||||
|
elif 'notfound' in error_str or 'not found' in error_msg:
|
||||||
|
return 'not_found'
|
||||||
|
elif 'permission' in error_str or 'forbidden' in error_str or 'access' in error_msg:
|
||||||
|
return 'permission'
|
||||||
|
elif 'connection' in error_str or 'connect' in error_msg or 'refused' in error_msg:
|
||||||
|
return 'connection'
|
||||||
|
elif 'timeout' in error_str or 'timed out' in error_msg:
|
||||||
|
return 'timeout'
|
||||||
|
elif 'file' in error_str or 'io' in error_str:
|
||||||
|
return 'file'
|
||||||
|
else:
|
||||||
|
return 'default'
|
||||||
|
|
||||||
|
|
||||||
|
def safe_error_response(error: Exception, context: str = None) -> tuple:
|
||||||
|
"""
|
||||||
|
Log the full error but return a safe message
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error: The exception that occurred
|
||||||
|
context: Optional context for logging (e.g., route name, operation)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (json_response, status_code)
|
||||||
|
"""
|
||||||
|
# Log the full error with traceback
|
||||||
|
log_message = f"Error in {context}: {type(error).__name__}: {error}" if context else f"Error: {type(error).__name__}: {error}"
|
||||||
|
logger.error(log_message)
|
||||||
|
logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
|
# Get safe message
|
||||||
|
error_category = classify_error(error)
|
||||||
|
safe_message = SAFE_ERROR_MESSAGES.get(error_category, SAFE_ERROR_MESSAGES['default'])
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': safe_message
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
def handle_api_errors(context: str = None):
|
||||||
|
"""
|
||||||
|
Decorator for API routes that handles errors safely
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/api/endpoint')
|
||||||
|
@handle_api_errors('endpoint_name')
|
||||||
|
def my_endpoint():
|
||||||
|
# ... code that might raise exceptions
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return safe_error_response(e, context or f.__name__)
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def log_and_sanitize(error: Exception, context: str = None) -> str:
|
||||||
|
"""
|
||||||
|
Log an error and return a sanitized message string
|
||||||
|
For use in existing code patterns without full decorator refactor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error: The exception that occurred
|
||||||
|
context: Optional context for logging
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Safe error message string
|
||||||
|
"""
|
||||||
|
log_message = f"Error in {context}: {type(error).__name__}: {error}" if context else f"Error: {type(error).__name__}: {error}"
|
||||||
|
logger.error(log_message)
|
||||||
|
logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
|
error_category = classify_error(error)
|
||||||
|
return SAFE_ERROR_MESSAGES.get(error_category, SAFE_ERROR_MESSAGES['default'])
|
||||||
189
src/core/phone_validation.py
Normal file
189
src/core/phone_validation.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
"""
|
||||||
|
Phone Number Validation Module
|
||||||
|
Validates and normalizes phone numbers for SMS operations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Phone number patterns
|
||||||
|
# US/Canada format: +1XXXXXXXXXX or 1XXXXXXXXXX or XXXXXXXXXX
|
||||||
|
US_PATTERN = re.compile(r'^(\+?1)?[-.\s]?(\d{3})[-.\s]?(\d{3})[-.\s]?(\d{4})$')
|
||||||
|
|
||||||
|
# International format with country code
|
||||||
|
INTERNATIONAL_PATTERN = re.compile(r'^\+?(\d{1,3})[-.\s]?(\d{1,14})$')
|
||||||
|
|
||||||
|
# Generic digits-only pattern (minimum 10 digits)
|
||||||
|
DIGITS_PATTERN = re.compile(r'^\+?[\d\s\-\.\(\)]{10,20}$')
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_phone_number(phone: str) -> str:
|
||||||
|
"""
|
||||||
|
Normalize a phone number to a consistent format.
|
||||||
|
Removes all non-digit characters except leading +.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Raw phone number string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Normalized phone number (digits only, possibly with leading +)
|
||||||
|
"""
|
||||||
|
if not phone:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# Strip whitespace
|
||||||
|
phone = phone.strip()
|
||||||
|
|
||||||
|
# Keep track of whether it starts with +
|
||||||
|
has_plus = phone.startswith('+')
|
||||||
|
|
||||||
|
# Remove all non-digit characters
|
||||||
|
digits = re.sub(r'[^\d]', '', phone)
|
||||||
|
|
||||||
|
# Add + back if it was there
|
||||||
|
if has_plus:
|
||||||
|
return '+' + digits
|
||||||
|
|
||||||
|
return digits
|
||||||
|
|
||||||
|
|
||||||
|
def validate_phone_number(phone: str, strict: bool = False) -> tuple[bool, str, str]:
|
||||||
|
"""
|
||||||
|
Validate a phone number and return normalized version.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Raw phone number string
|
||||||
|
strict: If True, require valid format. If False, just require minimum digits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_valid: bool, normalized: str, error_message: str)
|
||||||
|
"""
|
||||||
|
if not phone:
|
||||||
|
return False, '', 'Phone number is required'
|
||||||
|
|
||||||
|
# Normalize first
|
||||||
|
normalized = normalize_phone_number(phone)
|
||||||
|
|
||||||
|
# Remove leading + for digit counting
|
||||||
|
digits_only = normalized.lstrip('+')
|
||||||
|
|
||||||
|
# Check minimum length (10 digits for US, allow 7+ for international shortcodes)
|
||||||
|
if len(digits_only) < 7:
|
||||||
|
return False, normalized, 'Phone number too short (minimum 7 digits)'
|
||||||
|
|
||||||
|
# Check maximum length (15 digits per E.164 standard)
|
||||||
|
if len(digits_only) > 15:
|
||||||
|
return False, normalized, 'Phone number too long (maximum 15 digits)'
|
||||||
|
|
||||||
|
# Check for obvious invalid patterns
|
||||||
|
if digits_only.startswith('0000') or digits_only == '0' * len(digits_only):
|
||||||
|
return False, normalized, 'Invalid phone number'
|
||||||
|
|
||||||
|
if strict:
|
||||||
|
# US number validation
|
||||||
|
if len(digits_only) == 10 or (len(digits_only) == 11 and digits_only.startswith('1')):
|
||||||
|
# Check for valid US area code (first digit 2-9)
|
||||||
|
area_code_start = 1 if digits_only.startswith('1') else 0
|
||||||
|
area_code = digits_only[area_code_start:area_code_start + 3]
|
||||||
|
|
||||||
|
if area_code[0] in '01':
|
||||||
|
return False, normalized, 'Invalid area code'
|
||||||
|
|
||||||
|
# Format as +1XXXXXXXXXX
|
||||||
|
if digits_only.startswith('1'):
|
||||||
|
return True, '+' + digits_only, ''
|
||||||
|
else:
|
||||||
|
return True, '+1' + digits_only, ''
|
||||||
|
|
||||||
|
# International number with country code
|
||||||
|
if len(digits_only) >= 8 and len(digits_only) <= 15:
|
||||||
|
# Assume it's valid international if it has enough digits
|
||||||
|
if not normalized.startswith('+'):
|
||||||
|
normalized = '+' + normalized
|
||||||
|
return True, normalized, ''
|
||||||
|
|
||||||
|
return False, normalized, 'Phone number format not recognized'
|
||||||
|
|
||||||
|
# Non-strict: just check it has enough digits
|
||||||
|
return True, normalized, ''
|
||||||
|
|
||||||
|
|
||||||
|
def format_phone_display(phone: str) -> str:
|
||||||
|
"""
|
||||||
|
Format a phone number for display.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Normalized phone number
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted phone number for display
|
||||||
|
"""
|
||||||
|
normalized = normalize_phone_number(phone)
|
||||||
|
digits = normalized.lstrip('+')
|
||||||
|
|
||||||
|
# US format: (XXX) XXX-XXXX
|
||||||
|
if len(digits) == 10:
|
||||||
|
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
|
||||||
|
|
||||||
|
# US with country code: +1 (XXX) XXX-XXXX
|
||||||
|
if len(digits) == 11 and digits.startswith('1'):
|
||||||
|
return f"+1 ({digits[1:4]}) {digits[4:7]}-{digits[7:]}"
|
||||||
|
|
||||||
|
# International: +XX XXXX XXXX
|
||||||
|
if normalized.startswith('+'):
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
return phone
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_phone(phone: str) -> bool:
|
||||||
|
"""
|
||||||
|
Simple boolean check if phone number is valid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Phone number to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
is_valid, _, _ = validate_phone_number(phone, strict=False)
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_phone_list(phones: list, validate: bool = True) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Sanitize a list of phone numbers/contact dicts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phones: List of phone strings or dicts with 'phone' key
|
||||||
|
validate: Whether to validate each phone
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of dicts with 'phone', 'normalized', 'valid', 'error' keys
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for item in phones:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
phone = item.get('phone', '')
|
||||||
|
contact = item.copy()
|
||||||
|
else:
|
||||||
|
phone = str(item)
|
||||||
|
contact = {'phone': phone}
|
||||||
|
|
||||||
|
if validate:
|
||||||
|
is_valid, normalized, error = validate_phone_number(phone)
|
||||||
|
contact['phone'] = normalized if is_valid else phone
|
||||||
|
contact['_valid'] = is_valid
|
||||||
|
contact['_error'] = error
|
||||||
|
else:
|
||||||
|
contact['phone'] = normalize_phone_number(phone)
|
||||||
|
contact['_valid'] = True
|
||||||
|
contact['_error'] = ''
|
||||||
|
|
||||||
|
results.append(contact)
|
||||||
|
|
||||||
|
return results
|
||||||
@ -1,5 +1,6 @@
|
|||||||
Flask==3.0.0
|
Flask==3.0.0
|
||||||
Flask-Login==0.6.3
|
Flask-Login==0.6.3
|
||||||
|
Flask-WTF==1.2.1
|
||||||
Werkzeug==3.0.1
|
Werkzeug==3.0.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
typing-extensions==4.8.0
|
typing-extensions==4.8.0
|
||||||
|
|||||||
@ -6,14 +6,40 @@ Handles file upload operations
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from routes.api.auth_decorator import require_auth
|
from routes.api.auth_decorator import require_auth
|
||||||
from core.user_auth import require_login
|
from core.user_auth import require_login
|
||||||
|
from core.error_handler import log_and_sanitize
|
||||||
|
from core.phone_validation import validate_phone_number, normalize_phone_number
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_csv_value(value):
|
||||||
|
"""
|
||||||
|
Sanitize CSV values to prevent CSV injection attacks.
|
||||||
|
Prefixes dangerous characters with a single quote to prevent formula execution.
|
||||||
|
"""
|
||||||
|
if not value or not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Characters that trigger formula execution in spreadsheet apps
|
||||||
|
dangerous_chars = ['=', '+', '-', '@', '\t', '\r', '\n']
|
||||||
|
|
||||||
|
# Also check for URLs that could be malicious
|
||||||
|
if value.strip().startswith(tuple(dangerous_chars)):
|
||||||
|
return "'" + value
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_csv_row(row):
|
||||||
|
"""Sanitize all values in a CSV row dict"""
|
||||||
|
return {key: sanitize_csv_value(value) for key, value in row.items()}
|
||||||
|
|
||||||
upload_routes = Blueprint('upload_routes', __name__, url_prefix='/api')
|
upload_routes = Blueprint('upload_routes', __name__, url_prefix='/api')
|
||||||
|
|
||||||
# Dependencies will be injected
|
# Dependencies will be injected
|
||||||
@ -55,13 +81,16 @@ def upload_csv():
|
|||||||
|
|
||||||
reader = csv.DictReader(csvfile, delimiter=delimiter)
|
reader = csv.DictReader(csvfile, delimiter=delimiter)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
|
# Sanitize CSV values to prevent injection attacks
|
||||||
|
row = sanitize_csv_row(row)
|
||||||
|
|
||||||
# Clean field names
|
# Clean field names
|
||||||
cleaned_row = {}
|
cleaned_row = {}
|
||||||
for key, value in row.items():
|
for key, value in row.items():
|
||||||
if key:
|
if key:
|
||||||
clean_key = key.strip().lower().replace(' ', '_')
|
clean_key = key.strip().lower().replace(' ', '_')
|
||||||
cleaned_row[clean_key] = value.strip() if value else ''
|
cleaned_row[clean_key] = value.strip() if value else ''
|
||||||
|
|
||||||
# Map common field names
|
# Map common field names
|
||||||
recipient = {}
|
recipient = {}
|
||||||
for key, value in cleaned_row.items():
|
for key, value in cleaned_row.items():
|
||||||
@ -73,9 +102,13 @@ def upload_csv():
|
|||||||
recipient['message'] = value
|
recipient['message'] = value
|
||||||
else:
|
else:
|
||||||
recipient[key] = value
|
recipient[key] = value
|
||||||
|
|
||||||
if recipient.get('phone'):
|
if recipient.get('phone'):
|
||||||
recipients.append(recipient)
|
# Validate and normalize phone number
|
||||||
|
is_valid, normalized, error = validate_phone_number(recipient['phone'])
|
||||||
|
if is_valid:
|
||||||
|
recipient['phone'] = normalized
|
||||||
|
recipients.append(recipient)
|
||||||
|
|
||||||
# Save as contact list for reuse
|
# Save as contact list for reuse
|
||||||
list_id = None
|
list_id = None
|
||||||
@ -101,8 +134,7 @@ def upload_csv():
|
|||||||
return jsonify({"error": "Invalid file type"}), 400
|
return jsonify({"error": "Invalid file type"}), 400
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"CSV upload error: {e}")
|
return jsonify({"success": False, "error": log_and_sanitize(e, 'upload_csv')}), 500
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
|
||||||
|
|
||||||
@upload_routes.route('/campaign/upload', methods=['POST'])
|
@upload_routes.route('/campaign/upload', methods=['POST'])
|
||||||
@require_login()
|
@require_login()
|
||||||
@ -127,6 +159,9 @@ def upload_campaign_csv():
|
|||||||
preview_contacts = []
|
preview_contacts = []
|
||||||
|
|
||||||
for i, row in enumerate(csv_reader):
|
for i, row in enumerate(csv_reader):
|
||||||
|
# Sanitize CSV values to prevent injection attacks
|
||||||
|
row = sanitize_csv_row(row)
|
||||||
|
|
||||||
# Normalize field names
|
# Normalize field names
|
||||||
normalized_row = {}
|
normalized_row = {}
|
||||||
for key, value in row.items():
|
for key, value in row.items():
|
||||||
@ -138,12 +173,16 @@ def upload_campaign_csv():
|
|||||||
normalized_row['name'] = value.strip()
|
normalized_row['name'] = value.strip()
|
||||||
elif 'message' in normalized_key:
|
elif 'message' in normalized_key:
|
||||||
normalized_row['message'] = value.strip()
|
normalized_row['message'] = value.strip()
|
||||||
|
|
||||||
if 'phone' in normalized_row:
|
if 'phone' in normalized_row:
|
||||||
contacts.append(normalized_row)
|
# Validate and normalize phone number
|
||||||
# Add to preview (first 10)
|
is_valid, phone_normalized, error = validate_phone_number(normalized_row['phone'])
|
||||||
if i < 10:
|
if is_valid:
|
||||||
preview_contacts.append(normalized_row)
|
normalized_row['phone'] = phone_normalized
|
||||||
|
contacts.append(normalized_row)
|
||||||
|
# Add to preview (first 10)
|
||||||
|
if len(preview_contacts) < 10:
|
||||||
|
preview_contacts.append(normalized_row)
|
||||||
|
|
||||||
if not contacts:
|
if not contacts:
|
||||||
return jsonify({'success': False, 'error': 'No valid contacts found in CSV'}), 400
|
return jsonify({'success': False, 'error': 'No valid contacts found in CSV'}), 400
|
||||||
@ -173,8 +212,7 @@ def upload_campaign_csv():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error uploading campaign CSV: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'upload_campaign_csv')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
@upload_routes.route('/contacts/fetch-from-phone', methods=['GET'])
|
@upload_routes.route('/contacts/fetch-from-phone', methods=['GET'])
|
||||||
@require_login()
|
@require_login()
|
||||||
@ -205,8 +243,7 @@ def fetch_phone_contacts():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fetch phone contacts error: {e}")
|
return jsonify({"success": False, "error": log_and_sanitize(e, 'fetch_phone_contacts')}), 500
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
|
||||||
|
|
||||||
@upload_routes.route('/contacts/import-from-phone', methods=['POST'])
|
@upload_routes.route('/contacts/import-from-phone', methods=['POST'])
|
||||||
@require_login()
|
@require_login()
|
||||||
@ -265,5 +302,4 @@ def import_phone_contacts():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Import phone contacts error: {e}")
|
return jsonify({"success": False, "error": log_and_sanitize(e, 'import_phone_contacts')}), 500
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ Uses Flask-Login for reliable session management
|
|||||||
import logging
|
import logging
|
||||||
from flask import Blueprint, request, jsonify, session, render_template, redirect, url_for, current_app
|
from flask import Blueprint, request, jsonify, session, render_template, redirect, url_for, current_app
|
||||||
from flask_login import login_user, logout_user, current_user, login_required
|
from flask_login import login_user, logout_user, current_user, login_required
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from core.user_auth import UserManager, require_login
|
from core.user_auth import UserManager, require_login
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -24,9 +25,9 @@ def init_auth_routes(um: UserManager):
|
|||||||
@auth_bp.route('/login', methods=['GET'])
|
@auth_bp.route('/login', methods=['GET'])
|
||||||
def login_page():
|
def login_page():
|
||||||
"""Display login page"""
|
"""Display login page"""
|
||||||
# If already logged in, redirect to dashboard
|
# If already logged in, redirect to campaigns
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('campaigns'))
|
||||||
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ def login():
|
|||||||
'username': user.username,
|
'username': user.username,
|
||||||
'role': user.role
|
'role': user.role
|
||||||
},
|
},
|
||||||
'redirect': url_for('dashboard')
|
'redirect': url_for('campaigns')
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -6,6 +6,7 @@ Conversations API Routes - RESTful endpoints for conversation management
|
|||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify, request
|
||||||
from models.conversation import Conversation
|
from models.conversation import Conversation
|
||||||
from core.user_auth import require_login
|
from core.user_auth import require_login
|
||||||
|
from core.error_handler import log_and_sanitize
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -43,8 +44,7 @@ def list_conversations():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error listing conversations: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'list_conversations')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/<conversation_id>')
|
@conversations_bp.route('/<conversation_id>')
|
||||||
@ -63,8 +63,7 @@ def get_conversation_detail(conversation_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting conversation {conversation_id}: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_conversation_detail')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/<conversation_id>/read', methods=['PUT'])
|
@conversations_bp.route('/<conversation_id>/read', methods=['PUT'])
|
||||||
@ -83,8 +82,7 @@ def mark_conversation_read(conversation_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error marking conversation {conversation_id} as read: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'mark_conversation_read')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/<conversation_id>/notes', methods=['PUT'])
|
@conversations_bp.route('/<conversation_id>/notes', methods=['PUT'])
|
||||||
@ -106,8 +104,7 @@ def update_conversation_notes(conversation_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating notes for conversation {conversation_id}: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'update_conversation_notes')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/<conversation_id>/tags', methods=['POST'])
|
@conversations_bp.route('/<conversation_id>/tags', methods=['POST'])
|
||||||
@ -133,8 +130,7 @@ def manage_conversation_tags(conversation_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error managing tags for conversation {conversation_id}: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'manage_conversation_tags')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/search')
|
@conversations_bp.route('/search')
|
||||||
@ -158,8 +154,7 @@ def search_conversations():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching conversations: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'search_conversations')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/stats')
|
@conversations_bp.route('/stats')
|
||||||
@ -175,8 +170,7 @@ def get_conversation_stats():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting conversation stats: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_conversation_stats')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
@conversations_bp.route('/migrate', methods=['POST'])
|
@conversations_bp.route('/migrate', methods=['POST'])
|
||||||
@ -192,5 +186,4 @@ def migrate_existing_messages():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error migrating messages: {e}")
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'migrate_existing_messages')}), 500
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from datetime import datetime
|
|||||||
import os
|
import os
|
||||||
from models.contact_list import ContactList
|
from models.contact_list import ContactList
|
||||||
from core.user_auth import require_login
|
from core.user_auth import require_login
|
||||||
|
from core.error_handler import log_and_sanitize
|
||||||
|
|
||||||
lists_bp = Blueprint('lists', __name__)
|
lists_bp = Blueprint('lists', __name__)
|
||||||
model = ContactList()
|
model = ContactList()
|
||||||
@ -16,7 +17,7 @@ def get_lists():
|
|||||||
lists = model.get_all_lists()
|
lists = model.get_all_lists()
|
||||||
return jsonify({'success': True, 'lists': lists})
|
return jsonify({'success': True, 'lists': lists})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_lists')}), 500
|
||||||
|
|
||||||
|
|
||||||
@lists_bp.route('/api/lists', methods=['POST'])
|
@lists_bp.route('/api/lists', methods=['POST'])
|
||||||
@ -26,18 +27,18 @@ def create_list():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({'success': False, 'error': 'No data provided'}), 400
|
return jsonify({'success': False, 'error': 'No data provided'}), 400
|
||||||
|
|
||||||
name = data.get('name', '')
|
name = data.get('name', '')
|
||||||
contacts = data.get('contacts', [])
|
contacts = data.get('contacts', [])
|
||||||
filename = data.get('filename', 'manual_entry')
|
filename = data.get('filename', 'manual_entry')
|
||||||
|
|
||||||
if not name or not contacts:
|
if not name or not contacts:
|
||||||
return jsonify({'success': False, 'error': 'Name and contacts are required'}), 400
|
return jsonify({'success': False, 'error': 'Name and contacts are required'}), 400
|
||||||
|
|
||||||
list_id = model.create_list(name, filename, contacts)
|
list_id = model.create_list(name, filename, contacts)
|
||||||
return jsonify({'success': True, 'list_id': list_id, 'message': f'List created with ID {list_id}'})
|
return jsonify({'success': True, 'list_id': list_id, 'message': f'List created with ID {list_id}'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'create_list')}), 500
|
||||||
|
|
||||||
|
|
||||||
@lists_bp.route('/api/lists/<int:list_id>', methods=['GET'])
|
@lists_bp.route('/api/lists/<int:list_id>', methods=['GET'])
|
||||||
@ -49,7 +50,7 @@ def get_list(list_id):
|
|||||||
return jsonify({'success': False, 'error': 'List not found'}), 404
|
return jsonify({'success': False, 'error': 'List not found'}), 404
|
||||||
return jsonify({'success': True, 'list': data})
|
return jsonify({'success': True, 'list': data})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_list')}), 500
|
||||||
|
|
||||||
|
|
||||||
@lists_bp.route('/api/lists/<int:list_id>/contacts/<path:phone>', methods=['PUT'])
|
@lists_bp.route('/api/lists/<int:list_id>/contacts/<path:phone>', methods=['PUT'])
|
||||||
@ -62,7 +63,7 @@ def update_contact(list_id, phone):
|
|||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
return jsonify({'success': False, 'error': 'Contact not found'}), 404
|
return jsonify({'success': False, 'error': 'Contact not found'}), 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'update_contact')}), 500
|
||||||
|
|
||||||
|
|
||||||
@lists_bp.route('/api/lists/<int:list_id>', methods=['DELETE'])
|
@lists_bp.route('/api/lists/<int:list_id>', methods=['DELETE'])
|
||||||
@ -74,7 +75,7 @@ def delete_list(list_id):
|
|||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
return jsonify({'success': False, 'error': 'List not found'}), 404
|
return jsonify({'success': False, 'error': 'List not found'}), 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'delete_list')}), 500
|
||||||
|
|
||||||
|
|
||||||
@lists_bp.route('/api/lists/<int:list_id>/use', methods=['POST'])
|
@lists_bp.route('/api/lists/<int:list_id>/use', methods=['POST'])
|
||||||
@ -84,4 +85,4 @@ def use_list(list_id):
|
|||||||
model.mark_used(list_id)
|
model.mark_used(list_id)
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': log_and_sanitize(e, 'use_list')}), 500
|
||||||
|
|||||||
@ -4,6 +4,39 @@
|
|||||||
// Global WebSocket instance (shared across all Alpine components)
|
// Global WebSocket instance (shared across all Alpine components)
|
||||||
let globalSocket = null;
|
let globalSocket = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get CSRF token from meta tag
|
||||||
|
* @returns {string} CSRF token or empty string if not found
|
||||||
|
*/
|
||||||
|
function getCSRFToken() {
|
||||||
|
const meta = document.querySelector('meta[name="csrf-token"]');
|
||||||
|
return meta ? meta.getAttribute('content') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSRF-protected fetch wrapper for POST/PUT/DELETE requests
|
||||||
|
* Automatically includes CSRF token header
|
||||||
|
* @param {string} url - The URL to fetch
|
||||||
|
* @param {object} options - Fetch options (method, body, etc.)
|
||||||
|
* @returns {Promise<Response>} Fetch response
|
||||||
|
*/
|
||||||
|
async function secureFetch(url, options = {}) {
|
||||||
|
const method = (options.method || 'GET').toUpperCase();
|
||||||
|
|
||||||
|
// Add CSRF token for state-changing requests
|
||||||
|
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
'X-CSRFToken': getCSRFToken()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always include credentials
|
||||||
|
options.credentials = options.credentials || 'same-origin';
|
||||||
|
|
||||||
|
return fetch(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base Alpine.js app for shared functionality
|
* Base Alpine.js app for shared functionality
|
||||||
* Used on all pages via x-data="baseApp"
|
* Used on all pages via x-data="baseApp"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>{% block title %}SMS Campaign Manager{% endblock %}</title>
|
<title>{% block title %}SMS Campaign Manager{% endblock %}</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|||||||
@ -82,12 +82,6 @@
|
|||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="mt-6 text-center text-sm text-gray-600">
|
|
||||||
<p>Secure authentication with session management</p>
|
|
||||||
<p class="mt-2">🔒 Protected by API key and session tokens</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
194
test-setup-flow.sh
Executable file
194
test-setup-flow.sh
Executable file
@ -0,0 +1,194 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test Script for Security Setup Flow
|
||||||
|
# This simulates the full setup process without modifying real files
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${BOLD}Testing Security Setup Scripts${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 1: Check script files exist and are executable
|
||||||
|
echo -e "${BOLD}Test 1: Script File Validation${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
scripts=(
|
||||||
|
"android/setup-api-key.sh"
|
||||||
|
"scripts/update-api-keys.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
for script in "${scripts[@]}"; do
|
||||||
|
if [ -f "$script" ]; then
|
||||||
|
if [ -x "$script" ]; then
|
||||||
|
echo -e " ${GREEN}✅ $script (executable)${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⚠️ $script (not executable, fixing...)${NC}"
|
||||||
|
chmod +x "$script"
|
||||||
|
echo -e " ${GREEN}✅ Fixed permissions${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ $script not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 2: Syntax validation
|
||||||
|
echo -e "${BOLD}Test 2: Bash Syntax Validation${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for script in "${scripts[@]}"; do
|
||||||
|
if bash -n "$script" 2>&1; then
|
||||||
|
echo -e " ${GREEN}✅ $script syntax valid${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ $script has syntax errors${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 3: Check .env file
|
||||||
|
echo -e "${BOLD}Test 3: Environment Configuration${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
echo -e " ${GREEN}✅ .env file exists${NC}"
|
||||||
|
|
||||||
|
# Check for required keys
|
||||||
|
required_keys=("SECRET_KEY" "ADMIN_API_KEY" "USER_API_KEY" "PHONE_IP")
|
||||||
|
for key in "${required_keys[@]}"; do
|
||||||
|
if grep -q "^${key}=" ".env" 2>/dev/null; then
|
||||||
|
echo -e " ${GREEN}✅ $key configured${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⚠️ $key not found (may need configuration)${NC}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⚠️ .env file not found${NC}"
|
||||||
|
echo -e " ${BLUE}ℹ️ Will be created during setup${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 4: Test API key generation
|
||||||
|
echo -e "${BOLD}Test 4: API Key Generation${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if command -v python3 &> /dev/null; then
|
||||||
|
TEST_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>&1)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e " ${GREEN}✅ Python3 key generation works${NC}"
|
||||||
|
echo -e " ${BLUE}ℹ️ Sample key: ${TEST_KEY:0:16}...${NC}"
|
||||||
|
|
||||||
|
# Validate key format
|
||||||
|
if [[ "$TEST_KEY" =~ ^[a-f0-9]{64}$ ]]; then
|
||||||
|
echo -e " ${GREEN}✅ Key format correct (64 hex chars)${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ Key format incorrect${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ Python3 key generation failed${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ Python3 not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 5: Docker configuration
|
||||||
|
echo -e "${BOLD}Test 5: Docker Configuration Security${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -f "docker-compose.yml" ]; then
|
||||||
|
echo -e " ${GREEN}✅ docker-compose.yml exists${NC}"
|
||||||
|
|
||||||
|
# Check that privileged mode is NOT set
|
||||||
|
if grep -q "privileged: true" "docker-compose.yml"; then
|
||||||
|
echo -e " ${RED}❌ WARNING: Container still in privileged mode!${NC}"
|
||||||
|
echo -e " ${YELLOW} This should have been removed${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${GREEN}✅ Container not in privileged mode${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check that host networking is NOT set
|
||||||
|
if grep -q "network_mode: host" "docker-compose.yml"; then
|
||||||
|
echo -e " ${RED}❌ WARNING: Still using host networking!${NC}"
|
||||||
|
echo -e " ${YELLOW} This should have been removed${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${GREEN}✅ Container using isolated networking${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ docker-compose.yml not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 6: Check Termux server security
|
||||||
|
echo -e "${BOLD}Test 6: Termux Server Security Validation${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
TERMUX_SERVER="android/termux-sms-api-server.py"
|
||||||
|
|
||||||
|
if [ -f "$TERMUX_SERVER" ]; then
|
||||||
|
echo -e " ${GREEN}✅ Termux server file exists${NC}"
|
||||||
|
|
||||||
|
# Check that weak default was removed
|
||||||
|
if grep -q "'SECRET_KEY': os.environ.get('SMS_API_SECRET', 'termux-sms-campaign-2025')" "$TERMUX_SERVER"; then
|
||||||
|
echo -e " ${RED}❌ WARNING: Weak default secret still present!${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${GREEN}✅ Weak default secret removed${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for startup validation
|
||||||
|
if grep -q "if not CONFIG\['SECRET_KEY'\]:" "$TERMUX_SERVER"; then
|
||||||
|
echo -e " ${GREEN}✅ Startup validation added${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⚠️ Startup validation not found${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for command injection fix
|
||||||
|
if grep -q "shell=True" "$TERMUX_SERVER"; then
|
||||||
|
echo -e " ${YELLOW}⚠️ shell=True still present (check if safe)${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${GREEN}✅ No shell=True usage found${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e " ${RED}❌ Termux server not found${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${BOLD}Test Summary${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}╠════════════════════════════════════════════════════════════════════════╣${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${GREEN}✅ All automated tests passed${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${GREEN}✅ Scripts are ready for manual testing${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${BOLD}Next Steps:${NC} ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${YELLOW}1.${NC} Test on Android device ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${YELLOW}2.${NC} Test Ubuntu script with manual key entry ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}║${NC} ${YELLOW}3.${NC} Verify end-to-end integration ${BLUE}║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
Loading…
x
Reference in New Issue
Block a user