11 KiB
Player HTTPS Connection Issues - Analysis & Solutions
Problem Summary
Players can successfully connect to the DigiServer when using HTTP on port 80, but connections are refused/blocked when the server is on HTTPS.
Root Causes Identified
1. Missing CORS Headers on API Endpoints ⚠️ CRITICAL
Issue: The app imports Flask-Cors (requirements.txt line 31) but never initializes it in the application.
Location:
- app/extensions.py - CORS not initialized
- app/app.py - No CORS initialization in create_app()
Impact: Players making cross-origin requests (from device IP to server domain/IP) get CORS errors and connections are refused at the browser/HTTP client level.
Affected Endpoints:
/api/playlists- GET (primary endpoint for player playlist fetch)/api/auth/player- POST (authentication)/api/auth/verify- POST (token verification)/api/player-feedback- POST (player status updates)- All endpoints prefixed with
/api/*
2. SSL Certificate Trust Issues ⚠️ CRITICAL for Device-to-Server Communication
Issue: Players are likely receiving self-signed certificates from nginx.
Location:
- docker-compose.yml - Nginx container with SSL
- nginx.conf - SSL certificate paths point to self-signed certs
- data/nginx-ssl/ - Contains
cert.pemandkey.pem
Details:
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
Impact:
- Players using standard HTTP clients (Python
requests, JavaScriptfetch, Kivy's HTTP module) will reject self-signed certificates by default - This causes connection refusal with SSL certificate verification errors
- The player might be using hardcoded certificate verification (certificate pinning)
3. No Certificate Validation Bypass in Player API ⚠️ HIGH
Issue: The API endpoints don't provide a way for players to bypass SSL verification or explicitly trust the certificate.
What's Missing:
# Players likely need:
# - Endpoint to fetch and validate server certificate
# - API response with certificate fingerprint
# - Configuration to disable cert verification for self-signed setups
# - Or: Generate proper certificates with Let's Encrypt
4. Potential HTTP/HTTPS Redirect Issues
Location: nginx.conf
Issue: HTTP requests to "/" are redirected to HTTPS:
location / {
return 301 https://$host$request_uri; # Forces HTTPS
}
Impact:
- If player tries to connect via HTTP, it gets a 301 redirect to HTTPS
- If the player doesn't follow redirects or isn't configured for HTTPS, it fails
- The redirect URL depends on the
$hostvariable, which might not match player's expectations
5. ProxyFix Middleware May Lose Protocol Info
Location: app/app.py
Issue:
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
Detail: If nginx doesn't properly set X-Forwarded-Proto: https, the app might generate HTTP URLs in responses instead of HTTPS.
Config Check:
proxy_set_header X-Forwarded-Proto $scheme; # Should be in nginx.conf
✓ This is present in nginx.conf, so ProxyFix should work correctly.
6. Security Headers Might Block Requests
Location: nginx.conf
Issue:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
Impact: Overly restrictive CSP could block embedded resource loading from players.
7. Missing Player Certificate Configuration ⚠️ CRITICAL
Issue: Players (especially embedded devices) often have:
- Limited certificate stores
- Self-signed cert validation disabled by default in some frameworks
- No built-in mechanism to trust new certificates
What's Not Addressed:
- No endpoint to retrieve server certificate for device installation
- No configuration for certificate thumbprint verification
- No setup guide for device SSL configuration
Solutions by Priority
🔴 PRIORITY 1: Enable CORS for API Endpoints
Fix: Initialize Flask-CORS in the application.
File: app/extensions.py
from flask_cors import CORS
# Add after other extensions
File: app/app.py - In create_app() function
# After initializing extensions, add:
CORS(app, resources={
r"/api/*": {
"origins": ["*"], # Or specific origins: ["http://...", "https://..."]
"methods": ["GET", "POST", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"max_age": 3600
}
})
🔴 PRIORITY 2: Fix SSL Certificate Issues
Option A: Use Let's Encrypt (Recommended for production)
# Generate proper certificates with certbot
certbot certonly --standalone -d yourdomain.com --email your@email.com
Option B: Generate Self-Signed Certs with Longer Validity
# Current certs might be expired or have trust issues
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 \
-subj "/CN=digiserver/O=Organization/C=US"
Option C: Allow Players to Trust Self-Signed Cert
Add endpoint to serve certificate:
# In app/blueprints/api.py
@api_bp.route('/certificate', methods=['GET'])
def get_server_certificate():
"""Return server certificate for player installation."""
try:
with open('/etc/nginx/ssl/cert.pem', 'r') as f:
cert_content = f.read()
return jsonify({
'certificate': cert_content,
'certificate_format': 'PEM'
}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
🟡 PRIORITY 3: Update Configuration
File: app/config.py
Change:
# Line 28 - Currently set to False for development
SESSION_COOKIE_SECURE = False # Set to True in production with HTTPS
To:
class ProductionConfig(Config):
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_SAMESITE = 'Lax'
🟡 PRIORITY 4: Fix nginx Configuration
Verify in nginx.conf:
# Line 86-95: Ensure these headers are present
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Add this for player connections:
proxy_set_header X-Forwarded-Port 443;
Consider relaxing CORS headers at nginx level:
# Add to location / block in HTTPS server:
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
🟡 PRIORITY 5: Update Player Connection Code
If you control the player code, add:
# Python example for player connecting to server
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
class PlayerClient:
def __init__(self, server_url, hostname, quickconnect_code, verify_ssl=False):
self.server_url = server_url
self.session = requests.Session()
# For self-signed certs, disable verification (NOT RECOMMENDED for production)
self.session.verify = verify_ssl
# Or: Trust specific certificate
# self.session.verify = '/path/to/server-cert.pem'
self.hostname = hostname
self.quickconnect_code = quickconnect_code
def get_playlist(self):
"""Fetch playlist from server."""
try:
response = self.session.get(
f"{self.server_url}/api/playlists",
params={
'hostname': self.hostname,
'quickconnect_code': self.quickconnect_code
},
headers={'Authorization': f'Bearer {self.auth_code}'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.SSLError as e:
print(f"SSL Error: {e}")
# Retry without SSL verification if configured
if not self.session.verify:
raise
# Fall back to unverified connection
self.session.verify = False
return self.get_playlist()
Verification Steps
Test 1: Check CORS Headers
# This should include Access-Control-Allow-Origin
curl -v https://192.168.0.121/api/health -H "Origin: *"
Test 2: Check SSL Certificate
# View certificate details
openssl s_client -connect 192.168.0.121:443 -showcerts
# Check expiration
openssl x509 -in /srv/digiserver-v2/data/nginx-ssl/cert.pem -text -noout | grep -i valid
Test 3: Test API Endpoint
# Try fetching playlist (should fail with SSL error or CORS error initially)
curl -k https://192.168.0.121/api/playlists \
-G --data-urlencode "hostname=test" \
--data-urlencode "quickconnect_code=test123" \
-H "Origin: http://192.168.0.121"
Test 4: Player Connection Simulation
# From player device
import requests
session = requests.Session()
session.verify = False # Temp for testing
response = session.get(
'https://192.168.0.121/api/playlists',
params={'hostname': 'player1', 'quickconnect_code': 'abc123'}
)
print(response.json())
Summary of Changes Needed
| Issue | Fix | Priority | File |
|---|---|---|---|
| No CORS Headers | Initialize Flask-CORS | 🔴 HIGH | app/extensions.py, app/app.py |
| Self-Signed SSL Cert | Get Let's Encrypt cert or add trust endpoint | 🔴 HIGH | data/nginx-ssl/ |
| Certificate Validation | Add /certificate endpoint | 🟡 MEDIUM | app/blueprints/api.py |
| SESSION_COOKIE_SECURE | Update in ProductionConfig | 🟡 MEDIUM | app/config.py |
| X-Forwarded Headers | Verify nginx.conf | 🟡 MEDIUM | nginx.conf |
| CSP Too Restrictive | Relax CSP for player requests | 🟢 LOW | nginx.conf |
Quick Fix for Immediate Testing
To quickly test if CORS is the issue:
- Enable CORS temporarily:
docker exec digiserver-v2 python -c "
from app import create_app
from flask_cors import CORS
app = create_app('production')
CORS(app)
"
- Test player connection:
curl -k https://192.168.0.121/api/health
- If works, the issue is CORS + SSL certificates
Recommended Next Steps
- ✅ Enable Flask-CORS in the application
- ✅ Generate/obtain proper SSL certificates (Let's Encrypt recommended)
- ✅ Add certificate trust endpoint for devices
- ✅ Update nginx configuration for player device compatibility
- ✅ Create player connection guide documenting HTTPS setup
- ✅ Test with actual player device