diff --git a/lib/utils/oidc.js b/lib/utils/oidc.js index fd88cc844040f..279b6335da352 100644 --- a/lib/utils/oidc.js +++ b/lib/utils/oidc.js @@ -30,6 +30,7 @@ async function oidc ({ packageName, registry, opts, config }) { /** @see https://github.com/watson/ci-info/blob/v4.2.0/vendors.json#L161C13-L161C22 */ ciInfo.GITLAB )) { + log.silly('oidc', 'Not running OIDC, not in a supported CI environment') return undefined } @@ -67,14 +68,11 @@ async function oidc ({ packageName, registry, opts, config }) { process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ) { - log.silly('oidc', '"GITHUB_ACTIONS" detected with "ACTIONS_ID_" envs, fetching id_token') - /** * The specification for an audience is `npm:registry.npmjs.org`, * where "registry.npmjs.org" can be any supported registry. */ const audience = `npm:${new URL(registry).hostname}` - log.silly('oidc', `Using audience: ${audience}`) const url = new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL) url.searchParams.append('audience', audience) const startTime = Date.now() @@ -96,17 +94,19 @@ async function oidc ({ packageName, registry, opts, config }) { const json = await response.json() if (!response.ok) { - throw new Error(`Failed to fetch id_token from GitHub: received an invalid response`) + log.verbose('oidc', `Failed to fetch id_token from GitHub: received an invalid response`) + return undefined } if (!json.value) { - throw new Error(`Failed to fetch id_token from GitHub: missing value`) + log.verbose('oidc', `Failed to fetch id_token from GitHub: missing value`) + return undefined } - log.silly('oidc', 'GITHUB_ACTIONS valid fetch response for id_token') idToken = json.value } else { - throw new Error('GITHUB_ACTIONS detected. If you intend to publish using OIDC, please set workflow permissions for `id-token: write`') + log.silly('oidc', 'GITHUB_ACTIONS detected. If you intend to publish using OIDC, please set workflow permissions for `id-token: write`') + return undefined } } } @@ -130,22 +130,31 @@ async function oidc ({ packageName, registry, opts, config }) { } const escapedPackageName = npa(packageName).escapedName - const response = await npmFetch.json(new URL(`/-/npm/v1/oidc/token/exchange/package/${escapedPackageName}`, registry), { - ...{ - ...opts, - [authTokenKey]: idToken, // Use the idToken as the auth token for the request - }, - method: 'POST', - headers: { - ...opts.headers, - 'Content-Type': 'application/json', - // this will not work because the existing auth token will replace it. - // authorization: `Bearer ${idToken}`, - }, - }) + let response + try { + response = await npmFetch.json(new URL(`/-/npm/v1/oidc/token/exchange/package/${escapedPackageName}`, registry), { + ...{ + ...opts, + [authTokenKey]: idToken, // Use the idToken as the auth token for the request + }, + method: 'POST', + headers: { + ...opts.headers, + 'Content-Type': 'application/json', + // this will not work because the existing auth token will replace it. + // authorization: `Bearer ${idToken}`, + }, + }) + } catch (error) { + if (error?.body?.message) { + log.verbose('oidc', `Registry body response error message "${error.body.message}"`) + } + return undefined + } if (!response?.token) { - throw new Error('OIDC token exchange failure: missing token in response body') + log.verbose('oidc', 'OIDC token exchange failure: missing token in response body') + return undefined } /* * The "opts" object is a clone of npm.flatOptions and is passed through the `publish` command, @@ -157,10 +166,8 @@ async function oidc ({ packageName, registry, opts, config }) { config.set(authTokenKey, response.token, 'user') log.silly('oidc', `OIDC token successfully retrieved`) } catch (error) { - log.verbose('oidc', error.message) - if (error?.body?.message) { - log.verbose('oidc', `Registry body response error message "${error.body.message}"`) - } + /* istanbul ignore next */ + log.verbose('oidc', 'Failure checking OIDC config', error) } return undefined } diff --git a/mock-registry/lib/index.js b/mock-registry/lib/index.js index f442499627042..65cf4b8983aa3 100644 --- a/mock-registry/lib/index.js +++ b/mock-registry/lib/index.js @@ -631,11 +631,11 @@ class MockRegistry { } } - mockOidcTokenExchange ({ packageName, idToken, token, statusCode = 200 } = {}) { + mockOidcTokenExchange ({ packageName, idToken, statusCode = 200, body } = {}) { const encodedPackageName = npa(packageName).escapedName this.nock.post(this.fullPath(`/-/npm/v1/oidc/token/exchange/package/${encodedPackageName}`)) .matchHeader('authorization', `Bearer ${idToken}`) - .reply(statusCode, statusCode !== 500 ? { token } : { message: 'Internal Server Error' }) + .reply(statusCode, body || {}) } } diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js index 7b08a644adae5..13a284e0c1f5b 100644 --- a/test/lib/commands/publish.js +++ b/test/lib/commands/publish.js @@ -1060,6 +1060,27 @@ t.test('oidc token exchange', t => { })) t.test('token exchange 500 with fallback', oidcPublishTest({ + oidcOptions: { github: true }, + config: { + '//registry.npmjs.org/:_authToken': 'existing-fallback-token', + }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: 'github-jwt-id-token', + }, + mockOidcTokenExchangeOptions: { + statusCode: 500, + idToken: 'github-jwt-id-token', + body: { + message: 'oidc token exchange failed', + }, + }, + publishOptions: { + token: 'existing-fallback-token', + }, + })) + + t.test('token exchange 500 (with no body message) with fallback', oidcPublishTest({ oidcOptions: { github: true }, config: { '//registry.npmjs.org/:_authToken': 'existing-fallback-token', @@ -1138,7 +1159,9 @@ t.test('oidc token exchange', t => { }, mockOidcTokenExchangeOptions: { idToken: 'github-jwt-id-token', - token: 'exchange-token', + body: { + token: 'exchange-token', + }, }, publishOptions: { token: 'exchange-token', @@ -1152,7 +1175,9 @@ t.test('oidc token exchange', t => { }, mockOidcTokenExchangeOptions: { idToken: 'gitlab-jwt-id-token', - token: 'exchange-token', + body: { + token: 'exchange-token', + }, }, publishOptions: { token: 'exchange-token', @@ -1172,7 +1197,9 @@ t.test('oidc token exchange', t => { }, mockOidcTokenExchangeOptions: { idToken: 'github-jwt-id-token', - token: 'exchange-token', + body: { + token: 'exchange-token', + }, }, publishOptions: { token: 'exchange-token', @@ -1190,7 +1217,9 @@ t.test('oidc token exchange', t => { }, mockOidcTokenExchangeOptions: { idToken: 'github-jwt-id-token', - token: 'exchange-token', + body: { + token: 'exchange-token', + }, }, publishOptions: { token: 'exchange-token', @@ -1213,7 +1242,9 @@ t.test('oidc token exchange', t => { }, mockOidcTokenExchangeOptions: { idToken: 'github-jwt-id-token', - token: 'exchange-token', + body: { + token: 'exchange-token', + }, }, publishOptions: { token: 'exchange-token',