Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Text2SQL API package."""
73 changes: 73 additions & 0 deletions api/app_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Application factory for the text2sql Flask app."""

import logging
import os

from dotenv import load_dotenv
from flask import Flask, redirect, url_for
from flask_dance.contrib.google import make_google_blueprint
from flask_dance.contrib.github import make_github_blueprint
from flask_dance.consumer.storage.session import SessionStorage

from api.auth.oauth_handlers import setup_oauth_handlers
from api.routes.auth import auth_bp
from api.routes.graphs import graphs_bp
from api.routes.database import database_bp

# Load environment variables from .env file
load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")


def create_app():
"""Create and configure the Flask application."""
app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersekrit")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

# Google OAuth setup
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
google_bp = make_google_blueprint(
client_id=GOOGLE_CLIENT_ID,
client_secret=GOOGLE_CLIENT_SECRET,
scope=[
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"openid"
]
)
app.register_blueprint(google_bp, url_prefix="/login")

# GitHub OAuth setup
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
github_bp = make_github_blueprint(
client_id=GITHUB_CLIENT_ID,
client_secret=GITHUB_CLIENT_SECRET,
scope="user:email",
storage=SessionStorage()
)
app.register_blueprint(github_bp, url_prefix="/login")

# Set up OAuth signal handlers
setup_oauth_handlers(google_bp, github_bp)

# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(graphs_bp)
app.register_blueprint(database_bp)

@app.errorhandler(Exception)
def handle_oauth_error(error):
"""Handle OAuth-related errors gracefully"""
# Check if it's an OAuth-related error
if "token" in str(error).lower() or "oauth" in str(error).lower():
logging.warning("OAuth error occurred: %s", error)
from flask import session
session.clear()
return redirect(url_for("auth.home"))

# For other errors, let them bubble up
raise error

return app
17 changes: 17 additions & 0 deletions api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Authentication module for text2sql API

from .user_management import (
ensure_user_in_organizations,
update_identity_last_login,
validate_and_cache_user,
token_required
)
from .oauth_handlers import setup_oauth_handlers

__all__ = [
"ensure_user_in_organizations",
"update_identity_last_login",
"validate_and_cache_user",
"token_required",
"setup_oauth_handlers"
]
122 changes: 122 additions & 0 deletions api/auth/oauth_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""OAuth signal handlers for Google and GitHub authentication."""

import logging
import time

import requests
from flask import session
from flask_dance.consumer import oauth_authorized
from flask_dance.contrib.google import google
from flask_dance.contrib.github import github

from .user_management import ensure_user_in_organizations


def setup_oauth_handlers(google_bp, github_bp):
"""Set up OAuth signal handlers for both Google and GitHub blueprints."""

@oauth_authorized.connect_via(google_bp)
def google_logged_in(blueprint, token): # pylint: disable=unused-argument
"""Handle Google OAuth authorization callback."""
if not token:
return False

try:
# Get user profile
resp = google.get("/oauth2/v2/userinfo")
if resp.ok:
google_user = resp.json()
user_id = google_user.get("id")
email = google_user.get("email")
name = google_user.get("name")

# Validate required fields
if not user_id or not email:
logging.error("Missing required fields from Google OAuth response")
return False

if user_id and email:
# Check if identity exists in Organizations graph, create if new
_, _ = ensure_user_in_organizations(
user_id, email, name, "google", google_user.get("picture")
)

# If existing identity, just update last login
# (already done in ensure_user_in_organizations)

# Normalize user info structure for session
user_info_session = {
"id": str(user_id), # Ensure string type
"name": name or "",
"email": email,
"picture": google_user.get("picture", ""),
"provider": "google"
}
session["user_info"] = user_info_session
session["token_validated_at"] = time.time()
return False # Don't create default flask-dance entry in session

except (requests.RequestException, KeyError, ValueError, AttributeError) as e:
logging.error("Google OAuth signal error: %s", e)

return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@oauth_authorized.connect_via(github_bp)
def github_logged_in(blueprint, token): # pylint: disable=unused-argument
"""Handle GitHub OAuth authorization callback."""
if not token:
return False

try:
# Get user profile
resp = github.get("/user")
if resp.ok:
github_user = resp.json()

# Get user email (GitHub may require separate call for email)
email_resp = github.get("/user/emails")
email = None
if email_resp.ok:
emails = email_resp.json()
# Find primary email
for email_obj in emails:
if email_obj.get("primary", False):
email = email_obj.get("email")
break
# If no primary email found, use the first one
if not email and emails:
email = emails[0].get("email")

user_id = str(github_user.get("id"))
name = github_user.get("name") or github_user.get("login")

# Validate required fields
if not user_id or not email:
logging.error("Missing required fields from GitHub OAuth response")
return False

if user_id and email:
# Check if identity exists in Organizations graph, create if new
_, _ = ensure_user_in_organizations(
user_id, email, name, "github", github_user.get("avatar_url")
)

# If existing identity, just update last login
# (already done in ensure_user_in_organizations)

# Normalize user info structure for session
user_info_session = {
"id": user_id,
"name": name or "",
"email": email,
"picture": github_user.get("avatar_url", ""),
"provider": "github"
}
session["user_info"] = user_info_session
session["token_validated_at"] = time.time()
return False # Don't create default flask-dance entry in session

except (requests.RequestException, KeyError, ValueError, AttributeError) as e:
logging.error("GitHub OAuth signal error: %s", e)

return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading