From 61a223f3a51fdd60243e841b08165512316be2a3 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Sun, 28 Jun 2026 09:32:14 -0700 Subject: [PATCH 1/2] refactor: extract shared auth header resolution helper for provider adapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add resolveAuthHeadersWithFallback() to oidc-adapter-utils.js, which encapsulates the repeated OIDC-check-then-static-fallback pattern used across provider adapters. Updated Anthropic adapter to use the new helper for validationHeaders and modelsFetchHeaders, eliminating duplicated OIDC/static branching. The new helper combines resolveOidcAuthHeaders() with static-key fallback into a single call: 1. OIDC token available → use buildOidcHeaders(token) 2. OIDC configured but no token → return {} (fail-safe) 3. No OIDC → return staticHeaders Closes #5620 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/api-proxy/oidc-adapter-utils.js | 25 +++++++++++++ containers/api-proxy/providers/anthropic.js | 40 ++++++++++----------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/containers/api-proxy/oidc-adapter-utils.js b/containers/api-proxy/oidc-adapter-utils.js index e26f7939a..c92f23acd 100644 --- a/containers/api-proxy/oidc-adapter-utils.js +++ b/containers/api-proxy/oidc-adapter-utils.js @@ -88,9 +88,34 @@ function resolveOidcAuthHeaders({ oidcProvider, awsOidcProvider, buildOidcHeader return null; } +/** + * Resolve auth headers with automatic static-key fallback. + * + * Combines OIDC resolution with a static-key fallback into a single call, + * encapsulating the repeated pattern across provider adapters: + * 1. If OIDC provider has a token → use buildOidcHeaders(token) + * 2. If OIDC is configured but no token yet → return empty {} (fail-safe) + * 3. If no OIDC → return staticHeaders + * + * @param {object} opts + * @param {{ getToken: () => (string|undefined|null) }|null|undefined} [opts.oidcProvider] + * @param {unknown} [opts.awsOidcProvider] + * @param {(token: string) => Record} opts.buildOidcHeaders + * @param {Record} opts.staticHeaders - Headers to use when no OIDC is configured + * @returns {Record} + */ +function resolveAuthHeadersWithFallback({ oidcProvider, awsOidcProvider, buildOidcHeaders, staticHeaders }) { + const oidcHeaders = resolveOidcAuthHeaders({ oidcProvider, awsOidcProvider, buildOidcHeaders }); + if (oidcHeaders !== null) { + return oidcHeaders; + } + return staticHeaders; +} + module.exports = { isValidHeaderName, validateAuthHeaderEnv, createOidcRuntimeAdapterMethods, resolveOidcAuthHeaders, + resolveAuthHeadersWithFallback, }; diff --git a/containers/api-proxy/providers/anthropic.js b/containers/api-proxy/providers/anthropic.js index 1d3ce4282..539b6f93c 100644 --- a/containers/api-proxy/providers/anthropic.js +++ b/containers/api-proxy/providers/anthropic.js @@ -20,6 +20,7 @@ const { const { validateAuthHeaderEnv, resolveOidcAuthHeaders, + resolveAuthHeadersWithFallback, } = require('../oidc-adapter-utils'); const { createBaseAdapterConfig, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory'); const { AnthropicOidcTokenProvider } = require('../anthropic-oidc-token-provider'); @@ -127,20 +128,16 @@ function createAnthropicAdapter(env, deps = {}) { validationPath: '/v1/messages', validationMethod: 'POST', validationBody: '{}', - validationHeaders: () => { - if (oidcProvider && oidcProvider.isReady()) { - return { - 'Authorization': `Bearer ${oidcProvider.getToken()}`, - 'anthropic-version': '2023-06-01', - 'content-type': 'application/json', - }; - } - return { - [authHeaderName]: apiKey, - 'anthropic-version': '2023-06-01', - 'content-type': 'application/json', - }; - }, + validationHeaders: () => ({ + ...resolveAuthHeadersWithFallback({ + oidcProvider, + awsOidcProvider: null, + buildOidcHeaders: (token) => ({ 'Authorization': `Bearer ${token}` }), + staticHeaders: { [authHeaderName]: apiKey }, + }), + 'anthropic-version': '2023-06-01', + 'content-type': 'application/json', + }), validationSkip: () => { if (!oidcConfigured) return null; // After OIDC init, validate using the acquired token @@ -149,12 +146,15 @@ function createAnthropicAdapter(env, deps = {}) { }, skipModelsFetch: () => oidcConfigured && !oidcProvider?.isReady(), modelsPath: '/v1/models', - modelsFetchHeaders: () => { - if (oidcProvider && oidcProvider.isReady()) { - return { 'Authorization': `Bearer ${oidcProvider.getToken()}`, 'anthropic-version': '2023-06-01' }; - } - return { [authHeaderName]: apiKey, 'anthropic-version': '2023-06-01' }; - }, + modelsFetchHeaders: () => ({ + ...resolveAuthHeadersWithFallback({ + oidcProvider, + awsOidcProvider: null, + buildOidcHeaders: (token) => ({ 'Authorization': `Bearer ${token}` }), + staticHeaders: { [authHeaderName]: apiKey }, + }), + 'anthropic-version': '2023-06-01', + }), reflectionConfigured: !!apiKey || oidcRequested, reflectionExtra: () => ({ auth_type: oidcRequested ? 'github-oidc/anthropic' : 'static-key', From 0ddf68f98a0c8f72f0e0dad76abe6a037955cf13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:44:11 +0000 Subject: [PATCH 2/2] test: cover auth header fallback helper branches --- containers/api-proxy/proxy-utils.oidc.test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/containers/api-proxy/proxy-utils.oidc.test.js b/containers/api-proxy/proxy-utils.oidc.test.js index b1a366b6b..705d58e1a 100644 --- a/containers/api-proxy/proxy-utils.oidc.test.js +++ b/containers/api-proxy/proxy-utils.oidc.test.js @@ -4,6 +4,7 @@ const { validateAuthHeaderEnv, createOidcRuntimeAdapterMethods, resolveOidcAuthHeaders, + resolveAuthHeadersWithFallback, } = require('./oidc-adapter-utils'); describe('isValidHeaderName', () => { @@ -106,3 +107,38 @@ describe('resolveOidcAuthHeaders', () => { expect(headers).toEqual({ Authorization: 'Bearer bearer-oidc-token' }); }); }); + +describe('resolveAuthHeadersWithFallback', () => { + it('returns OIDC headers when token is available', () => { + const headers = resolveAuthHeadersWithFallback({ + oidcProvider: { getToken: () => 'oidc-token' }, + awsOidcProvider: null, + buildOidcHeaders: (token) => ({ Authorization: ['oidc', token].join(':') }), + staticHeaders: { 'x-api-key': 'static-token' }, + }); + + expect(headers).toEqual({ Authorization: 'oidc:oidc-token' }); + }); + + it('returns an empty object when OIDC is configured but token is unavailable', () => { + const headers = resolveAuthHeadersWithFallback({ + oidcProvider: { getToken: () => '' }, + awsOidcProvider: null, + buildOidcHeaders: () => ({ Authorization: 'ignored-token' }), + staticHeaders: { 'x-api-key': 'static-token' }, + }); + + expect(headers).toEqual({}); + }); + + it('falls back to static headers when OIDC is not configured', () => { + const headers = resolveAuthHeadersWithFallback({ + oidcProvider: null, + awsOidcProvider: null, + buildOidcHeaders: () => ({ Authorization: 'ignored-token' }), + staticHeaders: { 'x-api-key': 'static-token' }, + }); + + expect(headers).toEqual({ 'x-api-key': 'static-token' }); + }); +});