diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6290a497..48c94a96 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ jobs: matrix: node: [14, 16, 18, 20] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} @@ -29,7 +29,7 @@ jobs: windows: runs-on: windows-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - uses: actions/setup-node@v4 with: node-version: 14 @@ -40,7 +40,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - uses: actions/setup-node@v4 with: node-version: 14 diff --git a/CHANGELOG.md b/CHANGELOG.md index 81daafbc..4e81627c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://www.npmjs.com/package/google-auth-library?activeTab=versions +## [9.10.0](https://github.com/googleapis/google-auth-library-nodejs/compare/v9.9.0...v9.10.0) (2024-05-10) + + +### Features + +* Implement `UserRefreshClient#fetchIdToken` ([#1811](https://github.com/googleapis/google-auth-library-nodejs/issues/1811)) ([ae8bc54](https://github.com/googleapis/google-auth-library-nodejs/commit/ae8bc5476f5d93c8516d9a9eb553e7ce7c00edd5)) + + +### Bug Fixes + +* **deps:** Update dependency @googleapis/iam to v16 ([#1803](https://github.com/googleapis/google-auth-library-nodejs/issues/1803)) ([40406a0](https://github.com/googleapis/google-auth-library-nodejs/commit/40406a0512cde1d75d2af7dd23aa7aa7de38d30b)) +* **deps:** Update dependency @googleapis/iam to v17 ([#1808](https://github.com/googleapis/google-auth-library-nodejs/issues/1808)) ([4d67f07](https://github.com/googleapis/google-auth-library-nodejs/commit/4d67f07380f690a99c8facf7266db7cb2d6c69b3)) +* **deps:** Update dependency @googleapis/iam to v18 ([#1809](https://github.com/googleapis/google-auth-library-nodejs/issues/1809)) ([b2b9676](https://github.com/googleapis/google-auth-library-nodejs/commit/b2b9676f933c012fb2cd1789ad80b927af0de07c)) + ## [9.9.0](https://github.com/googleapis/google-auth-library-nodejs/compare/v9.8.0...v9.9.0) (2024-04-18) diff --git a/package.json b/package.json index 050f8d32..d3b29631 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "google-auth-library", - "version": "9.9.0", + "version": "9.10.0", "author": "Google Inc.", "description": "Google APIs Authentication Client Library for Node.js", "engines": { @@ -25,7 +25,7 @@ "jws": "^4.0.0" }, "devDependencies": { - "@compodoc/compodoc": "^1.1.7", + "@compodoc/compodoc": "1.1.23", "@types/base64-js": "^1.2.5", "@types/chai": "^4.1.7", "@types/jws": "^3.1.0", diff --git a/samples/package.json b/samples/package.json index e99b90a1..d0684f68 100644 --- a/samples/package.json +++ b/samples/package.json @@ -14,8 +14,8 @@ "license": "Apache-2.0", "dependencies": { "@google-cloud/storage": "^7.0.0", - "@googleapis/iam": "^15.0.0", - "google-auth-library": "^9.9.0", + "@googleapis/iam": "^18.0.0", + "google-auth-library": "^9.10.0", "node-fetch": "^2.3.0", "open": "^9.0.0", "server-destroy": "^1.0.1" diff --git a/src/auth/googleauth.ts b/src/auth/googleauth.ts index f3faedac..40b71d2f 100644 --- a/src/auth/googleauth.ts +++ b/src/auth/googleauth.ts @@ -317,7 +317,7 @@ export class GoogleAuth { let universeDomain: string; try { - universeDomain = await gcpMetadata.universe('universe_domain'); + universeDomain = await gcpMetadata.universe('universe-domain'); universeDomain ||= DEFAULT_UNIVERSE; } catch (e) { if (e && (e as GaxiosError)?.response?.status === 404) { diff --git a/src/auth/refreshclient.ts b/src/auth/refreshclient.ts index a53f1b1d..eca95d1b 100644 --- a/src/auth/refreshclient.ts +++ b/src/auth/refreshclient.ts @@ -13,12 +13,13 @@ // limitations under the License. import * as stream from 'stream'; -import {JWTInput} from './credentials'; +import {CredentialRequest, JWTInput} from './credentials'; import { GetTokenResponse, OAuth2Client, OAuth2ClientOptions, } from './oauth2client'; +import {stringify} from 'querystring'; export const USER_REFRESH_ACCOUNT_TYPE = 'authorized_user'; @@ -78,6 +79,26 @@ export class UserRefreshClient extends OAuth2Client { return super.refreshTokenNoCache(this._refreshToken); } + async fetchIdToken(targetAudience: string): Promise { + const res = await this.transporter.request({ + ...UserRefreshClient.RETRY_CONFIG, + url: this.endpoints.oauth2TokenUrl, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + data: stringify({ + client_id: this._clientId, + client_secret: this._clientSecret, + grant_type: 'refresh_token', + refresh_token: this._refreshToken, + target_audience: targetAudience, + }), + }); + + return res.data.id_token!; + } + /** * Create a UserRefreshClient credentials instance using the given input * options. diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index ff3d5bcd..3bb2ce95 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -55,6 +55,7 @@ import { import {BaseExternalAccountClient} from '../src/auth/baseexternalclient'; import {AuthClient, DEFAULT_UNIVERSE} from '../src/auth/authclient'; import {ExternalAccountAuthorizedUserClient} from '../src/auth/externalAccountAuthorizedUserClient'; +import {stringify} from 'querystring'; nock.disableNetConnect(); @@ -65,7 +66,7 @@ describe('googleauth', () => { const host = HOST_ADDRESS; const instancePath = `${BASE_PATH}/instance`; const svcAccountPath = `${instancePath}/service-accounts/default/email`; - const universeDomainPath = `${BASE_PATH}/universe/universe_domain`; + const universeDomainPath = `${BASE_PATH}/universe/universe-domain`; const API_KEY = 'test-123'; const PEM_PATH = './test/fixtures/private.pem'; const STUB_PROJECT = 'my-awesome-project'; @@ -1520,20 +1521,20 @@ describe('googleauth', () => { assert(client.idTokenProvider instanceof JWT); }); - it('should call getClient for getIdTokenClient', async () => { + it('should return a UserRefreshClient client for getIdTokenClient', async () => { // Set up a mock to return path to a valid credentials file. mockEnvVar( 'GOOGLE_APPLICATION_CREDENTIALS', - './test/fixtures/private.json' + './test/fixtures/refresh.json' ); + mockEnvVar('GOOGLE_CLOUD_PROJECT', 'some-project-id'); - const spy = sinon.spy(auth, 'getClient'); const client = await auth.getIdTokenClient('a-target-audience'); assert(client instanceof IdTokenClient); - assert(spy.calledOnce); + assert(client.idTokenProvider instanceof UserRefreshClient); }); - it('should fail when using UserRefreshClient', async () => { + it('should properly use `UserRefreshClient` client for `getIdTokenClient`', async () => { // Set up a mock to return path to a valid credentials file. mockEnvVar( 'GOOGLE_APPLICATION_CREDENTIALS', @@ -1541,16 +1542,59 @@ describe('googleauth', () => { ); mockEnvVar('GOOGLE_CLOUD_PROJECT', 'some-project-id'); - try { - await auth.getIdTokenClient('a-target-audience'); - } catch (e) { - assert(e instanceof Error); - assert( - e.message.startsWith('Cannot fetch ID token in this environment') - ); - return; - } - assert.fail('failed to throw'); + // Assert `UserRefreshClient` + const baseClient = await auth.getClient(); + assert(baseClient instanceof UserRefreshClient); + + // Setup variables + const idTokenPayload = Buffer.from(JSON.stringify({exp: 100})).toString( + 'base64' + ); + const testIdToken = `TEST.${idTokenPayload}.TOKEN`; + const targetAudience = 'a-target-audience'; + const tokenEndpoint = new URL(baseClient.endpoints.oauth2TokenUrl); + const expectedTokenRequestBody = stringify({ + client_id: baseClient._clientId, + client_secret: baseClient._clientSecret, + grant_type: 'refresh_token', + refresh_token: baseClient._refreshToken, + target_audience: targetAudience, + }); + const url = new URL('https://my-protected-endpoint.a.app'); + const expectedRes = {hello: true}; + + // Setup mock endpoints + nock(tokenEndpoint.origin) + .post(tokenEndpoint.pathname, expectedTokenRequestBody) + .reply(200, {id_token: testIdToken}); + nock(url.origin, { + reqheaders: { + authorization: `Bearer ${testIdToken}`, + }, + }) + .get(url.pathname) + .reply(200, expectedRes); + + // Make assertions + const client = await auth.getIdTokenClient(targetAudience); + assert(client instanceof IdTokenClient); + assert(client.idTokenProvider instanceof UserRefreshClient); + + const res = await client.request({url}); + assert.deepStrictEqual(res.data, expectedRes); + }); + + it('should call getClient for getIdTokenClient', async () => { + // Set up a mock to return path to a valid credentials file. + mockEnvVar( + 'GOOGLE_APPLICATION_CREDENTIALS', + './test/fixtures/private.json' + ); + + const spy = sinon.spy(auth, 'getClient'); + const client = await auth.getIdTokenClient('a-target-audience'); + assert(client instanceof IdTokenClient); + assert(spy.calledOnce); }); describe('getUniverseDomain', () => {