import os import time from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, current_app from flask_login import LoginManager, login_user, logout_user, login_required, current_user import requests from requests_oauthlib import OAuth2Session from models.user import User # Disable SSL requirement for local development os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Ensure OAuthlib allows HTTP for local development os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' auth_bp = Blueprint('auth', __name__) # Google OAuth configuration with your provided credentials GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET') GOOGLE_REDIRECT_URI = os.environ.get('GOOGLE_REDIRECT_URI', 'http://localhost:5000/auth/google/callback') print(f"OAuth Configuration Loaded - Client ID: {GOOGLE_CLIENT_ID[:10] if GOOGLE_CLIENT_ID else 'None'}...") def validate_oauth_config(): """Validate OAuth configuration and raise error if missing""" if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET: raise ValueError("Google OAuth credentials not found in environment variables. Please check your .env file.") # Google OAuth URLs GOOGLE_AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/v2/auth" GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token" GOOGLE_USER_INFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo" # Initialize login manager login_manager = LoginManager() def init_app(app): """Initialize the login manager with the app""" login_manager.init_app(app) # Set login view - using setattr to avoid type checking issues setattr(login_manager, 'login_view', 'auth.login') return login_manager @login_manager.user_loader def load_user(user_id): return User.get(user_id) @auth_bp.route("/login") def login(): """Display the login page""" return render_template('login.html') @auth_bp.route("/google/login") def google_login(): """Redirect to Google OAuth login""" try: # Validate OAuth configuration validate_oauth_config() # Clear any existing OAuth state to prevent conflicts session.pop('oauth_state', None) # Create OAuth2Session with Google google = OAuth2Session( GOOGLE_CLIENT_ID, scope=["openid", "email", "profile"], redirect_uri=GOOGLE_REDIRECT_URI ) # Generate authorization URL authorization_url, state = google.authorization_url( GOOGLE_AUTHORIZATION_BASE_URL, access_type="offline", prompt="select_account" ) # Store state in session for security session['oauth_state'] = state print(f"Stored OAuth state: {state}") print(f"Authorization URL: {authorization_url}") # Redirect user to Google for authorization return redirect(authorization_url) except Exception as e: print(f"Error in Google OAuth login: {e}") import traceback traceback.print_exc() return redirect(url_for('auth.login')) @auth_bp.route("/google/callback") def google_callback(): """Handle Google OAuth callback""" try: # Validate OAuth configuration validate_oauth_config() # Get state parameters received_state = request.args.get('state') stored_state = session.get('oauth_state') # Log state values for debugging print(f"Received state: {received_state}") print(f"Stored state: {stored_state}") # Verify state parameter (allow None for development/testing) if stored_state and received_state != stored_state: print("State parameter mismatch") return redirect(url_for('auth.login')) # Create OAuth2Session with Google google = OAuth2Session( GOOGLE_CLIENT_ID, state=stored_state, # Use stored state if available redirect_uri=GOOGLE_REDIRECT_URI ) # Fetch token token = google.fetch_token( GOOGLE_TOKEN_URL, client_secret=GOOGLE_CLIENT_SECRET, authorization_response=request.url, # Include these parameters to handle token fetching properly include_client_id=True ) # Fetch user info user_info = google.get(GOOGLE_USER_INFO_URL).json() print(f"User info: {user_info}") # Create or update user in our database user = User.create_or_update( id=f"google_{user_info['id']}", email=user_info['email'], name=user_info.get('name', user_info['email'].split('@')[0]), picture=user_info.get('picture') ) if user: login_user(user) # Clean up session session.pop('oauth_state', None) return redirect(url_for('index')) else: return redirect(url_for('auth.login')) except Exception as e: print(f"Error in Google OAuth callback: {e}") import traceback traceback.print_exc() # Log additional debugging information print(f"Request args: {request.args}") print(f"Session data: {dict(session)}") return redirect(url_for('auth.login')) @auth_bp.route("/guest/login", methods=['POST']) def guest_login(): """Login as a guest user""" name = request.form.get('name') if not name: return "Name is required", 400 # Create a guest user user = User.create_or_update( id=f"guest_{name.lower().replace(' ', '_')}_{int(time.time())}", email=f"{name.lower().replace(' ', '.')}@guest.texlab.com", name=name, picture=None ) if user: login_user(user) return redirect(url_for('index')) else: return "Failed to create user", 500 @auth_bp.route("/logout") @login_required def logout(): logout_user() return redirect(url_for('auth.login')) @auth_bp.route("/user") def get_current_user(): if current_user.is_authenticated: return jsonify({ 'authenticated': True, 'user': { 'id': current_user.id, 'email': current_user.email, 'name': current_user.name, 'picture': current_user.picture } }) else: return jsonify({'authenticated': False})