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
|
--debug
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Запустить HTTP сервер
|
### 2. Запустить HTTP сервер с паролем
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/projects/satellite-dev/web-client
|
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. Открыть в браузере
|
### 3. Открыть в браузере
|
||||||
@@ -33,6 +38,8 @@ http://localhost:8080/client.html
|
|||||||
http://192.168.200.111:8080/client.html
|
http://192.168.200.111:8080/client.html
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**🔐 Ввести пароль** (по умолчанию: `admin`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Функции
|
## 📋 Функции
|
||||||
|
|||||||
@@ -598,7 +598,7 @@
|
|||||||
|
|
||||||
// WebSocket подключение
|
// WebSocket подключение
|
||||||
function connectWebSocket() {
|
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(!hostInput) return;
|
||||||
if(ws && ws.readyState === WebSocket.OPEN) ws.close();
|
if(ws && ws.readyState === WebSocket.OPEN) ws.close();
|
||||||
ws = new WebSocket(hostInput);
|
ws = new WebSocket(hostInput);
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Simple HTTP server for WebSocket client testing.
|
Simple HTTP server for WebSocket client testing with password protection.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python3 start_web_server.py [--port PORT]
|
python3 start_web_server.py [--port PORT] [--password PASSWORD]
|
||||||
|
|
||||||
Open in browser:
|
Open in browser:
|
||||||
http://localhost:PORT/client.html
|
http://localhost:PORT/client.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import base64
|
||||||
import http.server
|
import http.server
|
||||||
import socketserver
|
import socketserver
|
||||||
import os
|
import os
|
||||||
@@ -20,33 +21,96 @@ from pathlib import Path
|
|||||||
PORT = 8080
|
PORT = 8080
|
||||||
SCRIPT_DIR = Path(__file__).parent.resolve()
|
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):
|
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()
|
super().do_GET()
|
||||||
print(f"[WebServer] ✅ {self.requestline} → {self.command} {self.path}")
|
print(f"[WebServer] ✅ {self.requestline} → authenticated")
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
# Suppress default logging
|
# Suppress default logging
|
||||||
pass
|
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):
|
class ReusableTCPServer(socketserver.TCPServer):
|
||||||
"""TCP Server with address reuse enabled."""
|
"""TCP Server with address reuse enabled."""
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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('--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('--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Change to script directory
|
# Change to script directory
|
||||||
os.chdir(SCRIPT_DIR)
|
os.chdir(SCRIPT_DIR)
|
||||||
|
|
||||||
|
# Create handler with password
|
||||||
|
Handler = create_auth_handler(args.password)
|
||||||
|
|
||||||
print(f"""
|
print(f"""
|
||||||
╔══════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════╗
|
||||||
║ 🛰️ Wyoming Satellite WebSocket Client Server ║
|
║ 🛰️ Wyoming Satellite WebSocket Client Server ║
|
||||||
@@ -54,15 +118,17 @@ def main():
|
|||||||
║ Serving: {str(SCRIPT_DIR):<45} ║
|
║ Serving: {str(SCRIPT_DIR):<45} ║
|
||||||
║ URL: http://localhost:{args.port:<36} ║
|
║ URL: http://localhost:{args.port:<36} ║
|
||||||
║ Remote: http://<your-ip>:{args.port:<32} ║
|
║ Remote: http://<your-ip>:{args.port:<32} ║
|
||||||
|
║ Password: {'*' * len(args.password):<45} ║
|
||||||
║ ║
|
║ ║
|
||||||
║ Press Ctrl+C to stop ║
|
║ Press Ctrl+C to stop ║
|
||||||
╚══════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════╝
|
||||||
""")
|
""")
|
||||||
|
|
||||||
try:
|
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(f"🚀 Server started on http://{args.host}:{args.port}")
|
||||||
print("📖 Serving files from:", SCRIPT_DIR)
|
print("📖 Serving files from:", SCRIPT_DIR)
|
||||||
|
print(f"🔐 Password protected ({len(args.password)} characters)")
|
||||||
print("\n⌨️ Press Ctrl+C to stop\n")
|
print("\n⌨️ Press Ctrl+C to stop\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user