3434from mcp .server .streamable_http_manager import StreamableHTTPSessionManager
3535from sqlalchemy .orm import selectinload
3636from sqlmodel import select
37- from sqlmodel .ext .asyncio .session import AsyncSession
3837
3938from langflow .api .utils import (
4039 CurrentActiveMCPUser ,
9190
9291
9392async def verify_project_auth (
94- db : AsyncSession ,
9593 project_id : UUID ,
9694 query_param : str | None ,
9795 header_param : str | None ,
@@ -110,26 +108,26 @@ async def verify_project_auth(
110108
111109 settings_service = get_settings_service ()
112110
113- project = (await db .exec (select (Folder ).where (Folder .id == project_id ))).first ()
114-
115- if not project :
116- raise HTTPException (status_code = 404 , detail = "Project not found" )
117-
118- auth_settings : AuthSettings | None = None
119- # Check if this project requires API key only authentication
120- if project .auth_settings :
121- auth_settings = AuthSettings (** project .auth_settings )
111+ # Resolve project authentication policy before API-key authentication opens
112+ # its owned transaction. Keep only scalar values after this scope exits.
113+ async with session_scope () as db :
114+ project = (await db .exec (select (Folder ).where (Folder .id == project_id ))).first ()
115+ if not project :
116+ raise HTTPException (status_code = 404 , detail = "Project not found" )
117+ project_user_id = project .user_id
118+ auth_settings = AuthSettings (** project .auth_settings ) if project .auth_settings else None
122119
123120 project_auth_type = auth_settings .auth_type if auth_settings else None
124121 if project_auth_type == "oauth" and composer_backend_token :
125122 mcp_composer_service : MCPComposerService = cast (
126123 MCPComposerService , get_service (ServiceType .MCP_COMPOSER_SERVICE )
127124 )
128125 if mcp_composer_service .validate_backend_auth_token (str (project_id ), composer_backend_token ):
129- if project .user_id :
130- project_user = await db .get (User , project .user_id )
131- if project_user :
132- return project_user
126+ if project_user_id :
127+ async with session_scope () as db :
128+ project_user = await db .get (User , project_user_id )
129+ if project_user :
130+ return project_user
133131 raise HTTPException (status_code = 404 , detail = "Project owner not found" )
134132
135133 # OAuth projects must present a valid API key at the Langflow transport endpoint: network-level
@@ -159,33 +157,30 @@ async def verify_project_auth(
159157 )
160158
161159 # Validate the API key
162- api_key_result = await authenticate_api_key (db , api_key )
160+ api_key_result = await authenticate_api_key (api_key )
163161 if not api_key_result :
164162 raise HTTPException (status_code = 401 , detail = "Invalid API key" )
165163 set_current_auth_context (AuthCredentialContext .from_api_key_result (api_key_result ))
166164 user = api_key_result .user
167165
168166 # Verify user has access to the project
169- project_access = (
170- await db .exec (select (Folder ).where (Folder .id == project_id , Folder .user_id == user .id ))
171- ).first ()
172-
173- if not project_access :
167+ if project_user_id != user .id :
174168 raise HTTPException (status_code = 404 , detail = "Project not found" )
175169
176170 return user
177171
178- return await _superuser_fallback (db , settings_service )
172+ return await _superuser_fallback (settings_service )
179173
180174
181- async def _superuser_fallback (db : AsyncSession , settings_service ) -> User :
175+ async def _superuser_fallback (settings_service ) -> User :
182176 """Resolve the configured superuser for unauthenticated MCP paths that allow fallback."""
183177 if not settings_service .auth_settings .SUPERUSER :
184178 raise HTTPException (
185179 status_code = status .HTTP_400_BAD_REQUEST ,
186180 detail = "Missing superuser username in auth settings" ,
187181 )
188- result = await get_user_by_username (db , settings_service .auth_settings .SUPERUSER )
182+ async with session_scope () as db :
183+ result = await get_user_by_username (db , settings_service .auth_settings .SUPERUSER )
189184 if result :
190185 logger .warning (AUTO_LOGIN_WARNING )
191186 set_current_auth_context (AuthCredentialContext (method = AUTH_METHOD_AUTO_LOGIN ))
@@ -206,51 +201,47 @@ async def verify_project_auth_conditional(
206201 - MCP Composer enabled + API key auth: Only allow API keys
207202 - All other cases: Use standard MCP auth (JWT + API keys)
208203 """
204+ # Extract token
205+ token : str | None = None
206+ auth_header = request .headers .get ("authorization" )
207+ if auth_header and auth_header .startswith ("Bearer " ):
208+ token = auth_header [7 :]
209+
210+ # Extract API keys
211+ api_key_query_value = request .query_params .get ("x-api-key" )
212+ api_key_header_value = request .headers .get ("x-api-key" )
213+ composer_backend_token = request .headers .get (COMPOSER_BACKEND_AUTH_HEADER )
214+
215+ # The composer path performs its own short project-policy read before auth.
216+ if get_settings_service ().settings .mcp_composer_enabled :
217+ return await verify_project_auth (
218+ project_id ,
219+ api_key_query_value ,
220+ api_key_header_value ,
221+ composer_backend_token ,
222+ )
223+
224+ # Preserve the existing not-found-before-auth behavior, but close this read
225+ # scope before API-key authentication opens its owned transaction.
209226 async with session_scope () as session :
210- # Get project to check auth settings
211227 project = (await session .exec (select (Folder ).where (Folder .id == project_id ))).first ()
212-
213228 if not project :
214229 raise HTTPException (status_code = 404 , detail = "Project not found" )
230+ project_user_id = project .user_id
215231
216- # Extract token
217- token : str | None = None
218- auth_header = request .headers .get ("authorization" )
219- if auth_header and auth_header .startswith ("Bearer " ):
220- token = auth_header [7 :]
221-
222- # Extract API keys
223- api_key_query_value = request .query_params .get ("x-api-key" )
224- api_key_header_value = request .headers .get ("x-api-key" )
225- composer_backend_token = request .headers .get (COMPOSER_BACKEND_AUTH_HEADER )
226-
227- # Check if this project requires API key only authentication
228- if get_settings_service ().settings .mcp_composer_enabled :
229- return await verify_project_auth (
230- session ,
231- project_id ,
232- api_key_query_value ,
233- api_key_header_value ,
234- composer_backend_token ,
235- )
236-
237- # For all other cases, use standard MCP authentication (allows JWT + API keys)
238- # Call the MCP auth function directly
239- from langflow .services .auth .utils import get_current_user_mcp
232+ # For all other cases, use standard MCP authentication (allows JWT + API keys).
233+ # This session has not executed a query before API-key authentication.
234+ from langflow .services .auth .utils import get_current_user_mcp
240235
236+ async with session_scope () as session :
241237 user = await get_current_user_mcp (
242238 token = token or "" , query_param = api_key_query_value , header_param = api_key_header_value , db = session
243239 )
244240
245- # Verify project access
246- project_access = (
247- await session .exec (select (Folder ).where (Folder .id == project_id , Folder .user_id == user .id ))
248- ).first ()
249-
250- if not project_access :
251- raise HTTPException (status_code = 404 , detail = "Project not found" )
241+ if project_user_id != user .id :
242+ raise HTTPException (status_code = 404 , detail = "Project not found" )
252243
253- return user
244+ return user
254245
255246
256247# Create project-specific context variable
0 commit comments