Skip to content

Commit 4c63025

Browse files
authored
Refactor provider OIDC auth-header plumbing into a shared resolver (#5994)
* Initial plan * refactor: dedupe oidc auth header wiring --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent a5799cc commit 4c63025

5 files changed

Lines changed: 96 additions & 30 deletions

File tree

containers/api-proxy/providers/anthropic.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const {
2121
const { createProviderAuthScaffold, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory');
2222
const { AnthropicOidcTokenProvider } = require('../anthropic-oidc-token-provider');
2323
const { ANTHROPIC_ENV } = require('../provider-env-constants');
24-
const { createProviderOidcAuth } = require('./cloud-oidc-init');
24+
const { createProviderOidcAuth, createProviderOidcHeaderResolver } = require('./cloud-oidc-init');
2525
const { bearerAuthHeaders, providerKeyHeaders } = require('./auth-headers');
2626

2727
let makeAnthropicTransform, loadCustomTransform, EXTENDED_CACHE_BETA;
@@ -116,10 +116,11 @@ function createAnthropicAdapter(env, deps = {}) {
116116
const composedBodyTransform = composeBodyTransforms(depsBodyTransform, optimisationsTransform);
117117
const buildOidcBearerAuthHeaders = (token) => bearerAuthHeaders(token);
118118
const buildStaticAuthHeaders = () => providerKeyHeaders(authHeaderName, apiKey);
119-
const resolveAuthHeadersForValidationAndModels = () => resolveAuthHeaders(
120-
buildOidcBearerAuthHeaders,
121-
buildStaticAuthHeaders(),
122-
);
119+
const oidcHeaderResolver = createProviderOidcHeaderResolver({
120+
resolveAuthHeaders,
121+
buildOidcHeaders: buildOidcBearerAuthHeaders,
122+
buildStaticHeaders: buildStaticAuthHeaders,
123+
});
123124
const adapterMethods = createAdapterMethods({
124125
apiKey,
125126
rawTarget,
@@ -131,7 +132,7 @@ function createAnthropicAdapter(env, deps = {}) {
131132
validationMethod: 'POST',
132133
validationBody: '{}',
133134
validationHeaders: () => ({
134-
...resolveAuthHeadersForValidationAndModels(),
135+
...oidcHeaderResolver.resolveHeaders(),
135136
'anthropic-version': '2023-06-01',
136137
'content-type': 'application/json',
137138
}),
@@ -144,7 +145,7 @@ function createAnthropicAdapter(env, deps = {}) {
144145
skipModelsFetch: () => oidcConfigured && !oidcProvider?.isReady(),
145146
modelsPath: '/v1/models',
146147
modelsFetchHeaders: () => ({
147-
...resolveAuthHeadersForValidationAndModels(),
148+
...oidcHeaderResolver.resolveHeaders(),
148149
'anthropic-version': '2023-06-01',
149150
}),
150151
reflectionConfigured: !!apiKey || oidcRequested,
@@ -167,10 +168,7 @@ function createAnthropicAdapter(env, deps = {}) {
167168
* @returns {Record<string, string>}
168169
*/
169170
getAuthHeaders(req) {
170-
const headers = resolveAuthHeaders(
171-
buildOidcBearerAuthHeaders,
172-
buildStaticAuthHeaders(),
173-
);
171+
const headers = oidcHeaderResolver.resolveHeaders();
174172

175173
// OIDC configured but token not yet ready: fail closed so static creds are not leaked.
176174
if (Object.keys(headers).length === 0) {

containers/api-proxy/providers/cloud-oidc-init.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,32 @@ function createProviderOidcAuth(env, {
180180
};
181181
}
182182

183+
/**
184+
* Build a shared OIDC/static auth-header resolver for provider adapters.
185+
*
186+
* @param {object} options
187+
* @param {(buildOidcHeaders: (token: string) => Record<string, string>, staticHeaders: Record<string, string>) => Record<string, string>} options.resolveAuthHeaders
188+
* @param {(token: string) => Record<string, string>} options.buildOidcHeaders
189+
* @param {() => Record<string, string>} options.buildStaticHeaders
190+
* @returns {{ resolveHeaders: () => Record<string, string> }}
191+
*/
192+
function createProviderOidcHeaderResolver({
193+
resolveAuthHeaders,
194+
buildOidcHeaders,
195+
buildStaticHeaders,
196+
}) {
197+
return {
198+
resolveHeaders() {
199+
return resolveAuthHeaders(
200+
buildOidcHeaders,
201+
buildStaticHeaders(),
202+
);
203+
},
204+
};
205+
}
206+
183207
module.exports = {
184208
resolveCloudOidcProviders,
185209
createProviderOidcAuth,
210+
createProviderOidcHeaderResolver,
186211
};

containers/api-proxy/providers/cloud-oidc-init.test.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
'use strict';
22

3-
const { resolveCloudOidcProviders, createProviderOidcAuth } = require('./cloud-oidc-init');
3+
const {
4+
resolveCloudOidcProviders,
5+
createProviderOidcAuth,
6+
createProviderOidcHeaderResolver,
7+
} = require('./cloud-oidc-init');
48

59
describe('resolveCloudOidcProviders', () => {
610
it('returns no providers when github-oidc is not configured', () => {
@@ -91,6 +95,43 @@ describe('createProviderOidcAuth', () => {
9195
expect(auth.skipModelsFetch()).toBe(false);
9296
});
9397

98+
describe('createProviderOidcHeaderResolver', () => {
99+
it('resolves OIDC headers when token is available', () => {
100+
const resolveAuthHeaders = jest.fn((buildOidcHeaders) => buildOidcHeaders('oidc-token'));
101+
const resolver = createProviderOidcHeaderResolver({
102+
resolveAuthHeaders,
103+
buildOidcHeaders: (token) => ({ Authorization: 'Bearer ' + token }),
104+
buildStaticHeaders: () => ({ 'x-api-key': 'static-key' }),
105+
});
106+
107+
expect(resolver.resolveHeaders()).toEqual({ Authorization: 'Bearer ' + 'oidc-' + 'token' });
108+
expect(resolveAuthHeaders).toHaveBeenCalledWith(
109+
expect.any(Function),
110+
{ 'x-api-key': 'static-key' },
111+
);
112+
});
113+
114+
it('returns static headers when OIDC is not configured', () => {
115+
const resolver = createProviderOidcHeaderResolver({
116+
resolveAuthHeaders: (_buildOidcHeaders, staticHeaders) => staticHeaders,
117+
buildOidcHeaders: (token) => ({ Authorization: 'Bearer ' + token }),
118+
buildStaticHeaders: () => ({ 'x-api-key': 'static-key' }),
119+
});
120+
121+
expect(resolver.resolveHeaders()).toEqual({ 'x-api-key': 'static-key' });
122+
});
123+
124+
it('returns empty headers when OIDC is configured but token is unavailable', () => {
125+
const resolver = createProviderOidcHeaderResolver({
126+
resolveAuthHeaders: () => ({}),
127+
buildOidcHeaders: (token) => ({ Authorization: 'Bearer ' + token }),
128+
buildStaticHeaders: () => ({ 'x-api-key': 'static-key' }),
129+
});
130+
131+
expect(resolver.resolveHeaders()).toEqual({});
132+
});
133+
});
134+
94135
it('isEnabled() returns true when staticAuthToken is set (no OIDC)', () => {
95136
const auth = createProviderOidcAuth({}, { staticAuthToken: 'my-api-key' });
96137

containers/api-proxy/providers/copilot.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const {
3535
deriveCopilotApiTarget,
3636
copilotTargetRequiresGitHubTokenPrefix,
3737
} = require('./copilot-auth');
38-
const { createProviderOidcAuth } = require('./cloud-oidc-init');
38+
const { createProviderOidcAuth, createProviderOidcHeaderResolver } = require('./cloud-oidc-init');
3939
const { bearerAuthHeaders, withCopilotIntegration } = require('./auth-headers');
4040
const { URL } = require('url');
4141
const { COPILOT_ENV } = require('../provider-env-constants');
@@ -102,6 +102,16 @@ function createCopilotAdapter(env, deps = {}) {
102102
? (body) => injectByokExtraBodyFields(body, byokExtraBodyFields)
103103
: null;
104104
const bodyTransform = composeBodyTransforms(sanitizedBodyTransform, byokBodyFieldTransform);
105+
const requiresGitHubTokenPrefix = copilotTargetRequiresGitHubTokenPrefix(rawTarget, env);
106+
const authPrefix = (requiresGitHubTokenPrefix && !apiKey) ? 'token' : 'Bearer';
107+
const inferenceAuthHeaderResolver = createProviderOidcHeaderResolver({
108+
resolveAuthHeaders,
109+
buildOidcHeaders: (token) => withCopilotIntegration(bearerAuthHeaders(token), integrationId),
110+
buildStaticHeaders: () => withCopilotIntegration({
111+
...(apiKey ? byokExtraHeaders : {}),
112+
'Authorization': authPrefix + ' ' + authToken,
113+
}, integrationId),
114+
});
105115

106116
// Pre-computed models path used by getModelsFetchConfig and getReflectionInfo.
107117
// For BYOK/custom providers the base path prefix is included (e.g. /api/v1/models
@@ -120,7 +130,7 @@ function createCopilotAdapter(env, deps = {}) {
120130
* @returns {{ url: string, opts: { method: string, headers: Record<string,string> } } & Record<string, unknown>}
121131
*/
122132
function buildCopilotModelsRequest(extra = {}) {
123-
const prefix = copilotTargetRequiresGitHubTokenPrefix(rawTarget, env) ? 'token' : 'Bearer';
133+
const prefix = requiresGitHubTokenPrefix ? 'token' : 'Bearer';
124134
return {
125135
url: `https://${rawTarget}/models`,
126136
opts: {
@@ -219,24 +229,14 @@ function buildCopilotModelsRequest(extra = {}) {
219229
// Enterprise/Business Copilot API requires 'token <value>' for GitHub OAuth tokens.
220230
// BYOK API keys use 'Bearer' regardless of target.
221231
// Standard api.githubcopilot.com and GHEC (*.ghe.com) also use 'Bearer' for all credentials.
222-
const requiresGitHubTokenPrefix = copilotTargetRequiresGitHubTokenPrefix(rawTarget, env);
223-
224232
const isModelsPath = reqPathname === '/models' || reqPathname.startsWith('/models/');
225233
if (isModelsPath && req.method === 'GET' && githubToken) {
226234
// /models always uses the GitHub OAuth token (not BYOK key)
227235
const prefix = requiresGitHubTokenPrefix ? 'token' : 'Bearer';
228236
return withCopilotIntegration({ 'Authorization': prefix + ' ' + githubToken }, integrationId);
229237
}
230238

231-
// For inference: BYOK keys use 'Bearer'; GitHub tokens use 'token' on Enterprise/Business/GHES
232-
const authPrefix = (requiresGitHubTokenPrefix && !apiKey) ? 'token' : 'Bearer';
233-
return resolveAuthHeaders(
234-
(token) => withCopilotIntegration(bearerAuthHeaders(token), integrationId),
235-
withCopilotIntegration({
236-
...(apiKey ? byokExtraHeaders : {}),
237-
'Authorization': authPrefix + ' ' + authToken,
238-
}, integrationId),
239-
);
239+
return inferenceAuthHeaderResolver.resolveHeaders();
240240
},
241241
bodyTransform,
242242
missingCredentialResponse: {

containers/api-proxy/providers/openai.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const { validateAuthHeaderEnv } = require('../oidc-adapter-utils');
1818
const { bearerAuthHeaders, providerKeyHeaders } = require('./auth-headers');
1919

2020
const { createProviderAuthScaffold, createAdapterMethods, buildProviderAdapter } = require('../adapter-factory');
21-
const { createProviderOidcAuth } = require('./cloud-oidc-init');
21+
const { createProviderOidcAuth, createProviderOidcHeaderResolver } = require('./cloud-oidc-init');
2222
const { OPENAI_ENV, COPILOT_ENV } = require('../provider-env-constants');
2323

2424
/**
@@ -78,6 +78,11 @@ function createOpenAIAdapter(env, deps = {}) {
7878
return bearerAuthHeaders(key);
7979
}
8080
const buildStaticAuthHeaders = () => buildTokenAuthHeaders(apiKey);
81+
const oidcHeaderResolver = createProviderOidcHeaderResolver({
82+
resolveAuthHeaders,
83+
buildOidcHeaders: buildTokenAuthHeaders,
84+
buildStaticHeaders: buildStaticAuthHeaders,
85+
});
8186

8287
const adapterMethods = createAdapterMethods({
8388
apiKey,
@@ -105,10 +110,7 @@ function createOpenAIAdapter(env, deps = {}) {
105110
isManagementPort: true,
106111
adapterMethods,
107112
getAuthHeaders() {
108-
return resolveAuthHeaders(
109-
buildTokenAuthHeaders,
110-
buildStaticAuthHeaders(),
111-
);
113+
return oidcHeaderResolver.resolveHeaders();
112114
},
113115
bodyTransform,
114116
missingCredentialResponse: {

0 commit comments

Comments
 (0)