feat: add OAuth debug route and template for URL generation verification

This commit is contained in:
Christian Krakau-Louis
2025-06-09 15:13:36 +02:00
parent d71dda4828
commit c6b89781c9
5 changed files with 248 additions and 2 deletions
+18
View File
@@ -0,0 +1,18 @@
# Standard OAuth redirect URIs
SPOTIFY_REDIRECT_URI=http://localhost:5000/auth/spotify/callback
GOOGLE_REDIRECT_URI=http://localhost:5000/users/login/google/callback
AUTHENTIK_REDIRECT_URI=http://localhost:5000/users/login/authentik/callback
DROPBOX_REDIRECT_URI=http://localhost:5000/users/dropbox/callback
# OAuth settings
USE_HTTPS=False # Set to True when behind a reverse proxy with HTTPS offloading
# PREFERRED_URL_SCHEME=https # Optional, set automatically if USE_HTTPS is True
# Static OAuth redirect URIs for production environments
# These override the dynamic URLs generated by url_for() when specified
# STATIC_OAUTH_URLS=True # Uncomment to use static URLs below
# OAUTH_SPOTIFY_AUTH_URL=https://your-domain.com/auth/spotify/callback
# OAUTH_SPOTIFY_LINK_URL=https://your-domain.com/users/spotify-link/callback
# OAUTH_GOOGLE_URL=https://your-domain.com/users/login/google/callback
# OAUTH_AUTHENTIK_URL=https://your-domain.com/users/login/authentik/callback
# OAUTH_DROPBOX_URL=https://your-domain.com/users/dropbox/callback
+2
View File
@@ -320,6 +320,7 @@ def create_app(config=None):
from musicround.routes.deezer_routes import deezer_bp
from musicround.routes.db_admin import db_admin_bp, init_admin
from musicround.routes.auth import auth_bp
from musicround.routes.oauth_debug import oauth_debug_bp
app.register_blueprint(core_bp)
app.register_blueprint(users_bp)
@@ -332,6 +333,7 @@ def create_app(config=None):
app.register_blueprint(deezer_bp)
app.register_blueprint(db_admin_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(oauth_debug_bp)
# Initialize the admin interface
init_admin(app)
+17 -2
View File
@@ -352,6 +352,21 @@ def get_oauth_redirect_uri(endpoint, provider=None):
"""
# Use Flask's url_for which respects PREFERRED_URL_SCHEME
if provider:
return url_for(endpoint, provider=provider, _external=True)
redirect_uri = url_for(endpoint, provider=provider, _external=True)
else:
return url_for(endpoint, _external=True)
redirect_uri = url_for(endpoint, _external=True)
# Log details about the generated URL for debugging
use_https = current_app.config.get('USE_HTTPS', False)
preferred_scheme = current_app.config.get('PREFERRED_URL_SCHEME', 'http')
current_app.logger.debug(
f"OAuth Redirect URI: {redirect_uri} | "
f"Endpoint: {endpoint} | "
f"USE_HTTPS: {use_https} | "
f"PREFERRED_URL_SCHEME: {preferred_scheme} | "
f"Request scheme: {request.scheme if request else 'N/A'} | "
f"X-Forwarded-Proto: {request.headers.get('X-Forwarded-Proto', 'N/A') if request else 'N/A'}"
)
return redirect_uri
+81
View File
@@ -0,0 +1,81 @@
"""
Debug route for OAuth URL generation
"""
from flask import Blueprint, render_template, jsonify, current_app, request, url_for
from flask_login import login_required
from musicround.helpers.auth_helpers import get_oauth_redirect_uri
# Create blueprint
oauth_debug_bp = Blueprint('oauth_debug', __name__)
@oauth_debug_bp.route('/debug/oauth-urls')
@login_required
def debug_oauth_urls():
"""
Debug endpoint to show OAuth URL generation with current configuration
This is useful for verifying proper HTTPS handling when behind a reverse proxy
Formats:
- HTML: Default view with pretty UI
- JSON: Add ?format=json or use Accept: application/json header
"""
# Get config settings
use_https = current_app.config.get('USE_HTTPS', False)
preferred_scheme = current_app.config.get('PREFERRED_URL_SCHEME', 'http')
# Generate all OAuth callback URLs using the helper function
oauth_urls = {
'spotify_auth': get_oauth_redirect_uri('auth.callback'),
'spotify_link': get_oauth_redirect_uri('users.spotify_link_callback'),
'google_login': get_oauth_redirect_uri('users.google_callback'),
'authentik_login': get_oauth_redirect_uri('users.authentik_callback'),
'dropbox_link': get_oauth_redirect_uri('users.dropbox_callback')
}
# Generate the same URLs directly with url_for for comparison
direct_urls = {
'spotify_auth': url_for('auth.callback', _external=True),
'spotify_link': url_for('users.spotify_link_callback', _external=True),
'google_login': url_for('users.google_callback', _external=True),
'authentik_login': url_for('users.authentik_callback', _external=True),
'dropbox_link': url_for('users.dropbox_callback', _external=True)
}
# Get request info
request_info = {
'url': request.url,
'host': request.host,
'scheme': request.scheme,
'headers': {
key: value for key, value in request.headers.items()
if key.lower() in ('x-forwarded-for', 'x-forwarded-proto',
'x-forwarded-host', 'host', 'origin', 'referer')
}
}
# Compile data for both JSON and HTML response
result = {
'config': {
'USE_HTTPS': use_https,
'PREFERRED_URL_SCHEME': preferred_scheme
},
'helper_generated_urls': oauth_urls,
'direct_url_for_urls': direct_urls,
'request_info': request_info
}
current_app.logger.info(f"OAuth Debug URLs generated")
# Check if JSON format is requested
wants_json = (request.args.get('format', '').lower() == 'json' or
request.headers.get('Accept', '').lower().find('application/json') >= 0)
if wants_json:
return jsonify(result)
else:
# Return HTML view
return render_template('oauth_debug.html',
config=result['config'],
helper_generated_urls=result['helper_generated_urls'],
direct_url_for_urls=result['direct_url_for_urls'],
request_info=result['request_info'])
+130
View File
@@ -0,0 +1,130 @@
{% extends 'base.html' %}
{% block title %}OAuth Debug Information{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-4 text-navy-800">OAuth Debug Information</h1>
<div class="mb-6">
<h2 class="text-xl font-semibold mb-2">Configuration</h2>
<div class="bg-white rounded-lg shadow p-4 overflow-x-auto">
<table class="min-w-full">
<tr>
<td class="py-2 px-4 border-b font-medium">USE_HTTPS</td>
<td class="py-2 px-4 border-b">{{ config.USE_HTTPS }}</td>
</tr>
<tr>
<td class="py-2 px-4 border-b font-medium">PREFERRED_URL_SCHEME</td>
<td class="py-2 px-4 border-b">{{ config.PREFERRED_URL_SCHEME }}</td>
</tr>
</table>
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-semibold mb-2">Helper-Generated URLs</h2>
<div class="bg-white rounded-lg shadow p-4 overflow-x-auto">
<table class="min-w-full">
<tr>
<th class="py-2 px-4 border-b text-left">Endpoint</th>
<th class="py-2 px-4 border-b text-left">URL</th>
</tr>
{% for name, url in helper_generated_urls.items() %}
<tr>
<td class="py-2 px-4 border-b font-medium">{{ name }}</td>
<td class="py-2 px-4 border-b break-all">{{ url }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-semibold mb-2">Direct url_for URLs</h2>
<div class="bg-white rounded-lg shadow p-4 overflow-x-auto">
<table class="min-w-full">
<tr>
<th class="py-2 px-4 border-b text-left">Endpoint</th>
<th class="py-2 px-4 border-b text-left">URL</th>
</tr>
{% for name, url in direct_url_for_urls.items() %}
<tr>
<td class="py-2 px-4 border-b font-medium">{{ name }}</td>
<td class="py-2 px-4 border-b break-all">{{ url }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-semibold mb-2">Request Information</h2>
<div class="bg-white rounded-lg shadow p-4 overflow-x-auto">
<table class="min-w-full">
<tr>
<td class="py-2 px-4 border-b font-medium">URL</td>
<td class="py-2 px-4 border-b break-all">{{ request_info.url }}</td>
</tr>
<tr>
<td class="py-2 px-4 border-b font-medium">Host</td>
<td class="py-2 px-4 border-b">{{ request_info.host }}</td>
</tr>
<tr>
<td class="py-2 px-4 border-b font-medium">Scheme</td>
<td class="py-2 px-4 border-b">{{ request_info.scheme }}</td>
</tr>
<tr>
<td class="py-2 px-4 border-b font-medium">Headers</td>
<td class="py-2 px-4 border-b">
<dl>
{% for header, value in request_info.headers.items() %}
<dt class="font-medium">{{ header }}</dt>
<dd class="pl-4 mb-2">{{ value }}</dd>
{% endfor %}
</dl>
</td>
</tr>
</table>
</div>
</div>
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-yellow-700">
This page shows debug information for OAuth redirect URLs. It helps verify that the proper URL scheme (HTTP/HTTPS) is being used.
</p>
<p class="text-sm text-yellow-700 mt-2">
To configure HTTPS, set <code class="bg-yellow-100 px-1 rounded">USE_HTTPS=True</code> in your <code class="bg-yellow-100 px-1 rounded">.env</code> file when running behind a reverse proxy that handles SSL termination.
</p>
</div>
</div>
</div>
<div class="bg-navy-50 border-l-4 border-navy-400 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-navy-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2h-1V9a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-navy-700">
<strong>Tips for debugging:</strong>
</p>
<ul class="list-disc pl-5 mt-1 text-sm text-navy-700">
<li>Check if the X-Forwarded-Proto header is set to "https" by your reverse proxy</li>
<li>Verify that helper-generated URLs match your expected protocol</li>
<li>If running behind Traefik, ensure it's configured to set the proper headers</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}