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 📱
|
||||
*Secure, Dockerized SMS automation system with Android integration*
|
||||
# SMS Campaign Manager
|
||||
|
||||
## 🔐 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
|
||||
- ✅ **API Keys** - Secure API access for scripts and automation
|
||||
- ✅ **24-Hour Sessions** - Stay logged in without re-entering credentials
|
||||
- ✅ **Role-Based Access** - Admin and User roles with different permissions
|
||||
- **Campaign Management**: Create, schedule, and monitor SMS campaigns
|
||||
- **Contact Import**: Upload contacts from CSV files with template variables
|
||||
- **Android Integration**: Send SMS through Termux API with ADB fallback
|
||||
- **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)
|
||||
[](./config/.env)
|
||||
```
|
||||
Ubuntu Server (Docker) Android Device (Termux)
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ Flask Web App │◄──────►│ Termux SMS API │
|
||||
│ Port 5000 │ │ Port 5001 │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
## Quick Start
|
||||
|
||||
### One-Command Deployment (Recommended - Tailscale)
|
||||
```bash
|
||||
# 1. Configure environment
|
||||
# Edit .env with your Android device's Tailscale IP
|
||||
nano .env
|
||||
cp .env.example .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
|
||||
|
||||
# 3. Start Ubuntu homelab
|
||||
./run.sh start
|
||||
# OR with docker-compose
|
||||
# 4. Start the application
|
||||
docker compose up -d
|
||||
|
||||
# 4. Verify everything is running
|
||||
curl http://YOUR_TAILSCALE_IP:5001/health # Android SMS API
|
||||
curl http://YOUR_TAILSCALE_IP:5000/ # Android Monitor
|
||||
curl http://localhost:5000/ # Ubuntu Dashboard
|
||||
# 5. Open web dashboard
|
||||
open http://localhost:5000
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
- 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)
|
||||
## Requirements
|
||||
|
||||
### 1. Clone and Configure
|
||||
```bash
|
||||
# Clone or navigate to project directory
|
||||
cd campaign_connector
|
||||
**Ubuntu Server**
|
||||
- Docker and Docker Compose
|
||||
- Tailscale (recommended) or local network access
|
||||
|
||||
# Create environment configuration
|
||||
cat > .env << 'EOF'
|
||||
# Android Device Configuration (use Tailscale IP for best reliability)
|
||||
PHONE_IP=YOUR_TAILSCALE_IP_HERE
|
||||
ADB_PORT=5555
|
||||
TERMUX_API_PORT=5001
|
||||
**Android Device**
|
||||
- Termux (from F-Droid)
|
||||
- Termux:API (from F-Droid)
|
||||
- SSH server enabled
|
||||
|
||||
# Flask Application
|
||||
FLASK_ENV=production
|
||||
SECRET_KEY=your-secure-secret-key-here
|
||||
DEFAULT_DELAY_SECONDS=3
|
||||
## Documentation
|
||||
|
||||
# SMS Retry Configuration
|
||||
SMS_MAX_RETRIES=3
|
||||
SMS_RETRY_BASE_DELAY=2
|
||||
SMS_MAX_RETRY_DELAY=8
|
||||
EOF
|
||||
Full documentation is available in the [docs/](docs/) directory:
|
||||
|
||||
# Edit with your actual Tailscale IP
|
||||
nano .env
|
||||
### Getting Started
|
||||
- [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
|
||||
```bash
|
||||
# Quick start using convenience script
|
||||
./run.sh start
|
||||
## Common Commands
|
||||
|
||||
# Or manually with docker compose
|
||||
```bash
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f sms-campaign
|
||||
|
||||
# Access web interface
|
||||
open http://localhost:5000
|
||||
# Stop services
|
||||
docker compose down
|
||||
|
||||
# Manage users
|
||||
python3 manage_users.py
|
||||
```
|
||||
|
||||
### 3. Deploy to Android Device
|
||||
```bash
|
||||
# Use the automated deployment script
|
||||
./scripts/deploy-android.sh
|
||||
## Support
|
||||
|
||||
# The script will:
|
||||
# - Test connectivity to your Android device
|
||||
# - Deploy shell scripts to ~/bin/
|
||||
# - Deploy Python apps to ~/projects/sms-campaign-manager/
|
||||
# - Start all services automatically
|
||||
# - Verify deployment success
|
||||
```
|
||||
For issues:
|
||||
1. Check [Troubleshooting](docs/guides/troubleshooting.md)
|
||||
2. Review logs: `docker compose logs`
|
||||
3. Verify configuration in `.env`
|
||||
|
||||
### 4. Verify Full System
|
||||
```bash
|
||||
# Test Ubuntu homelab → Android connection
|
||||
curl http://localhost:5000/api/phone/status
|
||||
## License
|
||||
|
||||
# Test Android SMS API (replace with your Tailscale IP)
|
||||
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.
|
||||
Copyright 2025 Campaign Connector Team
|
||||
|
||||
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
|
||||
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)
|
||||
'RATE_LIMIT_DELAY': 1.0, # Reduced from 2.0 to 1.0 seconds between messages for faster campaigns
|
||||
'ALLOWED_COMMANDS': [
|
||||
@ -768,18 +768,38 @@ def list_contacts():
|
||||
if __name__ == '__main__':
|
||||
# Create logs directory
|
||||
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(f"✅ API authentication configured")
|
||||
logger.info(f"Available commands: {CONFIG['ALLOWED_COMMANDS']}")
|
||||
|
||||
# Get local IP for display
|
||||
# Get local IP for display (secure method without shell=True)
|
||||
try:
|
||||
ip_result = subprocess.run([
|
||||
'ifconfig', '2>/dev/null', '|', 'grep', '-A1', 'wlan0', '|',
|
||||
'grep', 'inet', '|', 'awk', '{print $2}', '|', 'cut', '-d:', '-f2'
|
||||
], shell=True, capture_output=True, text=True)
|
||||
local_ip = ip_result.stdout.strip() or '10.0.0.193'
|
||||
except:
|
||||
import socket
|
||||
# Get IP by connecting to external host (doesn't actually send data)
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 80))
|
||||
local_ip = s.getsockname()[0]
|
||||
s.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not determine IP address: {e}")
|
||||
local_ip = '10.0.0.193'
|
||||
|
||||
print(f"""
|
||||
|
||||
@ -14,7 +14,13 @@ services:
|
||||
- ./src:/app/src # Live source for development
|
||||
- ./src/static:/app/src/static # Static assets
|
||||
- ./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:
|
||||
PHONE_IP: ${PHONE_IP:-10.0.0.193}
|
||||
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_UPLOAD: ${RATE_LIMIT_UPLOAD:-10 per hour, 50 per day}
|
||||
RATE_LIMIT_DATABASE_RESET: ${RATE_LIMIT_DATABASE_RESET:-2 per hour}
|
||||
network_mode: host # Required for ADB network connection (host mode needed for ADB)
|
||||
privileged: true # Required for USB access
|
||||
# SECURITY: Removed privileged mode and host networking
|
||||
# - 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
|
||||
stop_grace_period: 30s # Give container 30 seconds to gracefully shutdown
|
||||
healthcheck:
|
||||
|
||||
@ -233,6 +233,13 @@ After deployment:
|
||||
|
||||
For issues or questions:
|
||||
- Check logs: `docker compose logs -f`
|
||||
- Review [README.md](README.md) for detailed documentation
|
||||
- Verify all prerequisites are met
|
||||
- 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
|
||||
- Environment details (OS, Docker version)
|
||||
- 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
|
||||
- Disable inactive users
|
||||
|
||||
---
|
||||
## Related Documentation
|
||||
|
||||
**Created**: 2025-12-30
|
||||
**Version**: 2.0
|
||||
**Last Updated**: 2025-12-30
|
||||
|
||||
For API security documentation, see: [API Security](../security/api-security.md)
|
||||
- [Authentication Setup](../setup/authentication.md) - Login configuration
|
||||
- [API Security](../security/api-security.md) - API key authentication
|
||||
- [Security Setup](../security/security-setup.md) - Security configuration
|
||||
- [Quick Start](../setup/quick-start.md) - Getting started
|
||||
|
||||
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
|
||||
- API keys for programmatic access
|
||||
- Admin and user roles
|
||||
- 24-hour login sessions
|
||||
### Setup
|
||||
|
||||
### 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
|
||||
- Automatic fallback to ADB if Termux unavailable
|
||||
- Track message delivery status
|
||||
- Monitor device battery and connectivity
|
||||
- Auto-retry failed messages
|
||||
### Security
|
||||
|
||||
### Campaign Features
|
||||
|
||||
- Import contacts from CSV files
|
||||
- Personalize messages with template variables
|
||||
- Schedule message batches
|
||||
- Real-time analytics dashboard
|
||||
- Track SMS replies
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Security Setup](security/security-setup.md) | API keys, Docker security, best practices |
|
||||
| [API Security](security/api-security.md) | API authentication implementation |
|
||||
|
||||
### Deployment
|
||||
|
||||
- Docker Compose setup
|
||||
- Environment-based configuration
|
||||
- Automatic health monitoring
|
||||
- One-command deployment scripts
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Deployment Guide](deployment/deployment-guide.md) | Production deployment with Tailscale |
|
||||
|
||||
## 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)
|
||||
- Web dashboard for campaign management
|
||||
- REST API for external integrations
|
||||
- SQLite database for data storage
|
||||
### Development
|
||||
|
||||
2. **Termux API Server** (Android device, port 5001)
|
||||
- Communicates with Android SMS system
|
||||
- Provides device status information
|
||||
- Handles message sending
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Android Development](development/android-dev-setup.md) | Android device configuration |
|
||||
| [Termux Flask Setup](development/termux-flask-setup.md) | Termux server configuration |
|
||||
|
||||
3. **Android Monitor** (Android device, port 5000)
|
||||
- Dashboard running on Android
|
||||
- Device health monitoring
|
||||
- Service status tracking
|
||||
### Reference
|
||||
|
||||
## 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
|
||||
# 1. Configure environment
|
||||
cp .env.example .env
|
||||
nano .env # Set your Android device IP
|
||||
## System Architecture
|
||||
|
||||
# 2. Deploy to Android device
|
||||
./scripts/deploy-android.sh
|
||||
|
||||
# 3. Start the Flask application
|
||||
docker compose up -d
|
||||
|
||||
# 4. Open web dashboard
|
||||
open http://localhost:5000
|
||||
# Default login: admin / @thebunker
|
||||
```
|
||||
Ubuntu Server (Docker) Android Device (Termux)
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ Flask Web App │ │ Termux SMS API │
|
||||
│ Port 5000 │◄──────►│ Port 5001 │
|
||||
│ │ │ │
|
||||
│ - Campaign Mgmt │ │ - SMS Sending │
|
||||
│ - Contact Upload │ │ - Device Status │
|
||||
│ - 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
|
||||
- Automated appointment reminders
|
||||
- System notifications and alerts
|
||||
- Testing SMS integrations
|
||||
- Personal message automation
|
||||
**User Management**
|
||||
- Web-based login (no browser extensions needed)
|
||||
- Role-based access control (Admin/User)
|
||||
- API key authentication for 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
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Android device with Termux and Termux:API apps
|
||||
- SSH access to Android device (typically port 8022)
|
||||
- Network connection between server and Android
|
||||
- Tailscale recommended for reliable connectivity
|
||||
- Local network also works
|
||||
**Ubuntu Server**
|
||||
- Docker and Docker Compose
|
||||
- Tailscale (recommended) or local network access
|
||||
|
||||
**Android Device**
|
||||
- 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
|
||||
|
||||
```
|
||||
campaign_connector/
|
||||
├── src/ # Flask application code
|
||||
├── android/ # Android-side Python servers
|
||||
├── android/ # Android Termux servers
|
||||
├── docs/ # Documentation (this site)
|
||||
├── scripts/ # Deployment and utility scripts
|
||||
├── 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
|
||||
**File**: [src/core/auth.py](src/core/auth.py)
|
||||
All API endpoints require authentication via one of:
|
||||
|
||||
- ✅ API key-based authentication system
|
||||
- ✅ Three-tier access control (Admin, User, Termux)
|
||||
- ✅ Constant-time comparison to prevent timing attacks
|
||||
- ✅ SHA-256 key hashing for secure storage
|
||||
- ✅ Cryptographically secure key generation
|
||||
- **X-API-Key header**: `X-API-Key: YOUR_KEY`
|
||||
- **Bearer token**: `Authorization: Bearer YOUR_KEY`
|
||||
- **Session cookie**: From web login
|
||||
|
||||
### 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
|
||||
**Protected with `@require_auth()` decorator**:
|
||||
### Admin Key
|
||||
|
||||
- **Campaign Routes** ([src/routes/api/campaign_routes.py](src/routes/api/campaign_routes.py))
|
||||
- `/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
|
||||
Use for administrative operations:
|
||||
|
||||
- **SMS Routes** ([src/routes/api/sms_routes.py](src/routes/api/sms_routes.py))
|
||||
- `/api/sms/test/real` - User role required
|
||||
- `/api/sms/test` - User role required
|
||||
- `/api/sms/send/enhanced` - User role required
|
||||
- `/api/sms/status` - User role required
|
||||
- All user permissions
|
||||
- `/api/database/reset` - Database reset
|
||||
- System configuration changes
|
||||
|
||||
- **Upload Routes** ([src/routes/api/upload_routes.py](src/routes/api/upload_routes.py))
|
||||
- `/api/csv/upload` - User role required
|
||||
- `/api/campaign/upload` - User role required
|
||||
### User Key
|
||||
|
||||
- **Database Routes** ([src/routes/api/database_routes.py](src/routes/api/database_routes.py))
|
||||
- `/api/database/reset` - **Admin role required** ⚠️
|
||||
- `/api/database/stats` - User role required
|
||||
Use for standard operations:
|
||||
|
||||
#### Termux API Server (Android)
|
||||
**Protected with `verify_api_key()` function**:
|
||||
- Campaign management
|
||||
- SMS sending
|
||||
- CSV upload
|
||||
- Analytics viewing
|
||||
|
||||
- `/api/sms/send` - Authentication required
|
||||
- `/api/sms/send-reply` - Authentication required
|
||||
### Termux Key
|
||||
|
||||
### 3. Configuration Updates
|
||||
Use for Android communication:
|
||||
|
||||
- ✅ Updated [.gitignore](.gitignore) to prevent secret leaks
|
||||
- ✅ Created [.env.example](.env.example) template
|
||||
- ✅ Updated [src/app.py](src/app.py) to initialize auth manager
|
||||
- ✅ 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
|
||||
- Internal server-to-device requests
|
||||
- SMS sending via Termux API
|
||||
- Device status queries
|
||||
|
||||
### 4. Documentation Created
|
||||
## Protected Endpoints
|
||||
|
||||
- 📄 [SECURITY_SETUP.md](SECURITY_SETUP.md) - Complete setup guide
|
||||
- 📄 [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
|
||||
### Campaign Routes
|
||||
|
||||
---
|
||||
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
|
||||
cd /mnt/storagessd1tb/campaign_connector
|
||||
./generate-api-keys.sh
|
||||
All require User role minimum:
|
||||
|
||||
- `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
|
||||
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
|
||||
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
|
||||
### curl Examples
|
||||
|
||||
```bash
|
||||
# Restart Docker container
|
||||
docker-compose restart
|
||||
# X-API-Key header
|
||||
curl -H "X-API-Key: YOUR_KEY" http://localhost:5000/api/campaign/list
|
||||
|
||||
# Restart Termux API server (on Android)
|
||||
ssh android-dev@100.107.173.66 -p 8022 "~/bin/sms-service.sh restart"
|
||||
```
|
||||
# Bearer token
|
||||
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:5000/api/campaign/list
|
||||
|
||||
### Step 5: Test Authentication
|
||||
|
||||
```bash
|
||||
# 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 \
|
||||
# POST with data
|
||||
curl -X POST \
|
||||
-H "X-API-Key: YOUR_KEY" \
|
||||
-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
|
||||
**Android device communication**
|
||||
### Python Example
|
||||
|
||||
Permissions:
|
||||
- ✅ Send SMS via Termux
|
||||
- ✅ Query SMS history
|
||||
- ✅ Device status
|
||||
```python
|
||||
import requests
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
curl -H "X-API-Key: TERMUX_API_KEY" http://100.107.173.66:5001/api/sms/send \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone":"1234567890","message":"Test"}'
|
||||
API_KEY = "your-api-key-here"
|
||||
BASE_URL = "http://localhost:5000"
|
||||
|
||||
headers = {
|
||||
"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
|
||||
```
|
||||
X-API-Key: abc123...xyz
|
||||
```
|
||||
|
||||
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 })
|
||||
```json
|
||||
{
|
||||
"error": "Authentication required",
|
||||
"message": "Please provide valid API key"
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ **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
|
||||
- Monitor logs for unauthorized attempts
|
||||
- Use different keys per environment
|
||||
- Monitor failed authentication logs
|
||||
- Use HTTPS in production (Tailscale provides this)
|
||||
- Keep `.env` file permissions at 600
|
||||
- Back up keys securely (encrypted)
|
||||
|
||||
### ❌ DON'T:
|
||||
- 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
|
||||
## Troubleshooting
|
||||
|
||||
---
|
||||
### 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
|
||||
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
|
||||
**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
|
||||
# Check .env format (no spaces around =)
|
||||
cat .env | grep API_KEY
|
||||
|
||||
# Restart services
|
||||
docker-compose restart
|
||||
# Restart to reload
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
### Application won't start
|
||||
**Error**: "API keys must be configured"
|
||||
### Permission Denied
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Generate keys
|
||||
python3 src/core/auth.py
|
||||
Using wrong key type for endpoint:
|
||||
|
||||
# Add to .env
|
||||
nano .env
|
||||
- User key for admin endpoints returns 403
|
||||
- Check endpoint requires admin role
|
||||
- Use admin key for privileged operations
|
||||
|
||||
# Restart
|
||||
docker-compose up -d
|
||||
```
|
||||
## Related Documentation
|
||||
|
||||
### Web dashboard not working
|
||||
**Solution**: Use browser extension or update JavaScript (see above)
|
||||
|
||||
---
|
||||
|
||||
## 📊 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
|
||||
- [Security Setup](security-setup.md) - Complete security configuration
|
||||
- [Authentication Setup](../setup/authentication.md) - User login
|
||||
- [API Endpoints](../api/endpoints.md) - Full endpoint reference
|
||||
- [Environment Variables](../reference/environment-variables.md) - Configuration
|
||||
|
||||
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
|
||||
cd /mnt/storagessd1tb/campaign_connector
|
||||
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):
|
||||
```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
|
||||
Back up and edit your `.env` file:
|
||||
|
||||
```bash
|
||||
cd /mnt/storagessd1tb/campaign_connector
|
||||
docker-compose down
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
cp .env .env.backup
|
||||
nano .env
|
||||
```
|
||||
|
||||
### Restart Termux SMS API Server
|
||||
Add the security configuration:
|
||||
|
||||
```bash
|
||||
ssh android-dev@100.107.173.66 -p 8022
|
||||
~/bin/sms-service.sh restart
|
||||
```env
|
||||
# API Keys (generated above)
|
||||
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!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Test Authentication
|
||||
|
||||
### Test Flask API
|
||||
Secure the file:
|
||||
|
||||
```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
|
||||
|
||||
# This should SUCCEED (with your USER_API_KEY)
|
||||
curl -H "X-API-Key: YOUR_USER_API_KEY_HERE" 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
|
||||
```
|
||||
|
||||
### Test Termux API
|
||||
Verify Docker security:
|
||||
|
||||
```bash
|
||||
# This should FAIL (no API key)
|
||||
curl http://100.107.173.66:5001/api/sms/send \
|
||||
-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 inspect sms-campaign-manager | grep -E "Privileged|NetworkMode"
|
||||
# Expected: "Privileged": false, "NetworkMode": "bridge" or "default"
|
||||
```
|
||||
|
||||
---
|
||||
## Docker Security Configuration
|
||||
|
||||
## API Key Roles and Permissions
|
||||
The application runs with these security measures:
|
||||
|
||||
### Admin API Key (`ADMIN_API_KEY`)
|
||||
**Permissions**: Full access to all endpoints including:
|
||||
- ✅ All user permissions
|
||||
- ✅ Database reset (`/api/database/reset`)
|
||||
- ✅ System configuration changes
|
||||
### Container Isolation
|
||||
|
||||
**Use**: Personal admin access, automated admin scripts
|
||||
The container runs without privileged mode:
|
||||
|
||||
### User API Key (`USER_API_KEY`)
|
||||
**Permissions**: Regular application access:
|
||||
- ✅ Create and manage campaigns
|
||||
- ✅ Send SMS messages
|
||||
- ✅ Upload CSV files
|
||||
- ✅ View analytics and reports
|
||||
- ❌ Cannot reset database
|
||||
- ❌ 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'
|
||||
}
|
||||
})
|
||||
```yaml
|
||||
services:
|
||||
sms-campaign:
|
||||
# Runs with standard permissions
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "5037:5037"
|
||||
```
|
||||
|
||||
**⚠️ 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:
|
||||
- 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)
|
||||
## API Key Roles
|
||||
|
||||
### ❌ DON'T:
|
||||
- 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
|
||||
### Admin API Key
|
||||
|
||||
---
|
||||
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:
|
||||
```bash
|
||||
python3 src/core/auth.py
|
||||
```
|
||||
### User API Key
|
||||
|
||||
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
|
||||
4. Update any scripts/applications using the old keys
|
||||
4. Update external scripts using old keys
|
||||
5. Test all functionality
|
||||
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
|
||||
|
||||
### Issue: "Authentication required" error
|
||||
### Authentication Required Error
|
||||
|
||||
Missing or invalid API key:
|
||||
|
||||
**Solution**: Ensure you're passing the API key in the request:
|
||||
```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**:
|
||||
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
|
||||
Key mismatch or formatting issue:
|
||||
|
||||
### Issue: Application won't start - "API keys must be configured"
|
||||
```bash
|
||||
# Verify .env format (no spaces around =)
|
||||
cat .env | grep API_KEY
|
||||
|
||||
**Solution**:
|
||||
1. Generate keys: `python3 src/core/auth.py`
|
||||
2. Add keys to .env file
|
||||
3. Restart: `docker-compose restart`
|
||||
# Restart to reload environment
|
||||
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**:
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
Verify Docker configuration:
|
||||
|
||||
2. **Generate new keys**:
|
||||
```bash
|
||||
python3 src/core/auth.py
|
||||
```
|
||||
```bash
|
||||
# Should show "Privileged": false
|
||||
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**:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
## Environment Variables
|
||||
|
||||
---
|
||||
| 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:
|
||||
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`
|
||||
## Related Documentation
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Additional Security Hardening)
|
||||
|
||||
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)
|
||||
- [API Security](api-security.md) - Detailed API authentication guide
|
||||
- [Authentication Setup](../setup/authentication.md) - User login configuration
|
||||
- [Quick Start](../setup/quick-start.md) - Getting started guide
|
||||
- [Deployment Guide](../deployment/deployment-guide.md) - Production deployment
|
||||
|
||||
@ -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
|
||||
# Add to /mnt/storagessd1tb/campaign_connector/.env
|
||||
### Configure Admin User
|
||||
|
||||
# User Management (create initial admin)
|
||||
Add these lines to your `.env` file:
|
||||
|
||||
```env
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=ChangeThisSecurePassword123!
|
||||
ADMIN_PASSWORD=YourSecurePassword123!
|
||||
```
|
||||
|
||||
### Step 2: Restart Docker
|
||||
Restart the application:
|
||||
|
||||
```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:
|
||||
```
|
||||
http://localhost:5000/login
|
||||
```
|
||||
1. Open `http://localhost:5000/`
|
||||
2. You'll be redirected to `/login`
|
||||
3. Enter your credentials
|
||||
4. After login, sessions last 24 hours
|
||||
|
||||
Or via Tailscale:
|
||||
```
|
||||
http://your-tailscale-ip:5000/login
|
||||
```
|
||||
### Session Features
|
||||
|
||||
### Step 4: Log In
|
||||
- 24-hour session duration
|
||||
- HTTP-only cookies for security
|
||||
- Automatic session cleanup
|
||||
- Login tracking and auditing
|
||||
|
||||
- **Username**: `admin`
|
||||
- **Password**: Whatever you set in `.env`
|
||||
## API Key Authentication
|
||||
|
||||
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)
|
||||
```
|
||||
❌ Install ModHeader extension
|
||||
❌ Add X-API-Key header manually
|
||||
❌ Remember to enable it for localhost
|
||||
❌ Different keys for different roles
|
||||
```
|
||||
### Usage
|
||||
|
||||
### After (User Login)
|
||||
```
|
||||
✅ Visit /login
|
||||
✅ Enter username/password
|
||||
✅ Click "Sign In"
|
||||
✅ Stay logged in for 24 hours
|
||||
✅ No browser extensions needed!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👥 Managing Users
|
||||
|
||||
### Create Additional Users
|
||||
Include the key in request 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
|
||||
```
|
||||
|
||||
## User Roles
|
||||
|
||||
### Admin Role
|
||||
|
||||
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
|
||||
cd /mnt/storagessd1tb/campaign_connector
|
||||
python3 manage_users.py
|
||||
```
|
||||
|
||||
Interactive menu will guide you through:
|
||||
- Creating new users
|
||||
- Listing existing users
|
||||
- Deleting users
|
||||
- Changing passwords
|
||||
Available options:
|
||||
|
||||
### User Roles
|
||||
1. Create new user
|
||||
2. List all users
|
||||
3. Delete user
|
||||
4. Change password
|
||||
|
||||
**Admin**: Full access (you should be admin)
|
||||
**User**: Regular access (for team members)
|
||||
### Create User via CLI
|
||||
|
||||
---
|
||||
|
||||
## 🔐 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
|
||||
# Should redirect to login page
|
||||
curl -i http://localhost:5000/
|
||||
|
||||
# Should show login page
|
||||
curl http://localhost:5000/login
|
||||
python3 manage_users.py
|
||||
# Select option 1
|
||||
# Enter username, password, role
|
||||
```
|
||||
|
||||
### Test Authentication
|
||||
### Create User via API (Admin Only)
|
||||
|
||||
```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
|
||||
curl -X POST http://localhost:5000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"YourPassword"}'
|
||||
|
||||
# Should return:
|
||||
{
|
||||
"success": true,
|
||||
"message": "Login successful",
|
||||
"user": {
|
||||
"username": "admin",
|
||||
"role": "admin"
|
||||
},
|
||||
"redirect": "/"
|
||||
}
|
||||
```
|
||||
|
||||
### Test Session
|
||||
### Test API Authentication
|
||||
|
||||
```bash
|
||||
# Check auth status
|
||||
curl http://localhost:5000/api/auth/status
|
||||
# Should fail (no 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
|
||||
```
|
||||
|
||||
---
|
||||
## 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
|
||||
2. **Add user login** - new feature for web dashboard
|
||||
3. **Choose your preference**:
|
||||
- Web browsing: Use username/password login
|
||||
- Scripts/automation: Use API keys
|
||||
### Can't Log In
|
||||
|
||||
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
|
||||
# List users to verify admin exists
|
||||
# Verify user exists
|
||||
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
|
||||
# Change it via .env
|
||||
echo "ADMIN_PASSWORD=NewPassword123!" >> .env
|
||||
docker-compose restart
|
||||
# Via CLI
|
||||
python3 manage_users.py
|
||||
# Select option 4 (Change password)
|
||||
|
||||
# Or reset via .env
|
||||
nano .env
|
||||
# Update ADMIN_PASSWORD
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
**Database error?**
|
||||
```bash
|
||||
docker-compose logs -f sms-campaign
|
||||
```
|
||||
## Related Documentation
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- ✅ 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.
|
||||
- [Installation Guide](installation.md) - Initial setup
|
||||
- [Security Setup](../security/security-setup.md) - API key configuration
|
||||
- [User Management](../guides/user-management.md) - Detailed user guide
|
||||
- [API Endpoints](../api/endpoints.md) - Authentication endpoints
|
||||
|
||||
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
|
||||
✅ `.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
|
||||
cd /mnt/storagessd1tb/campaign_connector
|
||||
./scripts/deploy-android.sh
|
||||
```
|
||||
**Wait for:** `🎉 Deployment Complete!`
|
||||
|
||||
### 2. Restart Docker
|
||||
Wait for: `Deployment Complete!`
|
||||
|
||||
### Step 2: Start Docker
|
||||
|
||||
```bash
|
||||
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"
|
||||
docker compose down && docker compose build && docker compose up -d
|
||||
```
|
||||
|
||||
### Test 3: Create User (Terminal)
|
||||
Wait for container to be healthy:
|
||||
|
||||
```bash
|
||||
python3 manage_users.py
|
||||
# Select: 1 (Create new user)
|
||||
# Username: testuser
|
||||
# Password: TestPass123!
|
||||
# Role: 2 (User)
|
||||
docker compose ps
|
||||
# STATUS should show "healthy"
|
||||
```
|
||||
|
||||
### Test 4: Send Test SMS (Terminal)
|
||||
### Step 3: Verify Services
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/sms/test/real \
|
||||
-H "X-API-Key: 2dd80622e868a9365bc037106fd5b2bda8c520805faaf3aa2267269c0b9303f8" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone":"YOUR_NUMBER","message":"Test from secured API!"}'
|
||||
# Test Ubuntu server
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# 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
|
||||
- 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
|
||||
# User operations (most common)
|
||||
USER_API_KEY="2dd80622e868a9365bc037106fd5b2bda8c520805faaf3aa2267269c0b9303f8"
|
||||
# Should FAIL (no API key)
|
||||
curl http://localhost:5000/api/campaign/list
|
||||
# Expected: 401 Unauthorized
|
||||
|
||||
# Admin operations (database reset, user management)
|
||||
ADMIN_API_KEY="208da9821e9f945355cd4c65e22a0570d8cf367483cfaef42cfd858cefacb7dd"
|
||||
|
||||
# Android communication
|
||||
TERMUX_API_KEY="aee141babda29fb0e68b5eb462c7feb5885f29b12c735d29ea337c360b00d351"
|
||||
# Should SUCCEED (with API key from .env)
|
||||
curl -H "X-API-Key: YOUR_USER_API_KEY" http://localhost:5000/api/campaign/list
|
||||
# Expected: JSON response with campaigns
|
||||
```
|
||||
|
||||
### Usage Example
|
||||
```bash
|
||||
# With API key
|
||||
curl http://localhost:5000/api/endpoint \
|
||||
-H "X-API-Key: YOUR_API_KEY_HERE"
|
||||
### Send Test SMS
|
||||
|
||||
# Or with Bearer token
|
||||
curl http://localhost:5000/api/endpoint \
|
||||
-H "Authorization: Bearer YOUR_API_KEY_HERE"
|
||||
```bash
|
||||
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"}'
|
||||
```
|
||||
|
||||
---
|
||||
## Common Commands
|
||||
|
||||
## 📱 User Management
|
||||
### Service Management
|
||||
|
||||
### Create New User
|
||||
```bash
|
||||
python3 manage_users.py
|
||||
# Option 1: Create new user
|
||||
```
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
### List All Users
|
||||
```bash
|
||||
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
|
||||
# Stop services
|
||||
docker compose down
|
||||
|
||||
# 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
|
||||
# Restart Docker
|
||||
docker-compose restart
|
||||
# SSH to Android
|
||||
ssh -p 8022 android-dev@YOUR_ANDROID_IP
|
||||
|
||||
# Restart Android service
|
||||
./deploy-to-android.sh
|
||||
# Start all services
|
||||
~/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 |
|
||||
|----------|---------|
|
||||
| **[Deployment Guide](../deployment/deployment-guide.md)** | Complete deployment instructions |
|
||||
| **[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 |
|
||||
# List users
|
||||
python3 manage_users.py
|
||||
# Select option 2
|
||||
```
|
||||
|
||||
---
|
||||
## 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
|
||||
- [ ] Can access login page at `/login`
|
||||
- [ ] Can log in as admin
|
||||
- [ ] Dashboard works without ModHeader
|
||||
- [ ] Dashboard loads without errors
|
||||
- [ ] API calls require authentication
|
||||
- [ ] Can create new users via CLI
|
||||
- [ ] SMS sending works with authentication
|
||||
- [ ] Android health check passes
|
||||
- [ ] 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
|
||||
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
|
||||
## Related Documentation
|
||||
|
||||
---
|
||||
|
||||
**Everything is configured and ready to test!** 🚀
|
||||
|
||||
**Support:** See [Deployment Guide](../deployment/deployment-guide.md) for detailed deployment instructions.
|
||||
- [Installation Guide](installation.md) - Complete setup instructions
|
||||
- [Authentication Setup](authentication.md) - User login details
|
||||
- [User Management](../guides/user-management.md) - Managing users
|
||||
- [Testing Guide](../guides/testing.md) - Comprehensive testing
|
||||
- [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 flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||
from flask_login import LoginManager
|
||||
from flask_wtf.csrf import CSRFProtect, generate_csrf
|
||||
|
||||
# Generate cache version at startup (changes on each container restart)
|
||||
CACHE_VERSION = str(int(time.time()))
|
||||
@ -85,11 +86,12 @@ def create_app():
|
||||
app.config.update({
|
||||
'SESSION_COOKIE_SECURE': use_https,
|
||||
'SESSION_COOKIE_HTTPONLY': True,
|
||||
'SESSION_COOKIE_SAMESITE': 'Lax',
|
||||
'SESSION_COOKIE_SAMESITE': 'Strict', # SECURITY: Strict for better CSRF protection
|
||||
'PERMANENT_SESSION_LIFETIME': timedelta(hours=24),
|
||||
'REMEMBER_COOKIE_DURATION': timedelta(days=14),
|
||||
'REMEMBER_COOKIE_SECURE': use_https,
|
||||
'REMEMBER_COOKIE_HTTPONLY': True,
|
||||
'REMEMBER_COOKIE_SAMESITE': 'Strict', # SECURITY: Match session cookie
|
||||
})
|
||||
|
||||
# Initialize Flask-Login
|
||||
@ -100,10 +102,34 @@ def create_app():
|
||||
login_manager.login_message_category = 'warning'
|
||||
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
|
||||
def inject_cache_version():
|
||||
return {'cache_version': CACHE_VERSION}
|
||||
def inject_globals():
|
||||
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)
|
||||
try:
|
||||
@ -249,6 +275,25 @@ def create_app():
|
||||
limiter.exempt(app.view_functions['connection_routes.device_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
|
||||
@app.route('/health')
|
||||
@limiter.exempt
|
||||
|
||||
@ -86,12 +86,11 @@ class AuthManager:
|
||||
|
||||
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:
|
||||
- Header: X-API-Key
|
||||
- Header: Authorization: Bearer <key>
|
||||
- Query param: api_key
|
||||
"""
|
||||
# Check X-API-Key header
|
||||
api_key = request.headers.get('X-API-Key')
|
||||
@ -103,12 +102,7 @@ class AuthManager:
|
||||
if auth_header and auth_header.startswith('Bearer '):
|
||||
return auth_header[7:] # Remove 'Bearer ' prefix
|
||||
|
||||
# Check query parameter (less secure, for testing 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
|
||||
|
||||
# SECURITY: Query parameter support removed - headers only
|
||||
return None
|
||||
|
||||
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-Login==0.6.3
|
||||
Flask-WTF==1.2.1
|
||||
Werkzeug==3.0.1
|
||||
requests==2.31.0
|
||||
typing-extensions==4.8.0
|
||||
|
||||
@ -6,14 +6,40 @@ Handles file upload operations
|
||||
import logging
|
||||
import os
|
||||
import csv
|
||||
import re
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from werkzeug.utils import secure_filename
|
||||
from routes.api.auth_decorator import require_auth
|
||||
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__)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
# Dependencies will be injected
|
||||
@ -55,13 +81,16 @@ def upload_csv():
|
||||
|
||||
reader = csv.DictReader(csvfile, delimiter=delimiter)
|
||||
for row in reader:
|
||||
# Sanitize CSV values to prevent injection attacks
|
||||
row = sanitize_csv_row(row)
|
||||
|
||||
# Clean field names
|
||||
cleaned_row = {}
|
||||
for key, value in row.items():
|
||||
if key:
|
||||
clean_key = key.strip().lower().replace(' ', '_')
|
||||
cleaned_row[clean_key] = value.strip() if value else ''
|
||||
|
||||
|
||||
# Map common field names
|
||||
recipient = {}
|
||||
for key, value in cleaned_row.items():
|
||||
@ -73,9 +102,13 @@ def upload_csv():
|
||||
recipient['message'] = value
|
||||
else:
|
||||
recipient[key] = value
|
||||
|
||||
|
||||
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
|
||||
list_id = None
|
||||
@ -101,8 +134,7 @@ def upload_csv():
|
||||
return jsonify({"error": "Invalid file type"}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"CSV upload error: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
return jsonify({"success": False, "error": log_and_sanitize(e, 'upload_csv')}), 500
|
||||
|
||||
@upload_routes.route('/campaign/upload', methods=['POST'])
|
||||
@require_login()
|
||||
@ -127,6 +159,9 @@ def upload_campaign_csv():
|
||||
preview_contacts = []
|
||||
|
||||
for i, row in enumerate(csv_reader):
|
||||
# Sanitize CSV values to prevent injection attacks
|
||||
row = sanitize_csv_row(row)
|
||||
|
||||
# Normalize field names
|
||||
normalized_row = {}
|
||||
for key, value in row.items():
|
||||
@ -138,12 +173,16 @@ def upload_campaign_csv():
|
||||
normalized_row['name'] = value.strip()
|
||||
elif 'message' in normalized_key:
|
||||
normalized_row['message'] = value.strip()
|
||||
|
||||
|
||||
if 'phone' in normalized_row:
|
||||
contacts.append(normalized_row)
|
||||
# Add to preview (first 10)
|
||||
if i < 10:
|
||||
preview_contacts.append(normalized_row)
|
||||
# Validate and normalize phone number
|
||||
is_valid, phone_normalized, error = validate_phone_number(normalized_row['phone'])
|
||||
if is_valid:
|
||||
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:
|
||||
return jsonify({'success': False, 'error': 'No valid contacts found in CSV'}), 400
|
||||
@ -173,8 +212,7 @@ def upload_campaign_csv():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error uploading campaign CSV: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'upload_campaign_csv')}), 500
|
||||
|
||||
@upload_routes.route('/contacts/fetch-from-phone', methods=['GET'])
|
||||
@require_login()
|
||||
@ -205,8 +243,7 @@ def fetch_phone_contacts():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fetch phone contacts error: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
return jsonify({"success": False, "error": log_and_sanitize(e, 'fetch_phone_contacts')}), 500
|
||||
|
||||
@upload_routes.route('/contacts/import-from-phone', methods=['POST'])
|
||||
@require_login()
|
||||
@ -265,5 +302,4 @@ def import_phone_contacts():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Import phone contacts error: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
return jsonify({"success": False, "error": log_and_sanitize(e, 'import_phone_contacts')}), 500
|
||||
|
||||
@ -7,6 +7,7 @@ Uses Flask-Login for reliable session management
|
||||
import logging
|
||||
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_wtf.csrf import CSRFProtect
|
||||
from core.user_auth import UserManager, require_login
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -24,9 +25,9 @@ def init_auth_routes(um: UserManager):
|
||||
@auth_bp.route('/login', methods=['GET'])
|
||||
def login_page():
|
||||
"""Display login page"""
|
||||
# If already logged in, redirect to dashboard
|
||||
# If already logged in, redirect to campaigns
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard'))
|
||||
return redirect(url_for('campaigns'))
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
@ -77,7 +78,7 @@ def login():
|
||||
'username': user.username,
|
||||
'role': user.role
|
||||
},
|
||||
'redirect': url_for('dashboard')
|
||||
'redirect': url_for('campaigns')
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -6,6 +6,7 @@ Conversations API Routes - RESTful endpoints for conversation management
|
||||
from flask import Blueprint, jsonify, request
|
||||
from models.conversation import Conversation
|
||||
from core.user_auth import require_login
|
||||
from core.error_handler import log_and_sanitize
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -43,8 +44,7 @@ def list_conversations():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing conversations: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'list_conversations')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/<conversation_id>')
|
||||
@ -63,8 +63,7 @@ def get_conversation_detail(conversation_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting conversation {conversation_id}: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_conversation_detail')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/<conversation_id>/read', methods=['PUT'])
|
||||
@ -83,8 +82,7 @@ def mark_conversation_read(conversation_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error marking conversation {conversation_id} as read: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'mark_conversation_read')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/<conversation_id>/notes', methods=['PUT'])
|
||||
@ -106,8 +104,7 @@ def update_conversation_notes(conversation_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating notes for conversation {conversation_id}: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'update_conversation_notes')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/<conversation_id>/tags', methods=['POST'])
|
||||
@ -133,8 +130,7 @@ def manage_conversation_tags(conversation_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error managing tags for conversation {conversation_id}: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'manage_conversation_tags')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/search')
|
||||
@ -158,8 +154,7 @@ def search_conversations():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching conversations: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'search_conversations')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/stats')
|
||||
@ -175,8 +170,7 @@ def get_conversation_stats():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting conversation stats: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'get_conversation_stats')}), 500
|
||||
|
||||
|
||||
@conversations_bp.route('/migrate', methods=['POST'])
|
||||
@ -192,5 +186,4 @@ def migrate_existing_messages():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error migrating messages: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
return jsonify({'success': False, 'error': log_and_sanitize(e, 'migrate_existing_messages')}), 500
|
||||
|
||||
@ -3,6 +3,7 @@ from datetime import datetime
|
||||
import os
|
||||
from models.contact_list import ContactList
|
||||
from core.user_auth import require_login
|
||||
from core.error_handler import log_and_sanitize
|
||||
|
||||
lists_bp = Blueprint('lists', __name__)
|
||||
model = ContactList()
|
||||
@ -16,7 +17,7 @@ def get_lists():
|
||||
lists = model.get_all_lists()
|
||||
return jsonify({'success': True, 'lists': lists})
|
||||
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'])
|
||||
@ -26,18 +27,18 @@ def create_list():
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': 'No data provided'}), 400
|
||||
|
||||
|
||||
name = data.get('name', '')
|
||||
contacts = data.get('contacts', [])
|
||||
filename = data.get('filename', 'manual_entry')
|
||||
|
||||
|
||||
if not name or not contacts:
|
||||
return jsonify({'success': False, 'error': 'Name and contacts are required'}), 400
|
||||
|
||||
|
||||
list_id = model.create_list(name, filename, contacts)
|
||||
return jsonify({'success': True, 'list_id': list_id, 'message': f'List created with ID {list_id}'})
|
||||
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'])
|
||||
@ -49,7 +50,7 @@ def get_list(list_id):
|
||||
return jsonify({'success': False, 'error': 'List not found'}), 404
|
||||
return jsonify({'success': True, 'list': data})
|
||||
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'])
|
||||
@ -62,7 +63,7 @@ def update_contact(list_id, phone):
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'success': False, 'error': 'Contact not found'}), 404
|
||||
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'])
|
||||
@ -74,7 +75,7 @@ def delete_list(list_id):
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'success': False, 'error': 'List not found'}), 404
|
||||
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'])
|
||||
@ -84,4 +85,4 @@ def use_list(list_id):
|
||||
model.mark_used(list_id)
|
||||
return jsonify({'success': True})
|
||||
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)
|
||||
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
|
||||
* Used on all pages via x-data="baseApp"
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
@ -82,12 +82,6 @@
|
||||
Sign In
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<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