Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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 Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jsonschema = "~=4.25.0"
tqdm = "~=4.67.1"
python-multipart = "~=0.0.10"
jinja2 = "~=3.1.4"
structlog = "~=25.4.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Good addition; make sure runtime logging config lands with it.

structlog is added, but CI is failing because api.logging_config is missing. Add the new module (see my proposal in oauth_handlers.py comment) so this dependency is actually used; otherwise this line just introduces a new dep without value.

Run to confirm presence of the config module and its get_logger:


🏁 Script executed:

#!/bin/bash
fd -H -a 'logging_config.py' api || true
rg -nP '^\s*def\s+get_logger\(' api/logging_config.py || true

Length of output: 157


Add missing runtime logging configuration

Search confirmed there is no api/logging_config.py, so the import is unresolved and structlog is never used in the codebase. CI failures are occurring because api.logging_config cannot be found.

To resolve:

• Create a new module at api/logging_config.py with a get_logger function that wraps structlog.
• Update any callers (e.g., in oauth_handlers.py) to import and use this new get_logger.

Suggested diff:

+ # api/logging_config.py
+ import structlog
+
+ def get_logger(name=None):
+     """
+     Returns a structlog-compatible logger.
+     """
+     return structlog.get_logger(name)

Then, in oauth_handlers.py (or other modules where logging is needed), ensure you have:

from api.logging_config import get_logger

logger = get_logger(__name__)

This will ensure the structlog dependency is actually utilized at runtime.

🤖 Prompt for AI Agents
In Pipfile around line 19, CI fails because api.logging_config is missing and
structlog is never used; create a new module at api/logging_config.py that
exposes a get_logger(name) function which configures structlog (processor chain,
renderer, and binds context as needed) and returns a logger for the given name,
then update callers (e.g., oauth_handlers.py) to import get_logger from
api.logging_config and replace any existing logger setup with logger =
get_logger(__name__) so structlog is actually initialized and the import
resolves.


[dev-packages]
pytest = "~=8.4.1"
Expand Down
17 changes: 13 additions & 4 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions api/app_factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Application factory for the text2sql FastAPI app."""

import logging
import os
import secrets

Expand All @@ -11,13 +10,14 @@
from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.base import BaseHTTPMiddleware

from api.logging_config import get_logger
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Build is failing: api.logging_config import not found

Same E0401/E0611 here. Until api/logging_config.py is present, app import will fail.

Apply the same guarded import as stopgap:

-from api.logging_config import get_logger
+try:
+    from api.logging_config import get_logger
+except Exception:
+    import logging as _logging
+    def get_logger(name: str):
+        return _logging.getLogger(name)

And add the missing module (see suggested implementation below in a separate comment).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from api.logging_config import get_logger
# api/app_factory.py
# … other imports …
# Replace direct import (line 13) with guarded import + fallback
try:
from api.logging_config import get_logger
except Exception:
import logging as _logging
def get_logger(name: str):
return _logging.getLogger(name)
# … rest of factory implementation …
🧰 Tools
🪛 GitHub Actions: Pylint

[error] 13-13: E0401: Unable to import 'api.logging_config' (import-error)


[error] 13-13: E0611: No name 'logging_config' in module 'api' (no-name-in-module)

🤖 Prompt for AI Agents
In api/app_factory.py around line 13 the direct import "from api.logging_config
import get_logger" causes import failures while api/logging_config.py is
missing; wrap that import in a try/except ImportError and provide a lightweight
fallback (e.g., define or import a simple get_logger stub that returns a basic
logger) so app_factory can be imported during build, and also add the missing
module api/logging_config.py with the intended get_logger implementation so the
guarded fallback is temporary and the real implementation is available.

from api.routes.auth import auth_router, init_auth
from api.routes.graphs import graphs_router
from api.routes.database import database_router

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


class SecurityMiddleware(BaseHTTPMiddleware):
Expand Down Expand Up @@ -55,7 +55,7 @@ def create_app():
secret_key = os.getenv("FASTAPI_SECRET_KEY")
if not secret_key:
secret_key = secrets.token_hex(32)
logging.warning("FASTAPI_SECRET_KEY not set, using generated key. Set this in production!")
logger.warning("FASTAPI_SECRET_KEY not set, using generated key. Set this in production!")

# Add session middleware with explicit settings to ensure OAuth state persists
app.add_middleware(
Expand Down Expand Up @@ -87,8 +87,10 @@ def create_app():
async def handle_oauth_error(request: Request, exc: Exception):
"""Handle OAuth-related errors gracefully"""
# Check if it's an OAuth-related error
if "token" in str(exc).lower() or "oauth" in str(exc).lower():
logging.warning("OAuth error occurred: %s", exc)
exc_text = str(exc)
exc_text_lower = exc_text.lower()
if "token" in exc_text_lower or "oauth" in exc_text_lower:
logger.warning("OAuth error occurred", error=exc_text)
request.session.clear()
return RedirectResponse(url="/", status_code=302)

Expand Down
12 changes: 7 additions & 5 deletions api/auth/oauth_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
callbacks can invoke them when processing OAuth responses.
"""

