diff --git a/.env.demo b/.env.demo index e5d4518..7125882 100644 --- a/.env.demo +++ b/.env.demo @@ -27,6 +27,12 @@ LASTFM_API_KEY=your-lastfm-api-key SQLALCHEMY_DATABASE_URI=sqlite:///data/song_data.db 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_CLIENT_ID=your-spotify-client-id SPOTIFY_CLIENT_SECRET=your-spotify-client-secret diff --git a/musicround/__init__.py b/musicround/__init__.py index 0fae7cc..b58fc32 100644 --- a/musicround/__init__.py +++ b/musicround/__init__.py @@ -111,6 +111,10 @@ def create_app(config=None): # Configure the app 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 app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' diff --git a/musicround/config.py b/musicround/config.py index 2e1a76b..a9942b1 100644 --- a/musicround/config.py +++ b/musicround/config.py @@ -71,5 +71,9 @@ class Config: # Automation settings 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' + diff --git a/musicround/helpers/auth_helpers.py b/musicround/helpers/auth_helpers.py index f51eb7a..512ccc4 100644 --- a/musicround/helpers/auth_helpers.py +++ b/musicround/helpers/auth_helpers.py @@ -344,4 +344,14 @@ def update_oauth_tokens(user, tokens, auth_provider): except Exception as e: db.session.rollback() current_app.logger.error(f"Error updating {auth_provider} tokens: {str(e)}") - return False \ No newline at end of file + 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) \ No newline at end of file diff --git a/musicround/helpers/dropbox_helper.py b/musicround/helpers/dropbox_helper.py index 1bf7a5c..42dcdda 100644 --- a/musicround/helpers/dropbox_helper.py +++ b/musicround/helpers/dropbox_helper.py @@ -2,6 +2,7 @@ Helpers for Dropbox API integration """ from flask import current_app, url_for, redirect, session +from musicround.helpers.auth_helpers import get_oauth_redirect_uri import requests import json import os @@ -11,7 +12,7 @@ from flask_login import current_user def get_dropbox_auth_url(): """Get the authorization URL for Dropbox OAuth flow""" 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 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""" app_key = current_app.config.get('DROPBOX_APP_KEY') 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 = { 'code': code, diff --git a/musicround/routes/auth.py b/musicround/routes/auth.py index bb3c119..2f831b0 100644 --- a/musicround/routes/auth.py +++ b/musicround/routes/auth.py @@ -9,7 +9,7 @@ from musicround.models import User, db from datetime import datetime import requests 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 auth_bp = Blueprint('auth', __name__) @@ -37,7 +37,7 @@ def login_with_spotify(): return redirect(url_for('users.login')) # 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 # when registering the Spotify client. diff --git a/musicround/routes/users.py b/musicround/routes/users.py index 2e12578..7b81802 100644 --- a/musicround/routes/users.py +++ b/musicround/routes/users.py @@ -11,7 +11,7 @@ from sqlalchemy.exc import IntegrityError from musicround.models import db, User, Role, SystemSetting 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 users_bp = Blueprint('users', __name__, url_prefix='/users') @@ -72,14 +72,12 @@ def admin_required(f): def google_login(): """Initiate Google OAuth login flow""" if current_user.is_authenticated: - return redirect(url_for('core.index')) - - # Google login is disabled if client ID is not set + return redirect(url_for('core.index')) # Google login is disabled if client ID is not set if not current_app.config.get('GOOGLE_CLIENT_ID'): flash('Google login is not configured.', 'danger') 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) @users_bp.route('/login/google/callback') @@ -128,14 +126,12 @@ def google_callback(): def authentik_login(): """Initiate Authentik OAuth login flow""" if current_user.is_authenticated: - return redirect(url_for('core.index')) - - # Authentik login is disabled if client ID is not set + return redirect(url_for('core.index')) # Authentik login is disabled if client ID is not set if not current_app.config.get('AUTHENTIK_CLIENT_ID'): flash('Authentik login is not configured.', 'danger') 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) @users_bp.route('/login/authentik/callback') @@ -756,15 +752,14 @@ The Quizzical Beats Team @login_required def spotify_link(): """ - GET: Show management UI (manage_spotify.html) with current status and options. - POST: Trigger Spotify OAuth flow for linking/re-linking. + GET: Show management UI (manage_spotify.html) with current status and options. POST: Trigger Spotify OAuth flow for linking/re-linking. """ if request.method == 'POST': # Only POST triggers the OAuth flow if not current_app.config.get('SPOTIFY_CLIENT_ID'): flash('Spotify integration is not configured.', 'danger') 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') # GET: Show management UI