diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf43036 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Logs +*.log +logs/ +wifi_auto_auth.log* + +# Database files +*.db +wifi_log.db + +# Configuration files (personal) +config.json + +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.venv/ +.env + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4171892 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to WiFi-Auto-Auth + +Welcome! We're excited to have you contribute to this project. Don't worry if you're new to open source - we're here to help! + +## First Things First - Star This Repo! + +Click the ⭐ button at the top right of this page. It helps others find this project! + +--- + +## Quick Start (5 Simple Steps) + +### Step 1: Pick an Issue +- Go to the [Issues tab](../../issues) +- Look for `good first issue` label if you're new +- Comment "I'd like to work on this!" on the issue + +### Step 2: Set Up Your Workspace +```bash +# Fork this repo (click Fork button on GitHub), then: +git clone https://github.com/01bps/WiFi-Auto-Auth.git +cd WiFi-Auto-Auth +git checkout -b my-new-feature +``` +### Step 3: Make Your Changes + +Write your code +Test it locally +Make sure it works! + +### Step 4: Document What You Did +Open [HACKTOBERFEST.md](HACKTOBERFEST.md) and add your contribution at the bottom: +## What I Added/Fixed +**Contributor:** @your-github-username +**Issue:** #issue-number + +### What I Changed: +- Added/Fixed/Improved [describe in simple words] +- Modified these files: `filename.py` + +--- +### Step 5: Submit Your Work +```bash +git add . +git commit -m "Added [brief description] (fixes #issue-number)" +git push origin my-new-feature +``` + +Then go to GitHub and click "Create Pull Request" diff --git a/DASHBOARD.md b/DASHBOARD.md new file mode 100644 index 0000000..c4d9873 --- /dev/null +++ b/DASHBOARD.md @@ -0,0 +1,342 @@ +# WiFi Auto Auth Dashboard + +A beautiful web-based monitoring interface for WiFi Auto Auth that provides real-time visualization of login attempts, statistics, and historical data. + +## šŸš€ Quick Start + +1. **Install dependencies**: `pip install -r requirements.txt` +2. **Copy config**: `cp config.example.json config.json` (edit with your WiFi details) +3. **Generate data**: `python wifi_auto_login.py --login` (run a few times) +4. **Start dashboard**: `python wifi_auto_login.py --dashboard` +5. **Access**: Open http://127.0.0.1:8000 (username: admin, password: admin123) + +## Features + +### šŸŽÆ Core Features +- **Real-time Dashboard**: Live monitoring of WiFi login attempts +- **Interactive Charts**: Time-based visualizations using Chart.js +- **Advanced Filtering**: Filter by date range, status, and more +- **Responsive Design**: Works perfectly on desktop and mobile devices +- **Secure Access**: HTTP Basic Authentication protection +- **RESTful API**: Complete API for data access and integration + +### šŸ“Š Dashboard Components +- **Statistics Cards**: Total attempts, success rate, failure count +- **Login Attempts Table**: Detailed view of recent attempts +- **Time-based Charts**: Visual representation of login patterns +- **Success Rate Pie Chart**: Quick overview of success vs failure rates +- **Real-time Updates**: Auto-refresh every 30 seconds + +### šŸ”’ Security Features +- HTTP Basic Authentication +- Configurable credentials +- Session management +- Secure API endpoints + +## Installation + +### Prerequisites +- Python 3.7 or higher +- All dependencies from `requirements.txt` + +### Install Dependencies + +```bash +pip install -r requirements.txt +``` + +The dashboard requires these additional packages: +- `fastapi>=0.104.0` - Modern web framework +- `uvicorn>=0.24.0` - ASGI server +- `jinja2>=3.1.0` - Template engine +- `python-multipart>=0.0.6` - Form data handling +- `aiofiles>=23.2.0` - Async file operations + +## Configuration + +### Dashboard Settings + +Add dashboard configuration to your `config.json`: + +```json +{ + "wifi_url": "http://192.168.1.1/login", + "username": "your_username", + "password": "your_password", + "product_type": "router", + "dashboard": { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123" + } +} +``` + +### Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `host` | Dashboard server host | `127.0.0.1` | +| `port` | Dashboard server port | `8000` | +| `username` | Dashboard login username | `admin` | +| `password` | Dashboard login password | `admin123` | + +## Usage + +### Starting the Dashboard + +#### Method 1: Using CLI Command +```bash +python wifi_auto_login.py --dashboard +``` + +#### Method 2: Direct Server Start +```bash +python dashboard.py +``` + +#### Method 3: Custom Host/Port +```bash +python dashboard.py --host 0.0.0.0 --port 8080 +``` + +### Accessing the Dashboard + +1. **Open your browser** and navigate to: `http://127.0.0.1:8000` +2. **Enter credentials** when prompted: + - Username: `admin` (or your configured username) + - Password: `admin123` (or your configured password) +3. **Explore the dashboard** - you'll see all your WiFi login statistics and history + +### Dashboard Sections + +#### šŸ“ˆ Statistics Overview +- **Total Attempts**: All login attempts recorded +- **Successful**: Number of successful logins +- **Failed**: Number of failed attempts +- **Success Rate**: Percentage of successful logins + +#### šŸ” Filtering Options +- **Date Range**: Filter attempts by start and end date +- **Status Filter**: Show only successful or failed attempts +- **Result Limit**: Control number of results displayed (25, 50, 100, 200) + +#### šŸ“Š Visualizations +- **Login Attempts Over Time**: Line chart showing hourly login patterns +- **Success Rate Distribution**: Pie chart showing success vs failure ratio +- **Real-time Updates**: Charts refresh automatically every 30 seconds + +#### šŸ“‹ Login Attempts Table +- **Timestamp**: When the login attempt occurred +- **Username**: User account used for login +- **Session ID**: Unique session identifier +- **Status**: Success (200) or failure status codes +- **Message**: Response message from the WiFi system + +## API Endpoints + +The dashboard provides a RESTful API for integration: + +### Authentication +All API endpoints require HTTP Basic Authentication using the same credentials as the dashboard. + +### Available Endpoints + +#### `GET /` +Main dashboard page (HTML) + +#### `GET /api/attempts` +Get login attempts with optional filters + +**Query Parameters:** +- `start_date`: Filter attempts after this date (ISO format) +- `end_date`: Filter attempts before this date (ISO format) +- `status_filter`: `success` or `failed` +- `limit`: Maximum number of results (default: 50) + +**Example:** +```bash +curl -u admin:admin123 "http://localhost:8000/api/attempts?limit=10&status_filter=success" +``` + +#### `GET /api/stats` +Get dashboard statistics + +**Response:** +```json +{ + "total_attempts": 150, + "successful_attempts": 145, + "failed_attempts": 5, + "success_rate": 96.67, + "last_attempt": "2025-10-03T10:30:45" +} +``` + +#### `GET /api/hourly-stats` +Get hourly statistics for charts + +**Query Parameters:** +- `days`: Number of days to include (default: 7) + +**Example:** +```bash +curl -u admin:admin123 "http://localhost:8000/api/hourly-stats?days=3" +``` + +#### `GET /health` +Health check endpoint (no authentication required) + +## Development + +### Running in Development Mode + +```bash +python dashboard.py --debug +``` + +This enables: +- Auto-reload on code changes +- Detailed error messages +- Debug logging + +### Custom Styling + +The dashboard uses Bootstrap 5 and custom CSS. You can modify: +- `static/dashboard.css` - Custom styles +- `templates/dashboard.html` - Main dashboard template +- `templates/login.html` - Login page template + +### Adding New Features + +The dashboard is built with FastAPI, making it easy to extend: + +1. **Add new API endpoints** in `dashboard.py` +2. **Modify database queries** in the database functions +3. **Update templates** for new UI components +4. **Add JavaScript** for new interactive features + +## Troubleshooting + +### Common Issues + +#### "Database not found" Error +- **Cause**: The database hasn't been created yet +- **Solution**: Run the main script first: `python wifi_auto_login.py --login` + +#### "Port already in use" Error +- **Cause**: Another process is using port 8000 +- **Solution**: Use a different port: `python dashboard.py --port 8080` + +#### Authentication Issues +- **Cause**: Incorrect credentials +- **Solution**: Check your `config.json` dashboard settings + +#### Charts Not Loading +- **Cause**: No login data available +- **Solution**: Run some login attempts first to populate the database + +#### Permission Denied +- **Cause**: Insufficient permissions to bind to the specified host/port +- **Solution**: Use `127.0.0.1` instead of `0.0.0.0`, or run with appropriate permissions + +### Debug Mode + +Enable debug mode for detailed error information: + +```bash +python dashboard.py --debug +``` + +### Logs + +The dashboard uses the same logging configuration as the main application. Check: +- Console output for real-time logs +- Log files in the `logs/` directory (if configured) + +## Security Considerations + +### Production Deployment + +āš ļø **Important**: The default credentials are for development only! + +For production deployment: + +1. **Change default credentials** in `config.json` +2. **Use HTTPS** with a reverse proxy (nginx, Apache) +3. **Restrict access** by IP address if needed +4. **Use strong passwords** (12+ characters, mixed case, numbers, symbols) +5. **Consider additional authentication** methods if required + +### Network Security + +- The dashboard binds to `127.0.0.1` by default (localhost only) +- To allow network access, bind to `0.0.0.0` but ensure proper firewall rules +- Consider using a VPN for remote access + +## Performance + +### Optimization Tips + +- **Limit results** using the limit parameter for better performance +- **Use date filters** to reduce database query time +- **Regular maintenance** - consider archiving old login attempts +- **Monitor database size** - SQLite performance degrades with very large databases + +### Resource Usage + +- **Memory**: ~50MB for typical usage +- **CPU**: Minimal when idle, moderate during chart updates +- **Database**: Grows ~1KB per login attempt +- **Network**: ~100KB per dashboard page load + +## Integration Examples + +### Monitoring Scripts + +```python +import requests +from requests.auth import HTTPBasicAuth + +# Get current statistics +response = requests.get( + 'http://localhost:8000/api/stats', + auth=HTTPBasicAuth('admin', 'admin123') +) +stats = response.json() +print(f"Success rate: {stats['success_rate']}%") +``` + +### Automated Reporting + +```python +import requests +from requests.auth import HTTPBasicAuth +from datetime import datetime, timedelta + +# Get yesterday's login attempts +yesterday = datetime.now() - timedelta(days=1) +start_date = yesterday.strftime('%Y-%m-%dT00:00:00') +end_date = yesterday.strftime('%Y-%m-%dT23:59:59') + +response = requests.get( + f'http://localhost:8000/api/attempts?start_date={start_date}&end_date={end_date}', + auth=HTTPBasicAuth('admin', 'admin123') +) +attempts = response.json()['attempts'] +print(f"Yesterday's login attempts: {len(attempts)}") +``` + +## Support + +For issues, questions, or contributions: + +1. Check the troubleshooting section above +2. Review the application logs +3. Create an issue on the project repository +4. Include detailed error messages and system information + +## License + +This dashboard is part of the WiFi Auto Auth project and follows the same license terms. \ No newline at end of file diff --git a/HACKTOBERFEST.md b/HACKTOBERFEST.md new file mode 100644 index 0000000..eda7304 --- /dev/null +++ b/HACKTOBERFEST.md @@ -0,0 +1,48 @@ + +# Hacktoberfest 2025 - Our Contributors + +This page celebrates everyone who contributed to WiFi-Auto-Auth during Hacktoberfest! + +## ⭐ Star This Repo! + +Show your support by starring this repository! + +--- + +## šŸ“Š Stats + +- **Contributors:** 0 +- **Issues Fixed:** 0 +- **PRs Merged:** 0 + +--- + +## šŸ† All Contributions + +Keep Adding your contribution in the below template,One after the other: + +## Professional Logging System Implementation +**Contributor ID:** cmarchena +**Issue reference No.:** #6 + +### What I Changed: +- Implemented comprehensive professional logging system with configurable log levels +- Added CLI arguments for logging configuration (--log-level, --view-logs, etc.) +- Created logging configuration module with environment variable support +- Added log rotation for automatic file management +- Updated README with detailed logging options documentation +- Created .gitignore file to exclude logs, cache, and sensitive files + +**Files Changed:** `wifi_auto_login.py`, `config/logging_config.py`, `readme.md`, `.gitignore` + +--- + +## šŸ™ Thank You! + +Every contribution makes this project better. We appreciate you! + +**Don't forget to ⭐ star this repository!** + +--- + +*Updated: October 2025* diff --git a/MULTI_NETWORK.md b/MULTI_NETWORK.md new file mode 100644 index 0000000..e01d8b6 --- /dev/null +++ b/MULTI_NETWORK.md @@ -0,0 +1,343 @@ +# Multi-Network Support Guide + +WiFi Auto Auth now supports multiple network profiles, allowing you to automatically connect to different WiFi networks (home, work, school, etc.) with different credentials and settings. + +## Features + +- **Auto-Detection**: Automatically detects current network SSID and selects appropriate profile +- **Manual Selection**: Override auto-detection by specifying a network profile +- **Network-Specific Logging**: Track login attempts per network +- **Dashboard Integration**: View network-specific statistics in the web dashboard +- **Backward Compatibility**: Existing single-network configurations continue to work + +## Configuration + +### New Multi-Network Format + +Create or update your `config.json` file with the new multi-network format: + +```json +{ + "default_network": "home", + "networks": { + "home": { + "ssid": "HomeWiFi", + "wifi_url": "http://192.168.1.1/login", + "username": "your_home_username", + "password": "your_home_password", + "product_type": "router", + "description": "Home WiFi network" + }, + "work": { + "ssid": "OfficeWiFi", + "wifi_url": "http://10.0.0.1/login", + "username": "your_work_username", + "password": "your_work_password", + "product_type": "enterprise", + "description": "Work WiFi network" + }, + "school": { + "ssid": "SchoolWiFi", + "wifi_url": "http://172.16.1.1/login", + "username": "your_school_username", + "password": "your_school_password", + "product_type": "edu", + "description": "School WiFi network" + } + }, + "dashboard": { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123" + } +} +``` + +### Legacy Configuration Support + +Existing single-network configurations will continue to work: + +```json +{ + "wifi_url": "http://192.168.1.1/login", + "username": "your_username", + "password": "your_password", + "product_type": "router" +} +``` + +## Usage + +### Auto-Detection (Recommended) + +The script automatically detects your current network SSID and selects the appropriate profile: + +```bash +# Auto-detect current network and login +python wifi_auto_login.py --login + +# Auto-detect and show recent logs +python wifi_auto_login.py +``` + +### Manual Network Selection + +Override auto-detection by specifying a network profile: + +```bash +# Login using specific network profile +python wifi_auto_login.py --login --network work + +# Test connection for specific network +python wifi_auto_login.py --test --network home +``` + +### Network Management Commands + +```bash +# List all configured network profiles +python wifi_auto_login.py --list-networks + +# Detect current network and show matching profile +python wifi_auto_login.py --detect-network + +# View logs for specific network +python wifi_auto_login.py --view-logs 10 --network-filter work +``` + +## Command Reference + +### New Commands + +| Command | Description | +|---------|-------------| +| `--network PROFILE` | Use specific network profile | +| `--list-networks` | List all configured networks | +| `--detect-network` | Detect current network | +| `--network-filter PROFILE` | Filter logs by network | + +### Updated Commands + +| Command | Description | +|---------|-------------| +| `--login` | Auto-detects network or uses --network | +| `--test` | Tests connection for detected/specified network | +| `--view-logs N` | Shows network info in logs | + +## Network Detection + +The script uses platform-specific methods to detect your current WiFi network: + +### Windows +- Uses `netsh wlan show interfaces` command +- Requires WiFi adapter to be connected + +### macOS +- Uses `networksetup -getairportnetwork en0` command +- Falls back to `airport -I` utility + +### Linux +- Tries multiple methods: `iwgetid`, `nmcli`, `iwconfig` +- Requires appropriate network utilities installed + +## Database Schema + +The database now includes network information: + +```sql +CREATE TABLE login_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + network_name TEXT, -- New: Network profile name + network_ssid TEXT, -- New: Actual SSID + username TEXT, + password TEXT, + a TEXT, + response_status TEXT, + response_message TEXT +); +``` + +Existing databases are automatically upgraded with new columns. + +## Dashboard Features + +### Network Statistics +- Per-network success rates +- Network-specific attempt counts +- Last login time per network + +### Filtering +- Filter logs by network profile +- Network-specific historical data +- Multi-network overview + +### API Endpoints + +| Endpoint | Description | +|----------|-------------| +| `/api/network-stats` | Get per-network statistics | +| `/api/attempts?network_filter=PROFILE` | Filtered login attempts | + +## Migration Guide + +### From Single Network to Multi-Network + +1. **Backup your current config.json**: + ```bash + cp config.json config.json.backup + ``` + +2. **Update configuration format**: + ```bash + # Use the new example as template + cp config.example.json config.json + # Edit config.json with your networks + ``` + +3. **Test the configuration**: + ```bash + python wifi_auto_login.py --list-networks + python wifi_auto_login.py --detect-network + ``` + +### Gradual Migration + +You can keep using legacy format while adding new networks: + +```json +{ + "wifi_url": "http://192.168.1.1/login", + "username": "legacy_user", + "password": "legacy_pass", + "networks": { + "work": { + "ssid": "OfficeWiFi", + "wifi_url": "http://10.0.0.1/login", + "username": "work_user", + "password": "work_pass" + } + } +} +``` + +## Troubleshooting + +### Network Detection Issues + +1. **No SSID detected**: + ```bash + # Check if WiFi is connected + python wifi_auto_login.py --detect-network + + # Manually specify network + python wifi_auto_login.py --login --network home + ``` + +2. **Platform not supported**: + - Windows: Ensure you have admin privileges for netsh + - macOS: Check if networksetup is available + - Linux: Install wireless-tools or network-manager + +3. **Profile not found**: + ```bash + # List available profiles + python wifi_auto_login.py --list-networks + + # Check SSID matches exactly + python wifi_auto_login.py --detect-network + ``` + +### Configuration Issues + +1. **Invalid JSON**: + ```bash + # Validate JSON syntax + python -m json.tool config.json + ``` + +2. **Missing network profile**: + - Add the network to your config.json + - Ensure SSID matches exactly (case-sensitive) + +3. **Legacy compatibility**: + - Old format still works + - Gradually migrate to new format + +## Best Practices + +### Network Profile Design + +1. **Use descriptive names**: `home`, `work`, `coffee-shop` +2. **Include descriptions**: Help identify networks later +3. **Set default network**: For fallback when detection fails +4. **Test each profile**: Verify credentials before deployment + +### Security Considerations + +1. **Protect config.json**: Contains passwords in plain text +2. **Use different passwords**: Don't reuse across networks +3. **Regular updates**: Change passwords periodically +4. **Backup configurations**: Keep secure backups + +### Monitoring + +1. **Use dashboard**: Monitor per-network success rates +2. **Check logs regularly**: Identify authentication issues +3. **Network-specific analysis**: Filter logs by network +4. **Set up alerts**: Monitor for failure patterns + +## Examples + +### Complete Multi-Network Setup + +```bash +# 1. Set up configuration +cp config.example.json config.json +# Edit config.json with your networks + +# 2. Test configuration +python wifi_auto_login.py --list-networks +python wifi_auto_login.py --detect-network + +# 3. Test each network +python wifi_auto_login.py --test --network home +python wifi_auto_login.py --test --network work + +# 4. Set up automatic login +python wifi_auto_login.py --login + +# 5. Monitor via dashboard +python wifi_auto_login.py --dashboard +``` + +### Automated Network Switching + +```bash +#!/bin/bash +# Example script for automated network handling + +# Detect current network +CURRENT_NETWORK=$(python wifi_auto_login.py --detect-network 2>/dev/null | grep "Found matching profile" | cut -d: -f2 | xargs) + +if [ -n "$CURRENT_NETWORK" ]; then + echo "Logging into detected network: $CURRENT_NETWORK" + python wifi_auto_login.py --login --network "$CURRENT_NETWORK" +else + echo "No matching network profile found, using auto-detection" + python wifi_auto_login.py --login +fi +``` + +## Support + +If you encounter issues with multi-network support: + +1. Check the troubleshooting section above +2. Enable debug logging: `python wifi_auto_login.py --log-level DEBUG` +3. Test with single network first +4. Report issues with network detection details +5. Include platform information (Windows/macOS/Linux) + +The multi-network feature is designed to be backward compatible while providing powerful new capabilities for managing multiple WiFi environments. \ No newline at end of file diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..4831e44 --- /dev/null +++ b/config.example.json @@ -0,0 +1,12 @@ +{ + "wifi_url": "http://192.168.1.1/login", + "username": "your_username", + "password": "your_password", + "product_type": "router", + "dashboard": { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123" + } +} diff --git a/config/logging_config.py b/config/logging_config.py new file mode 100644 index 0000000..feab2dc --- /dev/null +++ b/config/logging_config.py @@ -0,0 +1,186 @@ +""" +Professional logging configuration for WiFi Auto Auth application. +Provides structured, configurable logging with multiple output handlers and log rotation. +""" + +import os +import sys +import logging +import logging.handlers +from pathlib import Path +from typing import Optional + + +class LoggerFactory: + """Factory class for creating and managing loggers.""" + + _loggers = {} + _configured = False + + @classmethod + def get_logger(cls, name: str) -> logging.Logger: + """ + Get or create a logger with the specified name. + + Args: + name: Logger name (typically __name__ of the calling module) + + Returns: + Configured logger instance + """ + if not cls._configured: + cls._setup_logging() + + if name not in cls._loggers: + cls._loggers[name] = logging.getLogger(name) + + return cls._loggers[name] + + @classmethod + def _setup_logging(cls) -> None: + """Set up the global logging configuration.""" + if cls._configured: + return + + # Create formatters + simple_formatter = logging.Formatter( + fmt='%(asctime)s [%(levelname)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + detailed_formatter = logging.Formatter( + fmt='%(asctime)s [%(levelname)s] [%(name)s:%(funcName)s:%(lineno)d] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # Set up root logger + root_logger = logging.getLogger() + root_logger.setLevel(cls._get_log_level()) + + # Clear existing handlers to avoid duplicates + root_logger.handlers.clear() + + # Console handler + if cls._is_console_logging_enabled(): + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(cls._get_console_log_level()) + console_handler.setFormatter(simple_formatter) + root_logger.addHandler(console_handler) + + # File handler with rotation + if cls._is_file_logging_enabled(): + file_handler = cls._create_file_handler(detailed_formatter) + file_handler.setLevel(logging.DEBUG) # Always capture all levels in file + root_logger.addHandler(file_handler) + + cls._configured = True + + @classmethod + def _get_log_level(cls) -> int: + """Get the overall log level from environment variables.""" + level_str = os.getenv('LOG_LEVEL', 'INFO').upper() + return getattr(logging, level_str, logging.INFO) + + @classmethod + def _get_console_log_level(cls) -> int: + """Get the console log level from environment variables.""" + level_str = os.getenv('CONSOLE_LOG_LEVEL', os.getenv('LOG_LEVEL', 'INFO')).upper() + return getattr(logging, level_str, logging.INFO) + + @classmethod + def _is_console_logging_enabled(cls) -> bool: + """Check if console logging is enabled.""" + return os.getenv('CONSOLE_LOGGING_ENABLED', 'true').lower() == 'true' + + @classmethod + def _is_file_logging_enabled(cls) -> bool: + """Check if file logging is enabled.""" + return os.getenv('LOG_FILE_ENABLED', 'true').lower() == 'true' + + @classmethod + def _create_file_handler(cls, formatter: logging.Formatter) -> logging.Handler: + """Create a rotating file handler.""" + log_dir = Path(os.getenv('LOG_DIR', './logs')) + log_dir.mkdir(exist_ok=True) + + log_file = log_dir / 'wifi_auto_auth.log' + + # Get rotation settings from environment + max_bytes = int(os.getenv('LOG_MAX_BYTES', '10485760')) # 10MB default + backup_count = int(os.getenv('LOG_BACKUP_COUNT', '5')) + + # Use RotatingFileHandler for size-based rotation + handler = logging.handlers.RotatingFileHandler( + log_file, + maxBytes=max_bytes, + backupCount=backup_count, + encoding='utf-8' + ) + + handler.setFormatter(formatter) + return handler + + @classmethod + def configure_from_args(cls, args) -> None: + """ + Configure logging based on command line arguments. + + Args: + args: Parsed command line arguments with logging options + """ + if hasattr(args, 'log_level'): + os.environ['LOG_LEVEL'] = args.log_level + if hasattr(args, 'log_file'): + os.environ['LOG_FILE_ENABLED'] = 'true' if args.log_file else 'false' + if hasattr(args, 'log_dir'): + os.environ['LOG_DIR'] = args.log_dir + if hasattr(args, 'console_logging'): + os.environ['CONSOLE_LOGGING_ENABLED'] = 'true' if args.console_logging else 'false' + + # Reconfigure logging with new settings + cls._configured = False + cls._setup_logging() + + +def get_logger(name: str) -> logging.Logger: + """ + Convenience function to get a logger. + + Args: + name: Logger name (typically __name__ of the calling module) + + Returns: + Configured logger instance + """ + return LoggerFactory.get_logger(name) + + +def setup_logging_from_env() -> None: + """Set up logging configuration from environment variables.""" + LoggerFactory._setup_logging() + + +# Convenience functions for common logging patterns +def log_function_entry(logger: logging.Logger, func_name: str, *args, **kwargs) -> None: + """Log function entry with parameters.""" + params = [] + if args: + params.append(f"args={args}") + if kwargs: + params.append(f"kwargs={kwargs}") + + param_str = ", ".join(params) if params else "no parameters" + logger.debug(f"Entering {func_name}({param_str})") + + +def log_function_exit(logger: logging.Logger, func_name: str, return_value=None) -> None: + """Log function exit with return value.""" + if return_value is not None: + logger.debug(f"Exiting {func_name} with return value: {return_value}") + else: + logger.debug(f"Exiting {func_name}") + + +def log_exception(logger: logging.Logger, exc: Exception, message: str = "Exception occurred") -> None: + """Log an exception with full traceback.""" + logger.exception(f"{message}: {exc}") \ No newline at end of file diff --git a/dashboard.py b/dashboard.py new file mode 100644 index 0000000..a77948b --- /dev/null +++ b/dashboard.py @@ -0,0 +1,466 @@ +""" +WiFi Auto Auth Dashboard - Web-based monitoring interface for WiFi login attempts +""" + +import sqlite3 +import json +import os +from datetime import datetime, timedelta +from typing import List, Dict, Optional +from fastapi import FastAPI, Request, Depends, HTTPException, status, Form +from fastapi.templating import Jinja2Templates +from fastapi.staticfiles import StaticFiles +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from pydantic import BaseModel +import uvicorn +import secrets +from pathlib import Path + +# Import existing logging configuration +try: + from config.logging_config import get_logger + logger = get_logger(__name__) +except ImportError: + import logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + +# --- CONFIGURATION --- +CONFIG_PATH = "config.json" +DB_NAME = "wifi_log.db" + +# Load configuration +def load_dashboard_config(): + """Load dashboard configuration from config.json""" + if not os.path.exists(CONFIG_PATH): + # Use default configuration if config.json doesn't exist + return { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123", + "secret_key": secrets.token_urlsafe(32) + } + + try: + with open(CONFIG_PATH, "r") as f: + config = json.load(f) + + return config.get("dashboard", { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123", + "secret_key": secrets.token_urlsafe(32) + }) + except (json.JSONDecodeError, KeyError): + logger.warning("Invalid config.json, using default dashboard configuration") + return { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123", + "secret_key": secrets.token_urlsafe(32) + } + +# Dashboard configuration +DASHBOARD_CONFIG = load_dashboard_config() + +# --- PYDANTIC MODELS --- +class LoginAttempt(BaseModel): + id: int + timestamp: str + network_name: Optional[str] = None + network_ssid: Optional[str] = None + username: str + a: str + response_status: str + response_message: str + +class DashboardStats(BaseModel): + total_attempts: int + successful_attempts: int + failed_attempts: int + success_rate: float + last_attempt: Optional[str] + +class FilterParams(BaseModel): + start_date: Optional[str] = None + end_date: Optional[str] = None + status_filter: Optional[str] = None + network_filter: Optional[str] = None + limit: int = 50 + +# --- FASTAPI APP SETUP --- +app = FastAPI(title="WiFi Auto Auth Dashboard", version="1.0.0") + +# Setup templates and static files +templates = Jinja2Templates(directory="templates") + +# Create static directory if it doesn't exist +static_dir = Path("static") +static_dir.mkdir(exist_ok=True) + +app.mount("/static", StaticFiles(directory="static"), name="static") + +# Simple authentication +security = HTTPBasic() + +def authenticate(credentials: HTTPBasicCredentials = Depends(security)): + """Simple HTTP Basic Authentication""" + correct_username = secrets.compare_digest(credentials.username, DASHBOARD_CONFIG["username"]) + correct_password = secrets.compare_digest(credentials.password, DASHBOARD_CONFIG["password"]) + + if not (correct_username and correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid credentials", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + +# --- DATABASE FUNCTIONS --- +def get_db_connection(): + """Get database connection""" + conn = sqlite3.connect(DB_NAME) + + if not os.path.exists(DB_NAME): + logger.warning(f"Database {DB_NAME} not found. Creating new database.") + + # Ensure table exists with multi-network support + cursor = conn.cursor() + + # Check if table exists and get its schema + cursor.execute("PRAGMA table_info(login_attempts)") + columns = [row[1] for row in cursor.fetchall()] + + if not columns: + # Create new table with network support + cursor.execute(""" + CREATE TABLE login_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + network_name TEXT, + network_ssid TEXT, + username TEXT, + password TEXT, + a TEXT, + response_status TEXT, + response_message TEXT + ) + """) + logger.info("Created new login_attempts table with network support") + else: + # Check if we need to add network columns to existing table + if 'network_name' not in columns: + cursor.execute("ALTER TABLE login_attempts ADD COLUMN network_name TEXT") + logger.info("Added network_name column to existing table") + + if 'network_ssid' not in columns: + cursor.execute("ALTER TABLE login_attempts ADD COLUMN network_ssid TEXT") + logger.info("Added network_ssid column to existing table") + + conn.commit() + return conn + +def get_login_attempts(filters: FilterParams, network_filter: Optional[str] = None) -> List[Dict]: + """Get login attempts with filters""" + conn = get_db_connection() + cursor = conn.cursor() + + # Check if table has network columns + cursor.execute("PRAGMA table_info(login_attempts)") + columns = [row[1] for row in cursor.fetchall()] + has_network_columns = 'network_name' in columns and 'network_ssid' in columns + + if has_network_columns: + query = """ + SELECT id, timestamp, network_name, network_ssid, username, a, response_status, response_message + FROM login_attempts + WHERE 1=1 + """ + else: + query = """ + SELECT id, timestamp, username, a, response_status, response_message + FROM login_attempts + WHERE 1=1 + """ + + params = [] + + if filters.start_date: + query += " AND timestamp >= ?" + params.append(filters.start_date) + + if filters.end_date: + query += " AND timestamp <= ?" + params.append(filters.end_date) + + if filters.status_filter: + if filters.status_filter == "success": + query += " AND response_status = '200'" + elif filters.status_filter == "failed": + query += " AND response_status != '200'" + + if network_filter and has_network_columns: + query += " AND network_name = ?" + params.append(network_filter) + + query += " ORDER BY timestamp DESC LIMIT ?" + params.append(filters.limit) + + cursor.execute(query, params) + rows = cursor.fetchall() + conn.close() + + if has_network_columns: + return [ + { + "id": row[0], + "timestamp": row[1], + "network_name": row[2], + "network_ssid": row[3], + "username": row[4], + "a": row[5], + "response_status": row[6], + "response_message": row[7] + } + for row in rows + ] + else: + return [ + { + "id": row[0], + "timestamp": row[1], + "network_name": "Legacy", + "network_ssid": "Unknown", + "username": row[2], + "a": row[3], + "response_status": row[4], + "response_message": row[5] + } + for row in rows + ] + +def get_dashboard_stats() -> DashboardStats: + """Get dashboard statistics""" + conn = get_db_connection() + cursor = conn.cursor() + + # Total attempts + cursor.execute("SELECT COUNT(*) FROM login_attempts") + total_attempts = cursor.fetchone()[0] + + # Successful attempts (assuming 200 is success) + cursor.execute("SELECT COUNT(*) FROM login_attempts WHERE response_status = '200'") + successful_attempts = cursor.fetchone()[0] + + # Failed attempts + failed_attempts = total_attempts - successful_attempts + + # Success rate + success_rate = (successful_attempts / total_attempts * 100) if total_attempts > 0 else 0 + + # Last attempt + cursor.execute("SELECT timestamp FROM login_attempts ORDER BY timestamp DESC LIMIT 1") + last_attempt_row = cursor.fetchone() + last_attempt = last_attempt_row[0] if last_attempt_row else None + + conn.close() + + return DashboardStats( + total_attempts=total_attempts, + successful_attempts=successful_attempts, + failed_attempts=failed_attempts, + success_rate=round(success_rate, 2), + last_attempt=last_attempt + ) + +def get_network_stats() -> List[Dict]: + """Get statistics per network profile""" + conn = get_db_connection() + cursor = conn.cursor() + + # Check if table has network columns + cursor.execute("PRAGMA table_info(login_attempts)") + columns = [row[1] for row in cursor.fetchall()] + has_network_columns = 'network_name' in columns and 'network_ssid' in columns + + if not has_network_columns: + return [] + + query = """ + SELECT + network_name, + network_ssid, + COUNT(*) as total_attempts, + SUM(CASE WHEN response_status = '200' THEN 1 ELSE 0 END) as successful_attempts, + MAX(timestamp) as last_attempt + FROM login_attempts + WHERE network_name IS NOT NULL + GROUP BY network_name, network_ssid + ORDER BY total_attempts DESC + """ + + cursor.execute(query) + rows = cursor.fetchall() + conn.close() + + stats = [] + for row in rows: + network_name, network_ssid, total, successful, last_attempt = row + failed = total - successful + success_rate = (successful / total * 100) if total > 0 else 0 + + stats.append({ + "network_name": network_name, + "network_ssid": network_ssid, + "total_attempts": total, + "successful_attempts": successful, + "failed_attempts": failed, + "success_rate": round(success_rate, 2), + "last_attempt": last_attempt + }) + + return stats + +def get_hourly_stats(days: int = 7) -> List[Dict]: + """Get hourly login attempt statistics for the last N days""" + conn = get_db_connection() + cursor = conn.cursor() + + start_date = datetime.now() - timedelta(days=days) + + query = """ + SELECT + strftime('%Y-%m-%d %H', timestamp) as hour, + COUNT(*) as total_attempts, + SUM(CASE WHEN response_status = '200' THEN 1 ELSE 0 END) as successful_attempts + FROM login_attempts + WHERE timestamp >= ? + GROUP BY strftime('%Y-%m-%d %H', timestamp) + ORDER BY hour + """ + + cursor.execute(query, (start_date.isoformat(),)) + rows = cursor.fetchall() + conn.close() + + return [ + { + "hour": row[0], + "total_attempts": row[1], + "successful_attempts": row[2], + "failed_attempts": row[1] - row[2] + } + for row in rows + ] + +# --- ROUTES --- +@app.get("/", response_class=HTMLResponse) +async def dashboard(request: Request, username: str = Depends(authenticate)): + """Main dashboard page""" + logger.info(f"Dashboard accessed by user: {username}") + + # Get recent login attempts + filters = FilterParams(limit=10) + recent_attempts = get_login_attempts(filters) + + # Get statistics + stats = get_dashboard_stats() + network_stats = get_network_stats() + + return templates.TemplateResponse("dashboard.html", { + "request": request, + "recent_attempts": recent_attempts, + "stats": stats, + "network_stats": network_stats, + "username": username + }) + +@app.get("/api/attempts") +async def get_attempts_api( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + status_filter: Optional[str] = None, + network_filter: Optional[str] = None, + limit: int = 50, + username: str = Depends(authenticate) +): + """API endpoint to get login attempts with filters""" + filters = FilterParams( + start_date=start_date, + end_date=end_date, + status_filter=status_filter, + network_filter=network_filter, + limit=limit + ) + + attempts = get_login_attempts(filters, network_filter) + logger.info(f"API call: Retrieved {len(attempts)} login attempts") + + return {"attempts": attempts} + +@app.get("/api/stats") +async def get_stats_api(username: str = Depends(authenticate)): + """API endpoint to get dashboard statistics""" + stats = get_dashboard_stats() + logger.info("API call: Retrieved dashboard statistics") + + return {"stats": stats} + +@app.get("/api/network-stats") +async def get_network_stats_api(username: str = Depends(authenticate)): + """API endpoint to get network-specific statistics""" + network_stats = get_network_stats() + logger.info("API call: Retrieved network statistics") + + return {"network_stats": network_stats} + return stats + +@app.get("/api/hourly-stats") +async def get_hourly_stats_api(days: int = 7, username: str = Depends(authenticate)): + """API endpoint to get hourly statistics""" + stats = get_hourly_stats(days) + logger.info(f"API call: Retrieved hourly statistics for last {days} days") + return {"hourly_stats": stats} + +@app.get("/login", response_class=HTMLResponse) +async def login_page(request: Request): + """Login page (for custom authentication if needed)""" + return templates.TemplateResponse("login.html", {"request": request}) + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return {"status": "healthy", "timestamp": datetime.now().isoformat()} + +# --- SERVER MANAGEMENT --- +def start_dashboard_server(host: str = None, port: int = None, debug: bool = False): + """Start the dashboard server""" + host = host or DASHBOARD_CONFIG["host"] + port = port or DASHBOARD_CONFIG["port"] + + logger.info(f"Starting WiFi Auto Auth Dashboard on http://{host}:{port}") + logger.info(f"Username: {DASHBOARD_CONFIG['username']}") + logger.info(f"Password: {DASHBOARD_CONFIG['password']}") + + uvicorn.run( + "dashboard:app", + host=host, + port=port, + reload=debug, + log_level="info" + ) + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="WiFi Auto Auth Dashboard") + parser.add_argument("--host", default="127.0.0.1", help="Host to bind the server") + parser.add_argument("--port", type=int, default=8000, help="Port to bind the server") + parser.add_argument("--debug", action="store_true", help="Enable debug mode") + + args = parser.parse_args() + start_dashboard_server(args.host, args.port, args.debug) \ No newline at end of file diff --git a/network_utils.py b/network_utils.py new file mode 100644 index 0000000..dc1074f --- /dev/null +++ b/network_utils.py @@ -0,0 +1,300 @@ +""" +Network utilities for WiFi Auto Auth - Multi-Network Support +Handles network detection, SSID identification, and network profile management. +""" + +import subprocess +import platform +import re +import json +import os +from typing import Optional, Dict, List, Tuple +from config.logging_config import get_logger + +logger = get_logger(__name__) + +class NetworkDetector: + """Handles network detection and SSID identification across different platforms.""" + + def __init__(self): + self.platform = platform.system().lower() + logger.debug(f"Initialized NetworkDetector for platform: {self.platform}") + + def get_current_ssid(self) -> Optional[str]: + """ + Get the SSID of the currently connected WiFi network. + + Returns: + str: SSID of current network, or None if not connected to WiFi + """ + try: + if self.platform == "windows": + return self._get_ssid_windows() + elif self.platform == "darwin": # macOS + return self._get_ssid_macos() + elif self.platform == "linux": + return self._get_ssid_linux() + else: + logger.warning(f"Unsupported platform: {self.platform}") + return None + except Exception as e: + logger.error(f"Failed to get current SSID: {e}") + return None + + def _get_ssid_windows(self) -> Optional[str]: + """Get SSID on Windows using netsh command.""" + try: + # Use netsh to get WiFi profile information + cmd = ["netsh", "wlan", "show", "profiles"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Get the currently connected profile + cmd_interfaces = ["netsh", "wlan", "show", "interfaces"] + interfaces_result = subprocess.run(cmd_interfaces, capture_output=True, text=True, check=True) + + # Parse the SSID from the interfaces output + for line in interfaces_result.stdout.split('\n'): + if 'SSID' in line and 'BSSID' not in line: + # Extract SSID (format: " SSID : NetworkName") + match = re.search(r'SSID\s*:\s*(.+)', line.strip()) + if match: + ssid = match.group(1).strip() + logger.debug(f"Detected Windows SSID: {ssid}") + return ssid + + logger.debug("No active WiFi connection found on Windows") + return None + + except subprocess.CalledProcessError as e: + logger.error(f"Windows netsh command failed: {e}") + return None + except Exception as e: + logger.error(f"Error getting Windows SSID: {e}") + return None + + def _get_ssid_macos(self) -> Optional[str]: + """Get SSID on macOS using airport utility.""" + try: + # Try using networksetup first + cmd = ["networksetup", "-getairportnetwork", "en0"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Parse output (format: "Current Wi-Fi Network: NetworkName") + if "Current Wi-Fi Network:" in result.stdout: + ssid = result.stdout.split("Current Wi-Fi Network:")[-1].strip() + if ssid and ssid != "You are not associated with an AirPort network.": + logger.debug(f"Detected macOS SSID: {ssid}") + return ssid + + # Fallback to airport utility + airport_cmd = ["/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport", "-I"] + airport_result = subprocess.run(airport_cmd, capture_output=True, text=True, check=True) + + for line in airport_result.stdout.split('\n'): + if 'SSID:' in line: + ssid = line.split('SSID:')[-1].strip() + logger.debug(f"Detected macOS SSID via airport: {ssid}") + return ssid + + logger.debug("No active WiFi connection found on macOS") + return None + + except subprocess.CalledProcessError as e: + logger.error(f"macOS network command failed: {e}") + return None + except Exception as e: + logger.error(f"Error getting macOS SSID: {e}") + return None + + def _get_ssid_linux(self) -> Optional[str]: + """Get SSID on Linux using various methods.""" + methods = [ + self._linux_iwgetid, + self._linux_nmcli, + self._linux_iwconfig + ] + + for method in methods: + try: + ssid = method() + if ssid: + logger.debug(f"Detected Linux SSID: {ssid}") + return ssid + except Exception as e: + logger.debug(f"Linux method {method.__name__} failed: {e}") + continue + + logger.debug("No active WiFi connection found on Linux") + return None + + def _linux_iwgetid(self) -> Optional[str]: + """Get SSID using iwgetid command.""" + cmd = ["iwgetid", "-r"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + ssid = result.stdout.strip() + return ssid if ssid else None + + def _linux_nmcli(self) -> Optional[str]: + """Get SSID using NetworkManager's nmcli.""" + cmd = ["nmcli", "-t", "-f", "active,ssid", "dev", "wifi"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + for line in result.stdout.split('\n'): + if line.startswith('yes:'): + ssid = line.split(':', 1)[1] + return ssid if ssid else None + return None + + def _linux_iwconfig(self) -> Optional[str]: + """Get SSID using iwconfig command.""" + cmd = ["iwconfig"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + for line in result.stdout.split('\n'): + if 'ESSID:' in line: + match = re.search(r'ESSID:"([^"]*)"', line) + if match: + return match.group(1) + return None + + +class NetworkProfileManager: + """Manages network profiles and configuration loading.""" + + def __init__(self, config_path: str = "config.json"): + self.config_path = config_path + self.detector = NetworkDetector() + logger.debug(f"Initialized NetworkProfileManager with config: {config_path}") + + def load_config(self) -> Dict: + """Load and parse configuration file.""" + if not os.path.exists(self.config_path): + raise FileNotFoundError( + f"Missing {self.config_path}. Please copy config.example.json to {self.config_path} and configure your networks." + ) + + with open(self.config_path, "r") as f: + config = json.load(f) + + logger.debug(f"Loaded configuration from {self.config_path}") + return config + + def get_available_networks(self) -> List[str]: + """Get list of configured network profile names.""" + try: + config = self.load_config() + + # Handle both new multi-network format and legacy format + if "networks" in config: + networks = list(config["networks"].keys()) + logger.debug(f"Found configured networks: {networks}") + return networks + else: + # Legacy single network configuration + logger.debug("Using legacy single network configuration") + return ["default"] + except Exception as e: + logger.error(f"Error getting available networks: {e}") + return [] + + def get_network_profile(self, network_name: Optional[str] = None, auto_detect: bool = True) -> Tuple[str, Dict]: + """ + Get network configuration for specified network or auto-detect current network. + + Args: + network_name: Specific network profile to use + auto_detect: Whether to auto-detect current network if network_name is None + + Returns: + Tuple of (network_name, network_config) + """ + config = self.load_config() + + # Handle legacy configuration format + if "networks" not in config: + logger.info("Using legacy configuration format") + legacy_config = { + "ssid": config.get("ssid", "Unknown"), + "wifi_url": config["wifi_url"], + "username": config["username"], + "password": config["password"], + "product_type": config.get("product_type", "0"), + "description": "Legacy configuration" + } + return "legacy", legacy_config + + networks = config["networks"] + + # If specific network requested, use it + if network_name: + if network_name in networks: + logger.info(f"Using specified network profile: {network_name}") + return network_name, networks[network_name] + else: + raise ValueError(f"Network profile '{network_name}' not found in configuration") + + # Auto-detect current network + if auto_detect: + current_ssid = self.detector.get_current_ssid() + if current_ssid: + logger.info(f"Detected current SSID: {current_ssid}") + + # Find matching network profile by SSID + for profile_name, profile_config in networks.items(): + if profile_config.get("ssid") == current_ssid: + logger.info(f"Found matching network profile: {profile_name}") + return profile_name, profile_config + + logger.warning(f"No network profile found for SSID: {current_ssid}") + else: + logger.warning("Could not detect current network SSID") + + # Fall back to default network + default_network = config.get("default_network", list(networks.keys())[0]) + if default_network in networks: + logger.info(f"Using default network profile: {default_network}") + return default_network, networks[default_network] + + # If default not found, use first available + first_network = list(networks.keys())[0] + logger.info(f"Using first available network profile: {first_network}") + return first_network, networks[first_network] + + def list_networks(self) -> Dict[str, Dict]: + """Get detailed information about all configured networks.""" + try: + config = self.load_config() + + if "networks" not in config: + # Legacy format + return { + "legacy": { + "ssid": config.get("ssid", "Unknown"), + "wifi_url": config["wifi_url"], + "description": "Legacy configuration" + } + } + + return config["networks"] + except Exception as e: + logger.error(f"Error listing networks: {e}") + return {} + + +# Convenience functions for backward compatibility +def get_current_ssid() -> Optional[str]: + """Get the SSID of the currently connected WiFi network.""" + detector = NetworkDetector() + return detector.get_current_ssid() + + +def get_network_profile(network_name: Optional[str] = None, auto_detect: bool = True) -> Tuple[str, Dict]: + """Get network configuration for specified network or auto-detect current network.""" + manager = NetworkProfileManager() + return manager.get_network_profile(network_name, auto_detect) + + +def list_available_networks() -> List[str]: + """Get list of configured network profile names.""" + manager = NetworkProfileManager() + return manager.get_available_networks() \ No newline at end of file diff --git a/readme.md b/readme.md index 971ce5e..a58781c 100644 --- a/readme.md +++ b/readme.md @@ -1,29 +1,128 @@ -# **Auto Wifi Login** -"Tired of entering the same Wi-Fi credentials every time you join the network? So was I! šŸ˜… At my institute, logging into Wi-Fi manually was a hassle, so I built this Auto WiFi Login Script to automate the process! +# **WiFi-Auto-Auth** +Tired of entering the same Wi-Fi credentials every time you join the network? So was I! At my institute, logging into Wi-Fi manually was a hassle, so I built this Auto WiFi Login Script to automate the process with the help of Python,SQLite and Crontab! -This script automatically logs into Wi-Fi networks using pre-saved credentials and now comes with SQLite integration to store login attempts. It keeps track of all login activities, captures dynamic session parameters (a), and provides a user-friendly log display for debugging. +This script automatically logs into Wi-Fi networks using pre-saved credentials and now comes with SQLite integration to store login attempts and all payload parameters. It keeps track of all login activities, captures dynamic session parameters (a), and provides a user-friendly log display for debugging. Ideal for schools, workplaces, or any location with recurring Wi-Fi logins, this script eliminates manual re-authentication and ensures effortless connectivity. It's fully customizable, works across different networks, and can even be automated on startup for a seamless experience. -Say goodbye to typing passwords repeatedly and hello to smart, automated Wi-Fi access +## **šŸš€ New: Web Dashboard** +**Beautiful web-based monitoring interface with real-time statistics and interactive charts!** -## **Features** +### **Dashboard Features** +- šŸ“Š Real-time statistics & success rates +- šŸ“ˆ Interactive time-based visualizations +- šŸ” Advanced filtering & search +- šŸ“± Mobile-responsive design +- šŸ”’ Secure authentication +- ⚔ Auto-refresh every 30 seconds -- Secure credential storage using SQLite database -- Login attempt logging with automatic cleanup -- Support for multiple network configurations -- Automatic error handling and retries -- Detailed status tracking and logging +### **Quick Start** +```bash +# Install dependencies +pip install -r requirements.txt -## **Requirements** +# Start dashboard +python wifi_auto_login.py --dashboard -Before running the script, ensure you have: -āœ” Python 3 installed -āœ” Required libraries -āœ” WiFi network login page details +# Access: http://127.0.0.1:8000 (admin/admin123) +``` -### For step-by-step setup instructions, please refer to [setup.md](https://github.com/01bps/RGIPT_Auto_Connect/blob/main/setup.md). +**šŸ“– Full documentation: [DASHBOARD.md](DASHBOARD.md)** + +## **🌐 NEW: Multi-Network Support** + +**Automatically handle multiple WiFi networks with intelligent auto-detection!** + +### **Multi-Network Features** +- šŸ  **Multiple Profiles**: Configure home, work, school networks +- šŸ” **Auto-Detection**: Automatically detects current SSID +- šŸ“± **Smart Selection**: Chooses appropriate credentials +- šŸ“Š **Network Analytics**: Per-network statistics in dashboard +- šŸ”„ **Seamless Switching**: No manual intervention needed +- šŸ“‹ **Easy Management**: List, detect, and filter by network + +### **Quick Multi-Network Setup** +```bash +# Copy and configure multi-network template +cp config.example.json config.json + +# List configured networks +python wifi_auto_login.py --list-networks + +# Auto-detect current network +python wifi_auto_login.py --detect-network + +# Login with auto-detection +python wifi_auto_login.py --login + +# Use specific network profile +python wifi_auto_login.py --login --network work +``` + +**šŸ“– Complete guide: [MULTI_NETWORK.md](MULTI_NETWORK.md)** + +## **Logging Options** + +This application features a comprehensive professional logging system that provides detailed insights into login attempts, debugging information, and system status. The logging system supports multiple output destinations, configurable log levels, and automatic log rotation. + +### **Command Line Arguments** + +The script accepts various logging-related command line arguments: + +- `--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}`: Set the logging level (default: INFO) +- `--log-file` / `--no-log-file`: Enable or disable file logging (default: enabled) +- `--log-dir DIR`: Directory for log files (default: ./logs) +- `--console-logging` / `--no-console-logging`: Enable or disable console logging (default: enabled) +- `--view-logs N`: View last N login attempts instead of performing login +- `--max-attempts N`: Maximum number of login attempts to show (default: 5) + +**Usage Examples:** + +```bash +# Run with debug logging +python wifi_auto_login.py --log-level DEBUG + +# View recent login attempts +python wifi_auto_login.py --view-logs 10 + +# Start the web dashboard +python wifi_auto_login.py --dashboard + +# Disable file logging, only console output +python wifi_auto_login.py --no-log-file + +# Custom log directory +python wifi_auto_login.py --log-dir /var/log/wifi-auth +``` + +### **Environment Variables** + +Configure logging behavior using environment variables: + +- `LOG_LEVEL`: Overall logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) +- `CONSOLE_LOG_LEVEL`: Separate level for console output +- `CONSOLE_LOGGING_ENABLED`: Enable/disable console logging (true/false) +- `LOG_FILE_ENABLED`: Enable/disable file logging (true/false) +- `LOG_DIR`: Directory for log files (default: ./logs) +- `LOG_MAX_BYTES`: Maximum log file size before rotation (default: 10485760 = 10MB) +- `LOG_BACKUP_COUNT`: Number of backup log files to keep (default: 5) + +**Example:** + +```bash +export LOG_LEVEL=DEBUG +export LOG_DIR=/home/user/logs +python wifi_auto_login.py +``` + +### **Log Rotation** + +The application automatically rotates log files when they reach the maximum size (default 10MB). It keeps up to 5 backup files, ensuring logs don't consume excessive disk space while maintaining historical data. + +Log files are stored in the configured log directory with the name `wifi_auto_auth.log`, and rotated files are named `wifi_auto_auth.log.1`, `wifi_auto_auth.log.2`, etc. + +### **For step-by-step setup instructions, please refer to [setup.md](https://github.com/01bps/WiFi-Auto-Auth/blob/main/setup.md)** ## **Security Notes** - Credentials are securely stored in an SQLite database within your home directory. diff --git a/requirements.txt b/requirements.txt index 64851aa..5f2396a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -python>=3.6 requests>=2.25.1 -sqlite3 +fastapi>=0.104.0 +uvicorn>=0.24.0 +jinja2>=3.1.0 +python-multipart>=0.0.6 diff --git a/setup.md b/setup.md index 63947d1..108ab91 100644 --- a/setup.md +++ b/setup.md @@ -7,6 +7,7 @@ Run the following command to install the requirements: ```bash pip install -r requirements.txt ``` + ## 2. Find Your Network's Login URL and Payload ``` 2.1 Connect to your WiFi network manually. @@ -15,6 +16,7 @@ pip install -r requirements.txt 2.4 Find the POST request URL inside the Network tab(should look like http://192.168.x.x:8090/login.xml). 2.5 Copy the form data parameters (like username, password, a, etc.). ``` + ## 3. Edit ```wifi_auto_login.py``` file Modify the ```def wifi_login()``` function to match your payload parameters. i.e: @@ -96,3 +98,28 @@ python wifi_auto_login.py We have succesfully setup the script now the wifi or LAN will get connected **automatically on system startup**! +## **Command-Line Interface (CLI) Usage** + +This script now includes a command-line interface for easier interaction. Here are the available options: + +| Command | Description | +| ---------------------- | ----------------------------------------------------------- | +| `python wifi_auto_login.py --login` | Performs a login attempt. This is the default action. | +| `python wifi_auto_login.py --view-logs` | Shows the 5 most recent login attempts. | +| `python wifi_auto_login.py --view-logs 10` | Shows the specified number of recent login attempts. | +| `python wifi_auto_login.py --setup` | Launches an interactive wizard to guide you through setup. | +| `python wifi_auto_login.py --test` | Tests the connection to the login server without logging in. | +| `python wifi_auto_login.py --clear-logs` | Deletes all stored login logs from the database. | +| `python wifi_auto_login.py --help` | Displays the help menu with all available commands. | + +### **Examples** + +**To perform a login:** +```bash +python wifi_auto_login.py --login +``` + +**To view the last 3 login attempts:** +```bash +python wifi_auto_login.py --view-logs 3 +``` diff --git a/static/dashboard.css b/static/dashboard.css new file mode 100644 index 0000000..8198eca --- /dev/null +++ b/static/dashboard.css @@ -0,0 +1,325 @@ +/* WiFi Auto Auth Dashboard - Custom Styles */ + +:root { + --primary-color: #667eea; + --secondary-color: #764ba2; + --success-color: #28a745; + --danger-color: #dc3545; + --warning-color: #ffc107; + --info-color: #17a2b8; + --light-color: #f8f9fa; + --dark-color: #343a40; + + --gradient-primary: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); + --gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%); + --gradient-danger: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); + --gradient-info: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + + --border-radius: 10px; + --box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + --transition: all 0.3s ease; +} + +/* Global Styles */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + line-height: 1.6; +} + +/* Navigation Enhancements */ +.navbar-brand { + font-weight: 700; + font-size: 1.5rem; +} + +.navbar-brand i { + margin-right: 0.5rem; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } +} + +/* Card Enhancements */ +.card { + border: none; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + transition: var(--transition); + overflow: hidden; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +.card-header { + background: var(--gradient-primary); + color: white; + border-bottom: none; + padding: 1rem 1.5rem; +} + +.card-header h5 { + margin: 0; + font-weight: 600; +} + +.card-body { + padding: 1.5rem; +} + +/* Statistics Cards */ +.card.bg-primary { + background: var(--gradient-primary) !important; + border: none; +} + +.card.bg-success { + background: var(--gradient-success) !important; + border: none; +} + +.card.bg-danger { + background: var(--gradient-danger) !important; + border: none; +} + +.card.bg-info { + background: var(--gradient-info) !important; + border: none; +} + +.card .fs-1 { + opacity: 0.8; + transition: var(--transition); +} + +.card:hover .fs-1 { + opacity: 1; + transform: scale(1.1); +} + +/* Form Enhancements */ +.form-control, .form-select { + border-radius: var(--border-radius); + border: 2px solid #e9ecef; + transition: var(--transition); + padding: 0.75rem 1rem; +} + +.form-control:focus, .form-select:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.form-label { + font-weight: 600; + color: var(--dark-color); + margin-bottom: 0.5rem; +} + +/* Button Enhancements */ +.btn { + border-radius: var(--border-radius); + font-weight: 600; + padding: 0.5rem 1.5rem; + transition: var(--transition); + border: none; +} + +.btn-primary { + background: var(--gradient-primary); + border: none; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); +} + +.btn-secondary:hover { + transform: translateY(-2px); +} + +.btn-outline-light:hover { + transform: translateY(-2px); +} + +/* Table Enhancements */ +.table { + border-radius: var(--border-radius); + overflow: hidden; + margin-bottom: 0; +} + +.table thead th { + border-bottom: none; + font-weight: 700; + text-transform: uppercase; + font-size: 0.85rem; + letter-spacing: 0.5px; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.02); +} + +.table-hover tbody tr:hover { + background-color: rgba(102, 126, 234, 0.05); + transform: scale(1.002); +} + +/* Badge Enhancements */ +.badge { + border-radius: 20px; + padding: 0.5em 0.8em; + font-weight: 600; + font-size: 0.75rem; +} + +.badge.bg-success { + background: var(--gradient-success) !important; +} + +.badge.bg-danger { + background: var(--gradient-danger) !important; +} + +.badge.bg-primary { + background: var(--gradient-primary) !important; +} + +/* Alert Enhancements */ +.alert { + border: none; + border-radius: var(--border-radius); + padding: 1rem 1.5rem; +} + +.alert-info { + background: linear-gradient(135deg, rgba(23, 162, 184, 0.1) 0%, rgba(23, 162, 184, 0.05) 100%); + color: #0c5460; + border-left: 4px solid var(--info-color); +} + +/* Chart Container Enhancements */ +canvas { + max-height: 400px !important; +} + +.card .card-body canvas { + background: rgba(255, 255, 255, 0.8); + border-radius: var(--border-radius); + padding: 1rem; +} + +/* Code Styling */ +code { + background: rgba(102, 126, 234, 0.1); + color: var(--primary-color); + padding: 0.2em 0.4em; + border-radius: 4px; + font-size: 0.85em; + font-weight: 600; +} + +/* Responsive Enhancements */ +@media (max-width: 768px) { + .card-body { + padding: 1rem; + } + + .table-responsive { + border-radius: var(--border-radius); + } + + .row.g-3 > * { + margin-bottom: 1rem; + } + + .btn { + width: 100%; + margin-bottom: 0.5rem; + } + + .d-flex.justify-content-between { + flex-direction: column; + align-items: flex-start !important; + } + + .navbar-nav.ms-auto { + margin-top: 1rem; + } +} + +/* Loading Animation */ +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Utility Classes */ +.text-gradient { + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 700; +} + +.glass-effect { + backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.shadow-custom { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +/* Dark Theme Support (Optional) */ +@media (prefers-color-scheme: dark) { + :root { + --light-color: #1a1a1a; + --dark-color: #f8f9fa; + } + + body { + background-color: #1a1a1a; + color: #f8f9fa; + } + + .card { + background-color: #2d2d2d; + color: #f8f9fa; + } + + .table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); + } + + .form-control, .form-select { + background-color: #2d2d2d; + border-color: #404040; + color: #f8f9fa; + } + + .form-control:focus, .form-select:focus { + background-color: #2d2d2d; + color: #f8f9fa; + } +} \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..6eb39b8 --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,453 @@ + + + + + + WiFi Auto Auth Dashboard + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+

