feat(BA-2806): Implement WebSocket connectionParams to HTTP headers conversion#6379
Conversation
… and remove deprecated router and supergraph configurations
There was a problem hiding this comment.
Pull Request Overview
This PR implements WebSocket connectionParams to HTTP headers conversion for the GraphQL gateway, enabling authentication parameters sent via WebSocket connection_init to be forwarded as HTTP headers in federation requests to subgraphs.
Key Changes:
- Added a custom plugin (
useConnectionParamsToHeadersPlugin) that intercepts subgraph execution to inject WebSocket connectionParams as HTTP headers - Updated
propagateHeaders.fromClientToSubgraphsto use context-based headers instead of request headers - Configured WebSocket subscriptions with connectionParams that map context headers
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| configs/graphql/supergraph.yaml | Removed legacy Apollo Router supergraph configuration |
| configs/graphql/router.yaml | Removed legacy Apollo Router configuration |
| configs/graphql/gateway.config.ts | Implemented WebSocket connectionParams to headers conversion plugin and updated gateway configuration |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| if (connectionParams && typeof connectionParams === 'object') { | ||
| // Wrap executor to inject connectionParams as HTTP headers | ||
| const originalExecutor = executor; | ||
| const wrappedExecutor = async (execRequest: any) => { |
There was a problem hiding this comment.
The execRequest parameter uses any type, which bypasses TypeScript's type safety. Consider using a proper type from the gateway library or defining an appropriate interface for the execution request.
| const wrappedExecutor = async (execRequest: any) => { | |
| interface ExecutorRequest { | |
| context: { | |
| headers?: Record<string, string>; | |
| [key: string]: any; | |
| }; | |
| extensions?: Record<string, any>; | |
| [key: string]: any; | |
| } | |
| const wrappedExecutor = async (execRequest: ExecutorRequest) => { |
| const originalExecutor = executor; | ||
| const wrappedExecutor = async (execRequest: any) => { | ||
| // Extract Authorization header from context if it exists | ||
| let reqHeaders = execRequest.context.headers || {}; |
There was a problem hiding this comment.
Mutating the original headers object with delete could cause unintended side effects. Create a copy of the headers object before modification: const reqHeaders = { ...(execRequest.context.headers || {}) }; followed by delete reqHeaders['content-length'];
| let reqHeaders = execRequest.context.headers || {}; | |
| const reqHeaders = { ...(execRequest.context.headers || {}) }; |
| connectionParams: { | ||
| token: '{context.headers.authorization}' | ||
| } |
There was a problem hiding this comment.
The string literal '{context.headers.authorization}' appears to be a template string but may not be processed as expected. Add a comment explaining the expected format or mechanism by which this placeholder is resolved at runtime.
| connectionParams: { | |
| token: '{context.headers.authorization}' | |
| } | |
| // Set connectionParams as a function to dynamically inject the Authorization header at runtime | |
| connectionParams: (context: any) => ({ | |
| token: context.headers?.authorization | |
| }) |
resolves #6378 (BA-2806)
Checklist: (if applicable)
ai.backend.testdocsdirectory