feat: migrate backend to fastAPI#1005
Conversation
|
This would be a good opportunity to expand integration tests to cover auth enabled [cookie + api token] |
I believe we can create those integration tests in another PR. There are other implications for that: we need a google account and a oauth app on the CI for us to test |
phact
left a comment
There was a problem hiding this comment.
approved assuming ci passes, recommend expanding unit tests here or in another PR
|
@lucaseduoli Good work! |
There was a problem hiding this comment.
Pull request overview
This pull request migrates the backend framework from Starlette to FastAPI, introducing dependency injection patterns and Pydantic models for request/response validation.
Changes:
- Replaced Starlette with FastAPI (version 0.115.0+) in dependencies
- Created new
dependencies.pymodule with FastAPI dependency injection for services and authentication - Refactored all API endpoints to use FastAPI decorators, Pydantic models, and
Depends()for dependency injection - Added
jwt_tokenfield to User dataclass and simplifiedAnonymousUserto use dataclass defaults
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml | Updated dependency from starlette to fastapi |
| uv.lock | Removed Darwin-specific platform markers from fastapi dependencies |
| src/dependencies.py | New module providing FastAPI dependency injection for services and authentication |
| src/session_manager.py | Added jwt_token field to User, refactored AnonymousUser, updated get_user_opensearch_client signature |
| src/main.py | Migrated from Starlette app to FastAPI app with add_api_route instead of Route objects |
| src/api/*.py | Refactored all endpoints to use FastAPI Depends(), Pydantic models, and removed manual request parsing |
| src/api/v1/*.py | Same refactoring for v1 API endpoints |
| CONTRIBUTING.md | Added tip about FastAPI interactive docs at /docs |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| user = request.state.user | ||
| jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token) | ||
| jwt_token = user.jwt_token |
There was a problem hiding this comment.
Incorrect indentation for the jwt_token assignment. This line is indented within the previous if block instead of being at the function level. This will cause the jwt_token to not be defined when current_config.edited is True, leading to a NameError.
| connector_type: str, | ||
| connection_id: str, |
There was a problem hiding this comment.
Missing query parameter extraction. The connection_id parameter is declared in the function signature but it's not being extracted from query parameters. In FastAPI, query parameters need to be explicitly defined or extracted. This should use connection_id: str = Query(...) from FastAPI to properly extract the query parameter, or it should be extracted from request.query_params manually.
| async def check_filename_exists( | ||
| filename: str, |
There was a problem hiding this comment.
Missing Query parameter import. The filename parameter is declared as a simple string parameter, but it's expected to come from query parameters based on the endpoint usage. This should import Query from FastAPI and use filename: str = Query(...) to properly extract the query parameter.
| async def get_ollama_models( | ||
| endpoint: Optional[str] = None, |
There was a problem hiding this comment.
Missing Query parameter import. The endpoint parameter is declared as an optional string parameter, but it's expected to come from query parameters based on the GET method usage. This should import Query from FastAPI and use endpoint: Optional[str] = Query(None) to properly extract the query parameter.
| async def check_provider_health( | ||
| provider: Optional[str] = None, | ||
| test_completion: bool = False, |
There was a problem hiding this comment.
Missing Query parameter import. The provider and test_completion parameters are declared in the function signature but should come from query parameters based on the GET method usage. These should import Query from FastAPI and use provider: Optional[str] = Query(None) and test_completion: bool = Query(False) to properly extract the query parameters.
| user = request.state.user | ||
| jwt_token = session_manager.get_effective_jwt_token(user.user_id, request.state.jwt_token) | ||
| # Get JWT token | ||
| jwt_token = user.jwt_token |
There was a problem hiding this comment.
Incorrect indentation for the jwt_token assignment. This line is indented too far, making it part of the previous if block instead of being at the function level. This will cause an IndentationError when running the code.
| jwt_token = user.jwt_token | |
| jwt_token = user.jwt_token |
|
example: |
| status_code=400 | ||
| ) | ||
| logger.error("Invalid request for flow reset", error=str(e)) | ||
| return JSONResponse({"success": False, "error": str(e)}, status_code=400) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this kind of problem you should avoid sending raw exception messages or stack traces back to clients. Instead, log the detailed exception information on the server for debugging and observability, and return a generic, user-safe error message in the HTTP response. This preserves developer visibility while preventing attackers from learning about internal implementation details.
In this specific function, the problematic behavior is in the except ValueError as e: block: it logs str(e) (which is fine) and then returns a JSON body whose "error" field is also str(e). To fix this without changing the overall behavior or status codes, we should keep the detailed message in the logs but replace the client-facing error message with a generic description of the problem, such as "Invalid request for flow reset". We should apply the same principle that is already used in the generic Exception handler below, which returns "Internal server error: ..." but should avoid appending str(e) as well (since that also leaks details). However, CodeQL’s alert is specifically for the ValueError block, so we will focus on ensuring that block no longer exposes str(e) and, for consistency and security hardening, also make the generic exception message not include str(e).
Concretely:
- In
src/api/flows.py, inreset_flow_endpoint, update theexcept ValueError as e:clause so that:- Logging remains as-is or is slightly improved (it already logs the error string).
- The JSON response uses a fixed, generic error message instead of
str(e).
- In the
except Exception as e:clause:- Keep logging
str(e)on the server. - Return a fully generic message like
"Internal server error"without interpolatingstr(e)into the response.
No new imports or helper methods are required; we only adjust the strings passed toJSONResponse.
- Keep logging
| @@ -48,10 +48,13 @@ | ||
|
|
||
| except ValueError as e: | ||
| logger.error("Invalid request for flow reset", error=str(e)) | ||
| return JSONResponse({"success": False, "error": str(e)}, status_code=400) | ||
| return JSONResponse( | ||
| {"success": False, "error": "Invalid request for flow reset"}, | ||
| status_code=400, | ||
| ) | ||
| except Exception as e: | ||
| logger.error("Unexpected error in flow reset", error=str(e)) | ||
| return JSONResponse( | ||
| {"success": False, "error": f"Internal server error: {str(e)}"}, | ||
| status_code=500 | ||
| {"success": False, "error": "Internal server error"}, | ||
| status_code=500, | ||
| ) |
| "success": False, | ||
| "error": f"Internal server error: {str(e)}" | ||
| }, | ||
| {"success": False, "error": f"Internal server error: {str(e)}"}, |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix information exposure via exceptions, you should avoid returning raw exception messages or stack traces to clients. Instead, log the detailed exception information server-side (including message and stack trace if desired) and return a generic error message to the user. This preserves debuggability for developers while preventing attackers from learning about internals.
For this specific function in src/api/flows.py, the best fix is to stop including str(e) in the JSON response for the generic except Exception as e: block. We should keep logging the error message (and potentially stack trace) using the existing logger, but the response body should contain a generic message like "Internal server error" without any details from e. We can also slightly improve logging by including exc_info=True so the stack trace is captured in logs, but that’s optional and not required for the fix.
Concretely:
- In the
except Exception as e:block (lines 52–57), keep or improve the logging call but change theJSONResponsepayload so that the"error"field is a static string and does not interpolatestr(e). - Do not change the
ValueErrorhandler on lines 49–51, because those messages are generally considered validation/user errors and are already treated as client-facing; the CodeQL alert is specifically about the generic exception handler. - No new imports or external libraries are needed; we can rely on the existing
loggerandJSONResponse.
| @@ -52,6 +52,6 @@ | ||
| except Exception as e: | ||
| logger.error("Unexpected error in flow reset", error=str(e)) | ||
| return JSONResponse( | ||
| {"success": False, "error": f"Internal server error: {str(e)}"}, | ||
| {"success": False, "error": "Internal server error"}, | ||
| status_code=500 | ||
| ) |
| return JSONResponse( | ||
| {"error": f"Invalid queryData format: {str(e)}"}, status_code=400 | ||
| ) | ||
| return JSONResponse({"error": f"Invalid queryData format: {str(e)}"}, status_code=400) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix information exposure via exceptions, avoid returning raw exception messages or stack traces to clients. Instead, log the detailed error on the server and return a generic, user-friendly message (optionally with a stable error code) that does not contain internal details.
For this specific case, the best fix without changing functionality is to stop including str(e) in the JSON response at line 91. We should keep logging the detailed exception for debugging (logger.error(...)), but the client-facing message should be generic, such as "Invalid queryData format". This preserves behavior (400 status code and indication of what went wrong at a high level) while removing exposure of the underlying exception message.
Concretely:
- In
create_knowledge_filterinsrc/api/knowledge_filter.py, change theJSONResponsein theexceptblock so that it no longer interpolateseorstr(e)into the response. - No new imports or helper methods are required; we already log the detailed exception.
| @@ -88,7 +88,7 @@ | ||
| normalized_query_data = normalize_query_data(body.queryData) | ||
| except Exception as e: | ||
| logger.error(f"Failed to normalize query_data: {e}") | ||
| return JSONResponse({"error": f"Invalid queryData format: {str(e)}"}, status_code=400) | ||
| return JSONResponse({"error": "Invalid queryData format"}, status_code=400) | ||
|
|
||
| jwt_token = user.jwt_token | ||
|
|
| return JSONResponse( | ||
| {"error": f"Invalid queryData format: {str(e)}"}, status_code=400 | ||
| ) | ||
| return JSONResponse({"error": f"Invalid queryData format: {str(e)}"}, status_code=400) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this issue you should avoid returning raw exception messages to the client. Instead, return a generic, user-safe error message, and log the detailed exception on the server side for debugging. This prevents potential leakage of internal details while preserving observability for developers.
For this specific code, the best fix with minimal functional change is to keep logging the detailed error (logger.error(...)) but change the HTTP response so that it does not interpolate str(e). Replace the current JSONResponse({"error": f"Invalid queryData format: {str(e)}"}, status_code=400) with a response that uses a generic message, such as {"error": "Invalid queryData format"}. No new imports are needed; we keep using the existing logger. The change is confined to src/api/knowledge_filter.py around lines 193–197.
| @@ -194,7 +194,7 @@ | ||
| normalized_query_data = normalize_query_data(query_data) | ||
| except Exception as e: | ||
| logger.error(f"Failed to normalize query_data: {e}") | ||
| return JSONResponse({"error": f"Invalid queryData format: {str(e)}"}, status_code=400) | ||
| return JSONResponse({"error": "Invalid queryData format"}, status_code=400) | ||
|
|
||
| updated_filter = { | ||
| "id": filter_id, |
| return JSONResponse( | ||
| {"error": f"Webhook processing failed: {str(e)}"}, status_code=500 | ||
| ) | ||
| return JSONResponse({"error": f"Webhook processing failed: {str(e)}"}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, the fix is to avoid sending raw exception data back to the client and instead return a generic, non-sensitive error message while logging detailed information on the server. Stack traces and exception messages should be captured only in server-side logs.
Concretely in src/api/knowledge_filter.py, within knowledge_filter_webhook, we should:
- Keep logging the error (ideally including the traceback) using
logger.error. - Remove the
traceback.print_exc()call, which prints to stderr and can leak details in some deployments. - Change the JSON response to use a generic error message that does not include
str(e)or any internal data.
We can achieve this by:
- Importing
tracebackat the top of the file (instead of inside the handler) and usingtraceback.format_exc()to capture the stack trace as a string, which we then pass tologger.erroras structured data. - Updating the
exceptblock to return something like{"error": "Webhook processing failed"}without interpolating the exception message.
No new functions are required; only modifications within the shown function and an additional import are needed.
| @@ -9,6 +9,7 @@ | ||
| from utils.logging_config import get_logger | ||
| from dependencies import get_knowledge_filter_service, get_monitor_service, get_session_manager, get_current_user | ||
| from session_manager import User | ||
| import traceback | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
| @@ -423,7 +424,9 @@ | ||
| filter_id=filter_id, | ||
| subscription_id=subscription_id, | ||
| error=str(e), | ||
| traceback=traceback.format_exc(), | ||
| ) | ||
| import traceback | ||
| traceback.print_exc() | ||
| return JSONResponse({"error": f"Webhook processing failed: {str(e)}"}, status_code=500) | ||
| return JSONResponse( | ||
| {"error": "Webhook processing failed"}, | ||
| status_code=500, | ||
| ) |
| error_msg = str(e) | ||
| if "AuthenticationException" in error_msg or "access denied" in error_msg.lower(): | ||
| return JSONResponse({"error": error_msg}, status_code=403) | ||
| return JSONResponse({"error": error_msg}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this kind of problem you stop sending raw exception messages or stack traces directly to the client. Instead, you log the detailed error (including stack trace) on the server and return a generic, non-sensitive message to the user, optionally with a stable error code they can reference.
For this specific code, the best fix with minimal behavior change is:
- Keep logging
str(e)and the traceback as is. - For the generic 500 case, stop returning
{"error": error_msg}and instead return a generic message such as{"error": "An internal error has occurred."}. - For the special authentication/authorization case, avoid exposing the full raw message. Instead, return a constrained, generic error message (for example,
"Authentication failed"or"Access denied") and optionally log the detailederror_msgas is already being done.
Concretely, in src/api/router.py:
- Replace the 403 branch so that it returns a fixed message, not
error_msg. - Replace the 500 branch so that it returns a fixed, generic message.
No new imports or helper functions are required; we only adjust the JSONResponse payloads in the except Exception as e: block at the end of _langflow_upload_ingest_task.
| @@ -168,5 +168,5 @@ | ||
| logger.error("Full traceback", traceback=traceback.format_exc()) | ||
| error_msg = str(e) | ||
| if "AuthenticationException" in error_msg or "access denied" in error_msg.lower(): | ||
| return JSONResponse({"error": error_msg}, status_code=403) | ||
| return JSONResponse({"error": error_msg}, status_code=500) | ||
| return JSONResponse({"error": "Access denied"}, status_code=403) | ||
| return JSONResponse({"error": "An internal error has occurred."}, status_code=500) |
| ) | ||
|
|
||
| logger.error("Failed to list conversations", error=str(e), user_id=user.user_id) | ||
| return JSONResponse({"error": f"Failed to list conversations: {str(e)}"}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix information exposure via exceptions, log the detailed error (and stack trace if desired) on the server, but return only a generic, non-sensitive message to the client. Do not include str(e) or other raw exception data in the API response.
Concretely for this file, we should update all the error-handling branches that currently return responses like {"error": f"...: {str(e)}"} to instead return a constant, generic error message. We will keep the existing logging as-is so that developers still have access to str(e) (and potentially stack traces if configured via logger). This preserves behavior in terms of status codes and high-level semantics while removing sensitive details from the user-visible surface.
Specifically:
- In
chat_list_endpoint(around lines 150–152), keep thelogger.errorcall but change the JSON response to something like{"error": "Failed to list conversations"}without interpolatingstr(e). - In
chat_get_endpoint(around lines 194–196), similarly return a generic"Failed to get conversation"message withoutstr(e). - In
chat_delete_endpoint(around lines 214–216), change the exception handler’s response to a generic"Failed to delete conversation"message.
No new imports or helper methods are required; we only alter the response bodies to remove the embedded exception text.
| @@ -149,7 +149,7 @@ | ||
| return JSONResponse({"conversations": conversations}) | ||
| except Exception as e: | ||
| logger.error("Failed to list conversations", error=str(e), user_id=user.user_id) | ||
| return JSONResponse({"error": f"Failed to list conversations: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to list conversations"}, status_code=500) | ||
|
|
||
|
|
||
| async def chat_get_endpoint( | ||
| @@ -193,7 +193,7 @@ | ||
| }) | ||
| except Exception as e: | ||
| logger.error("Failed to get conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to get conversation: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to get conversation"}, status_code=500) | ||
|
|
||
|
|
||
| async def chat_delete_endpoint( | ||
| @@ -213,4 +213,4 @@ | ||
| ) | ||
| except Exception as e: | ||
| logger.error("Failed to delete conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to delete conversation: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to delete conversation"}, status_code=500) |
| user_id = user.user_id | ||
| chat_id = request.path_params.get("chat_id") | ||
| logger.error("Failed to get conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to get conversation: {str(e)}"}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix information exposure through exceptions, you should avoid including raw exception messages or stack traces in responses sent to clients. Instead, log the detailed error on the server (so developers can debug) and return a generic, non-sensitive error message to the client, possibly with a stable error code if needed.
For this specific code, we should keep logging the exception with context (user_id, chat_id) but change the JSONResponse returned in the except block of chat_get_endpoint to no longer embed str(e). It should return a generic error message such as "Failed to get conversation" and a 500 status code. The logging call can remain as-is, because it is server-side and not exposed to users. We will make a minimal change: only update the response body in the except block of chat_get_endpoint (lines 194–196) in src/api/v1/chat.py. No new imports or helper methods are needed.
| @@ -193,7 +193,7 @@ | ||
| }) | ||
| except Exception as e: | ||
| logger.error("Failed to get conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to get conversation: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to get conversation"}, status_code=500) | ||
|
|
||
|
|
||
| async def chat_delete_endpoint( |
| status_code=500, | ||
| ) | ||
| logger.error("Failed to delete conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to delete conversation: {str(e)}"}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this kind of problem, avoid sending raw exception details (messages, stack traces) back to the client. Instead, log the full error details on the server (including the exception and optionally the stack trace) and return a generic, user-safe error message and status code in the HTTP response.
For this specific code, the minimal, non-breaking change is to modify the except block in chat_delete_endpoint so that:
- The
logger.errorcall continues to record the detailed error on the server (you may keepstr(e)there). - The JSON response body no longer includes
str(e)and instead uses a fixed, generic error message such as"Failed to delete conversation". - The HTTP status code remains 500 to preserve behavior, while the response body is sanitized.
No new imports or helper methods are strictly required. We only need to change the return statement within the except block in chat_delete_endpoint in src/api/v1/chat.py to remove the interpolation of str(e).
| @@ -213,4 +213,4 @@ | ||
| ) | ||
| except Exception as e: | ||
| logger.error("Failed to delete conversation", error=str(e), user_id=user.user_id, chat_id=chat_id) | ||
| return JSONResponse({"error": f"Failed to delete conversation: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to delete conversation"}, status_code=500) |
| {"error": f"Failed to retrieve models: {str(e)}"}, | ||
| status_code=500, | ||
| ) | ||
| return JSONResponse({"error": f"Failed to retrieve models: {str(e)}"}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To fix the issue, the user-facing error response should not include str(e) (or any detailed exception message). Instead, return a generic, non-sensitive error message, while ensuring the full details are logged on the server for debugging.
The best way to fix this without changing existing functionality is:
- Keep the
logger.errorcall so that the detailed exception (including its message) is recorded in server logs. - Change the
JSONResponsebody so it no longer interpolatesstr(e)and instead uses a generic string like"Internal server error while retrieving models.". - Optionally, you could include a generic error code or a correlation ID, but that would require additional context not present in the snippet, so we will not introduce that.
Concretely, in src/api/v1/models.py, within list_models_endpoint, update the except Exception as e: block:
- Leave line 80 (logging) unchanged.
- Change line 81 to remove
str(e)from the error message, returning a fixed, generic error message.
No new imports or helper methods are required.
| @@ -78,4 +78,4 @@ | ||
| return JSONResponse(models) | ||
| except Exception as e: | ||
| logger.error("Failed to list models for provider %s: %s", provider, str(e)) | ||
| return JSONResponse({"error": f"Failed to retrieve models: {str(e)}"}, status_code=500) | ||
| return JSONResponse({"error": "Failed to retrieve models due to an internal server error."}, status_code=500) |
|
@lucaseduoli good to merge? |
The project moved to FastAPI in PR langflow-ai#1005, this keeps the docs and templates up to date
This pull request updates a core dependency in the project by replacing
starlettewithfastapiin thepyproject.tomlfile. This change likely reflects a shift to using FastAPI directly as the main web framework, which is built on top of Starlette and provides additional features for building APIs.Dependency update:
starlettedependency withfastapi(version 0.115.0 or higher) inpyproject.toml, indicating a move to use FastAPI as the main framework.