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:
@@ -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`)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Функции
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user