When the request context carries a delegated API key ID (set by the in-process transport on behalf of a trusted caller like chatd), the handler must authenticate via the key_id field, skipping the header-based key extraction entirely. Validation succeeds or fails exactly as it would for a real API k
(t *testing.T)
| 182 | // LLM credentials must still be forwarded upstream while the Coder governance |
| 183 | // token is stripped. |
| 184 | func TestServeHTTP_DelegatedAPIKey(t *testing.T) { |
| 185 | t.Parallel() |
| 186 | |
| 187 | const testKeyID = "abcdef1234" |
| 188 | |
| 189 | tests := []struct { |
| 190 | name string |
| 191 | reqHeaders map[string]string |
| 192 | applyMocks func(t *testing.T, client *mock.MockDRPCClient, pool *mock.MockPooler, mockH *mockHandler) |
| 193 | expectStatus int |
| 194 | expectHandled bool |
| 195 | expectPresent map[string]string |
| 196 | expectAbsent []string |
| 197 | }{ |
| 198 | { |
| 199 | // Delegated + centralized: identity comes from the |
| 200 | // api key ID on the context, in lieu of a session |
| 201 | // token. No header credentials are sent and SessionKey |
| 202 | // is empty downstream. |
| 203 | name: "valid centralized", |
| 204 | applyMocks: func(t *testing.T, client *mock.MockDRPCClient, pool *mock.MockPooler, mockH *mockHandler) { |
| 205 | client.EXPECT().IsAuthorized(gomock.Any(), gomock.Any()).DoAndReturn( |
| 206 | func(_ context.Context, in *proto.IsAuthorizedRequest) (*proto.IsAuthorizedResponse, error) { |
| 207 | assert.Equal(t, testKeyID, in.GetKeyId(), "handler must use KeyId for delegated requests") |
| 208 | assert.Empty(t, in.GetKey(), "handler must not set Key for delegated requests") |
| 209 | return &proto.IsAuthorizedResponse{ |
| 210 | OwnerId: uuid.NewString(), |
| 211 | ApiKeyId: testKeyID, |
| 212 | Username: "u", |
| 213 | }, nil |
| 214 | }) |
| 215 | pool.EXPECT().Acquire(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( |
| 216 | func(_ context.Context, req aibridged.Request, _ aibridged.ClientFunc, _ aibridged.MCPProxyBuilder) (http.Handler, error) { |
| 217 | assert.Empty(t, req.SessionKey, |
| 218 | "delegated centralized request carries no session token") |
| 219 | return mockH, nil |
| 220 | }) |
| 221 | }, |
| 222 | expectStatus: http.StatusOK, |
| 223 | expectHandled: true, |
| 224 | expectAbsent: []string{ |
| 225 | "Authorization", |
| 226 | "X-Api-Key", |
| 227 | agplaibridge.HeaderCoderToken, |
| 228 | }, |
| 229 | }, |
| 230 | { |
| 231 | name: "valid BYOK preserves user credentials", |
| 232 | reqHeaders: map[string]string{ |
| 233 | // Marks BYOK; this header must be stripped before |
| 234 | // forwarding upstream. Its value is what gets |
| 235 | // surfaced downstream as the SessionKey because |
| 236 | // ExtractAuthToken prefers HeaderCoderToken. |
| 237 | agplaibridge.HeaderCoderToken: "coder-token-byok", |
| 238 | // The user's own LLM credential; must be preserved. |
| 239 | "Authorization": "Bearer sk-ant-oat01-user-token", |
| 240 | }, |
| 241 | applyMocks: func(t *testing.T, client *mock.MockDRPCClient, pool *mock.MockPooler, mockH *mockHandler) { |
nothing calls this directly
no test coverage detected