Reverse proxy fix
This commit is contained in:
parent
d9aa8303a7
commit
65bae58cfe
2 changed files with 213 additions and 91 deletions
|
|
@ -49,16 +49,66 @@ If you can access SoulSync directly from the Docker host machine:
|
||||||
- Set OAuth redirect URIs to localhost (as above)
|
- Set OAuth redirect URIs to localhost (as above)
|
||||||
- No SSH tunnel needed
|
- No SSH tunnel needed
|
||||||
|
|
||||||
## 🔧 For Advanced Users: Reverse Proxy
|
## 🔧 Reverse Proxy Setup (Caddy, Nginx, Traefik)
|
||||||
|
|
||||||
Set up nginx/traefik with proper SSL certificates for true HTTPS support. See community guides for Docker reverse proxy setups.
|
If you're running SoulSync behind a reverse proxy with HTTPS, you can use the **main app port (8008)** for OAuth callbacks instead of the standalone port 8888. This is the recommended approach for reverse proxy setups.
|
||||||
|
|
||||||
|
### Step 1: Set your redirect URI to your proxy URL
|
||||||
|
|
||||||
|
**In SoulSync Settings:**
|
||||||
|
- Set Spotify redirect URI to: `https://yourdomain.com/callback`
|
||||||
|
|
||||||
|
**In your Spotify Developer Dashboard:**
|
||||||
|
- Add the same redirect URI: `https://yourdomain.com/callback`
|
||||||
|
|
||||||
|
### Step 2: Ensure your reverse proxy forwards to port 8008
|
||||||
|
|
||||||
|
Your reverse proxy should forward traffic to SoulSync's main port (8008). The `/callback` path is handled by the main Flask app — no need to expose port 8888.
|
||||||
|
|
||||||
|
**Example Caddy config:**
|
||||||
|
```
|
||||||
|
soulsync.yourdomain.com {
|
||||||
|
reverse_proxy localhost:8008
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Nginx config:**
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name soulsync.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8008;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Authenticate normally
|
||||||
|
|
||||||
|
Click "Connect Spotify" in SoulSync settings. After authorizing on Spotify, you'll be redirected back through your reverse proxy automatically.
|
||||||
|
|
||||||
|
### Important notes for reverse proxy users
|
||||||
|
|
||||||
|
- The redirect URI **must use HTTPS** for non-localhost domains (Spotify requirement)
|
||||||
|
- The redirect URI in SoulSync settings **must exactly match** the one in your Spotify Dashboard
|
||||||
|
- Port 8888 is only needed for direct/local access — you do **not** need to expose it through your proxy
|
||||||
|
- Make sure your proxy passes query parameters through unmodified (most do by default)
|
||||||
|
|
||||||
## 📝 Summary
|
## 📝 Summary
|
||||||
|
|
||||||
The core issue is that **Spotify requires HTTPS for non-localhost** OAuth redirects. The SSH tunnel makes remote devices appear as localhost to bypass this requirement.
|
The core issue is that **Spotify requires HTTPS for non-localhost** OAuth redirects.
|
||||||
|
|
||||||
|
**Choose your approach:**
|
||||||
|
- **Reverse proxy with HTTPS**: Set redirect URI to `https://yourdomain.com/callback` (recommended for production)
|
||||||
|
- **SSH tunnel**: Makes remote devices appear as localhost — set redirect URI to `http://127.0.0.1:8888/callback`
|
||||||
|
- **Local access**: No special config needed — default `http://127.0.0.1:8888/callback` works
|
||||||
|
|
||||||
**Key points:**
|
**Key points:**
|
||||||
- ✅ Always use `127.0.0.1` in OAuth redirect URIs
|
- ✅ Reverse proxy users: use `https://yourdomain.com/callback` on port 8008
|
||||||
- ✅ Use SSH tunnel when accessing from different device
|
- ✅ SSH tunnel users: use `http://127.0.0.1:8888/callback` on port 8888
|
||||||
- ✅ Keep tunnel open during authentication
|
- ✅ Redirect URI must match exactly in SoulSync settings AND Spotify Dashboard
|
||||||
- ✅ Works with existing Docker setup - no changes needed
|
- ✅ Query parameters must be preserved through the redirect chain
|
||||||
240
web_server.py
240
web_server.py
|
|
@ -3334,72 +3334,111 @@ def auth_spotify():
|
||||||
if temp_spotify_client.sp and temp_spotify_client.sp.auth_manager:
|
if temp_spotify_client.sp and temp_spotify_client.sp.auth_manager:
|
||||||
# Get the authorization URL
|
# Get the authorization URL
|
||||||
auth_url = temp_spotify_client.sp.auth_manager.get_authorize_url()
|
auth_url = temp_spotify_client.sp.auth_manager.get_authorize_url()
|
||||||
|
configured_uri = config_manager.get_spotify_config().get('redirect_uri', 'http://127.0.0.1:8888/callback')
|
||||||
|
print(f"🎵 Spotify auth initiated — redirect_uri: {configured_uri}")
|
||||||
add_activity_item("🔐", "Spotify Auth Started", "Please complete OAuth in browser", "Now")
|
add_activity_item("🔐", "Spotify Auth Started", "Please complete OAuth in browser", "Now")
|
||||||
|
|
||||||
# Detect if accessing remotely
|
# Detect if accessing remotely
|
||||||
host = request.host.split(':')[0]
|
host = request.host.split(':')[0]
|
||||||
is_remote = host not in ['127.0.0.1', 'localhost']
|
is_remote = host not in ['127.0.0.1', 'localhost']
|
||||||
is_docker = os.path.exists('/.dockerenv')
|
is_docker = os.path.exists('/.dockerenv')
|
||||||
|
|
||||||
# If in Docker and accessing via 127.0.0.1, recommend localhost
|
# If in Docker and accessing via 127.0.0.1, recommend localhost
|
||||||
if is_docker and host == '127.0.0.1':
|
if is_docker and host == '127.0.0.1':
|
||||||
host = 'localhost'
|
host = 'localhost'
|
||||||
|
|
||||||
|
# Check if the redirect_uri uses port 8008 (main app) vs 8888 (standalone)
|
||||||
|
uses_main_port = ':8008' in configured_uri or ':8888' not in configured_uri
|
||||||
|
|
||||||
if is_remote or is_docker:
|
if is_remote or is_docker:
|
||||||
# Show instructions for remote/docker access
|
# Show instructions for remote/docker access
|
||||||
page_title = "🔐 Spotify Authentication (Remote/Docker)"
|
if uses_main_port:
|
||||||
step_1_text = "Click the link below to authenticate with Spotify"
|
# redirect_uri already points to port 8008 or a custom domain —
|
||||||
|
# callback will come through the main Flask app, no manual steps needed
|
||||||
return f'''
|
return f'''
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {{ font-family: Arial, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }}
|
body {{ font-family: Arial, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }}
|
||||||
code {{ background: #f0f0f0; padding: 10px; display: block; margin: 10px 0; }}
|
code {{ background: #f0f0f0; padding: 4px 8px; border-radius: 3px; }}
|
||||||
.highlight {{ background: #e8f5e9; }}
|
.info {{ background: #e3f2fd; border-left: 4px solid #2196F3; padding: 12px 16px; margin: 16px 0; border-radius: 4px; }}
|
||||||
.copy-btn {{
|
</style>
|
||||||
background: #1DB954;
|
</head>
|
||||||
color: white;
|
<body>
|
||||||
border: none;
|
<h1>🔐 Spotify Authentication</h1>
|
||||||
padding: 8px 16px;
|
<p>Click the link below to authenticate with Spotify:</p>
|
||||||
cursor: pointer;
|
<p><a href="{auth_url}" target="_blank" style="font-size: 18px; color: #1DB954;">Authenticate with Spotify</a></p>
|
||||||
border-radius: 4px;
|
<div class="info">
|
||||||
font-size: 14px;
|
<strong>Redirect URI:</strong> <code>{configured_uri}</code><br>
|
||||||
margin-left: 10px;
|
<small>After authorizing, Spotify will redirect back automatically. Make sure this URL matches your Spotify Dashboard redirect URI.</small>
|
||||||
}}
|
</div>
|
||||||
.copy-btn:hover {{ background: #1ed760; }}
|
<p>After authentication completes, you can close this window and return to SoulSync.</p>
|
||||||
.copied {{ background: #4CAF50 !important; }}
|
</body>
|
||||||
</style>
|
</html>
|
||||||
</head>
|
'''
|
||||||
<body>
|
else:
|
||||||
<h1>{page_title}</h1>
|
# redirect_uri still points to port 8888 — show manual steps AND suggest switching
|
||||||
<p><strong>Step 1:</strong> {step_1_text}</p>
|
return f'''
|
||||||
<p><a href="{auth_url}" target="_blank" style="font-size: 18px; color: #1DB954;">{auth_url}</a></p>
|
<html>
|
||||||
<hr>
|
<head>
|
||||||
<p><strong>Step 2:</strong> After authorizing, you'll see a blank page. The URL will look like:</p>
|
<style>
|
||||||
<code>http://127.0.0.1:8888/callback?code=...</code>
|
body {{ font-family: Arial, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }}
|
||||||
<p><strong>Step 3:</strong> Change <code style="display: inline; background: #ffe6e6; padding: 2px 6px;">127.0.0.1</code> to <code style="display: inline; background: #e8f5e9; padding: 2px 6px;">{host}</code> and press Enter:
|
code {{ background: #f0f0f0; padding: 10px; display: block; margin: 10px 0; }}
|
||||||
<button class="copy-btn" onclick="copyIP()">Copy IP</button>
|
.highlight {{ background: #e8f5e9; }}
|
||||||
</p>
|
.warning {{ background: #fff3e0; border-left: 4px solid #ff9800; padding: 12px 16px; margin: 16px 0; border-radius: 4px; }}
|
||||||
<code class="highlight">http://{host}:8888/callback?code=...</code>
|
.copy-btn {{
|
||||||
<p>Authentication will then complete!</p>
|
background: #1DB954;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}}
|
||||||
|
.copy-btn:hover {{ background: #1ed760; }}
|
||||||
|
.copied {{ background: #4CAF50 !important; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🔐 Spotify Authentication (Remote/Docker)</h1>
|
||||||
|
|
||||||
<script>
|
<div class="warning">
|
||||||
function copyIP() {{
|
<strong>Using a reverse proxy?</strong> Your redirect URI is set to <code style="display:inline; padding: 2px 6px;">{configured_uri}</code>
|
||||||
navigator.clipboard.writeText('{host}').then(() => {{
|
which uses port 8888. If you're behind a reverse proxy (Caddy, Nginx, Traefik), change the
|
||||||
const btn = event.target;
|
redirect URI in SoulSync settings to use your proxy URL on the main port instead, e.g.:<br>
|
||||||
btn.textContent = '✓ Copied!';
|
<code style="display:inline; padding: 2px 6px; background: #e8f5e9;">https://{host}/callback</code><br>
|
||||||
btn.classList.add('copied');
|
Then update the same URI in your <a href="https://developer.spotify.com/dashboard" target="_blank">Spotify Dashboard</a>.
|
||||||
setTimeout(() => {{
|
This avoids the need for manual URL editing below.
|
||||||
btn.textContent = 'Copy IP';
|
</div>
|
||||||
btn.classList.remove('copied');
|
|
||||||
}}, 2000);
|
<p><strong>Step 1:</strong> Click the link below to authenticate with Spotify</p>
|
||||||
}});
|
<p><a href="{auth_url}" target="_blank" style="font-size: 18px; color: #1DB954;">{auth_url}</a></p>
|
||||||
}}
|
<hr>
|
||||||
</script>
|
<p><strong>Step 2:</strong> After authorizing, you'll see a blank page. The URL will look like:</p>
|
||||||
</body>
|
<code>http://127.0.0.1:8888/callback?code=...</code>
|
||||||
</html>
|
<p><strong>Step 3:</strong> Change <code style="display: inline; background: #ffe6e6; padding: 2px 6px;">127.0.0.1</code> to <code style="display: inline; background: #e8f5e9; padding: 2px 6px;">{host}</code> and press Enter:
|
||||||
'''
|
<button class="copy-btn" onclick="copyIP()">Copy IP</button>
|
||||||
|
</p>
|
||||||
|
<code class="highlight">http://{host}:8888/callback?code=...</code>
|
||||||
|
<p>Authentication will then complete!</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyIP() {{
|
||||||
|
navigator.clipboard.writeText('{host}').then(() => {{
|
||||||
|
const btn = event.target;
|
||||||
|
btn.textContent = 'Copied!';
|
||||||
|
btn.classList.add('copied');
|
||||||
|
setTimeout(() => {{
|
||||||
|
btn.textContent = 'Copy IP';
|
||||||
|
btn.classList.remove('copied');
|
||||||
|
}}, 2000);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
else:
|
else:
|
||||||
# Local access - simple message
|
# Local access - simple message
|
||||||
return f'<h1>🔐 Spotify Authentication</h1><p>Click the link below to authenticate:</p><p><a href="{auth_url}" target="_blank">{auth_url}</a></p><p>After authentication, return to the app.</p>'
|
return f'<h1>🔐 Spotify Authentication</h1><p>Click the link below to authenticate:</p><p><a href="{auth_url}" target="_blank">{auth_url}</a></p><p>After authentication, return to the app.</p>'
|
||||||
|
|
@ -3530,18 +3569,28 @@ def auth_tidal():
|
||||||
def spotify_callback():
|
def spotify_callback():
|
||||||
"""
|
"""
|
||||||
Handles Spotify OAuth callback via the main Flask app (port 8008).
|
Handles Spotify OAuth callback via the main Flask app (port 8008).
|
||||||
This allows reverse proxy users to use a redirect_uri pointing at the main app.
|
This is the recommended callback for reverse proxy / Docker setups.
|
||||||
The dedicated HTTPServer on port 8888 continues to work for direct access.
|
The dedicated HTTPServer on port 8888 continues to work for direct/local access.
|
||||||
"""
|
"""
|
||||||
global spotify_client
|
global spotify_client
|
||||||
auth_code = request.args.get('code')
|
auth_code = request.args.get('code')
|
||||||
|
|
||||||
if not auth_code:
|
if not auth_code:
|
||||||
error = request.args.get('error', 'Unknown error')
|
error = request.args.get('error')
|
||||||
if 'error' not in request.args:
|
if error:
|
||||||
# Spurious request (e.g., healthcheck) - ignore silently
|
print(f"🔴 Spotify OAuth error on port 8008: Spotify returned error: {error}")
|
||||||
return '', 204
|
add_activity_item("❌", "Spotify Auth Failed", f"Spotify returned error: {error}", "Now")
|
||||||
return f"<h1>Spotify Authentication Failed</h1><p>OAuth error: {error}</p>", 400
|
return f"<h1>Spotify Authentication Failed</h1><p>Spotify returned error: {error}</p>", 400
|
||||||
|
|
||||||
|
# No code AND no error — check if query params were stripped
|
||||||
|
if request.args:
|
||||||
|
print(f"🔴 Spotify callback on port 8008 received unexpected params: {dict(request.args)}")
|
||||||
|
else:
|
||||||
|
# Completely empty — likely a healthcheck or spurious request
|
||||||
|
pass
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
print(f"🎵 Spotify callback received on port 8008 with authorization code")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from core.spotify_client import SpotifyClient
|
from core.spotify_client import SpotifyClient
|
||||||
|
|
@ -3549,10 +3598,13 @@ def spotify_callback():
|
||||||
from config.settings import config_manager
|
from config.settings import config_manager
|
||||||
|
|
||||||
config = config_manager.get_spotify_config()
|
config = config_manager.get_spotify_config()
|
||||||
|
configured_uri = config.get('redirect_uri', "http://127.0.0.1:8888/callback")
|
||||||
|
print(f"🎵 Using redirect_uri for token exchange: {configured_uri}")
|
||||||
|
|
||||||
auth_manager = SpotifyOAuth(
|
auth_manager = SpotifyOAuth(
|
||||||
client_id=config['client_id'],
|
client_id=config['client_id'],
|
||||||
client_secret=config['client_secret'],
|
client_secret=config['client_secret'],
|
||||||
redirect_uri=config.get('redirect_uri', "http://127.0.0.1:8888/callback"),
|
redirect_uri=configured_uri,
|
||||||
scope="user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email",
|
scope="user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email",
|
||||||
cache_path='config/.spotify_cache'
|
cache_path='config/.spotify_cache'
|
||||||
)
|
)
|
||||||
|
|
@ -3571,7 +3623,7 @@ def spotify_callback():
|
||||||
else:
|
else:
|
||||||
raise Exception("Failed to exchange authorization code for access token")
|
raise Exception("Failed to exchange authorization code for access token")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"🔴 Spotify OAuth callback error: {e}")
|
print(f"🔴 Spotify OAuth callback error on port 8008: {e}")
|
||||||
add_activity_item("❌", "Spotify Auth Failed", f"Token processing failed: {str(e)}", "Now")
|
add_activity_item("❌", "Spotify Auth Failed", f"Token processing failed: {str(e)}", "Now")
|
||||||
return f"<h1>Spotify Authentication Failed</h1><p>{str(e)}</p>", 400
|
return f"<h1>Spotify Authentication Failed</h1><p>{str(e)}</p>", 400
|
||||||
|
|
||||||
|
|
@ -27806,38 +27858,49 @@ def start_oauth_callback_servers():
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
# Spotify callback server
|
# Spotify callback server (port 8888 — for direct/local access only)
|
||||||
class SpotifyCallbackHandler(BaseHTTPRequestHandler):
|
class SpotifyCallbackHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
print(f"🎵 Spotify callback received: {self.path}")
|
|
||||||
parsed_url = urllib.parse.urlparse(self.path)
|
parsed_url = urllib.parse.urlparse(self.path)
|
||||||
|
|
||||||
|
# Only process requests to /callback — ignore everything else
|
||||||
|
if parsed_url.path != '/callback':
|
||||||
|
self.send_response(404)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'Not found. Spotify callback is at /callback')
|
||||||
|
return
|
||||||
|
|
||||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||||
|
print(f"🎵 Spotify callback received on port 8888: {self.path}")
|
||||||
|
|
||||||
if 'code' in query_params:
|
if 'code' in query_params:
|
||||||
auth_code = query_params['code'][0]
|
auth_code = query_params['code'][0]
|
||||||
print(f"🎵 Received Spotify authorization code: {auth_code[:10]}...")
|
print(f"🎵 Received Spotify authorization code: {auth_code[:10]}...")
|
||||||
|
|
||||||
# Manually trigger the token exchange using spotipy's auth manager
|
# Manually trigger the token exchange using spotipy's auth manager
|
||||||
try:
|
try:
|
||||||
from core.spotify_client import SpotifyClient
|
from core.spotify_client import SpotifyClient
|
||||||
from spotipy.oauth2 import SpotifyOAuth
|
from spotipy.oauth2 import SpotifyOAuth
|
||||||
from config.settings import config_manager
|
from config.settings import config_manager
|
||||||
|
|
||||||
# Get Spotify config
|
# Get Spotify config
|
||||||
config = config_manager.get_spotify_config()
|
config = config_manager.get_spotify_config()
|
||||||
|
configured_uri = config.get('redirect_uri', "http://127.0.0.1:8888/callback")
|
||||||
|
print(f"🎵 Using redirect_uri for token exchange: {configured_uri}")
|
||||||
|
|
||||||
# Create auth manager and exchange code for token
|
# Create auth manager and exchange code for token
|
||||||
auth_manager = SpotifyOAuth(
|
auth_manager = SpotifyOAuth(
|
||||||
client_id=config['client_id'],
|
client_id=config['client_id'],
|
||||||
client_secret=config['client_secret'],
|
client_secret=config['client_secret'],
|
||||||
redirect_uri=config.get('redirect_uri', "http://127.0.0.1:8888/callback"),
|
redirect_uri=configured_uri,
|
||||||
scope="user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email",
|
scope="user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email",
|
||||||
cache_path='config/.spotify_cache'
|
cache_path='config/.spotify_cache'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract the authorization code and exchange it for tokens
|
# Extract the authorization code and exchange it for tokens
|
||||||
token_info = auth_manager.get_access_token(auth_code, as_dict=True)
|
token_info = auth_manager.get_access_token(auth_code, as_dict=True)
|
||||||
|
|
||||||
if token_info:
|
if token_info:
|
||||||
# Reinitialize the global client with new tokens
|
# Reinitialize the global client with new tokens
|
||||||
global spotify_client
|
global spotify_client
|
||||||
|
|
@ -27862,22 +27925,31 @@ def start_oauth_callback_servers():
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(f'<h1>Spotify Authentication Failed</h1><p>{str(e)}</p>'.encode())
|
self.wfile.write(f'<h1>Spotify Authentication Failed</h1><p>{str(e)}</p>'.encode())
|
||||||
else:
|
elif 'error' in query_params:
|
||||||
error = query_params.get('error', ['Unknown error'])[0]
|
error = query_params['error'][0]
|
||||||
print(f"🔴 Spotify OAuth error: {error}")
|
print(f"🔴 Spotify OAuth error returned by Spotify: {error}")
|
||||||
print(f"🔴 Full Spotify callback URL: {self.path}")
|
print(f"🔴 Full callback URL: {self.path}")
|
||||||
print(f"🔴 All query params: {query_params}")
|
add_activity_item("❌", "Spotify Auth Failed", f"Spotify returned error: {error}", "Now")
|
||||||
|
|
||||||
# Only show error toast if it's not just a spurious request
|
|
||||||
if 'error' in query_params:
|
|
||||||
add_activity_item("❌", "Spotify Auth Failed", f"OAuth error: {error}", "Now")
|
|
||||||
else:
|
|
||||||
print("🔴 Spurious Spotify callback without code or error - ignoring")
|
|
||||||
|
|
||||||
self.send_response(400)
|
self.send_response(400)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(f'<h1>Spotify Authentication Failed</h1><p>{error}</p>'.encode())
|
self.wfile.write(f'<h1>Spotify Authentication Failed</h1><p>Spotify returned error: {error}</p>'.encode())
|
||||||
|
else:
|
||||||
|
# No code AND no error — callback was hit without OAuth params
|
||||||
|
print(f"🔴 Spotify callback received without OAuth parameters (no code or error)")
|
||||||
|
print(f"🔴 Path: {self.path} | Query params: {query_params}")
|
||||||
|
print(f"🔴 This usually means the redirect lost its query parameters (reverse proxy issue)")
|
||||||
|
self.send_response(400)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
msg = (
|
||||||
|
'<h1>Spotify Authentication Failed</h1>'
|
||||||
|
'<p>The callback was received but no authorization code or error was included.</p>'
|
||||||
|
'<p><strong>If you are using a reverse proxy:</strong> Your proxy may be stripping query parameters '
|
||||||
|
'during the redirect. Try setting your Spotify redirect URI to use port 8008 instead '
|
||||||
|
'(e.g. <code>https://yourdomain.com/callback</code>) — the main app handles callbacks too.</p>'
|
||||||
|
)
|
||||||
|
self.wfile.write(msg.encode())
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
pass # Suppress server logs
|
pass # Suppress server logs
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue