MemPrepMate / src /routes /auth.py
Christian Kniep
'add more loggging'
7cf6441
"""
Authentication routes for HuggingFace OAuth.
Feature: 012-profile-contact-ui
"""
import os
from flask import Blueprint, redirect, session, url_for, flash, request
from ..services.auth_service import auth_service
from ..services.storage_service import create_or_update_user, get_user_profile
from ..services.backend_client import backend_client
bp = Blueprint("auth", __name__)
@bp.route("/login")
def login():
"""Initiate HuggingFace OAuth login flow."""
import logging
from flask import current_app
logger = logging.getLogger(__name__)
# Generate redirect_uri from current request to ensure it matches the callback
# This handles both localhost and deployed environments automatically
redirect_uri = url_for("auth.callback", _external=True)
logger.info(f"[OAUTH] Starting login flow")
logger.info(f"[OAUTH] Generated redirect_uri: {redirect_uri}")
logger.info(f"[OAUTH] Request URL: {request.url}")
logger.info(f"[OAUTH] Request host: {request.host}")
logger.info(f"[OAUTH] Request scheme: {request.scheme}")
logger.info(f"[OAUTH] Session interface type: {type(current_app.session_interface).__name__}")
logger.info(f"[OAUTH] Session interface module: {type(current_app.session_interface).__module__}")
# Store redirect_uri in session for callback verification
session["oauth_redirect_uri"] = redirect_uri
session["test_marker"] = "login_triggered" # Test value to verify session persistence
# Make session permanent to ensure cookie gets set with proper expiry
session.permanent = True
logger.info(f"[OAUTH] Session data stored: oauth_redirect_uri={redirect_uri}")
logger.info(f"[OAUTH] Session keys before authorize_redirect: {list(session.keys())}")
logger.info(f"[OAUTH] Session.modified: {session.modified}")
logger.info(f"[OAUTH] Session.permanent: {session.permanent}")
logger.info(f"[OAUTH] Session ID (if server-side): {getattr(session, 'sid', 'N/A')}")
# Call authorize_redirect - Authlib will add _state_* key to session
# Flask will automatically save the complete session (including state) via after_request
response = auth_service.hf.authorize_redirect(redirect_uri)
logger.info(f"[OAUTH] Session keys after authorize_redirect: {list(session.keys())}")
logger.info(f"[OAUTH] OAuth redirect location: {response.location if hasattr(response, 'location') else 'N/A'}")
logger.info(f"[OAUTH] Response type: {type(response).__name__}")
logger.info(f"[OAUTH] Response Set-Cookie preview: {[h[:80] for h in response.headers.getlist('Set-Cookie')]}")
# Flask's after_request middleware will automatically save session with ALL data including state
return response
@bp.route("/callback")
def callback():
"""
Handle OAuth callback from HuggingFace.
Exchange code for token, fetch user info, create/update user profile.
"""
import logging
logger = logging.getLogger(__name__)
logger.info(f"[OAUTH] === CALLBACK ENDPOINT HIT ===")
logger.info(f"[OAUTH] Request URL: {request.url}")
logger.info(f"[OAUTH] Request path: {request.path}")
logger.info(f"[OAUTH] Request args: {dict(request.args)}")
try:
# Debug session state and cookies
from flask import current_app
logger.info(f"[OAUTH] Cookies received: {list(request.cookies.keys())}")
logger.info(f"[OAUTH] Cookie values (redacted): {[(k, v[:20] + '...' if len(v) > 20 else v) for k, v in request.cookies.items()]}")
logger.info(f"[OAUTH] Session interface type: {type(current_app.session_interface).__name__}")
logger.info(f"[OAUTH] Session interface module: {type(current_app.session_interface).__module__}")
logger.info(f"[OAUTH] Session keys: {list(session.keys())}")
logger.info(f"[OAUTH] Session ID (if server-side): {getattr(session, 'sid', 'N/A')}")
logger.info(f"[OAUTH] Test marker from login: {session.get('test_marker')}")
# Check for state key (Authlib uses _state_huggingface_<random>)
state_keys = [k for k in session.keys() if k.startswith('_state_huggingface')]
logger.info(f"[OAUTH] State keys in session: {state_keys}")
logger.info(f"[OAUTH] State from URL: {request.args.get('state')}")
# Additional diagnostics
if not state_keys:
logger.error(f"[OAUTH] CRITICAL: No state keys in session! Session data lost!")
logger.error(f"[OAUTH] All session keys: {dict(session)}")
if 'flask_session' not in type(current_app.session_interface).__module__:
logger.error(f"[OAUTH] ERROR: Using client-side sessions instead of Flask-Session!")
logger.error(f"[OAUTH] Session interface: {type(current_app.session_interface)}")
# Exchange authorization code for access token
# Authlib automatically validates the state and uses the redirect_uri from the session
# If state validation fails, it will raise an exception that we catch below
token = auth_service.fetch_token()
# Fetch user information
userinfo = auth_service.fetch_userinfo(token)
# Extract user data
user_id = userinfo.get("preferred_username")
display_name = userinfo.get("name", user_id)
profile_picture_url = userinfo.get("picture")
if not user_id:
flash("Failed to retrieve user information from HuggingFace", "danger")
return redirect(url_for("auth.login"))
# Check if user already exists
existing_profile = get_user_profile(user_id)
if existing_profile:
# Update existing user (last_login, display_name)
user_profile = create_or_update_user(user_id, display_name, profile_picture_url)
else:
# New user - create backend session first, then store with returned session_id
try:
backend_response = backend_client.create_session(
user_id=user_id,
contact_id="user-profile",
description=f"{display_name}'s Profile",
is_reference=False
)
backend_session_id = backend_response.get("session_id")
if not backend_session_id:
raise ValueError("Backend did not return session_id")
# Create user profile in SQLite with backend's session_id
user_profile = create_or_update_user(
user_id,
display_name,
profile_picture_url,
session_id=backend_session_id
)
except Exception as e:
import logging
logging.error(f"Failed to create backend session: {str(e)}", exc_info=True)
flash(f"Failed to initialize profile: {str(e)}", "danger")
return redirect(url_for("auth.login"))
# Store user info in Flask session
session.permanent = True
session["user_id"] = user_id
session["display_name"] = display_name
session["profile_picture_url"] = profile_picture_url
session["session_id"] = user_profile.session_id
session["profile_session_id"] = user_profile.session_id # Feature: 001-contact-session-fixes - cache for facts/messages
session["access_token"] = token.get("access_token")
# CRITICAL: Manually save session before redirect
# Same issue as login - redirect response bypasses automatic session save
response = redirect(url_for("profile.view_profile"))
from flask import current_app
current_app.session_interface.save_session(current_app, session, response)
logger.info(f"[OAUTH] Session saved with user_id={user_id}, redirecting to profile")
return response
except Exception as e:
# Log the full error for debugging
import logging
logger = logging.getLogger(__name__)
logger.error(f"Authentication failed: {str(e)}", exc_info=True)
logger.debug(f"Session keys at error: {list(session.keys())}")
logger.debug(f"Request args: {request.args}")
# Clear any stale OAuth state
for key in list(session.keys()):
if key.startswith('_state_'):
session.pop(key)
flash(f"Authentication failed: {str(e)}", "danger")
return redirect(url_for("auth.login"))
@bp.route("/logout")
def logout():
"""Clear session and log out user."""
user_name = session.get("display_name", "User")
session.clear()
return redirect(url_for("auth.login"))