import logging
from typing import Dict, Any

from fastapi import FastAPI, Request
from authlib.integrations.starlette_client import OAuth

from api.logging_config import get_logger
from .user_management import ensure_user_in_organizations

logger = get_logger(__name__)
Comment on lines +12 to +15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Fix import error: add api/logging_config.get_logger (blocking CI).

Pylint/pytest fail with Unable to import 'api.logging_config'. The PR introduces the usage but not the module. Add a minimal, production-ready api/logging_config.py that returns a structlog BoundLogger and integrates with stdlib logging.

Here’s a drop-in module you can add as api/logging_config.py:

# api/logging_config.py
from __future__ import annotations

import logging
import os
import sys

import structlog


_CONFIGURED = False


def _configure_logging() -> None:
    # Stdlib logging baseline
    log_level = os.getenv("LOG_LEVEL", "INFO").upper()
    json_logs = os.getenv("JSON_LOGS", "0").lower() in {"1", "true", "yes"}

    handler = logging.StreamHandler(sys.stdout)

    # Use ProcessorFormatter so stdlib handlers render structlog events
    from structlog.stdlib import ProcessorFormatter

    formatter = ProcessorFormatter(
        processor=structlog.processors.JSONRenderer() if json_logs
        else structlog.dev.ConsoleRenderer(colors=True),
        foreign_pre_chain=[
            structlog.stdlib.add_log_level,
            structlog.processors.TimeStamper(fmt="iso"),
        ],
    )
    handler.setFormatter(formatter)

    root = logging.getLogger()
    root.handlers = [handler]
    root.setLevel(log_level)

    structlog.configure(
        processors=[
            structlog.contextvars.merge_contextvars,
            structlog.stdlib.add_logger_name,
            structlog.stdlib.add_log_level,
            structlog.stdlib.PositionalArgumentsFormatter(),
            structlog.processors.StackInfoRenderer(),
            structlog.processors.format_exc_info,
            # Hand off to ProcessorFormatter above for final rendering
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )


def get_logger(name: str | None = None):
    global _CONFIGURED
    if not _CONFIGURED:
        _configure_logging()
        _CONFIGURED = True
    return structlog.get_logger(name)

🏁 Script executed:

#!/bin/bash
# Verify import sites and ensure no lingering stdlib `logging` imports remain for these modules
rg -nP 'from\s+api\.logging_config\s+import\s+get_logger' --type=py
rg -nP '^\s*import\s+logging\b' api/auth/oauth_handlers.py api/graph.py api/routes/auth.py api/loaders/mysql_loader.py

Length of output: 804


🏁 Script executed:

#!/bin/bash
# Check if api/logging_config.py exists in the repository
if [ -f api/logging_config.py ]; then
  echo "exists"
else
  echo "missing"
fi

Length of output: 57


Add missing api/logging_config.py to satisfy get_logger imports

The PR introduces calls to from api.logging_config import get_logger in multiple modules, but the api/logging_config.py file is not present—causing import errors and failing CI. Please add a new api/logging_config.py with a production-ready get_logger implementation (for example, the snippet below) so that all imports resolve correctly.

Modules referencing get_logger that need this file:

  • api/auth/user_management.py (line 11)
  • api/app_factory.py (line 13)
  • api/graph.py (line 10)
  • api/auth/oauth_handlers.py (line 12)
  • api/routes/database.py (line 10)
  • api/loaders/postgres_loader.py (line 11)
  • api/routes/auth.py (line 15)
  • api/loaders/mysql_loader.py (line 12)
  • api/routes/graphs.py (line 22)

Suggested drop-in implementation for api/logging_config.py:

+ # api/logging_config.py
+ from __future__ import annotations
+
+ import logging
+ import os
+ import sys
+
+ import structlog
+
+ _CONFIGURED = False
+
+ def _configure_logging() -> None:
+     log_level = os.getenv("LOG_LEVEL", "INFO").upper()
+     json_logs = os.getenv("JSON_LOGS", "0").lower() in {"1", "true", "yes"}
+
+     handler = logging.StreamHandler(sys.stdout)
+     from structlog.stdlib import ProcessorFormatter
+     formatter = ProcessorFormatter(
+         processor=(
+             structlog.processors.JSONRenderer()
+             if json_logs
+             else structlog.dev.ConsoleRenderer(colors=True)
+         ),
+         foreign_pre_chain=[
+             structlog.stdlib.add_log_level,
+             structlog.processors.TimeStamper(fmt="iso"),
+         ],
+     )
+     handler.setFormatter(formatter)
+
+     root = logging.getLogger()
+     root.handlers = [handler]
+     root.setLevel(log_level)
+
+     structlog.configure(
+         processors=[
+             structlog.contextvars.merge_contextvars,
+             structlog.stdlib.add_logger_name,
+             structlog.stdlib.add_log_level,
+             structlog.stdlib.PositionalArgumentsFormatter(),
+             structlog.processors.StackInfoRenderer(),
+             structlog.processors.format_exc_info,
+             structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
+         ],
+         logger_factory=structlog.stdlib.LoggerFactory(),
+         cache_logger_on_first_use=True,
+     )
+
+ def get_logger(name: str | None = None):
+     global _CONFIGURED
+     if not _CONFIGURED:
+         _configure_logging()
+         _CONFIGURED = True
+     return structlog.get_logger(name)

Please add this file to unblock CI.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from api.logging_config import get_logger
from .user_management import ensure_user_in_organizations
logger = get_logger(__name__)
# File: api/logging_config.py
from __future__ import annotations
import logging
import os
import sys
import structlog
_CONFIGURED = False
def _configure_logging() -> None:
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
json_logs = os.getenv("JSON_LOGS", "0").lower() in {"1", "true", "yes"}
handler = logging.StreamHandler(sys.stdout)
from structlog.stdlib import ProcessorFormatter
formatter = ProcessorFormatter(
processor=(
structlog.processors.JSONRenderer()
if json_logs
else structlog.dev.ConsoleRenderer(colors=True)
),
foreign_pre_chain=[
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
],
)
handler.setFormatter(formatter)
root = logging.getLogger()
root.handlers = [handler]
root.setLevel(log_level)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
def get_logger(name: str | None = None):
global _CONFIGURED
if not _CONFIGURED:
_configure_logging()
_CONFIGURED = True
return structlog.get_logger(name)
🧰 Tools
🪛 GitHub Actions: Pylint

[error] 12-12: E0401: Unable to import 'api.logging_config' (import-error)


[error] 12-12: E0611: No name 'logging_config' in module 'api' (no-name-in-module)

🤖 Prompt for AI Agents
In api/logging_config.py (new file), add a production-ready get_logger
implementation so imports like "from api.logging_config import get_logger"
resolve; create a module that configures Python logging (formatter,
StreamHandler, optional file handler, log level from env, and returns a logger
with provided name) and ensure the implementation is idempotent (doesn't add
duplicate handlers) and safe to call from all listed modules
(api/auth/user_management.py line 11, api/app_factory.py line 13, api/graph.py
line 10, api/auth/oauth_handlers.py line 12, api/routes/database.py line 10,
api/loaders/postgres_loader.py line 11, api/routes/auth.py line 15,
api/loaders/mysql_loader.py line 12, api/routes/graphs.py line 22); add tests or
run CI to verify no import errors.



def setup_oauth_handlers(app: FastAPI, oauth: OAuth):
"""Set up OAuth handlers for both Google and GitHub."""
Expand All @@ -30,7 +32,7 @@ async def handle_google_callback(_request: Request,

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

# Check if identity exists in Organizations graph, create if new
Expand All @@ -44,7 +46,7 @@ async def handle_google_callback(_request: Request,

return True
except Exception as exc: # capture exception for logging
logging.error("Error handling Google OAuth callback: %s", exc)
logger.error("Error handling Google OAuth callback", error=str(exc))
return False
Comment on lines +49 to 50
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Log exceptions with stack traces.

Swap logger.error(..., error=str(exc)) for logger.exception(...) to preserve stack traces and improve debuggability with structlog.

Apply:

-        except Exception as exc:  # capture exception for logging
-            logger.error("Error handling Google OAuth callback", error=str(exc))
+        except Exception:
+            logger.exception("Error handling Google OAuth callback")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.error("Error handling Google OAuth callback", error=str(exc))
return False
except Exception:
logger.exception("Error handling Google OAuth callback")
return False
🤖 Prompt for AI Agents
In api/auth/oauth_handlers.py around lines 49-50, the current logger call uses
logger.error("Error handling Google OAuth callback", error=str(exc)) which drops
the exception stack; replace it with logger.exception("Error handling Google
OAuth callback") (or logger.error(..., exc_info=True) if you prefer explicit
exc_info) so the full stack trace is captured by structlog for proper debugging.


async def handle_github_callback(_request: Request,
Expand All @@ -58,7 +60,7 @@ async def handle_github_callback(_request: Request,

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

# Check if identity exists in Organizations graph, create if new
Expand All @@ -72,7 +74,7 @@ async def handle_github_callback(_request: Request,

return True
except Exception as exc: # capture exception for logging
logging.error("Error handling GitHub OAuth callback: %s", exc)
logger.error("Error handling GitHub OAuth callback", error=str(exc))
return False
Comment on lines +77 to 78
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same here—use logger.exception for full trace.

Consistent with the Google path.

-        except Exception as exc:  # capture exception for logging
-            logger.error("Error handling GitHub OAuth callback", error=str(exc))
+        except Exception:
+            logger.exception("Error handling GitHub OAuth callback")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.error("Error handling GitHub OAuth callback", error=str(exc))
return False
except Exception:
logger.exception("Error handling GitHub OAuth callback")
return False
🤖 Prompt for AI Agents
In api/auth/oauth_handlers.py around lines 77-78, the GitHub OAuth error logging
uses logger.error with a stringified exception which loses the traceback;
replace that call with logger.exception("Error handling GitHub OAuth callback")
so the full stack trace and exception context are logged (keep the subsequent
return False unchanged), matching the Google path's handling.


# Store handlers in app state for use in route callbacks
Expand Down
107 changes: 69 additions & 38 deletions api/auth/user_management.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""User management and authentication functions for text2sql API."""

import logging
import time
from functools import wraps
from typing import Tuple, Optional, Dict, Any

import requests
from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from authlib.integrations.starlette_client import OAuth

from api.extensions import db
from api.logging_config import get_logger

logger = get_logger(__name__)

Comment on lines +11 to 14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Build is failing: api.logging_config import not found

CI shows Pylint E0401/E0611 for this import. Either api/logging_config.py was not committed or the package path is misconfigured. Until the module exists, all modules using get_logger will crash at import-time.

Apply a small guard so the app still runs with stdlib logging if the module is missing:

-from api.logging_config import get_logger
+try:
+    from api.logging_config import get_logger
+except Exception:  # ImportError in CI; keep app usable
+    import logging as _logging
+    def get_logger(name: str):
+        return _logging.getLogger(name)

Additionally, ensure api/logging_config.py is added to the repo. If you want, I can provide a minimal structlog-based implementation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from api.logging_config import get_logger
logger = get_logger(__name__)
try:
from api.logging_config import get_logger
except Exception: # ImportError in CI; keep app usable
import logging as _logging
def get_logger(name: str):
return _logging.getLogger(name)
logger = get_logger(__name__)
🧰 Tools
🪛 GitHub Actions: Pylint

[error] 11-11: E0401: Unable to import 'api.logging_config' (import-error)


[error] 11-11: E0611: No name 'logging_config' in module 'api' (no-name-in-module)

🤖 Prompt for AI Agents
In api/auth/user_management.py around lines 11 to 14 the import from
api.logging_config raises import errors in CI because the module may be missing;
wrap the import in a try/except ImportError that falls back to Python's stdlib
logging (configure basic logging if necessary and set logger =
logging.getLogger(__name__)), and add a TODO or comment to ensure
api/logging_config.py is committed to the repo (or create the minimal
structlog-based implementation) so callers use the intended logger when
available.


def ensure_user_in_organizations(provider_user_id, email, name, provider, picture=None):
Expand All @@ -22,19 +22,24 @@ def ensure_user_in_organizations(provider_user_id, email, name, provider, pictur
"""
# Input validation
if not provider_user_id or not email or not provider:
logging.error("Missing required parameters: provider_user_id=%s, email=%s, provider=%s",
provider_user_id, email, provider)
logger.error(
"Missing required parameters",
provider_user_id=provider_user_id,
email=email,
provider=provider,
)
return False, None
return False, None
Comment on lines +25 to 32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unreachable duplicate return

There are two consecutive return False, None statements. Line 32 is unreachable and is flagged by Pylint W0101.

Apply:

         logger.error(
             "Missing required parameters",
             provider_user_id=provider_user_id,
             email=email,
             provider=provider,
         )
-        return False, None
-        return False, None
+        return False, None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.error(
"Missing required parameters",
provider_user_id=provider_user_id,
email=email,
provider=provider,
)
return False, None
return False, None
logger.error(
"Missing required parameters",
provider_user_id=provider_user_id,
email=email,
provider=provider,
)
return False, None
🧰 Tools
🪛 GitHub Actions: Pylint

[warning] 32-32: W0101: Unreachable code (unreachable)

🤖 Prompt for AI Agents
In api/auth/user_management.py around lines 25 to 32, there are two consecutive
identical return statements making the second one unreachable; remove the
duplicate so only a single return False, None remains immediately after the
logger.error call (ensure no extra whitespace or logic is lost when deleting the
redundant line).


# Validate email format (basic check)
if "@" not in email or "." not in email:
logging.error("Invalid email format: %s", email)
logger.error("Invalid email format", email=email)
return False, None

# Validate provider is in allowed list
allowed_providers = ["google", "github"]
if provider not in allowed_providers:
logging.error("Invalid provider: %s", provider)
logger.error("Invalid provider", provider=provider)
return False, None

try:
Expand Down Expand Up @@ -99,43 +104,55 @@ def ensure_user_in_organizations(provider_user_id, email, name, provider, pictur
# Determine the type of operation for logging
if is_new_identity and not had_other_identities:
# Brand new user (first identity)
logging.info("NEW USER CREATED: provider=%s, provider_user_id=%s, "
"email=%s, name=%s", provider, provider_user_id, email, name)
logger.info(
"NEW USER CREATED",
provider=provider,
provider_user_id=provider_user_id,
email=email,
name=name,
)
return True, {"identity": identity, "user": user}
elif is_new_identity and had_other_identities:
# New identity for existing user (cross-provider linking)
logging.info("NEW IDENTITY LINKED TO EXISTING USER: provider=%s, "
"provider_user_id=%s, email=%s, name=%s",
provider, provider_user_id, email, name)
logger.info(
"NEW IDENTITY LINKED TO EXISTING_USER",
provider=provider,
provider_user_id=provider_user_id,
email=email,
name=name,
)
return True, {"identity": identity, "user": user}
else:
# Existing identity login
logging.info("Existing identity found: provider=%s, email=%s", provider, email)
logger.info("Existing identity found", provider=provider, email=email)
return False, {"identity": identity, "user": user}
else:
logging.error("Failed to create/update identity and user: email=%s", email)
logger.error("Failed to create/update identity and user", email=email)
return False, None

except (AttributeError, ValueError, KeyError) as e:
logging.error("Error managing user in Organizations graph: %s", e)
logger.error("Error managing user in Organizations graph", error=str(e))
return False, None
except Exception as e:
logging.error("Unexpected error managing user in Organizations graph: %s", e)
logger.error("Unexpected error managing user in Organizations graph", error=str(e))
return False, None


def update_identity_last_login(provider, provider_user_id):
"""Update the last login timestamp for an existing identity"""
# Input validation
if not provider or not provider_user_id:
logging.error("Missing required parameters: provider=%s, provider_user_id=%s",
provider, provider_user_id)
logger.error(
"Missing required parameters",
provider=provider,
provider_user_id=provider_user_id,
)
return

# Validate provider is in allowed list
allowed_providers = ["google", "github"]
if provider not in allowed_providers:
logging.error("Invalid provider: %s", provider)
logger.error("Invalid provider", provider=provider)
return

try:
Expand All @@ -145,18 +162,32 @@ def update_identity_last_login(provider, provider_user_id):
SET identity.last_login = timestamp()
RETURN identity
"""
organizations_graph.query(update_query, {
"provider": provider,
"provider_user_id": provider_user_id
})
logging.info("Updated last login for identity: provider=%s, provider_user_id=%s",
provider, provider_user_id)
organizations_graph.query(
update_query,
{
"provider": provider,
"provider_user_id": provider_user_id,
},
)
logger.info(
"Updated last login for identity",
provider=provider,
provider_user_id=provider_user_id,
)
except (AttributeError, ValueError, KeyError) as e:
logging.error("Error updating last login for identity %s/%s: %s",
provider, provider_user_id, e)
logger.error(
"Error updating last login for identity",
provider=provider,
provider_user_id=provider_user_id,
error=str(e),
)
except Exception as e:
logging.error("Unexpected error updating last login for identity %s/%s: %s",
provider, provider_user_id, e)
logger.error(
"Unexpected error updating last login for identity",
provider=provider,
provider_user_id=provider_user_id,
error=str(e),
)


async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str, Any]], bool]:
Expand Down Expand Up @@ -192,17 +223,17 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
)
request.session["google_token"] = new_token
resp = await oauth.google.get("/oauth2/v2/userinfo", token=new_token)
logging.info("Google access token refreshed successfully")
logger.info("Google access token refreshed successfully")
except Exception as e:
logging.error("Google token refresh failed: %s", e)
logger.error("Google token refresh failed", error=str(e))
request.session.pop("google_token", None)
request.session.pop("user_info", None)
return None, False

if resp.status_code == 200:
google_user = resp.json()
if not google_user.get("id") or not google_user.get("email"):
logging.warning("Invalid Google user data received")
logger.warning("Invalid Google user data received")
request.session.pop("google_token", None)
request.session.pop("user_info", None)
return None, False
Expand All @@ -219,7 +250,7 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
request.session["token_validated_at"] = current_time
return user_info, True
except Exception as e:
logging.warning("Google OAuth validation error: %s", e)
logger.warning("Google OAuth validation error", error=str(e))
request.session.pop("google_token", None)
request.session.pop("user_info", None)

Expand All @@ -231,7 +262,7 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
if resp.status_code == 200:
github_user = resp.json()
if not github_user.get("id"):
logging.warning("Invalid GitHub user data received")
logger.warning("Invalid GitHub user data received")
request.session.pop("github_token", None)
request.session.pop("user_info", None)
return None, False
Expand All @@ -248,7 +279,7 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
email = email_resp.json()[0].get("email")

if not email:
logging.warning("No email found for GitHub user")
logger.warning("No email found for GitHub user")
request.session.pop("github_token", None)
request.session.pop("user_info", None)
return None, False
Expand All @@ -264,7 +295,7 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
request.session["token_validated_at"] = current_time
return user_info, True
except Exception as e:
logging.warning("GitHub OAuth validation error: %s", e)
logger.warning("GitHub OAuth validation error", error=str(e))
request.session.pop("github_token", None)
request.session.pop("user_info", None)

Expand All @@ -273,7 +304,7 @@ async def validate_and_cache_user(request: Request) -> Tuple[Optional[Dict[str,
return None, False

except Exception as e:
logging.error("Unexpected error in validate_and_cache_user: %s", e)
logger.error("Unexpected error in validate_and_cache_user", error=str(e))
request.session.pop("user_info", None)
return None, False

Expand Down Expand Up @@ -312,7 +343,7 @@ async def wrapper(request: Request, *args, **kwargs):
except HTTPException:
raise
except Exception as e:
logging.error("Unexpected error in token_required: %s", e)
logger.error("Unexpected error in token_required", error=str(e))
request.session.pop("user_info", None)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
Expand Down
Loading
Loading