diff --git a/.changeset/poor-files-learn.md b/.changeset/poor-files-learn.md new file mode 100644 index 00000000000..147fca8aa67 --- /dev/null +++ b/.changeset/poor-files-learn.md @@ -0,0 +1,5 @@ +--- +"@firebase/auth": patch +--- + +Add X-Firebase-gmpid header to requests diff --git a/packages/auth/src/api/authentication/token.test.ts b/packages/auth/src/api/authentication/token.test.ts index 529bfe3bd7c..f676ac91fac 100644 --- a/packages/auth/src/api/authentication/token.test.ts +++ b/packages/auth/src/api/authentication/token.test.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as sinon from 'sinon'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -41,7 +42,10 @@ describe('requestStsToken', () => { fetch.setUp(); }); - afterEach(fetch.tearDown); + afterEach(() => { + fetch.tearDown(); + sinon.restore(); + }); it('should POST to the correct endpoint', async () => { const mock = fetch.mock(endpoint, { @@ -90,6 +94,23 @@ describe('requestStsToken', () => { ); }); + it('should include whatever headers come from auth impl', async () => { + sinon.stub(auth, '_getAdditionalHeaders').returns(Promise.resolve({ + 'look-at-me-im-a-header': 'header-value', + 'anotherheader': 'header-value-2', + })); + + const mock = fetch.mock(endpoint, { + 'access_token': 'new-access-token', + 'expires_in': '3600', + 'refresh_token': 'new-refresh-token' + }); + await requestStsToken(auth, 'old-refresh-token'); + + expect(mock.calls[0].headers.get('look-at-me-im-a-header')).to.eq('header-value'); + expect(mock.calls[0].headers.get('anotherheader')).to.eq('header-value-2'); + }); + it('should handle errors', async () => { const mock = fetch.mock( endpoint, diff --git a/packages/auth/src/api/authentication/token.ts b/packages/auth/src/api/authentication/token.ts index 3966685def6..d0a2df9cd33 100644 --- a/packages/auth/src/api/authentication/token.ts +++ b/packages/auth/src/api/authentication/token.ts @@ -22,7 +22,8 @@ import { querystring } from '@firebase/util'; import { _getFinalTarget, _performFetchWithErrorHandling, - HttpMethod + HttpMethod, + HttpHeader } from '../index'; import { FetchProvider } from '../../core/util/fetch_provider'; import { Auth } from '../../model/public_types'; @@ -52,7 +53,7 @@ export async function requestStsToken( const response = await _performFetchWithErrorHandling( auth, {}, - () => { + async () => { const body = querystring({ 'grant_type': 'refresh_token', 'refresh_token': refreshToken @@ -65,12 +66,12 @@ export async function requestStsToken( `key=${apiKey}` ); + const headers = await (auth as AuthInternal)._getAdditionalHeaders(); + headers[HttpHeader.CONTENT_TYPE] = 'application/x-www-form-urlencoded'; + return FetchProvider.fetch()(url, { method: HttpMethod.POST, - headers: { - 'X-Client-Version': (auth as AuthInternal)._getSdkClientVersion(), - 'Content-Type': 'application/x-www-form-urlencoded' - }, + headers, body }); } diff --git a/packages/auth/src/api/index.test.ts b/packages/auth/src/api/index.test.ts index fb864c87400..47dbf228630 100644 --- a/packages/auth/src/api/index.test.ts +++ b/packages/auth/src/api/index.test.ts @@ -94,6 +94,21 @@ describe('api/_performApiRequest', () => { ); }); + it('should include whatever headers the auth impl attaches', async () => { + sinon.stub(auth, '_getAdditionalHeaders').returns(Promise.resolve({ + 'look-at-me-im-a-header': 'header-value', + 'anotherheader': 'header-value-2', + })); + + const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse); + await _performApiRequest< + typeof request, + typeof serverResponse + >(auth, HttpMethod.POST, Endpoint.SIGN_UP, request); + expect(mock.calls[0].headers.get('look-at-me-im-a-header')).to.eq('header-value'); + expect(mock.calls[0].headers.get('anotherheader')).to.eq('header-value-2'); + }); + it('should set the framework in clientVersion if logged', async () => { auth._logFramework('Mythical'); const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse); diff --git a/packages/auth/src/api/index.ts b/packages/auth/src/api/index.ts index 2547fb58d53..bff8f633a13 100644 --- a/packages/auth/src/api/index.ts +++ b/packages/auth/src/api/index.ts @@ -36,7 +36,8 @@ export const enum HttpMethod { export const enum HttpHeader { CONTENT_TYPE = 'Content-Type', X_FIREBASE_LOCALE = 'X-Firebase-Locale', - X_CLIENT_VERSION = 'X-Client-Version' + X_CLIENT_VERSION = 'X-Client-Version', + X_FIREBASE_GMPID = 'X-Firebase-gmpid' } export const enum Endpoint { @@ -84,7 +85,7 @@ export async function _performApiRequest( request?: T, customErrorMap: Partial> = {} ): Promise { - return _performFetchWithErrorHandling(auth, customErrorMap, () => { + return _performFetchWithErrorHandling(auth, customErrorMap, async () => { let body = {}; let params = {}; if (request) { @@ -102,15 +103,11 @@ export async function _performApiRequest( ...params }).slice(1); - const headers = new (FetchProvider.headers())(); - headers.set(HttpHeader.CONTENT_TYPE, 'application/json'); - headers.set( - HttpHeader.X_CLIENT_VERSION, - (auth as AuthInternal)._getSdkClientVersion() - ); + const headers = await (auth as AuthInternal)._getAdditionalHeaders(); + headers[HttpHeader.CONTENT_TYPE] = 'application/json'; if (auth.languageCode) { - headers.set(HttpHeader.X_FIREBASE_LOCALE, auth.languageCode); + headers[HttpHeader.X_FIREBASE_LOCALE] = auth.languageCode; } return FetchProvider.fetch()( diff --git a/packages/auth/src/core/auth/auth_impl.test.ts b/packages/auth/src/core/auth/auth_impl.test.ts index 72a11b04944..4e226973a0c 100644 --- a/packages/auth/src/core/auth/auth_impl.test.ts +++ b/packages/auth/src/core/auth/auth_impl.test.ts @@ -460,4 +460,20 @@ describe('core/auth/auth_impl', () => { expect(spy).not.to.have.been.called; }); }); + + context ('#_getAdditionalHeaders', () => { + it('always adds the client version', async () => { + expect(await auth._getAdditionalHeaders()).to.eql({ + 'X-Client-Version': 'v', + }); + }); + + it('adds the gmp app ID if available', async () => { + auth.app.options.appId = 'app-id'; + expect(await auth._getAdditionalHeaders()).to.eql({ + 'X-Client-Version': 'v', + 'X-Firebase-gmpid': 'app-id', + }); + }); + }); }); diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 0579a088d14..b71e4e24bb4 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -58,6 +58,7 @@ import { _assert } from '../util/assert'; import { _getInstance } from '../util/instantiator'; import { _getUserLanguage } from '../util/navigator'; import { _getClientVersion } from '../util/version'; +import { HttpHeader } from '../../api'; interface AsyncAction { (): Promise; @@ -577,8 +578,15 @@ export class AuthImpl implements AuthInternal, _FirebaseService { _getFrameworks(): readonly string[] { return this.frameworks; } - _getSdkClientVersion(): string { - return this.clientVersion; + async _getAdditionalHeaders(): Promise> { + // Additional headers on every request + const headers: Record = { + [HttpHeader.X_CLIENT_VERSION]: this.clientVersion, + }; + if (this.app.options.appId) { + headers[HttpHeader.X_FIREBASE_GMPID] = this.app.options.appId; + } + return headers; } } diff --git a/packages/auth/src/model/auth.ts b/packages/auth/src/model/auth.ts index 4fbe6876eac..6aaa6c6781f 100644 --- a/packages/auth/src/model/auth.ts +++ b/packages/auth/src/model/auth.ts @@ -81,7 +81,7 @@ export interface AuthInternal extends Auth { _getPersistence(): string; _logFramework(framework: string): void; _getFrameworks(): readonly string[]; - _getSdkClientVersion(): string; + _getAdditionalHeaders(): Promise>; readonly name: AppName; readonly config: ConfigInternal;