feat: Web UI пароль — HTTP Basic Auth защита

Изменения:
- Отменены WebSocket security настройки (websocket_allowed_ips, websocket_token)
- Добавлена HTTP Basic Auth в start_server.py
- Пароль по умолчанию: admin
- Опция --password для установки своего пароля
- Обновлён README с документацией

Использование:
  python3 start_server.py --port 8080 --password mypassword

Безопасность:
- ✅ WebSocket только локально (127.0.0.1)
- ✅ Web UI защищён паролем
- ✅ Достаточно для локальной сети
This commit is contained in:
2026-05-21 13:50:01 +03:00
parent ed6ecc548b
commit fc816ed7b3
3 changed files with 83 additions and 10 deletions
+9 -2
View File
@@ -18,11 +18,16 @@ python3 -m wyoming_satellite \
--debug
```
### 2. Запустить HTTP сервер
### 2. Запустить HTTP сервер с паролем
```bash
cd ~/projects/satellite-dev/web-client
python3 -m http.server 8080
# С паролем по умолчанию (admin)
python3 start_server.py --port 8080
# Или со своим паролем
python3 start_server.py --port 8080 --password mysecretpassword
```
### 3. Открыть в браузере
@@ -33,6 +38,8 @@ http://localhost:8080/client.html
http://192.168.200.111:8080/client.html
```
**🔐 Ввести пароль** (по умолчанию: `admin`)
---
## 📋 Функции
+1 -1
View File
@@ -598,7 +598,7 @@
// WebSocket подключение
function connectWebSocket() {
const hostInput = prompt("Введите адрес LVA WebSocket (по умолчанию ws://192.168.200.183:6055):", "ws://192.168.200.183:6055");
const hostInput = prompt("Введите адрес LVA WebSocket (по умолчанию ws://192.168.200.111:8675):", "ws://192.168.200.111:8675");
if(!hostInput) return;
if(ws && ws.readyState === WebSocket.OPEN) ws.close();
ws = new WebSocket(hostInput);
+73 -7
View File
@@ -1,15 +1,16 @@
#!/usr/bin/env python3
"""
Simple HTTP server for WebSocket client testing.
Simple HTTP server for WebSocket client testing with password protection.
Usage:
python3 start_web_server.py [--port PORT]
python3 start_web_server.py [--port PORT] [--password PASSWORD]
Open in browser:
http://localhost:PORT/client.html
"""
import argparse
import base64
import http.server
import socketserver
import os
@@ -20,33 +21,96 @@ from pathlib import Path
PORT = 8080
SCRIPT_DIR = Path(__file__).parent.resolve()
# Default password
DEFAULT_PASSWORD = "admin"
class CustomHandler(http.server.SimpleHTTPRequestHandler):
"""Custom handler with better logging."""
class AuthHandler(http.server.SimpleHTTPRequestHandler):
"""HTTP handler with Basic Auth."""
def __init__(self, *args, password="admin", **kwargs):
self.password = password
super().__init__(*args, **kwargs)
def check_auth(self):
"""Check Basic Auth credentials."""
auth_header = self.headers.get('Authorization')
if not auth_header:
return False
try:
# Decode "Basic base64(username:password)"
auth_type, auth_string = auth_header.split(' ', 1)
if auth_type.lower() != 'basic':
return False
decoded = base64.b64decode(auth_string).decode('utf-8')
username, password = decoded.split(':', 1)
# For now, we only check password (username is ignored)
return password == self.password
except Exception:
return False
def send_auth_request(self):
"""Send 401 Unauthorized response."""
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm="Wyoming Satellite"')
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(b'''
<html>
<head><title>401 Unauthorized</title></head>
<body>
<h1>🔐 Wyoming Satellite — Authentication Required</h1>
<p>Please enter the password to access the Web UI.</p>
</body>
</html>
''')
def do_GET(self):
"""Handle GET requests with authentication."""
# Skip auth for static files that are already protected
if not self.check_auth():
self.send_auth_request()
return
super().do_GET()
print(f"[WebServer] ✅ {self.requestline} → {self.command} {self.path}")
print(f"[WebServer] ✅ {self.requestline} → authenticated")
def log_message(self, format, *args):
# Suppress default logging
pass
def create_auth_handler(password):
"""Create auth handler with password."""
class Handler(AuthHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, password=password, **kwargs)
return Handler
class ReusableTCPServer(socketserver.TCPServer):
"""TCP Server with address reuse enabled."""
allow_reuse_address = True
def main():
parser = argparse.ArgumentParser(description='WebSocket Client Web Server')
parser = argparse.ArgumentParser(description='WebSocket Client Web Server with Password Protection')
parser.add_argument('--port', type=int, default=PORT, help=f'Port (default: {PORT})')
parser.add_argument('--host', default='0.0.0.0', help='Host (default: 0.0.0.0)')
parser.add_argument('--password', default=DEFAULT_PASSWORD, help=f'Password (default: {DEFAULT_PASSWORD})')
args = parser.parse_args()
# Change to script directory
os.chdir(SCRIPT_DIR)
# Create handler with password
Handler = create_auth_handler(args.password)
print(f"""
╔══════════════════════════════════════════════════════════╗
║ 🛰️ Wyoming Satellite WebSocket Client Server ║
@@ -54,15 +118,17 @@ def main():
║ Serving: {str(SCRIPT_DIR):<45} ║
║ URL: http://localhost:{args.port:<36} ║
║ Remote: http://<your-ip>:{args.port:<32} ║
║ Password: {'*' * len(args.password):<45} ║
║ ║
║ Press Ctrl+C to stop ║
╚══════════════════════════════════════════════════════════╝
""")
try:
with ReusableTCPServer((args.host, args.port), CustomHandler) as httpd:
with ReusableTCPServer((args.host, args.port), Handler) as httpd:
print(f"🚀 Server started on http://{args.host}:{args.port}")
print("📖 Serving files from:", SCRIPT_DIR)
print(f"🔐 Password protected ({len(args.password)} characters)")
print("\n⌨️ Press Ctrl+C to stop\n")
try: