204941a1dc
- Created a detailed database schema document outlining tables, relationships, and key fields. - Added OAuth integration documentation covering Spotify and Dropbox authentication processes. - Introduced a FAQ section addressing common user inquiries about the application. - Developed a user-friendly index page for easy navigation of the documentation. - Specified documentation dependencies in requirements.txt for building the documentation site. - Expanded user guide with sections on account management, creating rounds, exporting rounds, getting started, importing songs, and user interface navigation. - Updated mkdocs.yml for improved site structure and navigation.
5.3 KiB
5.3 KiB
OAuth Integration
This document details how Quizzical Beats integrates with OAuth providers, including Spotify and Dropbox.
Overview
Quizzical Beats uses OAuth 2.0 to authenticate with third-party services. The current OAuth implementation provides:
- API access to third-party services (Spotify API, Dropbox files)
- Token storage and refresh mechanisms
- Fallback strategies when tokens expire
OAuth Provider Configuration
Spotify OAuth
Spotify OAuth is used for API access:
SPOTIFY_CLIENT_ID = os.environ.get('SPOTIFY_CLIENT_ID')
SPOTIFY_CLIENT_SECRET = os.environ.get('SPOTIFY_CLIENT_SECRET')
SPOTIFY_REDIRECT_URI = os.environ.get('SPOTIFY_REDIRECT_URI', 'http://localhost:5000/auth/spotify/callback')
Dropbox OAuth
Dropbox OAuth enables file export functionality:
DROPBOX_APP_KEY = os.environ.get('DROPBOX_APP_KEY')
DROPBOX_APP_SECRET = os.environ.get('DROPBOX_APP_SECRET')
DROPBOX_REDIRECT_URI = os.environ.get('DROPBOX_REDIRECT_URI', 'http://localhost:5000/users/dropbox/callback')
Dropbox Integration Implementation
The Dropbox OAuth integration is implemented directly in the User model:
class User(db.Model):
# Other user fields...
# Dropbox OAuth fields
dropbox_id = db.Column(db.String(100), nullable=True)
dropbox_token = db.Column(db.Text(), nullable=True)
dropbox_refresh_token = db.Column(db.Text(), nullable=True)
dropbox_token_expiry = db.Column(db.DateTime(), nullable=True)
dropbox_export_path = db.Column(db.String(255), nullable=True)
Dropbox Authentication Flow
- User initiates Dropbox connection from their profile page
- Application redirects to Dropbox's authorization page
- User grants permission to the application
- Dropbox redirects back to our callback URL with an authorization code
- Application exchanges the code for access and refresh tokens
- Tokens and basic user info are stored in the user's record
Example of the callback handler:
@users_bp.route('/dropbox/callback')
@login_required
def dropbox_callback():
# Handle errors
if 'error' in request.args:
flash(f'Dropbox authorization failed: {error}', 'error')
return redirect(url_for('users.profile'))
# Exchange authorization code for tokens
code = request.args.get('code')
token_info = exchange_code_for_token(code)
# Store tokens in the user model
current_user.dropbox_token = token_info.get('access_token')
current_user.dropbox_refresh_token = token_info.get('refresh_token')
# Store expiration time
expires_in = token_info.get('expires_in', 14400) # Default to 4 hours
current_user.dropbox_token_expiry = datetime.now() + timedelta(seconds=expires_in)
# Get and store account info
account_info = get_dropbox_account_info(current_user.dropbox_token)
if account_info:
current_user.dropbox_id = account_info.get('account_id')
db.session.commit()
return redirect(url_for('users.profile'))
Token Management
Token Refresh
Tokens are refreshed when they expire. The Dropbox implementation uses:
def get_current_user_dropbox_token():
"""Get a valid Dropbox access token for the current user, refreshing if needed"""
if not current_user or not current_user.is_authenticated:
return None
# Check if token exists and is valid
if (current_user.dropbox_token and
current_user.dropbox_token_expiry and
current_user.dropbox_token_expiry > datetime.now() + timedelta(minutes=5)):
return current_user.dropbox_token
# Token is missing or about to expire - try to refresh
if current_user.dropbox_refresh_token:
# Refresh the token
token_info = refresh_dropbox_token(current_user.dropbox_refresh_token)
if token_info and 'access_token' in token_info:
# Update token in database
current_user.dropbox_token = token_info['access_token']
expires_in = token_info.get('expires_in', 14400)
current_user.dropbox_token_expiry = datetime.now() + timedelta(seconds=expires_in)
db.session.commit()
return current_user.dropbox_token
return None
Token Revocation
Users can disconnect their Dropbox accounts:
@users_bp.route('/dropbox/disconnect', methods=['POST'])
@login_required
def dropbox_disconnect():
"""Disconnect user's Dropbox account"""
# Revoke token if present
if current_user.dropbox_token:
try:
revoke_token(current_user.dropbox_token)
except Exception as e:
# Log the error but continue
pass
# Clear Dropbox credentials
current_user.dropbox_token = None
current_user.dropbox_refresh_token = None
current_user.dropbox_token_expiry = None
current_user.dropbox_id = None
db.session.commit()
return redirect(url_for('users.profile'))
Security Considerations
When working with OAuth:
- Always use HTTPS in production
- Store tokens securely
- Implement proper token refresh
- Handle token revocation when users disconnect accounts
- Request minimal scope access
- Validate all OAuth-related inputs
- Use the official provider documentation for the most up-to-date OAuth implementation details