diff --git a/web-client/README.md b/web-client/README.md index 96f19db..de16311 100644 --- a/web-client/README.md +++ b/web-client/README.md @@ -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`) + --- ## 📋 Функции diff --git a/web-client/client.html b/web-client/client.html index cb4c5e6..bc713e3 100644 --- a/web-client/client.html +++ b/web-client/client.html @@ -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); diff --git a/web-client/start_server.py b/web-client/start_server.py index 17646bf..4f1b71e 100755 --- a/web-client/start_server.py +++ b/web-client/start_server.py @@ -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''' + + 401 Unauthorized + +

🔐 Wyoming Satellite — Authentication Required

+

Please enter the password to access the Web UI.

+ + + ''') 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://:{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: