"""Development HTTPS server for testing production settings locally. Generates a self-signed certificate (if not present) and runs the application via Waitress wrapped in a TLS socket. This simulates the production environment where Nginx handles TLS termination. Usage: python run_https.py The server will listen on https://localhost:8443 Accept the self-signed certificate warning in your browser to proceed. """ import os import socket import subprocess import ssl import sys from waitress.server import create_server from app.app import create_app CERT_FILE = 'certs/localhost.pem' KEY_FILE = 'certs/localhost-key.pem' def generate_self_signed_cert(): """Generate a self-signed certificate for local HTTPS testing. Uses OpenSSL to create a key and certificate valid for 365 days. Skips generation if certificate files already exist. """ if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE): print('[OK] Self-signed certificate already exists.') return os.makedirs('certs', exist_ok=True) print('[INFO] Generating self-signed certificate for localhost...') try: subprocess.run([ 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-keyout', KEY_FILE, '-out', CERT_FILE, '-days', '365', '-nodes', '-subj', '/CN=localhost' ], check=True, capture_output=True) print('[OK] Certificate generated: certs/localhost.pem') except FileNotFoundError: print('[ERROR] OpenSSL not found. Install OpenSSL or use:') print(' winget install OpenSSL.OpenSSL') print(' OR download from https://slproweb.com/products/Win32OpenSSL.html') sys.exit(1) except subprocess.CalledProcessError as e: print(f'[ERROR] Certificate generation failed: {e}') sys.exit(1) def main(): """Start the HTTPS development server.""" port = int(os.getenv('HTTPS_PORT', 8443)) host = os.getenv('HOST', '127.0.0.1') # Ensure certificate exists generate_self_signed_cert() # Create the Flask application app = create_app() # Create SSL context context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(CERT_FILE, KEY_FILE) # Create a TCP socket, wrap it with TLS, then pass to Waitress sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) sock.listen(5) # Wrap the socket with TLS ssl_sock = context.wrap_socket(sock, server_side=True) # ===================================================================== # WSGI Middleware: Tell Flask the connection is HTTPS # # Waitress doesn't know the underlying socket is TLS, so Flask sees # wsgi.url_scheme = "http". Without this middleware, force_https() # would cause an infinite redirect loop (ERR_TOO_MANY_REDIRECTS). # ===================================================================== class ForceHTTPSMiddleware: """WSGI middleware that sets url_scheme to 'https'. Since we're wrapping the TCP socket with SSL before passing it to Waitress, Flask's request.is_secure returns False because Waitress reports wsgi.url_scheme='http'. This middleware fixes that so Flask correctly identifies the connection as HTTPS. """ def __init__(self, wsgi_app): self.wsgi_app = wsgi_app def __call__(self, environ, start_response): environ['wsgi.url_scheme'] = 'https' environ['HTTPS'] = 'on' return self.wsgi_app(environ, start_response) # Wrap the Flask app with the HTTPS middleware app.wsgi_app = ForceHTTPSMiddleware(app.wsgi_app) print(f'\n╔══════════════════════════════════════════════════════╗') print(f'║ TEAM TRYOUTS - Development HTTPS Server ║') print(f'╠══════════════════════════════════════════════════════╣') print(f'║ URL: https://{host}:{port} ║') print(f'║ Cert: self-signed (accept browser warning) ║') print(f'║ Press Ctrl+C to stop ║') print(f'╚══════════════════════════════════════════════════════╝\n') # Create Waitress server with the SSL-wrapped socket server = create_server( app, sockets=[ssl_sock], threads=4, channel_timeout=30, ) server.run() if __name__ == '__main__': main()