{{ stats.total_attempts }}

+

Total Attempts

+
+
+ +
+
+
+
+
+ +
+
+
+
+
+

{{ stats.successful_attempts }}

+

Successful

+
+
+ +
+
+
+
+
+ +
+
+
+
+
+

{{ stats.failed_attempts }}

+

Failed

+
+
+ +
+
+
+
+
+ +
+
+
+
+
+

{{ stats.success_rate|round(1) }}%

+

Success Rate

+
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+ Filters +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+ +
+
+
+
+ Login Attempts Over Time +
+
+
+ +
+
+
+ + +
+
+
+
+ Success Rate Distribution +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+ Recent Login Attempts +
+
{{ recent_attempts|length }} results
+
+
+
+ + + + + + + + + + + + {% for attempt in recent_attempts %} + + + + + + + + {% endfor %} + +
TimestampUsernameSession IDStatusMessage
+ {{ attempt.timestamp }} + + {{ attempt.username }} + + {{ attempt.a }} + + {% if attempt.response_status == "200" %} + + {{ attempt.response_status }} + + {% else %} + + {{ attempt.response_status }} + + {% endif %} + + {{ attempt.response_message }} +
+
+
+
+
+
+ + {% if stats.last_attempt %} +
+
+
+ + Last Activity: {{ stats.last_attempt }} +
+
+
+ {% endif %} +
+ + + + + + + + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..b8c5622 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,143 @@ + + + + + + WiFi Auto Auth Dashboard - Login + + + + + + + + + +
+
+
+ + +
+ + WiFi Auto Auth Dashboard v1.0.0 + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/wifi_auto_login.py b/wifi_auto_login.py index c2a4153..0539d61 100644 --- a/wifi_auto_login.py +++ b/wifi_auto_login.py @@ -2,110 +2,613 @@ import requests import datetime import re +import argparse +import json +import os -# Database setup + +# --- CONFIGURATION --- +CONFIG_PATH = "config.json" + +def load_config(): + """Load configuration file and return config dict""" + if not os.path.exists(CONFIG_PATH): + raise FileNotFoundError( + "Missing config.json. Please copy config.example.json to config.json and fill in your details." + ) + + with open(CONFIG_PATH, "r") as f: + config = json.load(f) + + return config + +# Initialize logging first +from config.logging_config import setup_logging_from_env, get_logger +setup_logging_from_env() +logger = get_logger(__name__) + +# Import network utilities for multi-network support +try: + from network_utils import NetworkProfileManager, get_current_ssid + MULTI_NETWORK_SUPPORT = True + logger.info("Multi-network support enabled") +except ImportError as e: + logger.warning(f"Multi-network support disabled: {e}") + MULTI_NETWORK_SUPPORT = False + +# Global variables - will be loaded when needed +URL = None +USERNAME = None +PASSWORD = None +PRODUCT_TYPE = None + +# --- DATABASE SETUP --- DB_NAME = "wifi_log.db" def setup_database(): """Create the database and table if they do not exist.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() - cursor.execute(""" - CREATE TABLE IF NOT EXISTS login_attempts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp TEXT, - username TEXT, - password TEXT, - a TEXT, - response_status TEXT, - response_message TEXT - ) - """) + + # Check if the table exists and get its schema + cursor.execute("PRAGMA table_info(login_attempts)") + columns = [row[1] for row in cursor.fetchall()] + + if not columns: + # Create new table with network support + cursor.execute(""" + CREATE TABLE login_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + network_name TEXT, + network_ssid TEXT, + username TEXT, + password TEXT, + a TEXT, + response_status TEXT, + response_message TEXT + ) + """) + logger.info("Created new login_attempts table with network support") + else: + # Check if we need to add network columns to existing table + if 'network_name' not in columns: + cursor.execute("ALTER TABLE login_attempts ADD COLUMN network_name TEXT") + logger.info("Added network_name column to existing table") + + if 'network_ssid' not in columns: + cursor.execute("ALTER TABLE login_attempts ADD COLUMN network_ssid TEXT") + logger.info("Added network_ssid column to existing table") conn.commit() conn.close() -def log_attempt(username, password, a, response_status, response_message): +def log_attempt(username, password, a, response_status, response_message, network_name=None, network_ssid=None): """Log each login attempt in the database.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute(""" - INSERT INTO login_attempts (timestamp, username, password, a, response_status, response_message) - VALUES (?, ?, ?, ?, ?, ?) - """, (datetime.datetime.now(), username, "******", a, response_status, response_message)) + INSERT INTO login_attempts (timestamp, network_name, network_ssid, username, password, a, response_status, response_message) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, (datetime.datetime.now(), network_name, network_ssid, username, "******", a, response_status, response_message)) conn.commit() conn.close() +# --- HELPER FUNCTIONS --- def extract_message(response_text): """Extracts the meaningful message from the XML response.""" match = re.search(r"", response_text) return match.group(1) if match else "Unknown response" -def wifi_login(): +# --- MAIN WIFI LOGIN FUNCTION --- +def wifi_login(network_name=None): """Perform the WiFi login request and log the result.""" - url = "POST url from the inspect element" # Change Required - username = "username" - password = "password" - a_value = str(int(datetime.datetime.now().timestamp())) # Generate dynamic 'a' value, you may refer to the screenshots in the setup.md file + network_profile_name = "legacy" + network_ssid = "Unknown" + + try: + if MULTI_NETWORK_SUPPORT: + # Use multi-network configuration + manager = NetworkProfileManager() + network_profile_name, network_config = manager.get_network_profile(network_name, auto_detect=True) + network_ssid = network_config.get("ssid", "Unknown") + + URL = network_config["wifi_url"] + USERNAME = network_config["username"] + PASSWORD = network_config["password"] + PRODUCT_TYPE = network_config.get("product_type", "0") + + print(f"\n🌐 Using Network Profile: {network_profile_name}") + print(f"šŸ“” Network SSID: {network_ssid}") + print(f"šŸ”— Login URL: {URL}") + else: + # Fallback to legacy single network configuration + config = load_config() + URL = config["wifi_url"] + USERNAME = config["username"] + PASSWORD = config["password"] + PRODUCT_TYPE = config.get("product_type", "0") + network_ssid = config.get("ssid", "Unknown") + print(f"\n🌐 Using Legacy Configuration") + + except Exception as e: + logger.error(f"Configuration error: {e}") + print(f"āŒ Configuration Error: {e}") + return + + a_value = str(int(datetime.datetime.now().timestamp())) # Generate dynamic 'a' value payload = { "mode": "191", - "username": username, - "password": password, + "username": USERNAME, + "password": PASSWORD, "a": a_value, - "producttype": "0" + "producttype": PRODUCT_TYPE } try: - response = requests.post(url, data=payload) + response = requests.post(URL, data=payload) response_status = response.status_code response_message = extract_message(response.text) print(f"\nšŸ“Œ Login Attempt") print(f"Time: {datetime.datetime.now()}") - print(f"Username: {username}") + print(f"Username: {USERNAME}") print(f"Session ID (a): {a_value}") print(f"Status: {response_status}") print(f"Message: {response_message}") print("-" * 80) - # Log the attempt in SQLite - log_attempt(username, password, a_value, response_status, response_message) + # Log the attempt in SQLite with network information + log_attempt(USERNAME, PASSWORD, a_value, response_status, response_message, + network_profile_name, network_ssid) except requests.exceptions.RequestException as e: print(f"āŒ Error: {e}") - log_attempt(username, password, a_value, "FAILED", str(e)) + log_attempt(USERNAME, PASSWORD, a_value, "FAILED", str(e), + network_profile_name, network_ssid) -def view_logs(limit=5): +# --- VIEW LOGIN LOGS --- +def view_logs(limit=5, network_filter=None): """Display login logs in a readable format.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() - cursor.execute(""" - SELECT timestamp, username, a, response_status, response_message - FROM login_attempts - ORDER BY timestamp DESC - LIMIT ? - """, (limit,)) + + # Check if table has new network columns + cursor.execute("PRAGMA table_info(login_attempts)") + columns = [row[1] for row in cursor.fetchall()] + has_network_columns = 'network_name' in columns and 'network_ssid' in columns + + if has_network_columns: + base_query = """ + SELECT timestamp, network_name, network_ssid, username, a, response_status, response_message + FROM login_attempts + """ + if network_filter: + query = base_query + "WHERE network_name = ? ORDER BY timestamp DESC LIMIT ?" + cursor.execute(query, (network_filter, limit)) + else: + query = base_query + "ORDER BY timestamp DESC LIMIT ?" + cursor.execute(query, (limit,)) + else: + # Legacy table structure + cursor.execute(""" + SELECT timestamp, username, a, response_status, response_message + FROM login_attempts + ORDER BY timestamp DESC + LIMIT ? + """, (limit,)) logs = cursor.fetchall() conn.close() if not logs: - print("No login attempts found.") + filter_msg = f" for network '{network_filter}'" if network_filter else "" + logger.info(f"No login attempts found in database{filter_msg}") return - print("\nšŸ“Œ Recent Login Attempts") - print("=" * 80) + filter_msg = f" for network '{network_filter}'" if network_filter else "" + logger.info(f"Recent login attempts retrieved from database{filter_msg}") + logger.info("=" * 80) for log in logs: - timestamp, username, a, status, message = log - print(f"Time: {timestamp}") - print(f"Username: {username}") - print(f"Session ID (a): {a}") - print(f"Status: {status}") - print(f"Message: {message}") - print("-" * 80) + if has_network_columns: + timestamp, network_name, network_ssid, username, a, status, message = log + logger.info(f"Time: {timestamp}") + logger.info(f"Network: {network_name} ({network_ssid})") + logger.info(f"Username: {username}") + logger.info(f"Session ID (a): {a}") + logger.info(f"Status: {status}") + logger.info(f"Message: {message}") + else: + timestamp, username, a, status, message = log + logger.info(f"Time: {timestamp}") + logger.info(f"Username: {username}") + logger.info(f"Session ID (a): {a}") + logger.info(f"Status: {status}") + logger.info(f"Message: {message}") + logger.info("-" * 80) + +def parse_arguments(): + """Parse command line arguments for logging configuration.""" + parser = argparse.ArgumentParser(description='WiFi Auto Login with Professional Logging') + + # Logging configuration arguments + parser.add_argument( + '--log-level', + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], + help='Set the logging level (default: INFO)' + ) + parser.add_argument( + '--log-file', + action='store_true', + default=True, + help='Enable file logging (default: enabled)' + ) + parser.add_argument( + '--no-log-file', + action='store_false', + dest='log_file', + help='Disable file logging' + ) + parser.add_argument( + '--log-dir', + default='./logs', + help='Directory for log files (default: ./logs)' + ) + parser.add_argument( + '--console-logging', + action='store_true', + default=True, + help='Enable console logging (default: enabled)' + ) + parser.add_argument( + '--no-console-logging', + action='store_false', + dest='console_logging', + help='Disable console logging' + ) + + # Application arguments + parser.add_argument( + '--view-logs', + type=int, + metavar='N', + help='View last N login attempts instead of performing login' + ) + parser.add_argument( + '--max-attempts', + type=int, + default=5, + help='Maximum number of login attempts to show when viewing logs (default: 5)' + ) + + return parser.parse_args() + + +def clear_logs(): + """Deletes all logs from the login_attempts table.""" + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("DELETE FROM login_attempts") + conn.commit() + conn.close() + print("āœ… All logs have been cleared.") + +def test_connection(network_name=None): + """Tests if the login URL is reachable.""" + try: + if MULTI_NETWORK_SUPPORT: + manager = NetworkProfileManager() + network_profile_name, network_config = manager.get_network_profile(network_name, auto_detect=True) + url = network_config["wifi_url"] + print(f"šŸ”— Testing connection for network '{network_profile_name}' to {url}...") + else: + config = load_config() + url = config["wifi_url"] + print(f"šŸ”— Testing connection to {url}...") + + response = requests.head(url, timeout=5) # Use HEAD to be efficient + if response.status_code == 200: + print(f"āœ… Connection successful! The server responded with status {response.status_code}.") + else: + print(f"āš ļø Connection successful, but the server responded with status {response.status_code}.") + except requests.exceptions.RequestException as e: + print(f"āŒ Connection failed: {e}") + except Exception as e: + print(f"āŒ Configuration error: {e}") + +def run_setup_wizard(): + """Guides the user through an interactive setup process.""" + print("--- WiFi-Auto-Auth Interactive Setup ---") + print("This wizard will help you configure the script.") + print() + + # Ask about multi-network setup + print("Choose configuration type:") + print("1. Single Network (Legacy)") + print("2. Multi-Network (Recommended)") + + choice = input("\nEnter your choice (1 or 2): ").strip() + + if choice == "2": + setup_multi_network() + else: + setup_single_network() + +def setup_single_network(): + """Set up single network configuration.""" + print("\n--- Single Network Setup ---") + + url = input("1. Enter the POST request URL from your network's login page: ") + username = input("2. Enter your login username: ") + password = input("3. Enter your login password: ") + product_type = input("4. Enter product type (optional, press Enter for default): ") or "0" + + config = { + "wifi_url": url, + "username": username, + "password": password, + "product_type": product_type, + "dashboard": { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123" + } + } + + save_config(config) + print("\nāœ… Single network setup complete!") + +def setup_multi_network(): + """Set up multi-network configuration.""" + print("\n--- Multi-Network Setup ---") + + networks = {} + default_network = None + + while True: + print(f"\n--- Network Profile #{len(networks) + 1} ---") + + profile_name = input("Enter network profile name (e.g., home, work, school): ").strip() + if not profile_name: + break + + ssid = input(f"Enter SSID for {profile_name}: ").strip() + url = input(f"Enter login URL for {profile_name}: ").strip() + username = input(f"Enter username for {profile_name}: ").strip() + password = input(f"Enter password for {profile_name}: ").strip() + product_type = input(f"Enter product type for {profile_name} (optional): ").strip() or "0" + description = input(f"Enter description for {profile_name} (optional): ").strip() or f"{profile_name.title()} network" + + networks[profile_name] = { + "ssid": ssid, + "wifi_url": url, + "username": username, + "password": password, + "product_type": product_type, + "description": description + } + + if not default_network: + default_network = profile_name + + add_more = input(f"\nAdd another network profile? (y/N): ").strip().lower() + if add_more not in ['y', 'yes']: + break + + if not networks: + print("No networks configured. Falling back to single network setup.") + setup_single_network() + return + + # Ask for default network + if len(networks) > 1: + print(f"\nAvailable networks: {', '.join(networks.keys())}") + default_choice = input(f"Enter default network (press Enter for '{default_network}'): ").strip() + if default_choice in networks: + default_network = default_choice + + config = { + "default_network": default_network, + "networks": networks, + "dashboard": { + "host": "127.0.0.1", + "port": 8000, + "username": "admin", + "password": "admin123" + } + } + + save_config(config) + print(f"\nāœ… Multi-network setup complete! {len(networks)} networks configured.") + print(f"Default network: {default_network}") + +def save_config(config): + """Save configuration to config.json file.""" + try: + with open(CONFIG_PATH, 'w') as f: + json.dump(config, f, indent=2) + print(f"\nšŸ’¾ Configuration saved to {CONFIG_PATH}") + except Exception as e: + print(f"\nāŒ Error saving configuration: {e}") + +def list_networks(): + """List all configured network profiles.""" + if not MULTI_NETWORK_SUPPORT: + print("āŒ Multi-network support not available. Using legacy configuration.") + return + + try: + manager = NetworkProfileManager() + networks = manager.list_networks() + current_ssid = get_current_ssid() + + print("\nšŸ“¶ Configured Network Profiles:") + print("=" * 60) + + for name, config in networks.items(): + ssid = config.get("ssid", "Unknown") + url = config.get("wifi_url", "Unknown") + description = config.get("description", "No description") + + # Mark current network + current_marker = " šŸ“ CURRENT" if ssid == current_ssid else "" + + print(f"🌐 Network: {name}{current_marker}") + print(f" SSID: {ssid}") + print(f" URL: {url}") + print(f" Description: {description}") + print("-" * 60) + + if current_ssid: + print(f"\nšŸ“” Currently connected to: {current_ssid}") + else: + print("\nšŸ“” No WiFi connection detected") + + except Exception as e: + print(f"āŒ Error listing networks: {e}") + +def detect_network(): + """Detect current network and show matching profile.""" + if not MULTI_NETWORK_SUPPORT: + print("āŒ Multi-network support not available.") + return + + try: + current_ssid = get_current_ssid() + + if not current_ssid: + print("šŸ“” No WiFi connection detected") + return + + print(f"šŸ“” Current SSID: {current_ssid}") + + manager = NetworkProfileManager() + try: + network_name, network_config = manager.get_network_profile(auto_detect=True) + print(f"āœ… Found matching profile: {network_name}") + print(f" Description: {network_config.get('description', 'No description')}") + print(f" Login URL: {network_config.get('wifi_url', 'Unknown')}") + except Exception: + print("āš ļø No matching network profile found") + print("šŸ’” You may need to add this network to your config.json") + + except Exception as e: + print(f"āŒ Error detecting network: {e}") + +def start_dashboard(): + """Start the web dashboard server.""" + try: + import subprocess + import sys + print("šŸš€ Starting WiFi Auto Auth Dashboard...") + print("šŸ“Š Dashboard will be available at: http://127.0.0.1:8000") + print("šŸ”‘ Default credentials: admin / admin123") + print("šŸ›‘ Press Ctrl+C to stop the server") + + # Start the dashboard server + subprocess.run([sys.executable, "dashboard.py"], check=True) + except subprocess.CalledProcessError as e: + print(f"āŒ Error starting dashboard: {e}") + except KeyboardInterrupt: + print("\nšŸ›‘ Dashboard server stopped.") + except ImportError: + print("āŒ Dashboard dependencies not installed. Please run: pip install -r requirements.txt") + except FileNotFoundError: + print("āŒ Dashboard server not found. Please ensure dashboard.py exists.") if __name__ == "__main__": - setup_database() # Ensure the database is set up - wifi_login() # Attempt login - view_logs(5) # Show last 5 login attempts + parser = argparse.ArgumentParser( + description="A script to automatically log into captive portal WiFi networks with multi-network support." + ) + + parser.add_argument( + '--login', + action='store_true', + help="Perform a login attempt." + ) + parser.add_argument( + '--network', '-n', + type=str, + metavar='PROFILE', + help="Specify which network profile to use (overrides auto-detection)." + ) + parser.add_argument( + '--view-logs', + nargs='?', + const=5, + type=int, + metavar='N', + help="View the last N login attempts. Defaults to 5 if no number is provided." + ) + parser.add_argument( + '--network-filter', + type=str, + metavar='PROFILE', + help="Filter logs by network profile name." + ) + parser.add_argument( + '--list-networks', + action='store_true', + help="List all configured network profiles." + ) + parser.add_argument( + '--detect-network', + action='store_true', + help="Detect current network and show matching profile." + ) + parser.add_argument( + '--setup', + action='store_true', + help="Run the interactive setup wizard to configure credentials." + ) + parser.add_argument( + '--test', + action='store_true', + help="Test the connection to the login URL without logging in." + ) + parser.add_argument( + '--clear-logs', + action='store_true', + help="Clear all login logs from the database." + ) + parser.add_argument( + '--dashboard', + action='store_true', + help="Start the web dashboard server for monitoring login attempts." + ) + + args = parser.parse_args() + + # For operations that don't need config, handle them first + if args.setup: + run_setup_wizard() + elif args.dashboard: + start_dashboard() + elif args.list_networks: + list_networks() + elif args.detect_network: + detect_network() + else: + # For operations that need database/config + try: + setup_database() # Ensure the database is always set up + + if args.login: + wifi_login(args.network) + elif args.view_logs is not None: + view_logs(args.view_logs, args.network_filter) + elif args.test: + test_connection(args.network) + elif args.clear_logs: + clear_logs() + else: + print("No arguments provided. Performing default login action.") + wifi_login(args.network) + view_logs(1, args.network_filter) + + except FileNotFoundError as e: + print(f"āŒ Configuration Error: {e}") + print("šŸ’” Run 'python wifi_auto_login.py --setup' to configure the application.") + print("šŸ“– Or copy config.example.json to config.json and edit it manually.")