diff --git a/src/auth/oauth2client.ts b/src/auth/oauth2client.ts index 31af6a9c..2187461d 100644 --- a/src/auth/oauth2client.ts +++ b/src/auth/oauth2client.ts @@ -742,15 +742,6 @@ export class OAuth2Client extends AuthClient { */ async getRequestHeaders(url?: string): Promise { const headers = (await this.getRequestMetadataAsync(url)).headers; - // quota_project_id, stored in application_default_credentials.json, is set in - // the x-goog-user-project header, to indicate an alternate account for - // billing and quota: - if ( - !headers['x-goog-user-project'] && // don't override a value the user sets. - this.quotaProjectId - ) { - headers['x-goog-user-project'] = this.quotaProjectId; - } return headers; } @@ -793,9 +784,20 @@ export class OAuth2Client extends AuthClient { credentials.token_type = credentials.token_type || 'Bearer'; tokens.refresh_token = credentials.refresh_token; this.credentials = tokens; - const headers = { + const headers: {[index: string]: string} = { Authorization: credentials.token_type + ' ' + tokens.access_token, }; + + // quota_project_id, stored in application_default_credentials.json, is set in + // the x-goog-user-project header, to indicate an alternate account for + // billing and quota: + if ( + !headers['x-goog-user-project'] && // don't override a value the user sets. + this.quotaProjectId + ) { + headers['x-goog-user-project'] = this.quotaProjectId; + } + return {headers, res: r.res}; } @@ -896,12 +898,14 @@ export class OAuth2Client extends AuthClient { let r2: GaxiosResponse; try { const r = await this.getRequestMetadataAsync(opts.url); + opts.headers = opts.headers || {}; + if (r.headers?.['x-goog-user-project']) { + opts.headers['x-goog-user-project'] = r.headers['x-goog-user-project']; + } if (r.headers && r.headers.Authorization) { - opts.headers = opts.headers || {}; opts.headers.Authorization = r.headers.Authorization; } if (this.apiKey) { - opts.headers = opts.headers || {}; opts.headers['X-Goog-Api-Key'] = this.apiKey; } r2 = await this.transporter.request(opts); diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index 5777c6fb..4ae37080 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -1444,54 +1444,70 @@ describe('googleauth', () => { }); it('getRequestHeaders populates x-goog-user-project with quota_project if present', async () => { - // Fake a home directory in our fixtures path. - mockEnvVar('GCLOUD_PROJECT', 'my-fake-project'); - mockEnvVar('HOME', './test/fixtures/config-with-quota'); - mockEnvVar('APPDATA', './test/fixtures/config-with-quota/.config'); - // The first time auth.getClient() is called /token endpoint is used to - // fetch a JWT. - const req = nock('https://oauth2.googleapis.com') - .post('/token') - .reply(200, {}); - + const tokenReq = mockApplicationDefaultCredentials( + './test/fixtures/config-with-quota' + ); const auth = new GoogleAuth(); const headers = await auth.getRequestHeaders(); assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project'); - req.done(); + tokenReq.done(); }); it('getRequestHeaders does not populate x-goog-user-project if quota_project is not present', async () => { - // Fake a home directory in our fixtures path. - mockEnvVar('GCLOUD_PROJECT', 'my-fake-project'); - mockEnvVar('HOME', './test/fixtures/config-no-quota'); - mockEnvVar('APPDATA', './test/fixtures/config-no-quota/.config'); - // The first time auth.getClient() is called /token endpoint is used to - // fetch a JWT. - const req = nock('https://oauth2.googleapis.com') - .post('/token') - .reply(200, {}); - + const tokenReq = mockApplicationDefaultCredentials( + './test/fixtures/config-no-quota' + ); const auth = new GoogleAuth(); const headers = await auth.getRequestHeaders(); assert.strictEqual(headers['x-goog-user-project'], undefined); - req.done(); + tokenReq.done(); }); it('getRequestHeaders populates x-goog-user-project when called on returned client', async () => { + const tokenReq = mockApplicationDefaultCredentials( + './test/fixtures/config-with-quota' + ); + const auth = new GoogleAuth(); + const client = await auth.getClient(); + const headers = await client.getRequestHeaders(); + assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project'); + tokenReq.done(); + }); + + it('populates x-goog-user-project when request is made', async () => { + const tokenReq = mockApplicationDefaultCredentials( + './test/fixtures/config-with-quota' + ); + const auth = new GoogleAuth(); + const client = await auth.getClient(); + const apiReq = nock(BASE_URL) + .post(ENDPOINT) + .reply(function(uri) { + assert.strictEqual( + this.req.headers['x-goog-user-project'][0], + 'my-quota-project' + ); + return [200, RESPONSE_BODY]; + }); + const res = await client.request({ + url: BASE_URL + ENDPOINT, + method: 'POST', + data: {test: true}, + }); + assert.strictEqual(RESPONSE_BODY, res.data); + tokenReq.done(); + apiReq.done(); + }); + + function mockApplicationDefaultCredentials(path: string) { // Fake a home directory in our fixtures path. mockEnvVar('GCLOUD_PROJECT', 'my-fake-project'); - mockEnvVar('HOME', './test/fixtures/config-with-quota'); - mockEnvVar('APPDATA', './test/fixtures/config-with-quota/.config'); + mockEnvVar('HOME', path); + mockEnvVar('APPDATA', `${path}/.config`); // The first time auth.getClient() is called /token endpoint is used to // fetch a JWT. - const req = nock('https://oauth2.googleapis.com') + return nock('https://oauth2.googleapis.com') .post('/token') .reply(200, {}); - - const auth = new GoogleAuth(); - const client = await auth.getClient(); - const headers = await client.getRequestHeaders(); - assert.strictEqual(headers['x-goog-user-project'], 'my-quota-project'); - req.done(); - }); + } });