feat: add reverse proxy support with HTTPS configuration

This commit is contained in:
Christian Krakau-Louis
2025-06-09 15:01:24 +02:00
parent bc4055ded5
commit d71dda4828
7 changed files with 37 additions and 17 deletions
+6
View File
@@ -27,6 +27,12 @@ LASTFM_API_KEY=your-lastfm-api-key
SQLALCHEMY_DATABASE_URI=sqlite:///data/song_data.db SQLALCHEMY_DATABASE_URI=sqlite:///data/song_data.db
SQLALCHEMY_TRACK_MODIFICATIONS=False SQLALCHEMY_TRACK_MODIFICATIONS=False
# HTTPS Configuration (for reverse proxy environments)
# Set to True when running behind a reverse proxy with HTTPS offloading (e.g., Traefik)
USE_HTTPS=False
# Uncomment and set to 'https' for production environments behind HTTPS proxy
# PREFERRED_URL_SCHEME=https
# Spotify API configuration # Spotify API configuration
SPOTIFY_CLIENT_ID=your-spotify-client-id SPOTIFY_CLIENT_ID=your-spotify-client-id
SPOTIFY_CLIENT_SECRET=your-spotify-client-secret SPOTIFY_CLIENT_SECRET=your-spotify-client-secret
+4
View File
@@ -111,6 +111,10 @@ def create_app(config=None):
# Configure the app # Configure the app
app.config.from_object(Config) app.config.from_object(Config)
# Set preferred URL scheme for reverse proxy support
if app.config.get('USE_HTTPS'):
app.config['PREFERRED_URL_SCHEME'] = 'https'
# Explicitly set the database URI to ensure correct path # Explicitly set the database URI to ensure correct path
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
+4
View File
@@ -71,5 +71,9 @@ class Config:
# Automation settings # Automation settings
AUTOMATION_TOKEN = os.getenv("AUTOMATION_TOKEN", "change-this-token-in-production") AUTOMATION_TOKEN = os.getenv("AUTOMATION_TOKEN", "change-this-token-in-production")
# Reverse proxy settings
USE_HTTPS = os.getenv("USE_HTTPS", "False") == "True" # Force HTTPS URL generation
PREFERRED_URL_SCHEME = 'https' if USE_HTTPS else 'http'
+11 -1
View File
@@ -344,4 +344,14 @@ def update_oauth_tokens(user, tokens, auth_provider):
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
current_app.logger.error(f"Error updating {auth_provider} tokens: {str(e)}") current_app.logger.error(f"Error updating {auth_provider} tokens: {str(e)}")
return False return False
def get_oauth_redirect_uri(endpoint, provider=None):
"""
Generate OAuth redirect URI with proper scheme handling for reverse proxy environments
"""
# Use Flask's url_for which respects PREFERRED_URL_SCHEME
if provider:
return url_for(endpoint, provider=provider, _external=True)
else:
return url_for(endpoint, _external=True)
+3 -2
View File
@@ -2,6 +2,7 @@
Helpers for Dropbox API integration Helpers for Dropbox API integration
""" """
from flask import current_app, url_for, redirect, session from flask import current_app, url_for, redirect, session
from musicround.helpers.auth_helpers import get_oauth_redirect_uri
import requests import requests
import json import json
import os import os
@@ -11,7 +12,7 @@ from flask_login import current_user
def get_dropbox_auth_url(): def get_dropbox_auth_url():
"""Get the authorization URL for Dropbox OAuth flow""" """Get the authorization URL for Dropbox OAuth flow"""
app_key = current_app.config.get('DROPBOX_APP_KEY') app_key = current_app.config.get('DROPBOX_APP_KEY')
redirect_uri = url_for('users.dropbox_callback', _external=True) redirect_uri = get_oauth_redirect_uri('users.dropbox_callback')
# Add the required scopes for our application # Add the required scopes for our application
scopes = ["files.content.read", "files.content.write", "sharing.write","account_info.read"] scopes = ["files.content.read", "files.content.write", "sharing.write","account_info.read"]
@@ -23,7 +24,7 @@ def exchange_code_for_token(code):
"""Exchange the authorization code for an access token""" """Exchange the authorization code for an access token"""
app_key = current_app.config.get('DROPBOX_APP_KEY') app_key = current_app.config.get('DROPBOX_APP_KEY')
app_secret = current_app.config.get('DROPBOX_APP_SECRET') app_secret = current_app.config.get('DROPBOX_APP_SECRET')
redirect_uri = url_for('users.dropbox_callback', _external=True) redirect_uri = get_oauth_redirect_uri('users.dropbox_callback')
data = { data = {
'code': code, 'code': code,
+2 -2
View File
@@ -9,7 +9,7 @@ from musicround.models import User, db
from datetime import datetime from datetime import datetime
import requests import requests
import secrets import secrets
from musicround.helpers.auth_helpers import oauth, find_or_create_user, update_oauth_tokens, get_spotify_user_info from musicround.helpers.auth_helpers import oauth, find_or_create_user, update_oauth_tokens, get_spotify_user_info, get_oauth_redirect_uri
# Create blueprint # Create blueprint
auth_bp = Blueprint('auth', __name__) auth_bp = Blueprint('auth', __name__)
@@ -37,7 +37,7 @@ def login_with_spotify():
return redirect(url_for('users.login')) return redirect(url_for('users.login'))
# The redirect URI should point to *this* blueprint's callback # The redirect URI should point to *this* blueprint's callback
redirect_uri = url_for('auth.callback', _external=True) redirect_uri = get_oauth_redirect_uri('auth.callback')
# Ensure 'show_dialog': 'true' is part of authorize_params in auth_helpers.py # Ensure 'show_dialog': 'true' is part of authorize_params in auth_helpers.py
# when registering the Spotify client. # when registering the Spotify client.
+7 -12
View File
@@ -11,7 +11,7 @@ from sqlalchemy.exc import IntegrityError
from musicround.models import db, User, Role, SystemSetting from musicround.models import db, User, Role, SystemSetting
from musicround.helpers.utils import get_available_voices from musicround.helpers.utils import get_available_voices
from musicround.helpers.auth_helpers import oauth, find_or_create_user, update_oauth_tokens, get_google_user_info, get_authentik_user_info, get_spotify_user_info from musicround.helpers.auth_helpers import oauth, find_or_create_user, update_oauth_tokens, get_google_user_info, get_authentik_user_info, get_spotify_user_info, get_oauth_redirect_uri
from musicround.helpers.spotify_helper import get_spotify_token, get_current_user_spotify_token, get_spotify_user_info as spotify_helper_get_user_info from musicround.helpers.spotify_helper import get_spotify_token, get_current_user_spotify_token, get_spotify_user_info as spotify_helper_get_user_info
users_bp = Blueprint('users', __name__, url_prefix='/users') users_bp = Blueprint('users', __name__, url_prefix='/users')
@@ -72,14 +72,12 @@ def admin_required(f):
def google_login(): def google_login():
"""Initiate Google OAuth login flow""" """Initiate Google OAuth login flow"""
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('core.index')) return redirect(url_for('core.index')) # Google login is disabled if client ID is not set
# Google login is disabled if client ID is not set
if not current_app.config.get('GOOGLE_CLIENT_ID'): if not current_app.config.get('GOOGLE_CLIENT_ID'):
flash('Google login is not configured.', 'danger') flash('Google login is not configured.', 'danger')
return redirect(url_for('users.login')) return redirect(url_for('users.login'))
redirect_uri = url_for('users.google_callback', _external=True) redirect_uri = get_oauth_redirect_uri('users.google_callback')
return oauth.google.authorize_redirect(redirect_uri) return oauth.google.authorize_redirect(redirect_uri)
@users_bp.route('/login/google/callback') @users_bp.route('/login/google/callback')
@@ -128,14 +126,12 @@ def google_callback():
def authentik_login(): def authentik_login():
"""Initiate Authentik OAuth login flow""" """Initiate Authentik OAuth login flow"""
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('core.index')) return redirect(url_for('core.index')) # Authentik login is disabled if client ID is not set
# Authentik login is disabled if client ID is not set
if not current_app.config.get('AUTHENTIK_CLIENT_ID'): if not current_app.config.get('AUTHENTIK_CLIENT_ID'):
flash('Authentik login is not configured.', 'danger') flash('Authentik login is not configured.', 'danger')
return redirect(url_for('users.login')) return redirect(url_for('users.login'))
redirect_uri = url_for('users.authentik_callback', _external=True) redirect_uri = get_oauth_redirect_uri('users.authentik_callback')
return oauth.authentik.authorize_redirect(redirect_uri) return oauth.authentik.authorize_redirect(redirect_uri)
@users_bp.route('/login/authentik/callback') @users_bp.route('/login/authentik/callback')
@@ -756,15 +752,14 @@ The Quizzical Beats Team
@login_required @login_required
def spotify_link(): def spotify_link():
""" """
GET: Show management UI (manage_spotify.html) with current status and options. GET: Show management UI (manage_spotify.html) with current status and options. POST: Trigger Spotify OAuth flow for linking/re-linking.
POST: Trigger Spotify OAuth flow for linking/re-linking.
""" """
if request.method == 'POST': if request.method == 'POST':
# Only POST triggers the OAuth flow # Only POST triggers the OAuth flow
if not current_app.config.get('SPOTIFY_CLIENT_ID'): if not current_app.config.get('SPOTIFY_CLIENT_ID'):
flash('Spotify integration is not configured.', 'danger') flash('Spotify integration is not configured.', 'danger')
return redirect(url_for('users.spotify_link')) # Redirect to spotify_link page return redirect(url_for('users.spotify_link')) # Redirect to spotify_link page
redirect_uri = url_for('users.spotify_link_callback', _external=True) redirect_uri = get_oauth_redirect_uri('users.spotify_link_callback')
return oauth.spotify.authorize_redirect(redirect_uri, show_dialog='true') return oauth.spotify.authorize_redirect(redirect_uri, show_dialog='true')
# GET: Show management UI # GET: Show management UI