From dcdcfe602a0b8ce8ba86c464d28aed64a5569741 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 30 Apr 2021 09:40:14 -0700 Subject: [PATCH 1/4] Enable platform logging for app-check --- packages/app-check/src/index.ts | 3 ++- packages/app/src/constants.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app-check/src/index.ts b/packages/app-check/src/index.ts index b57064caac4..27c81d10e89 100644 --- a/packages/app-check/src/index.ts +++ b/packages/app-check/src/index.ts @@ -24,6 +24,7 @@ import { import { factory, internalFactory } from './factory'; import { initializeDebugMode } from './debug'; import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; +import { name, version } from '../package.json'; const APP_CHECK_NAME: AppCheckComponentName = 'appCheck'; const APP_CHECK_NAME_INTERNAL: AppCheckInternalComponentName = @@ -55,7 +56,7 @@ function registerAppCheck(firebase: _FirebaseNamespace): void { ) ); - // TODO: register AppCheck version with firebase.registerVersion() before BETA. We don't want to report version in EAP + firebase.registerVersion(name, version); } registerAppCheck(firebase as _FirebaseNamespace); diff --git a/packages/app/src/constants.ts b/packages/app/src/constants.ts index 285cb840db2..054b81d8791 100644 --- a/packages/app/src/constants.ts +++ b/packages/app/src/constants.ts @@ -18,6 +18,7 @@ export const DEFAULT_ENTRY_NAME = '[DEFAULT]'; import { name as appName } from '../package.json'; import { name as analyticsName } from '../../analytics/package.json'; +import { name as appCheckName } from '../../app-check/package.json'; import { name as authName } from '../../auth/package.json'; import { name as databaseName } from '../../database/package.json'; import { name as functionsName } from '../../functions/package.json'; @@ -32,6 +33,7 @@ import { name as packageName } from '../../../package.json'; export const PLATFORM_LOG_STRING = { [appName]: 'fire-core', [analyticsName]: 'fire-analytics', + [appCheckName]: 'fire-app-check', [authName]: 'fire-auth', [databaseName]: 'fire-rtdb', [functionsName]: 'fire-fn', From aba7ab7de395b77e65d9bf48e7eb42ed5330dbb7 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 30 Apr 2021 14:02:50 -0700 Subject: [PATCH 2/4] Correct typings path --- packages/app-check/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-check/package.json b/packages/app-check/package.json index c9d61bf00a6..c0c5ef28949 100644 --- a/packages/app-check/package.json +++ b/packages/app-check/package.json @@ -52,7 +52,7 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "nyc": { "extension": [ ".ts" From f6fac557d602e69722279caa0496b264e3e7d712 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 30 Apr 2021 15:01:59 -0700 Subject: [PATCH 3/4] Send platform string to exchange endpoint --- packages/app-check/src/client.test.ts | 21 +++++--- packages/app-check/src/client.ts | 28 ++++++----- packages/app-check/src/factory.ts | 12 +++-- packages/app-check/src/index.ts | 3 +- packages/app-check/src/internal-api.test.ts | 55 +++++++++++---------- packages/app-check/src/internal-api.ts | 20 +++++--- packages/app-check/test/util.ts | 21 ++++++++ 7 files changed, 108 insertions(+), 52 deletions(-) diff --git a/packages/app-check/src/client.test.ts b/packages/app-check/src/client.test.ts index 7699dd53949..86f81bcc6e0 100644 --- a/packages/app-check/src/client.test.ts +++ b/packages/app-check/src/client.test.ts @@ -19,7 +19,7 @@ import '../test/setup'; import { expect } from 'chai'; import { stub, SinonStub, useFakeTimers } from 'sinon'; import { FirebaseApp } from '@firebase/app-types'; -import { getFakeApp } from '../test/util'; +import { getFakeApp, getFakePlatformLoggingProvider } from '../test/util'; import { getExchangeRecaptchaTokenRequest, exchangeToken } from './client'; import { FirebaseError } from '@firebase/util'; import { ERROR_FACTORY, AppCheckError } from './errors'; @@ -64,9 +64,14 @@ describe('client', () => { ); const response = await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token') + getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getFakePlatformLoggingProvider('a/1.2.3 fire-app-check/2.3.4') ); + expect( + (fetchStub.args[0][1]?.['headers'] as any)['X-Firebase-Client'] + ).to.equal('a/1.2.3 fire-app-check/2.3.4'); + expect(response).to.deep.equal({ token: 'fake-appcheck-token', expireTimeMillis: 3600 @@ -85,7 +90,8 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token') + getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getFakePlatformLoggingProvider() ); } catch (e) { expect(e).instanceOf(FirebaseError); @@ -113,7 +119,8 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token') + getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getFakePlatformLoggingProvider() ); } catch (e) { expect(e).instanceOf(FirebaseError); @@ -140,7 +147,8 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token') + getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getFakePlatformLoggingProvider() ); } catch (e) { expect(e).instanceOf(FirebaseError); @@ -173,7 +181,8 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token') + getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getFakePlatformLoggingProvider() ); } catch (e) { expect(e).instanceOf(FirebaseError); diff --git a/packages/app-check/src/client.ts b/packages/app-check/src/client.ts index f3bb7f13d07..c1f94dbb38c 100644 --- a/packages/app-check/src/client.ts +++ b/packages/app-check/src/client.ts @@ -23,7 +23,7 @@ import { import { FirebaseApp } from '@firebase/app-types'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { AppCheckToken } from '@firebase/app-check-types'; -import { version } from '../package.json'; +import { Provider } from '@firebase/component'; /** * Response JSON returned from AppCheck server endpoint. @@ -39,18 +39,24 @@ interface AppCheckRequest { body: { [key: string]: string }; } -export async function exchangeToken({ - url, - body -}: AppCheckRequest): Promise { - const options = { +export async function exchangeToken( + { url, body }: AppCheckRequest, + platformLoggerProvider: Provider<'platform-logger'> +): Promise { + const headers: HeadersInit = { + 'Content-Type': 'application/json' + }; + // If platform logger exists, add the platform info string to the header. + const platformLogger = platformLoggerProvider.getImmediate({ + optional: true + }); + if (platformLogger) { + headers['X-Firebase-Client'] = platformLogger.getPlatformInfoString(); + } + const options: RequestInit = { method: 'POST', body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json', - // JS platform identifier + appCheck version only - 'X-Firebase-Client': `fire-js/ fire-app-check/${version}` - } + headers }; let response; try { diff --git a/packages/app-check/src/factory.ts b/packages/app-check/src/factory.ts index f8cc3d4afb3..802ea49e1d1 100644 --- a/packages/app-check/src/factory.ts +++ b/packages/app-check/src/factory.ts @@ -24,6 +24,7 @@ import { addTokenListener, removeTokenListener } from './internal-api'; +import { Provider } from '@firebase/component'; export function factory(app: FirebaseApp): FirebaseAppCheck { return { @@ -32,10 +33,15 @@ export function factory(app: FirebaseApp): FirebaseAppCheck { }; } -export function internalFactory(app: FirebaseApp): FirebaseAppCheckInternal { +export function internalFactory( + app: FirebaseApp, + platformLoggerProvider: Provider<'platform-logger'> +): FirebaseAppCheckInternal { return { - getToken: forceRefresh => getToken(app, forceRefresh), - addTokenListener: listener => addTokenListener(app, listener), + getToken: forceRefresh => + getToken(app, platformLoggerProvider, forceRefresh), + addTokenListener: listener => + addTokenListener(app, platformLoggerProvider, listener), removeTokenListener: listener => removeTokenListener(app, listener) }; } diff --git a/packages/app-check/src/index.ts b/packages/app-check/src/index.ts index 27c81d10e89..30cd4f5c5cb 100644 --- a/packages/app-check/src/index.ts +++ b/packages/app-check/src/index.ts @@ -50,7 +50,8 @@ function registerAppCheck(firebase: _FirebaseNamespace): void { container => { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app').getImmediate(); - return internalFactory(app); + const platformLoggerProvider = container.getProvider('platform-logger'); + return internalFactory(app, platformLoggerProvider); }, ComponentType.PUBLIC ) diff --git a/packages/app-check/src/internal-api.test.ts b/packages/app-check/src/internal-api.test.ts index 850b122a67a..7706a93dbad 100644 --- a/packages/app-check/src/internal-api.test.ts +++ b/packages/app-check/src/internal-api.test.ts @@ -23,6 +23,7 @@ import { FAKE_SITE_KEY, getFakeApp, getFakeCustomTokenProvider, + getFakePlatformLoggingProvider, removegreCAPTCHAScriptsOnPage } from '../test/util'; import { activate } from './api'; @@ -40,6 +41,8 @@ import { getState, clearState, setState, getDebugState } from './state'; import { AppCheckTokenListener } from '@firebase/app-check-interop-types'; import { Deferred } from '@firebase/util'; +const fakePlatformLoggingProvider = getFakePlatformLoggingProvider(); + describe('internal api', () => { let app: FirebaseApp; @@ -70,7 +73,7 @@ describe('internal api', () => { const customProviderSpy = spy(customTokenProvider, 'getToken'); activate(app, customTokenProvider); - const token = await getToken(app); + const token = await getToken(app, fakePlatformLoggingProvider); expect(customProviderSpy).to.be.called; expect(token).to.deep.equal({ @@ -91,7 +94,7 @@ describe('internal api', () => { 'exchangeToken' ).returns(Promise.resolve(fakeRecaptchaAppCheckToken)); - const token = await getToken(app); + const token = await getToken(app, fakePlatformLoggingProvider); expect(reCAPTCHASpy).to.be.called; @@ -112,7 +115,7 @@ describe('internal api', () => { const error = new Error('oops, something went wrong'); stub(client, 'exchangeToken').returns(Promise.reject(error)); - const token = await getToken(app); + const token = await getToken(app, fakePlatformLoggingProvider); expect(reCAPTCHASpy).to.be.called; expect(token).to.deep.equal({ @@ -135,10 +138,10 @@ describe('internal api', () => { const listener1 = spy(); const listener2 = spy(); - addTokenListener(app, listener1); - addTokenListener(app, listener2); + addTokenListener(app, fakePlatformLoggingProvider, listener1); + addTokenListener(app, fakePlatformLoggingProvider, listener2); - await getToken(app); + await getToken(app, fakePlatformLoggingProvider); expect(listener1).to.be.calledWith({ token: fakeCachedAppCheckToken.token @@ -161,10 +164,10 @@ describe('internal api', () => { const listener1 = spy(); const listener2 = spy(); - addTokenListener(app, listener1); - addTokenListener(app, listener2); + addTokenListener(app, fakePlatformLoggingProvider, listener1); + addTokenListener(app, fakePlatformLoggingProvider, listener2); - await getToken(app); + await getToken(app, fakePlatformLoggingProvider); expect(listener1).to.be.calledWith({ token: fakeRecaptchaAppCheckToken.token @@ -185,10 +188,10 @@ describe('internal api', () => { }; const listener2 = spy(); - addTokenListener(app, listener1); - addTokenListener(app, listener2); + addTokenListener(app, fakePlatformLoggingProvider, listener1); + addTokenListener(app, fakePlatformLoggingProvider, listener2); - await getToken(app); + await getToken(app, fakePlatformLoggingProvider); expect(listener2).to.be.calledWith({ token: fakeRecaptchaAppCheckToken.token @@ -206,7 +209,7 @@ describe('internal api', () => { const clientStub = stub(client, 'exchangeToken'); expect(getState(app).token).to.equal(undefined); - expect(await getToken(app)).to.deep.equal({ + expect(await getToken(app, fakePlatformLoggingProvider)).to.deep.equal({ token: fakeCachedAppCheckToken.token }); expect(getState(app).token).to.equal(fakeCachedAppCheckToken); @@ -224,7 +227,7 @@ describe('internal api', () => { Promise.resolve(fakeRecaptchaAppCheckToken) ); const storageWriteStub = stub(storage, 'writeTokenToStorage'); - const result = await getToken(app); + const result = await getToken(app, fakePlatformLoggingProvider); expect(result).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); expect(storageWriteStub).has.been.calledWith( app, @@ -238,7 +241,7 @@ describe('internal api', () => { setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken }); const clientStub = stub(client, 'exchangeToken'); - expect(await getToken(app)).to.deep.equal({ + expect(await getToken(app, fakePlatformLoggingProvider)).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); expect(clientStub).to.not.have.been.called; @@ -255,7 +258,9 @@ describe('internal api', () => { Promise.resolve(fakeRecaptchaAppCheckToken) ); - expect(await getToken(app, true)).to.deep.equal({ + expect( + await getToken(app, fakePlatformLoggingProvider, true) + ).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); }); @@ -271,7 +276,7 @@ describe('internal api', () => { debugState.token.resolve('my-debug-token'); activate(app, FAKE_SITE_KEY); - const token = await getToken(app); + const token = await getToken(app, fakePlatformLoggingProvider); expect(exchangeTokenStub.args[0][0].body['debug_token']).to.equal( 'my-debug-token' ); @@ -283,7 +288,7 @@ describe('internal api', () => { it('adds token listeners', () => { const listener = (): void => {}; - addTokenListener(app, listener); + addTokenListener(app, fakePlatformLoggingProvider, listener); expect(getState(app).tokenListeners[0]).to.equal(listener); }); @@ -293,7 +298,7 @@ describe('internal api', () => { expect(getState(app).tokenListeners.length).to.equal(0); expect(getState(app).tokenRefresher).to.equal(undefined); - addTokenListener(app, listener); + addTokenListener(app, fakePlatformLoggingProvider, listener); expect(getState(app).tokenRefresher?.isRunning()).to.be.true; }); @@ -316,7 +321,7 @@ describe('internal api', () => { } }); - addTokenListener(app, fakeListener); + addTokenListener(app, fakePlatformLoggingProvider, fakeListener); }); it('notifies the listener with the valid token in storage', done => { @@ -337,7 +342,7 @@ describe('internal api', () => { done(); }; - addTokenListener(app, fakeListener); + addTokenListener(app, fakePlatformLoggingProvider, fakeListener); clock.tick(1); }); @@ -355,7 +360,7 @@ describe('internal api', () => { debugState.token.resolve('my-debug-token'); activate(app, FAKE_SITE_KEY); - addTokenListener(app, fakeListener); + addTokenListener(app, fakePlatformLoggingProvider, fakeListener); }); it('does NOT start token refresher in debug mode', () => { @@ -365,7 +370,7 @@ describe('internal api', () => { debugState.token.resolve('my-debug-token'); activate(app, FAKE_SITE_KEY); - addTokenListener(app, () => {}); + addTokenListener(app, fakePlatformLoggingProvider, () => {}); const state = getState(app); expect(state.tokenRefresher).is.undefined; @@ -375,7 +380,7 @@ describe('internal api', () => { describe('removeTokenListener', () => { it('should remove token listeners', () => { const listener = (): void => {}; - addTokenListener(app, listener); + addTokenListener(app, fakePlatformLoggingProvider, listener); expect(getState(app).tokenListeners.length).to.equal(1); removeTokenListener(app, listener); @@ -385,7 +390,7 @@ describe('internal api', () => { it('should stop proactively refreshing token after deleting the last listener', () => { const listener = (): void => {}; - addTokenListener(app, listener); + addTokenListener(app, fakePlatformLoggingProvider, listener); expect(getState(app).tokenListeners.length).to.equal(1); expect(getState(app).tokenRefresher?.isRunning()).to.be.true; diff --git a/packages/app-check/src/internal-api.ts b/packages/app-check/src/internal-api.ts index 5da560db0df..1e6a76dccaf 100644 --- a/packages/app-check/src/internal-api.ts +++ b/packages/app-check/src/internal-api.ts @@ -36,6 +36,7 @@ import { getDebugToken, isDebugMode } from './debug'; import { base64 } from '@firebase/util'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { logger } from './logger'; +import { Provider } from '@firebase/component'; // Initial hardcoded value agreed upon across platforms for initial launch. // Format left open for possible dynamic error values and other fields in the future. @@ -62,6 +63,7 @@ export function formatDummyToken( */ export async function getToken( app: FirebaseApp, + platformLoggerProvider: Provider<'platform-logger'>, forceRefresh = false ): Promise { ensureActivated(app); @@ -71,7 +73,8 @@ export async function getToken( */ if (isDebugMode()) { const tokenFromDebugExchange: AppCheckToken = await exchangeToken( - getExchangeDebugTokenRequest(app, await getDebugToken()) + getExchangeDebugTokenRequest(app, await getDebugToken()), + platformLoggerProvider ); return { token: tokenFromDebugExchange.token }; } @@ -115,7 +118,8 @@ export async function getToken( throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); }); token = await exchangeToken( - getExchangeRecaptchaTokenRequest(app, attestedClaimsToken) + getExchangeRecaptchaTokenRequest(app, attestedClaimsToken), + platformLoggerProvider ); } } catch (e) { @@ -145,6 +149,7 @@ export async function getToken( export function addTokenListener( app: FirebaseApp, + platformLoggerProvider: Provider<'platform-logger'>, listener: AppCheckTokenListener ): void { const state = getState(app); @@ -174,7 +179,7 @@ export function addTokenListener( * invoke the listener with the valid token, then start the token refresher */ if (!newState.tokenRefresher) { - const tokenRefresher = createTokenRefresher(app); + const tokenRefresher = createTokenRefresher(app, platformLoggerProvider); newState.tokenRefresher = tokenRefresher; } @@ -217,7 +222,10 @@ export function removeTokenListener( }); } -function createTokenRefresher(app: FirebaseApp): Refresher { +function createTokenRefresher( + app: FirebaseApp, + platformLoggerProvider: Provider<'platform-logger'> +): Refresher { return new Refresher( // Keep in mind when this fails for any reason other than the ones // for which we should retry, it will effectively stop the proactive refresh. @@ -227,9 +235,9 @@ function createTokenRefresher(app: FirebaseApp): Refresher { // If there is a token, we force refresh it because we know it's going to expire soon let result; if (!state.token) { - result = await getToken(app); + result = await getToken(app, platformLoggerProvider); } else { - result = await getToken(app, true); + result = await getToken(app, platformLoggerProvider, true); } // getToken() always resolves. In case the result has an error field defined, it means the operation failed, and we should retry. diff --git a/packages/app-check/test/util.ts b/packages/app-check/test/util.ts index 4d95ed220bd..4b7100308a6 100644 --- a/packages/app-check/test/util.ts +++ b/packages/app-check/test/util.ts @@ -18,6 +18,12 @@ import { FirebaseApp } from '@firebase/app-types'; import { AppCheckProvider } from '@firebase/app-check-types'; import { GreCAPTCHA, RECAPTCHA_URL } from '../src/recaptcha'; +import { + Provider, + ComponentContainer, + Component, + ComponentType +} from '@firebase/component'; export const FAKE_SITE_KEY = 'fake-site-key'; @@ -51,6 +57,21 @@ export function getFakeCustomTokenProvider(): AppCheckProvider { }; } +export function getFakePlatformLoggingProvider( + fakeLogString: string = 'a/1.2.3 b/2.3.4' +): Provider<'platform-logger'> { + const container = new ComponentContainer('test'); + container.addComponent( + new Component( + 'platform-logger', + () => ({ getPlatformInfoString: () => fakeLogString }), + ComponentType.PRIVATE + ) + ); + + return container.getProvider('platform-logger'); +} + export function getFakeGreCAPTCHA(): GreCAPTCHA { return { ready: callback => callback(), From 67840cf7ca1e3ecc6960521e213910b7306456e1 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 3 May 2021 09:15:25 -0700 Subject: [PATCH 4/4] Add changeset --- .changeset/loud-bears-rule.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-bears-rule.md diff --git a/.changeset/loud-bears-rule.md b/.changeset/loud-bears-rule.md new file mode 100644 index 00000000000..280263245a1 --- /dev/null +++ b/.changeset/loud-bears-rule.md @@ -0,0 +1,5 @@ +--- +'@firebase/app': patch +--- + +Add AppCheck platform logging string.