Skip to content

Commit

Permalink
chore: Add X-Goog-Api-Client metric header to outgoing authorized h…
Browse files Browse the repository at this point in the history
…ttp requests (#2763)

* chore: Add `X-Goog-Api-Client` metric header to outgoing requests

* fix lint

* remove quota project id headers from HTTP/2
  • Loading branch information
jonathanedey authored Nov 7, 2024
1 parent 5affb73 commit dcef2ae
Show file tree
Hide file tree
Showing 18 changed files with 86 additions and 74 deletions.
1 change: 0 additions & 1 deletion src/auth/auth-api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { ProjectConfig, ProjectConfigServerResponse, UpdateProjectConfigRequest
/** Firebase Auth request header. */
const FIREBASE_AUTH_HEADERS = {
'X-Client-Version': `Node/Admin/${utils.getSdkVersion()}`,
'X-Goog-Api-Client': `gl-node/${process.versions.node} fire-admin/${utils.getSdkVersion()}`
};
/** Firebase Auth request timeout duration in milliseconds. */
const FIREBASE_AUTH_TIMEOUT = 25000;
Expand Down
1 change: 0 additions & 1 deletion src/messaging/messaging-api-request-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const FIREBASE_MESSAGING_TIMEOUT = 15000;
const FIREBASE_MESSAGING_HTTP_METHOD: HttpMethod = 'POST';
const FIREBASE_MESSAGING_HEADERS = {
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'X-Goog-Api-Client': `gl-node/${process.versions.node} fire-admin/${getSdkVersion()}`,
'access_token_auth': 'true',
};

Expand Down
6 changes: 6 additions & 0 deletions src/utils/api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import url = require('url');
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import * as zlibmod from 'zlib';
import { getMetricsHeader } from '../utils/index';

/** Http method type definition. */
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
Expand Down Expand Up @@ -1085,6 +1086,9 @@ export class AuthorizedHttpClient extends HttpClient {
if (!requestCopy.httpAgent && this.app.options.httpAgent) {
requestCopy.httpAgent = this.app.options.httpAgent;
}

requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader()

return super.send(requestCopy);
});
}
Expand All @@ -1108,6 +1112,8 @@ export class AuthorizedHttp2Client extends Http2Client {
const authHeader = 'Authorization';
requestCopy.headers[authHeader] = `Bearer ${token}`;

requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader()

return super.send(requestCopy);
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export function getSdkVersion(): string {
return sdkVersion;
}

export function getMetricsHeader(): string {
return `gl-node/${process.versions.node} fire-admin/${getSdkVersion()}`
}

/**
* Renames properties on an object given a mapping from old to new property names.
*
Expand Down
3 changes: 2 additions & 1 deletion test/unit/app-check/app-check-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import * as sinon from 'sinon';
import { HttpClient } from '../../../src/utils/api-request';
import * as utils from '../utils';
import * as mocks from '../../resources/mocks';
import { getSdkVersion } from '../../../src/utils';
import { getMetricsHeader, getSdkVersion } from '../../../src/utils';

import { FirebaseApp } from '../../../src/app/firebase-app';
import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal';
Expand All @@ -46,6 +46,7 @@ describe('AppCheckApiClient', () => {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '
Expand Down
6 changes: 3 additions & 3 deletions test/unit/auth/auth-api-request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error
import { ActionCodeSettingsBuilder } from '../../../src/auth/action-code-settings-builder';
import { SAMLConfigServerResponse } from '../../../src/auth/auth-config';
import { expectUserImportResult } from './user-import-builder.spec';
import { getSdkVersion } from '../../../src/utils/index';
import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index';
import {
UserImportRecord, OIDCAuthProviderConfig, SAMLAuthProviderConfig, OIDCUpdateAuthProviderRequest,
SAMLUpdateAuthProviderRequest, UserIdentifier, UpdateRequest, UpdateMultiFactorInfoRequest,
Expand Down Expand Up @@ -863,12 +863,12 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => {
const mockAccessToken: string = utils.generateRandomAccessToken();
const expectedHeaders: {[key: string]: string} = {
'X-Client-Version': `Node/Admin/${getSdkVersion()}`,
'X-Goog-Api-Client': `gl-node/${process.versions.node} fire-admin/${getSdkVersion()}`,
'X-Goog-Api-Client': getMetricsHeader(),
'Authorization': 'Bearer ' + mockAccessToken,
};
const expectedHeadersEmulator: {[key: string]: string} = {
'X-Client-Version': `Node/Admin/${getSdkVersion()}`,
'X-Goog-Api-Client': `gl-node/${process.versions.node} fire-admin/${getSdkVersion()}`,
'X-Goog-Api-Client': getMetricsHeader(),
'Authorization': 'Bearer owner',
};
const callParams = (path: string, method: any, data: any): HttpRequestConfig => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { DataConnectApiClient, FirebaseDataConnectError }
from '../../../src/data-connect/data-connect-api-client-internal';
import { FirebaseApp } from '../../../src/app/firebase-app';
import { ConnectorConfig } from '../../../src/data-connect';
import { getSdkVersion } from '../../../src/utils';
import { getMetricsHeader, getSdkVersion } from '../../../src/utils';

describe('DataConnectApiClient', () => {

Expand All @@ -44,6 +44,7 @@ describe('DataConnectApiClient', () => {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '
Expand Down
3 changes: 3 additions & 0 deletions test/unit/database/database.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Database, DatabaseService } from '../../../src/database/database';
import { ServiceAccountCredential } from '../../../src/app/credential-internal';
import * as utils from '../utils';
import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request';
import { getMetricsHeader } from '../../../src/utils';

describe('Database', () => {
let mockApp: FirebaseApp;
Expand Down Expand Up @@ -332,6 +333,7 @@ describe('Database', () => {
url,
headers: {
Authorization: 'Bearer ' + mockAccessToken,
'X-Goog-Api-Client': getMetricsHeader(),
},
};

Expand Down Expand Up @@ -485,6 +487,7 @@ describe('Database', () => {
headers: {
'Authorization': 'Bearer ' + mockAccessToken,
'content-type': 'application/json; charset=utf-8',
'X-Goog-Api-Client': getMetricsHeader(),
},
data,
};
Expand Down
70 changes: 21 additions & 49 deletions test/unit/eventarc/eventarc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import * as mocks from '../../resources/mocks';
import * as utils from '../utils';
import * as chai from 'chai';
import chaiExclude from 'chai-exclude';
import { getSdkVersion } from '../../../src/utils/index';
import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index';

const expect = chai.expect;
chai.use(chaiExclude);
Expand Down Expand Up @@ -56,6 +56,14 @@ describe('eventarc', () => {
let mockApp: FirebaseApp;
let eventarc: Eventarc;

const getExpectedHeaders = (mockAccessToken: string): object => {
return {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken,
'X-Goog-Api-Client': getMetricsHeader(),
}
}

before(() => {
mockApp = mocks.app();
eventarc = new Eventarc(mockApp);
Expand Down Expand Up @@ -133,10 +141,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -151,10 +156,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED},${TEST_EVENT2_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down Expand Up @@ -196,10 +198,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/other-project-id/locations/us-west1/channels/my-channel2:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -214,10 +213,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/other-project-id/locations/us-west1/channels/my-channel2:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED},${TEST_EVENT2_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down Expand Up @@ -259,10 +255,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-west1/channels/my-channel:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -277,10 +270,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-west1/channels/my-channel:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED},${TEST_EVENT2_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down Expand Up @@ -322,10 +312,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/my-channel:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -340,10 +327,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/my-channel:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED},${TEST_EVENT2_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down Expand Up @@ -478,10 +462,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -498,10 +479,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down Expand Up @@ -542,10 +520,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});

Expand All @@ -562,10 +537,7 @@ describe('eventarc', () => {
method: 'POST',
url: 'https://eventarcpublishing.googleapis.com/v1/projects/project_id/locations/us-central1/channels/firebase:publishEvents',
data: `{"events":[${TEST_EVENT1_SERIALIZED}]}`,
headers: {
'X-Firebase-Client': 'fire-admin-node/' + getSdkVersion(),
Authorization: 'Bearer ' + mockAccessToken
}
headers: getExpectedHeaders(mockAccessToken)
});
});
});
Expand Down
31 changes: 22 additions & 9 deletions test/unit/extensions/extensions-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import * as utils from '../utils';
import * as mocks from '../../resources/mocks';
import { FirebaseApp } from '../../../src/app/firebase-app';
import { ExtensionsApiClient, FirebaseExtensionsError } from '../../../src/extensions/extensions-api-client-internal';
import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request';
import { HttpClient } from '../../../src/utils/api-request';
import { SettableProcessingState } from '../../../src/extensions/extensions-api';
import { getMetricsHeader, getSdkVersion } from '../../../src/utils';

const testProjectId = 'test-project';
const testInstanceId = 'test-instance';
Expand All @@ -38,6 +39,13 @@ describe('Extension API client', () => {
projectId: 'test-project',
serviceAccountId: '[email protected]'
};

const EXPECTED_HEADERS = {
'Authorization': 'Bearer mock-token',
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
}

before(() => {
app = mocks.appWithOptions(mockOptions);
Expand Down Expand Up @@ -71,14 +79,19 @@ describe('Extension API client', () => {
detailMessage: 'done processing',
},
}
const expected = sinon.match((req: HttpRequestConfig) => {
const url = 'https://firebaseextensions.googleapis.com/' +
'v1beta/projects/test-project/instances/test-instance/runtimeData';
return req.method == 'PATCH' && req.url == url && req.data == testRuntimeData;
}, 'Incorrect URL or Method');
httpClientStub.withArgs(expected).resolves(utils.responseFrom(testRuntimeData, 200));
await expect(apiClient.updateRuntimeData(testProjectId, testInstanceId, testRuntimeData))
.to.eventually.deep.equal(testRuntimeData);
const url = 'https://firebaseextensions.googleapis.com/' +
'v1beta/projects/test-project/instances/test-instance/runtimeData';
httpClientStub = httpClientStub.resolves(utils.responseFrom(testRuntimeData, 200));
return apiClient.updateRuntimeData(testProjectId, testInstanceId, testRuntimeData)
.then((runtimeData) => {
expect(runtimeData).to.deep.equal(testRuntimeData)
expect(httpClientStub).to.have.been.calledOnce.and.calledWith({
method: 'PATCH',
url: url,
headers: EXPECTED_HEADERS,
data: testRuntimeData
})
})
});

it('should convert errors in FirebaseErrors', async () => {
Expand Down
3 changes: 2 additions & 1 deletion test/unit/functions/functions-api-client-internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import * as chai from 'chai';
import * as sinon from 'sinon';
import * as utils from '../utils';
import * as mocks from '../../resources/mocks';
import { getSdkVersion } from '../../../src/utils';
import { getSdkVersion, getMetricsHeader } from '../../../src/utils';

import { FirebaseApp } from '../../../src/app/firebase-app';
import { FirebaseFunctionsError, FunctionsApiClient, Task } from '../../../src/functions/functions-api-client-internal';
Expand All @@ -47,6 +47,7 @@ describe('FunctionsApiClient', () => {
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`,
'Authorization': 'Bearer mock-token',
'x-goog-user-project': 'test-project',
'X-Goog-Api-Client': getMetricsHeader(),
};

const noProjectId = 'Failed to determine project ID. Initialize the SDK with service '
Expand Down
2 changes: 2 additions & 0 deletions test/unit/installations/installations-request-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import * as mocks from '../../resources/mocks';
import { FirebaseApp } from '../../../src/app/firebase-app';
import { HttpClient } from '../../../src/utils/api-request';
import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler';
import { getMetricsHeader } from '../../../src/utils';

chai.should();
chai.use(sinonChai);
Expand Down Expand Up @@ -57,6 +58,7 @@ describe('FirebaseInstallationsRequestHandler', () => {
mockApp = mocks.app();
expectedHeaders = {
Authorization: 'Bearer ' + mockAccessToken,
'X-Goog-Api-Client': getMetricsHeader(),
};
return mockApp.INTERNAL.getToken();
});
Expand Down
Loading

0 comments on commit dcef2ae

Please sign in to comment.