From 2e48e6c928d953cd92e28ba054152511a177c672 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 4 Oct 2021 13:07:47 -0700 Subject: [PATCH 01/16] Add recaptcha enterprise provider --- packages/app-check/src/api.ts | 6 +- packages/app-check/src/client.test.ts | 17 +++++ packages/app-check/src/client.ts | 7 +- packages/app-check/src/providers.ts | 61 ++++++++++++++++ packages/app-check/src/public-types.ts | 8 ++- packages/app-check/src/recaptcha.test.ts | 91 +++++++++++++++++++++--- packages/app-check/src/recaptcha.ts | 13 ++-- packages/app-check/src/util.ts | 5 +- packages/app-check/test/util.ts | 17 ++++- 9 files changed, 203 insertions(+), 22 deletions(-) diff --git a/packages/app-check/src/api.ts b/packages/app-check/src/api.ts index b962f756ddf..596b1a402a3 100644 --- a/packages/app-check/src/api.ts +++ b/packages/app-check/src/api.ts @@ -43,7 +43,11 @@ declare module '@firebase/component' { } } -export { ReCaptchaV3Provider, CustomProvider } from './providers'; +export { + ReCaptchaV3Provider, + CustomProvider, + ReCaptchaEnterpriseProvider +} from './providers'; /** * Activate App Check for the given app. Can be called only once per app. diff --git a/packages/app-check/src/client.test.ts b/packages/app-check/src/client.test.ts index 769c3f49fb5..562b31124f5 100644 --- a/packages/app-check/src/client.test.ts +++ b/packages/app-check/src/client.test.ts @@ -51,6 +51,23 @@ describe('client', () => { }); }); + it('creates exchange recaptcha enterprise token request correctly', () => { + const request = getExchangeRecaptchaTokenRequest( + app, + 'fake-recaptcha-token', + true + ); + const { projectId, appId, apiKey } = app.options; + + expect(request).to.deep.equal({ + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:exchangeRecaptchaToken?key=${apiKey}`, + body: { + // eslint-disable-next-line camelcase + recaptcha_enterprise_token: 'fake-recaptcha-token' + } + }); + }); + it('returns a AppCheck token', async () => { // To get a consistent expireTime/issuedAtTime. const clock = useFakeTimers(); diff --git a/packages/app-check/src/client.ts b/packages/app-check/src/client.ts index c51f34402e0..81fbb3848de 100644 --- a/packages/app-check/src/client.ts +++ b/packages/app-check/src/client.ts @@ -105,15 +105,16 @@ export async function exchangeToken( export function getExchangeRecaptchaTokenRequest( app: FirebaseApp, - reCAPTCHAToken: string + reCAPTCHAToken: string, + isEnterprise: boolean = false ): AppCheckRequest { const { projectId, appId, apiKey } = app.options; + const fieldName = isEnterprise ? 'recaptcha_enterprise_token' : 'recaptcha_token'; return { url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, body: { - // eslint-disable-next-line - recaptcha_token: reCAPTCHAToken + [fieldName]: reCAPTCHAToken } }; } diff --git a/packages/app-check/src/providers.ts b/packages/app-check/src/providers.ts index 6c694f5cd89..fb5cd2db629 100644 --- a/packages/app-check/src/providers.ts +++ b/packages/app-check/src/providers.ts @@ -88,6 +88,67 @@ export class ReCaptchaV3Provider implements AppCheckProvider { } } +/** + * App Check provider that can obtain a reCAPTCHA Enterprise token and exchange it + * for an App Check token. + * + * @public + */ +export class ReCaptchaEnterpriseProvider implements AppCheckProvider { + private _app?: FirebaseApp; + private _platformLoggerProvider?: Provider<'platform-logger'>; + /** + * Create a ReCaptchaV3Provider instance. + * @param siteKey - ReCAPTCHA V3 siteKey. + */ + constructor(private _siteKey: string) {} + + /** + * Returns an App Check token. + * @internal + */ + async getToken(): Promise { + if (!this._app || !this._platformLoggerProvider) { + // This should only occur if user has not called initializeAppCheck(). + // We don't have an appName to provide if so. + // This should already be caught in the top level `getToken()` function. + throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { + appName: '' + }); + } + const attestedClaimsToken = await getReCAPTCHAToken(this._app).catch(_e => { + // reCaptcha.execute() throws null which is not very descriptive. + throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); + }); + return exchangeToken( + getExchangeRecaptchaTokenRequest(this._app, attestedClaimsToken, true), + this._platformLoggerProvider + ); + } + + /** + * @internal + */ + initialize(app: FirebaseApp): void { + this._app = app; + this._platformLoggerProvider = _getProvider(app, 'platform-logger'); + initializeRecaptcha(app, this._siteKey, true).catch(() => { + /* we don't care about the initialization result */ + }); + } + + /** + * @internal + */ + isEqual(otherProvider: unknown): boolean { + if (otherProvider instanceof ReCaptchaEnterpriseProvider) { + return this._siteKey === otherProvider._siteKey; + } else { + return false; + } + } +} + /** * Custom provider class. * @public diff --git a/packages/app-check/src/public-types.ts b/packages/app-check/src/public-types.ts index ae955c9d8c5..11a4a3950d3 100644 --- a/packages/app-check/src/public-types.ts +++ b/packages/app-check/src/public-types.ts @@ -16,7 +16,11 @@ */ import { FirebaseApp } from '@firebase/app'; -import { CustomProvider, ReCaptchaV3Provider } from './providers'; +import { + CustomProvider, + ReCaptchaEnterpriseProvider, + ReCaptchaV3Provider +} from './providers'; export { Unsubscribe, PartialObserver } from '@firebase/util'; /** @@ -56,7 +60,7 @@ export interface AppCheckOptions { /** * reCAPTCHA provider or custom provider. */ - provider: CustomProvider | ReCaptchaV3Provider; + provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider; /** * If set to true, enables automatic background refresh of App Check token. */ diff --git a/packages/app-check/src/recaptcha.test.ts b/packages/app-check/src/recaptcha.test.ts index 7edfadaf19f..0aea85a9851 100644 --- a/packages/app-check/src/recaptcha.test.ts +++ b/packages/app-check/src/recaptcha.test.ts @@ -26,12 +26,12 @@ import { findgreCAPTCHAScriptsOnPage, FAKE_SITE_KEY } from '../test/util'; -import { initialize, getToken } from './recaptcha'; +import { initialize, getToken, GreCAPTCHATopLevel } from './recaptcha'; import * as utils from './util'; import { getState } from './state'; import { Deferred } from '@firebase/util'; import { initializeAppCheck } from './api'; -import { ReCaptchaV3Provider } from './providers'; +import { ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from './providers'; describe('recaptcha', () => { let app: FirebaseApp; @@ -45,9 +45,9 @@ describe('recaptcha', () => { return deleteApp(app); }); - describe('initialize()', () => { + describe('initialize() - V3', () => { it('sets reCAPTCHAState', async () => { - self.grecaptcha = getFakeGreCAPTCHA(); + self.grecaptcha = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; expect(getState(app).reCAPTCHAState).to.equal(undefined); await initialize(app, FAKE_SITE_KEY); expect(getState(app).reCAPTCHAState?.initialized).to.be.instanceof( @@ -75,7 +75,7 @@ describe('recaptcha', () => { it('creates invisible widget', async () => { const grecaptchaFake = getFakeGreCAPTCHA(); const renderStub = stub(grecaptchaFake, 'render').callThrough(); - self.grecaptcha = grecaptchaFake; + self.grecaptcha = grecaptchaFake as GreCAPTCHATopLevel; await initialize(app, FAKE_SITE_KEY); @@ -88,7 +88,50 @@ describe('recaptcha', () => { }); }); - describe('getToken()', () => { + describe('initialize() - Enterprise', () => { + it('sets reCAPTCHAState', async () => { + self.grecaptcha = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; + expect(getState(app).reCAPTCHAState).to.equal(undefined); + await initialize(app, FAKE_SITE_KEY, true); + expect(getState(app).reCAPTCHAState?.initialized).to.be.instanceof( + Deferred + ); + }); + + it('loads reCAPTCHA script if it was not loaded already', async () => { + const fakeRecaptcha = getFakeGreCAPTCHA(); + let count = 0; + stub(utils, 'getRecaptcha').callsFake(() => { + count++; + if (count === 1) { + return undefined; + } + + return fakeRecaptcha; + }); + + expect(findgreCAPTCHAScriptsOnPage().length).to.equal(0); + await initialize(app, FAKE_SITE_KEY, true); + expect(findgreCAPTCHAScriptsOnPage().length).to.equal(1); + }); + + it('creates invisible widget', async () => { + const grecaptchaFake = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; + const renderStub = stub(grecaptchaFake.enterprise, 'render').callThrough(); + self.grecaptcha = grecaptchaFake; + + await initialize(app, FAKE_SITE_KEY, true); + + expect(renderStub).to.be.calledWith(`fire_app_check_${app.name}`, { + sitekey: FAKE_SITE_KEY, + size: 'invisible' + }); + + expect(getState(app).reCAPTCHAState?.widgetId).to.equal('fake_widget_1'); + }); + }); + + describe('getToken() - V3', () => { it('throws if AppCheck has not been activated yet', () => { return expect(getToken(app)).to.eventually.rejectedWith( /appCheck\/use-before-activation/ @@ -100,7 +143,7 @@ describe('recaptcha', () => { const executeStub = stub(grecaptchaFake, 'execute').returns( Promise.resolve('fake-recaptcha-token') ); - self.grecaptcha = grecaptchaFake; + self.grecaptcha = grecaptchaFake as GreCAPTCHATopLevel; initializeAppCheck(app, { provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) }); @@ -116,7 +159,7 @@ describe('recaptcha', () => { stub(grecaptchaFake, 'execute').returns( Promise.resolve('fake-recaptcha-token') ); - self.grecaptcha = grecaptchaFake; + self.grecaptcha = grecaptchaFake as GreCAPTCHATopLevel; initializeAppCheck(app, { provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) }); @@ -125,4 +168,36 @@ describe('recaptcha', () => { expect(token).to.equal('fake-recaptcha-token'); }); }); + + describe('getToken() - Enterprise', () => { + it('calls recaptcha.execute with correct widgetId', async () => { + const grecaptchaFake = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; + const executeStub = stub(grecaptchaFake.enterprise, 'execute').returns( + Promise.resolve('fake-recaptcha-token') + ); + self.grecaptcha = grecaptchaFake; + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + await getToken(app); + + expect(executeStub).to.have.been.calledWith('fake_widget_1', { + action: 'fire_app_check' + }); + }); + + it('resolves with token returned by recaptcha.execute', async () => { + const grecaptchaFake = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; + stub(grecaptchaFake.enterprise, 'execute').returns( + Promise.resolve('fake-recaptcha-token') + ); + self.grecaptcha = grecaptchaFake; + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + const token = await getToken(app); + + expect(token).to.equal('fake-recaptcha-token'); + }); + }); }); diff --git a/packages/app-check/src/recaptcha.ts b/packages/app-check/src/recaptcha.ts index 91ea94bfbd2..8ae64b4f384 100644 --- a/packages/app-check/src/recaptcha.ts +++ b/packages/app-check/src/recaptcha.ts @@ -24,7 +24,8 @@ export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; export function initialize( app: FirebaseApp, - siteKey: string + siteKey: string, + isEnterprise: boolean = false ): Promise { const state = getState(app); const initialized = new Deferred(); @@ -38,10 +39,10 @@ export function initialize( document.body.appendChild(invisibleDiv); - const grecaptcha = getRecaptcha(); + const grecaptcha = getRecaptcha(isEnterprise); if (!grecaptcha) { loadReCAPTCHAScript(() => { - const grecaptcha = getRecaptcha(); + const grecaptcha = getRecaptcha(isEnterprise); if (!grecaptcha) { // it shouldn't happen. @@ -120,10 +121,14 @@ function loadReCAPTCHAScript(onload: () => void): void { declare global { interface Window { - grecaptcha: GreCAPTCHA | undefined; + grecaptcha: GreCAPTCHATopLevel | undefined; } } +export interface GreCAPTCHATopLevel extends GreCAPTCHA { + enterprise: GreCAPTCHA; +} + export interface GreCAPTCHA { ready: (callback: () => void) => void; execute: (siteKey: string, options: { action: string }) => Promise; diff --git a/packages/app-check/src/util.ts b/packages/app-check/src/util.ts index 9102d335eb2..649d60f46b4 100644 --- a/packages/app-check/src/util.ts +++ b/packages/app-check/src/util.ts @@ -20,7 +20,10 @@ import { getState } from './state'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { FirebaseApp } from '@firebase/app'; -export function getRecaptcha(): GreCAPTCHA | undefined { +export function getRecaptcha(isEnterprise: boolean = false): GreCAPTCHA | undefined { + if (isEnterprise) { + return self.grecaptcha?.enterprise; + } return self.grecaptcha; } diff --git a/packages/app-check/test/util.ts b/packages/app-check/test/util.ts index bd0a77885f4..2b5f052e8a9 100644 --- a/packages/app-check/test/util.ts +++ b/packages/app-check/test/util.ts @@ -16,7 +16,11 @@ */ import { FirebaseApp, initializeApp, _registerComponent } from '@firebase/app'; -import { GreCAPTCHA, RECAPTCHA_URL } from '../src/recaptcha'; +import { + GreCAPTCHA, + GreCAPTCHATopLevel, + RECAPTCHA_URL +} from '../src/recaptcha'; import { Provider, ComponentContainer, @@ -102,12 +106,19 @@ export function getFakePlatformLoggingProvider( return container.getProvider('platform-logger'); } -export function getFakeGreCAPTCHA(): GreCAPTCHA { - return { +export function getFakeGreCAPTCHA( + isTopLevel: boolean = true +): GreCAPTCHATopLevel | GreCAPTCHA { + const greCaptchaTopLevel: GreCAPTCHA = { ready: callback => callback(), render: (_container, _parameters) => 'fake_widget_1', execute: (_siteKey, _options) => Promise.resolve('fake_recaptcha_token') }; + if (isTopLevel) { + (greCaptchaTopLevel as GreCAPTCHATopLevel).enterprise = + getFakeGreCAPTCHA(false); + } + return greCaptchaTopLevel; } /** From 9efa5a5d79bcc0a61ec821d835558afbb2a83eda Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 4 Oct 2021 13:24:02 -0700 Subject: [PATCH 02/16] start on app-check-compat --- common/api-review/app-check.api.md | 199 ++++++++++++----------- packages/app-check-compat/src/index.ts | 7 +- packages/app-check-compat/src/service.ts | 7 +- packages/app-check/src/client.ts | 4 +- packages/app-check/src/recaptcha.test.ts | 5 +- packages/app-check/src/util.ts | 4 +- 6 files changed, 127 insertions(+), 99 deletions(-) diff --git a/common/api-review/app-check.api.md b/common/api-review/app-check.api.md index 154e7ce2295..7a67b90bd71 100644 --- a/common/api-review/app-check.api.md +++ b/common/api-review/app-check.api.md @@ -1,94 +1,105 @@ -## API Report File for "@firebase/app-check" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app'; -import { PartialObserver } from '@firebase/util'; -import { Unsubscribe } from '@firebase/util'; - -// @public -export interface AppCheck { - app: FirebaseApp; -} - -// @internal (undocumented) -export type _AppCheckComponentName = 'app-check'; - -// @internal (undocumented) -export type _AppCheckInternalComponentName = 'app-check-internal'; - -// @public -export interface AppCheckOptions { - isTokenAutoRefreshEnabled?: boolean; - provider: CustomProvider | ReCaptchaV3Provider; -} - -// @public -export interface AppCheckToken { - readonly expireTimeMillis: number; - // (undocumented) - readonly token: string; -} - -// @public -export type AppCheckTokenListener = (token: AppCheckTokenResult) => void; - -// @public -export interface AppCheckTokenResult { - readonly token: string; -} - -// Warning: (ae-forgotten-export) The symbol "AppCheckProvider" needs to be exported by the entry point index.d.ts -// -// @public -export class CustomProvider implements AppCheckProvider { - constructor(_customProviderOptions: CustomProviderOptions); - // Warning: (ae-forgotten-export) The symbol "AppCheckTokenInternal" needs to be exported by the entry point index.d.ts - // - // @internal (undocumented) - getToken(): Promise; - // @internal (undocumented) - initialize(app: FirebaseApp): void; - // @internal (undocumented) - isEqual(otherProvider: unknown): boolean; -} - -// @public -export interface CustomProviderOptions { - getToken: () => Promise; -} - -// @public -export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise; - -// @public -export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck; - -// @public -export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver): Unsubscribe; - -// @public -export function onTokenChanged(appCheckInstance: AppCheck, onNext: (tokenResult: AppCheckTokenResult) => void, onError?: (error: Error) => void, onCompletion?: () => void): Unsubscribe; - -export { PartialObserver } - -// @public -export class ReCaptchaV3Provider implements AppCheckProvider { - constructor(_siteKey: string); - // @internal - getToken(): Promise; - // @internal (undocumented) - initialize(app: FirebaseApp): void; - // @internal (undocumented) - isEqual(otherProvider: unknown): boolean; - } - -// @public -export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void; - -export { Unsubscribe } - - -``` +## API Report File for "@firebase/app-check" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { PartialObserver } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export interface AppCheck { + app: FirebaseApp; +} + +// @internal (undocumented) +export type _AppCheckComponentName = 'app-check'; + +// @internal (undocumented) +export type _AppCheckInternalComponentName = 'app-check-internal'; + +// @public +export interface AppCheckOptions { + isTokenAutoRefreshEnabled?: boolean; + provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider; +} + +// @public +export interface AppCheckToken { + readonly expireTimeMillis: number; + // (undocumented) + readonly token: string; +} + +// @public +export type AppCheckTokenListener = (token: AppCheckTokenResult) => void; + +// @public +export interface AppCheckTokenResult { + readonly token: string; +} + +// Warning: (ae-forgotten-export) The symbol "AppCheckProvider" needs to be exported by the entry point index.d.ts +// +// @public +export class CustomProvider implements AppCheckProvider { + constructor(_customProviderOptions: CustomProviderOptions); + // Warning: (ae-forgotten-export) The symbol "AppCheckTokenInternal" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + // @internal (undocumented) + isEqual(otherProvider: unknown): boolean; +} + +// @public +export interface CustomProviderOptions { + getToken: () => Promise; +} + +// @public +export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise; + +// @public +export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver): Unsubscribe; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, onNext: (tokenResult: AppCheckTokenResult) => void, onError?: (error: Error) => void, onCompletion?: () => void): Unsubscribe; + +export { PartialObserver } + +// @public +export class ReCaptchaEnterpriseProvider implements AppCheckProvider { + constructor(_siteKey: string); + // @internal + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + // @internal (undocumented) + isEqual(otherProvider: unknown): boolean; + } + +// @public +export class ReCaptchaV3Provider implements AppCheckProvider { + constructor(_siteKey: string); + // @internal + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + // @internal (undocumented) + isEqual(otherProvider: unknown): boolean; + } + +// @public +export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void; + +export { Unsubscribe } + + +``` diff --git a/packages/app-check-compat/src/index.ts b/packages/app-check-compat/src/index.ts index 925b6ca7b5e..2715598270c 100644 --- a/packages/app-check-compat/src/index.ts +++ b/packages/app-check-compat/src/index.ts @@ -28,7 +28,11 @@ import { } from '@firebase/component'; import { AppCheckService } from './service'; import { FirebaseAppCheck } from '@firebase/app-check-types'; -import { ReCaptchaV3Provider, CustomProvider } from '@firebase/app-check'; +import { + ReCaptchaV3Provider, + ReCaptchaEnterpriseProvider, + CustomProvider +} from '@firebase/app-check'; const factory: InstanceFactory<'appCheck-compat'> = ( container: ComponentContainer @@ -46,6 +50,7 @@ export function registerAppCheck(): void { factory, ComponentType.PUBLIC ).setServiceProps({ + ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, CustomProvider }) diff --git a/packages/app-check-compat/src/service.ts b/packages/app-check-compat/src/service.ts index fb0045aad9a..db3f2d1ef46 100644 --- a/packages/app-check-compat/src/service.ts +++ b/packages/app-check-compat/src/service.ts @@ -26,6 +26,7 @@ import { CustomProvider, initializeAppCheck, ReCaptchaV3Provider, + ReCaptchaEnterpriseProvider, setTokenAutoRefreshEnabled as setTokenAutoRefreshEnabledExp, getToken as getTokenExp, onTokenChanged as onTokenChangedExp @@ -43,10 +44,14 @@ export class AppCheckService siteKeyOrProvider: string | AppCheckProvider, isTokenAutoRefreshEnabled?: boolean ): void { - let provider: ReCaptchaV3Provider | CustomProvider; + let provider: + | ReCaptchaV3Provider + | CustomProvider + | ReCaptchaEnterpriseProvider; if (typeof siteKeyOrProvider === 'string') { provider = new ReCaptchaV3Provider(siteKeyOrProvider); } else if ( + siteKeyOrProvider instanceof ReCaptchaEnterpriseProvider || siteKeyOrProvider instanceof ReCaptchaV3Provider || siteKeyOrProvider instanceof CustomProvider ) { diff --git a/packages/app-check/src/client.ts b/packages/app-check/src/client.ts index 81fbb3848de..9836f32c929 100644 --- a/packages/app-check/src/client.ts +++ b/packages/app-check/src/client.ts @@ -109,7 +109,9 @@ export function getExchangeRecaptchaTokenRequest( isEnterprise: boolean = false ): AppCheckRequest { const { projectId, appId, apiKey } = app.options; - const fieldName = isEnterprise ? 'recaptcha_enterprise_token' : 'recaptcha_token'; + const fieldName = isEnterprise + ? 'recaptcha_enterprise_token' + : 'recaptcha_token'; return { url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, diff --git a/packages/app-check/src/recaptcha.test.ts b/packages/app-check/src/recaptcha.test.ts index 0aea85a9851..bdb1eb9b4e8 100644 --- a/packages/app-check/src/recaptcha.test.ts +++ b/packages/app-check/src/recaptcha.test.ts @@ -117,7 +117,10 @@ describe('recaptcha', () => { it('creates invisible widget', async () => { const grecaptchaFake = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; - const renderStub = stub(grecaptchaFake.enterprise, 'render').callThrough(); + const renderStub = stub( + grecaptchaFake.enterprise, + 'render' + ).callThrough(); self.grecaptcha = grecaptchaFake; await initialize(app, FAKE_SITE_KEY, true); diff --git a/packages/app-check/src/util.ts b/packages/app-check/src/util.ts index 649d60f46b4..dd699141da8 100644 --- a/packages/app-check/src/util.ts +++ b/packages/app-check/src/util.ts @@ -20,7 +20,9 @@ import { getState } from './state'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { FirebaseApp } from '@firebase/app'; -export function getRecaptcha(isEnterprise: boolean = false): GreCAPTCHA | undefined { +export function getRecaptcha( + isEnterprise: boolean = false +): GreCAPTCHA | undefined { if (isEnterprise) { return self.grecaptcha?.enterprise; } From f1d7b39c5e29488cac8588fa0abfcb300efdddbf Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 8 Oct 2021 13:38:03 -0700 Subject: [PATCH 03/16] Correct Enterprise URL --- packages/app-check/src/recaptcha.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/app-check/src/recaptcha.ts b/packages/app-check/src/recaptcha.ts index 8ae64b4f384..634029fa2fe 100644 --- a/packages/app-check/src/recaptcha.ts +++ b/packages/app-check/src/recaptcha.ts @@ -21,6 +21,7 @@ import { Deferred } from '@firebase/util'; import { getRecaptcha, ensureActivated } from './util'; export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; +export const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js'; export function initialize( app: FirebaseApp, @@ -53,7 +54,7 @@ export function initialize( renderInvisibleWidget(app, siteKey, grecaptcha, divId); initialized.resolve(grecaptcha); }); - }); + }, isEnterprise); } else { grecaptcha.ready(() => { renderInvisibleWidget(app, siteKey, grecaptcha, divId); @@ -112,9 +113,9 @@ function renderInvisibleWidget( }); } -function loadReCAPTCHAScript(onload: () => void): void { +function loadReCAPTCHAScript(onload: () => void, isEnterprise: boolean = false): void { const script = document.createElement('script'); - script.src = `${RECAPTCHA_URL}`; + script.src = isEnterprise ? RECAPTCHA_ENTERPRISE_URL : RECAPTCHA_URL; script.onload = onload; document.head.appendChild(script); } From 1ac24a59e4905e048cc0edb6a8907a3b28cb6521 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 8 Oct 2021 13:38:36 -0700 Subject: [PATCH 04/16] prettier --- packages/app-check/src/recaptcha.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/app-check/src/recaptcha.ts b/packages/app-check/src/recaptcha.ts index 634029fa2fe..54054a124d8 100644 --- a/packages/app-check/src/recaptcha.ts +++ b/packages/app-check/src/recaptcha.ts @@ -21,7 +21,8 @@ import { Deferred } from '@firebase/util'; import { getRecaptcha, ensureActivated } from './util'; export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; -export const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js'; +export const RECAPTCHA_ENTERPRISE_URL = + 'https://www.google.com/recaptcha/enterprise.js'; export function initialize( app: FirebaseApp, @@ -113,7 +114,10 @@ function renderInvisibleWidget( }); } -function loadReCAPTCHAScript(onload: () => void, isEnterprise: boolean = false): void { +function loadReCAPTCHAScript( + onload: () => void, + isEnterprise: boolean = false +): void { const script = document.createElement('script'); script.src = isEnterprise ? RECAPTCHA_ENTERPRISE_URL : RECAPTCHA_URL; script.onload = onload; From 52311079227c3afc9d07a0da1c87104f4cac51dd Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 8 Oct 2021 14:06:28 -0700 Subject: [PATCH 05/16] Add changeset --- .changeset/ten-impalas-wink.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/ten-impalas-wink.md diff --git a/.changeset/ten-impalas-wink.md b/.changeset/ten-impalas-wink.md new file mode 100644 index 00000000000..a7da9a8e5a6 --- /dev/null +++ b/.changeset/ten-impalas-wink.md @@ -0,0 +1,6 @@ +--- +'@firebase/app-check': minor +'@firebase/app-check-compat': minor +--- + +Add ReCAPTCHA Enterprise as an attestation option for App Check. From facc66f2d29041c5caf829b9aa824e6966c62331 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 4 Oct 2021 13:24:02 -0700 Subject: [PATCH 06/16] start on app-check-compat From 3fe71135767da2808067d354d665bf02aed8170a Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 21 Oct 2021 12:54:18 -0700 Subject: [PATCH 07/16] Add enterprise endpoint --- packages/app-check/src/client.ts | 6 +++++- packages/app-check/src/constants.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/app-check/src/client.ts b/packages/app-check/src/client.ts index 9836f32c929..2a3ab414b34 100644 --- a/packages/app-check/src/client.ts +++ b/packages/app-check/src/client.ts @@ -18,6 +18,7 @@ import { BASE_ENDPOINT, EXCHANGE_DEBUG_TOKEN_METHOD, + EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD, EXCHANGE_RECAPTCHA_TOKEN_METHOD } from './constants'; import { FirebaseApp } from '@firebase/app'; @@ -112,9 +113,12 @@ export function getExchangeRecaptchaTokenRequest( const fieldName = isEnterprise ? 'recaptcha_enterprise_token' : 'recaptcha_token'; + const exchangeMethod = isEnterprise + ? EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD + : EXCHANGE_RECAPTCHA_TOKEN_METHOD; return { - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${exchangeMethod}?key=${apiKey}`, body: { [fieldName]: reCAPTCHAToken } diff --git a/packages/app-check/src/constants.ts b/packages/app-check/src/constants.ts index 56cdd623427..080734f10cc 100644 --- a/packages/app-check/src/constants.ts +++ b/packages/app-check/src/constants.ts @@ -18,6 +18,7 @@ export const BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1beta'; export const EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaToken'; +export const EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken'; export const EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken'; export const TOKEN_REFRESH_TIME = { From e5725c292aed61d1fa378ab8214bfde966dec6e8 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 21 Oct 2021 13:22:32 -0700 Subject: [PATCH 08/16] Fix tests --- packages/app-check/src/client.test.ts | 2 +- packages/app-check/src/constants.ts | 3 ++- packages/app-check/test/util.ts | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/app-check/src/client.test.ts b/packages/app-check/src/client.test.ts index 562b31124f5..1c00f309aa8 100644 --- a/packages/app-check/src/client.test.ts +++ b/packages/app-check/src/client.test.ts @@ -60,7 +60,7 @@ describe('client', () => { const { projectId, appId, apiKey } = app.options; expect(request).to.deep.equal({ - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:exchangeRecaptchaToken?key=${apiKey}`, + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:exchangeRecaptchaEnterpriseToken?key=${apiKey}`, body: { // eslint-disable-next-line camelcase recaptcha_enterprise_token: 'fake-recaptcha-token' diff --git a/packages/app-check/src/constants.ts b/packages/app-check/src/constants.ts index 080734f10cc..ecb7fb49a87 100644 --- a/packages/app-check/src/constants.ts +++ b/packages/app-check/src/constants.ts @@ -18,7 +18,8 @@ export const BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1beta'; export const EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaToken'; -export const EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken'; +export const EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = + 'exchangeRecaptchaEnterpriseToken'; export const EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken'; export const TOKEN_REFRESH_TIME = { diff --git a/packages/app-check/test/util.ts b/packages/app-check/test/util.ts index 2b5f052e8a9..fa9f1afb41a 100644 --- a/packages/app-check/test/util.ts +++ b/packages/app-check/test/util.ts @@ -19,6 +19,7 @@ import { FirebaseApp, initializeApp, _registerComponent } from '@firebase/app'; import { GreCAPTCHA, GreCAPTCHATopLevel, + RECAPTCHA_ENTERPRISE_URL, RECAPTCHA_URL } from '../src/recaptcha'; import { @@ -130,7 +131,11 @@ export function findgreCAPTCHAScriptsOnPage(): HTMLScriptElement[] { const scriptTags = window.document.getElementsByTagName('script'); const tags = []; for (const tag of Object.values(scriptTags)) { - if (tag.src && tag.src.includes(RECAPTCHA_URL)) { + if ( + tag.src && + (tag.src.includes(RECAPTCHA_URL) || + tag.src.includes(RECAPTCHA_ENTERPRISE_URL)) + ) { tags.push(tag); } } From 5950d04e3028c6ad3dd72efee45e2427956187ba Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 21 Oct 2021 14:32:41 -0700 Subject: [PATCH 09/16] update index.d.ts --- packages/firebase/compat/index.d.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/firebase/compat/index.d.ts b/packages/firebase/compat/index.d.ts index a81d7d37e92..3435748ab64 100644 --- a/packages/firebase/compat/index.d.ts +++ b/packages/firebase/compat/index.d.ts @@ -1551,6 +1551,15 @@ declare namespace firebase.appCheck { */ constructor(siteKey: string); } + /* + * ReCAPTCHA Enterprise token provider. + */ + class ReCaptchaEnterpriseProvider { + /** + * @param keyId - ReCAPTCHA Enterprise key ID. + */ + constructor(keyId: string); + } /* * Custom token provider. */ @@ -1581,8 +1590,8 @@ declare namespace firebase.appCheck { /** * Activate AppCheck * @param provider This can be a `ReCaptchaV3Provider` instance, - * a `CustomProvider` instance, an object with a custom `getToken()` - * method, or a reCAPTCHA site key. + * a `ReCaptchaEnterpriseProvider` instance, a `CustomProvider` instance, + * an object with a custom `getToken()` method, or a reCAPTCHA site key. * @param isTokenAutoRefreshEnabled If true, the SDK automatically * refreshes App Check tokens as needed. If undefined, defaults to the * value of `app.automaticDataCollectionEnabled`, which defaults to @@ -1591,6 +1600,7 @@ declare namespace firebase.appCheck { activate( provider: | ReCaptchaV3Provider + | ReCaptchaEnterpriseProvider | CustomProvider | AppCheckProvider | { getToken: () => AppCheckToken } From 3df0e4bbcc214eb203f97d9f6657462d4f060091 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 26 Oct 2021 16:32:18 -0700 Subject: [PATCH 10/16] Add more tests --- packages/app-check/src/api.test.ts | 36 ++++++++++++++++++++- packages/app-check/src/internal-api.test.ts | 27 ++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/packages/app-check/src/api.test.ts b/packages/app-check/src/api.test.ts index 8f30d4b7b91..b2fc31818d6 100644 --- a/packages/app-check/src/api.test.ts +++ b/packages/app-check/src/api.test.ts @@ -41,7 +41,7 @@ import * as internalApi from './internal-api'; import * as indexeddb from './indexeddb'; import * as debug from './debug'; import { deleteApp, FirebaseApp } from '@firebase/app'; -import { CustomProvider, ReCaptchaV3Provider } from './providers'; +import { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from './providers'; import { AppCheckService } from './factory'; import { AppCheckToken } from './public-types'; import { getDebugToken } from './debug'; @@ -83,6 +83,16 @@ describe('api', () => { }) ).to.throw(/appCheck\/already-initialized/); }); + it('can only be called once (if given different ReCaptchaEnterpriseProviders)', () => { + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + expect(() => + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY + 'X') + }) + ).to.throw(/appCheck\/already-initialized/); + }); it('can only be called once (if given different CustomProviders)', () => { initializeAppCheck(app, { provider: new CustomProvider({ @@ -107,6 +117,16 @@ describe('api', () => { }) ).to.equal(appCheckInstance); }); + it('can be called multiple times (if given equivalent ReCaptchaEnterpriseProviders)', () => { + const appCheckInstance = initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + expect( + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }) + ).to.equal(appCheckInstance); + }); it('can be called multiple times (if given equivalent CustomProviders)', () => { const appCheckInstance = initializeAppCheck(app, { provider: new CustomProvider({ @@ -178,6 +198,20 @@ describe('api', () => { ); }); + it('initialize reCAPTCHA when a ReCaptchaEnterpriseProvider is provided', () => { + const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize').returns( + Promise.resolve({} as any) + ); + initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + expect(initReCAPTCHAStub).to.have.been.calledWithExactly( + app, + FAKE_SITE_KEY, + true + ); + }); + it('sets activated to true', () => { expect(getState(app).activated).to.equal(false); initializeAppCheck(app, { diff --git a/packages/app-check/src/internal-api.test.ts b/packages/app-check/src/internal-api.test.ts index 81953c0e022..20030352daf 100644 --- a/packages/app-check/src/internal-api.test.ts +++ b/packages/app-check/src/internal-api.test.ts @@ -41,7 +41,7 @@ import * as util from './util'; import { getState, clearState, setState, getDebugState } from './state'; import { AppCheckTokenListener } from './public-types'; import { Deferred } from '@firebase/util'; -import { ReCaptchaV3Provider } from './providers'; +import { ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from './providers'; import { AppCheckService } from './factory'; import { ListenerType } from './types'; @@ -92,7 +92,7 @@ describe('internal api', () => { }); }); - it('uses reCAPTCHA token to exchange for AppCheck token', async () => { + it('uses reCAPTCHA (V3) token to exchange for AppCheck token', async () => { const appCheck = initializeAppCheck(app, { provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) }); @@ -115,6 +115,29 @@ describe('internal api', () => { expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); }); + it('uses reCAPTCHA (Enterprise) token to exchange for AppCheck token', async () => { + const appCheck = initializeAppCheck(app, { + provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY) + }); + + const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').returns( + Promise.resolve(fakeRecaptchaToken) + ); + const exchangeTokenStub: SinonStub = stub( + client, + 'exchangeToken' + ).returns(Promise.resolve(fakeRecaptchaAppCheckToken)); + + const token = await getToken(appCheck as AppCheckService); + + expect(reCAPTCHASpy).to.be.called; + + expect(exchangeTokenStub.args[0][0].body['recaptcha_enterprise_token']).to.equal( + fakeRecaptchaToken + ); + expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); + }); + it('resolves with a dummy token and an error if failed to get a token', async () => { const errorStub = stub(console, 'error'); const appCheck = initializeAppCheck(app, { From 5e63c0eead7b7ed8644e5466b8e3a8ec8664be51 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 28 Oct 2021 13:42:29 -0700 Subject: [PATCH 11/16] Update .changeset/ten-impalas-wink.md Co-authored-by: Feiyang --- .changeset/ten-impalas-wink.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/ten-impalas-wink.md b/.changeset/ten-impalas-wink.md index a7da9a8e5a6..e5b709853d5 100644 --- a/.changeset/ten-impalas-wink.md +++ b/.changeset/ten-impalas-wink.md @@ -1,6 +1,7 @@ --- '@firebase/app-check': minor '@firebase/app-check-compat': minor +'firebase': minor --- Add ReCAPTCHA Enterprise as an attestation option for App Check. From d9a94e58c9861df63d55b371f838df93745f2a37 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 28 Oct 2021 14:37:13 -0700 Subject: [PATCH 12/16] Address PR comments --- packages/app-check/src/api.test.ts | 7 +- packages/app-check/src/client.test.ts | 19 ++--- packages/app-check/src/client.ts | 29 ++++--- packages/app-check/src/providers.ts | 49 ++++------- packages/app-check/src/recaptcha.test.ts | 14 +-- packages/app-check/src/recaptcha.ts | 104 +++++++++++++++++------ 6 files changed, 130 insertions(+), 92 deletions(-) diff --git a/packages/app-check/src/api.test.ts b/packages/app-check/src/api.test.ts index b2fc31818d6..51e1315539a 100644 --- a/packages/app-check/src/api.test.ts +++ b/packages/app-check/src/api.test.ts @@ -186,7 +186,7 @@ describe('api', () => { }); it('initialize reCAPTCHA when a ReCaptchaV3Provider is provided', () => { - const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize').returns( + const initReCAPTCHAStub = stub(reCAPTCHA, 'initializeV3').returns( Promise.resolve({} as any) ); initializeAppCheck(app, { @@ -199,7 +199,7 @@ describe('api', () => { }); it('initialize reCAPTCHA when a ReCaptchaEnterpriseProvider is provided', () => { - const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize').returns( + const initReCAPTCHAStub = stub(reCAPTCHA, 'initializeEnterprise').returns( Promise.resolve({} as any) ); initializeAppCheck(app, { @@ -207,8 +207,7 @@ describe('api', () => { }); expect(initReCAPTCHAStub).to.have.been.calledWithExactly( app, - FAKE_SITE_KEY, - true + FAKE_SITE_KEY ); }); diff --git a/packages/app-check/src/client.test.ts b/packages/app-check/src/client.test.ts index 1c00f309aa8..5500cc06fea 100644 --- a/packages/app-check/src/client.test.ts +++ b/packages/app-check/src/client.test.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { stub, SinonStub, useFakeTimers } from 'sinon'; import { FirebaseApp } from '@firebase/app'; import { getFakeApp, getFakePlatformLoggingProvider } from '../test/util'; -import { getExchangeRecaptchaTokenRequest, exchangeToken } from './client'; +import { getExchangeRecaptchaV3TokenRequest, exchangeToken, getExchangeRecaptchaEnterpriseTokenRequest } from './client'; import { FirebaseError } from '@firebase/util'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { BASE_ENDPOINT } from './constants'; @@ -36,7 +36,7 @@ describe('client', () => { }); it('creates exchange recaptcha token request correctly', () => { - const request = getExchangeRecaptchaTokenRequest( + const request = getExchangeRecaptchaV3TokenRequest( app, 'fake-recaptcha-token' ); @@ -52,10 +52,9 @@ describe('client', () => { }); it('creates exchange recaptcha enterprise token request correctly', () => { - const request = getExchangeRecaptchaTokenRequest( + const request = getExchangeRecaptchaEnterpriseTokenRequest( app, - 'fake-recaptcha-token', - true + 'fake-recaptcha-token' ); const { projectId, appId, apiKey } = app.options; @@ -82,7 +81,7 @@ describe('client', () => { ); const response = await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getExchangeRecaptchaV3TokenRequest(app, 'fake-custom-token'), getFakePlatformLoggingProvider('a/1.2.3 fire-app-check/2.3.4') ); @@ -110,7 +109,7 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getExchangeRecaptchaV3TokenRequest(app, 'fake-custom-token'), getFakePlatformLoggingProvider() ); } catch (e) { @@ -139,7 +138,7 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getExchangeRecaptchaV3TokenRequest(app, 'fake-custom-token'), getFakePlatformLoggingProvider() ); } catch (e) { @@ -167,7 +166,7 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getExchangeRecaptchaV3TokenRequest(app, 'fake-custom-token'), getFakePlatformLoggingProvider() ); } catch (e) { @@ -201,7 +200,7 @@ describe('client', () => { try { await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), + getExchangeRecaptchaV3TokenRequest(app, 'fake-custom-token'), getFakePlatformLoggingProvider() ); } catch (e) { diff --git a/packages/app-check/src/client.ts b/packages/app-check/src/client.ts index 2a3ab414b34..94564191f64 100644 --- a/packages/app-check/src/client.ts +++ b/packages/app-check/src/client.ts @@ -104,23 +104,30 @@ export async function exchangeToken( }; } -export function getExchangeRecaptchaTokenRequest( +export function getExchangeRecaptchaV3TokenRequest( app: FirebaseApp, - reCAPTCHAToken: string, - isEnterprise: boolean = false + reCAPTCHAToken: string ): AppCheckRequest { const { projectId, appId, apiKey } = app.options; - const fieldName = isEnterprise - ? 'recaptcha_enterprise_token' - : 'recaptcha_token'; - const exchangeMethod = isEnterprise - ? EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD - : EXCHANGE_RECAPTCHA_TOKEN_METHOD; return { - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${exchangeMethod}?key=${apiKey}`, + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, body: { - [fieldName]: reCAPTCHAToken + 'recaptcha_token': reCAPTCHAToken + } + }; +} + +export function getExchangeRecaptchaEnterpriseTokenRequest( + app: FirebaseApp, + reCAPTCHAToken: string +): AppCheckRequest { + const { projectId, appId, apiKey } = app.options; + + return { + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD}?key=${apiKey}`, + body: { + 'recaptcha_enterprise_token': reCAPTCHAToken } }; } diff --git a/packages/app-check/src/providers.ts b/packages/app-check/src/providers.ts index fb5cd2db629..ffb6efbf403 100644 --- a/packages/app-check/src/providers.ts +++ b/packages/app-check/src/providers.ts @@ -18,12 +18,13 @@ import { FirebaseApp, _getProvider } from '@firebase/app'; import { Provider } from '@firebase/component'; import { issuedAtTime } from '@firebase/util'; -import { exchangeToken, getExchangeRecaptchaTokenRequest } from './client'; +import { exchangeToken, getExchangeRecaptchaEnterpriseTokenRequest, getExchangeRecaptchaV3TokenRequest } from './client'; import { AppCheckError, ERROR_FACTORY } from './errors'; import { CustomProviderOptions } from './public-types'; import { getToken as getReCAPTCHAToken, - initialize as initializeRecaptcha + initializeV3 as initializeRecaptchaV3, + initializeEnterprise as initializeRecaptchaEnterprise } from './recaptcha'; import { AppCheckProvider, AppCheckTokenInternal } from './types'; @@ -47,21 +48,15 @@ export class ReCaptchaV3Provider implements AppCheckProvider { * @internal */ async getToken(): Promise { - if (!this._app || !this._platformLoggerProvider) { - // This should only occur if user has not called initializeAppCheck(). - // We don't have an appName to provide if so. - // This should already be caught in the top level `getToken()` function. - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: '' - }); - } - const attestedClaimsToken = await getReCAPTCHAToken(this._app).catch(_e => { + // Top-level `getToken()` has already checked that App Check is initialized + // and therefore this._app and this._platformLoggerProvider are available. + const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch(_e => { // reCaptcha.execute() throws null which is not very descriptive. throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); }); return exchangeToken( - getExchangeRecaptchaTokenRequest(this._app, attestedClaimsToken), - this._platformLoggerProvider + getExchangeRecaptchaV3TokenRequest(this._app!, attestedClaimsToken), + this._platformLoggerProvider! ); } @@ -71,7 +66,7 @@ export class ReCaptchaV3Provider implements AppCheckProvider { initialize(app: FirebaseApp): void { this._app = app; this._platformLoggerProvider = _getProvider(app, 'platform-logger'); - initializeRecaptcha(app, this._siteKey).catch(() => { + initializeRecaptchaV3(app, this._siteKey).catch(() => { /* we don't care about the initialization result */ }); } @@ -108,21 +103,15 @@ export class ReCaptchaEnterpriseProvider implements AppCheckProvider { * @internal */ async getToken(): Promise { - if (!this._app || !this._platformLoggerProvider) { - // This should only occur if user has not called initializeAppCheck(). - // We don't have an appName to provide if so. - // This should already be caught in the top level `getToken()` function. - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: '' - }); - } - const attestedClaimsToken = await getReCAPTCHAToken(this._app).catch(_e => { + // Top-level `getToken()` has already checked that App Check is initialized + // and therefore this._app and this._platformLoggerProvider are available. + const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch(_e => { // reCaptcha.execute() throws null which is not very descriptive. throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); }); return exchangeToken( - getExchangeRecaptchaTokenRequest(this._app, attestedClaimsToken, true), - this._platformLoggerProvider + getExchangeRecaptchaEnterpriseTokenRequest(this._app!, attestedClaimsToken), + this._platformLoggerProvider! ); } @@ -132,7 +121,7 @@ export class ReCaptchaEnterpriseProvider implements AppCheckProvider { initialize(app: FirebaseApp): void { this._app = app; this._platformLoggerProvider = _getProvider(app, 'platform-logger'); - initializeRecaptcha(app, this._siteKey, true).catch(() => { + initializeRecaptchaEnterprise(app, this._siteKey).catch(() => { /* we don't care about the initialization result */ }); } @@ -162,14 +151,6 @@ export class CustomProvider implements AppCheckProvider { * @internal */ async getToken(): Promise { - if (!this._app) { - // This should only occur if user has not called initializeAppCheck(). - // We don't have an appName to provide if so. - // This should already be caught in the top level `getToken()` function. - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: '' - }); - } // custom provider const customToken = await this._customProviderOptions.getToken(); // Try to extract IAT from custom token, in case this token is not diff --git a/packages/app-check/src/recaptcha.test.ts b/packages/app-check/src/recaptcha.test.ts index bdb1eb9b4e8..1e63e9ccbea 100644 --- a/packages/app-check/src/recaptcha.test.ts +++ b/packages/app-check/src/recaptcha.test.ts @@ -26,7 +26,7 @@ import { findgreCAPTCHAScriptsOnPage, FAKE_SITE_KEY } from '../test/util'; -import { initialize, getToken, GreCAPTCHATopLevel } from './recaptcha'; +import { initializeV3, initializeEnterprise, getToken, GreCAPTCHATopLevel } from './recaptcha'; import * as utils from './util'; import { getState } from './state'; import { Deferred } from '@firebase/util'; @@ -49,7 +49,7 @@ describe('recaptcha', () => { it('sets reCAPTCHAState', async () => { self.grecaptcha = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; expect(getState(app).reCAPTCHAState).to.equal(undefined); - await initialize(app, FAKE_SITE_KEY); + await initializeV3(app, FAKE_SITE_KEY); expect(getState(app).reCAPTCHAState?.initialized).to.be.instanceof( Deferred ); @@ -68,7 +68,7 @@ describe('recaptcha', () => { }); expect(findgreCAPTCHAScriptsOnPage().length).to.equal(0); - await initialize(app, FAKE_SITE_KEY); + await initializeV3(app, FAKE_SITE_KEY); expect(findgreCAPTCHAScriptsOnPage().length).to.equal(1); }); @@ -77,7 +77,7 @@ describe('recaptcha', () => { const renderStub = stub(grecaptchaFake, 'render').callThrough(); self.grecaptcha = grecaptchaFake as GreCAPTCHATopLevel; - await initialize(app, FAKE_SITE_KEY); + await initializeV3(app, FAKE_SITE_KEY); expect(renderStub).to.be.calledWith(`fire_app_check_${app.name}`, { sitekey: FAKE_SITE_KEY, @@ -92,7 +92,7 @@ describe('recaptcha', () => { it('sets reCAPTCHAState', async () => { self.grecaptcha = getFakeGreCAPTCHA() as GreCAPTCHATopLevel; expect(getState(app).reCAPTCHAState).to.equal(undefined); - await initialize(app, FAKE_SITE_KEY, true); + await initializeEnterprise(app, FAKE_SITE_KEY); expect(getState(app).reCAPTCHAState?.initialized).to.be.instanceof( Deferred ); @@ -111,7 +111,7 @@ describe('recaptcha', () => { }); expect(findgreCAPTCHAScriptsOnPage().length).to.equal(0); - await initialize(app, FAKE_SITE_KEY, true); + await initializeEnterprise(app, FAKE_SITE_KEY); expect(findgreCAPTCHAScriptsOnPage().length).to.equal(1); }); @@ -123,7 +123,7 @@ describe('recaptcha', () => { ).callThrough(); self.grecaptcha = grecaptchaFake; - await initialize(app, FAKE_SITE_KEY, true); + await initializeEnterprise(app, FAKE_SITE_KEY); expect(renderStub).to.be.calledWith(`fire_app_check_${app.name}`, { sitekey: FAKE_SITE_KEY, diff --git a/packages/app-check/src/recaptcha.ts b/packages/app-check/src/recaptcha.ts index 54054a124d8..5e7933d8b92 100644 --- a/packages/app-check/src/recaptcha.ts +++ b/packages/app-check/src/recaptcha.ts @@ -24,48 +24,92 @@ export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; export const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js'; -export function initialize( +export function initializeV3( app: FirebaseApp, - siteKey: string, - isEnterprise: boolean = false + siteKey: string ): Promise { const state = getState(app); const initialized = new Deferred(); setState(app, { ...state, reCAPTCHAState: { initialized } }); + const divId = makeDiv(app); - const divId = `fire_app_check_${app.name}`; - const invisibleDiv = document.createElement('div'); - invisibleDiv.id = divId; - invisibleDiv.style.display = 'none'; + const grecaptcha = getRecaptcha(false); + if (!grecaptcha) { + loadReCAPTCHAV3Script(() => { + const grecaptcha = getRecaptcha(false); - document.body.appendChild(invisibleDiv); + if (!grecaptcha) { + // it shouldn't happen. + throw new Error('no recaptcha'); + } + queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); + }); + } else { + queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); + } + return initialized.promise; +} +export function initializeEnterprise( + app: FirebaseApp, + siteKey: string +): Promise { + const state = getState(app); + const initialized = new Deferred(); - const grecaptcha = getRecaptcha(isEnterprise); + setState(app, { ...state, reCAPTCHAState: { initialized } }); + const divId = makeDiv(app); + + const grecaptcha = getRecaptcha(true); if (!grecaptcha) { - loadReCAPTCHAScript(() => { - const grecaptcha = getRecaptcha(isEnterprise); + loadReCAPTCHAEnterpriseScript(() => { + const grecaptcha = getRecaptcha(true); if (!grecaptcha) { // it shouldn't happen. throw new Error('no recaptcha'); } - grecaptcha.ready(() => { - // Invisible widgets allow us to set a different siteKey for each widget, so we use them to support multiple apps - renderInvisibleWidget(app, siteKey, grecaptcha, divId); - initialized.resolve(grecaptcha); - }); - }, isEnterprise); - } else { - grecaptcha.ready(() => { - renderInvisibleWidget(app, siteKey, grecaptcha, divId); - initialized.resolve(grecaptcha); + queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); }); + } else { + queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); } - return initialized.promise; } +/** + * Add listener to render the widget and resolve the promise when + * the grecaptcha.ready() event fires. + */ +function queueWidgetRender( + app: FirebaseApp, + siteKey: string, + grecaptcha: GreCAPTCHA, + container: string, + initialized: Deferred): void { + grecaptcha.ready(() => { + // Invisible widgets allow us to set a different siteKey for each widget, + // so we use them to support multiple apps + renderInvisibleWidget(app, siteKey, grecaptcha, container); + initialized.resolve(grecaptcha); + }); +} + +/** + * Add invisible div to page. + */ +function makeDiv( + app: FirebaseApp): string { + + const divId = `fire_app_check_${app.name}`; + const invisibleDiv = document.createElement('div'); + invisibleDiv.id = divId; + invisibleDiv.style.display = 'none'; + + document.body.appendChild(invisibleDiv); + return divId; +} + export async function getToken(app: FirebaseApp): Promise { ensureActivated(app); @@ -114,12 +158,20 @@ function renderInvisibleWidget( }); } -function loadReCAPTCHAScript( - onload: () => void, - isEnterprise: boolean = false +function loadReCAPTCHAV3Script( + onload: () => void +): void { + const script = document.createElement('script'); + script.src = RECAPTCHA_URL; + script.onload = onload; + document.head.appendChild(script); +} + +function loadReCAPTCHAEnterpriseScript( + onload: () => void ): void { const script = document.createElement('script'); - script.src = isEnterprise ? RECAPTCHA_ENTERPRISE_URL : RECAPTCHA_URL; + script.src = RECAPTCHA_ENTERPRISE_URL; script.onload = onload; document.head.appendChild(script); } From 1477169d286bcbb1bd2aac3672301862c88fbfca Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 28 Oct 2021 14:43:01 -0700 Subject: [PATCH 13/16] Update doc comment --- packages/app-check/src/public-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-check/src/public-types.ts b/packages/app-check/src/public-types.ts index 11a4a3950d3..1fc1fd8ec01 100644 --- a/packages/app-check/src/public-types.ts +++ b/packages/app-check/src/public-types.ts @@ -58,7 +58,7 @@ export type _AppCheckComponentName = 'app-check'; */ export interface AppCheckOptions { /** - * reCAPTCHA provider or custom provider. + * reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider. */ provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider; /** From 8f98ff5b14debfde4d776c8cad8dde6442bdf06b Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 28 Oct 2021 15:05:30 -0700 Subject: [PATCH 14/16] Address doc-related PR comments --- packages/app-check/src/public-types.ts | 2 +- packages/firebase/compat/index.d.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app-check/src/public-types.ts b/packages/app-check/src/public-types.ts index 1fc1fd8ec01..8ffed332cf5 100644 --- a/packages/app-check/src/public-types.ts +++ b/packages/app-check/src/public-types.ts @@ -58,7 +58,7 @@ export type _AppCheckComponentName = 'app-check'; */ export interface AppCheckOptions { /** - * reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider. + * A reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider. */ provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider; /** diff --git a/packages/firebase/compat/index.d.ts b/packages/firebase/compat/index.d.ts index 3435748ab64..f9bd191cdd6 100644 --- a/packages/firebase/compat/index.d.ts +++ b/packages/firebase/compat/index.d.ts @@ -1543,20 +1543,20 @@ declare namespace firebase.appCheck { token: string; } /* - * ReCAPTCHA v3 token provider. + * reCAPTCHA v3 token provider. */ class ReCaptchaV3Provider { /** - * @param siteKey - ReCAPTCHA v3 site key (public key). + * @param siteKey - reCAPTCHA v3 site key (public key). */ constructor(siteKey: string); } /* - * ReCAPTCHA Enterprise token provider. + * reCAPTCHA Enterprise token provider. */ class ReCaptchaEnterpriseProvider { /** - * @param keyId - ReCAPTCHA Enterprise key ID. + * @param keyId - reCAPTCHA Enterprise key ID. */ constructor(keyId: string); } From 7adaa26e1ae0fa8fb475ab2e83e56f50ab3f8e19 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 29 Oct 2021 09:36:20 -0700 Subject: [PATCH 15/16] Formatting pass --- packages/app-check/src/api.test.ts | 6 +++- packages/app-check/src/client.test.ts | 6 +++- packages/app-check/src/internal-api.test.ts | 6 ++-- packages/app-check/src/providers.ts | 31 ++++++++++++++------- packages/app-check/src/recaptcha.test.ts | 7 ++++- packages/app-check/src/recaptcha.ts | 29 ++++++++----------- 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/packages/app-check/src/api.test.ts b/packages/app-check/src/api.test.ts index 51e1315539a..2d869875a28 100644 --- a/packages/app-check/src/api.test.ts +++ b/packages/app-check/src/api.test.ts @@ -41,7 +41,11 @@ import * as internalApi from './internal-api'; import * as indexeddb from './indexeddb'; import * as debug from './debug'; import { deleteApp, FirebaseApp } from '@firebase/app'; -import { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from './providers'; +import { + CustomProvider, + ReCaptchaEnterpriseProvider, + ReCaptchaV3Provider +} from './providers'; import { AppCheckService } from './factory'; import { AppCheckToken } from './public-types'; import { getDebugToken } from './debug'; diff --git a/packages/app-check/src/client.test.ts b/packages/app-check/src/client.test.ts index 5500cc06fea..afa8399b916 100644 --- a/packages/app-check/src/client.test.ts +++ b/packages/app-check/src/client.test.ts @@ -20,7 +20,11 @@ import { expect } from 'chai'; import { stub, SinonStub, useFakeTimers } from 'sinon'; import { FirebaseApp } from '@firebase/app'; import { getFakeApp, getFakePlatformLoggingProvider } from '../test/util'; -import { getExchangeRecaptchaV3TokenRequest, exchangeToken, getExchangeRecaptchaEnterpriseTokenRequest } from './client'; +import { + getExchangeRecaptchaV3TokenRequest, + exchangeToken, + getExchangeRecaptchaEnterpriseTokenRequest +} from './client'; import { FirebaseError } from '@firebase/util'; import { ERROR_FACTORY, AppCheckError } from './errors'; import { BASE_ENDPOINT } from './constants'; diff --git a/packages/app-check/src/internal-api.test.ts b/packages/app-check/src/internal-api.test.ts index 20030352daf..779b84fb9fc 100644 --- a/packages/app-check/src/internal-api.test.ts +++ b/packages/app-check/src/internal-api.test.ts @@ -132,9 +132,9 @@ describe('internal api', () => { expect(reCAPTCHASpy).to.be.called; - expect(exchangeTokenStub.args[0][0].body['recaptcha_enterprise_token']).to.equal( - fakeRecaptchaToken - ); + expect( + exchangeTokenStub.args[0][0].body['recaptcha_enterprise_token'] + ).to.equal(fakeRecaptchaToken); expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); }); diff --git a/packages/app-check/src/providers.ts b/packages/app-check/src/providers.ts index ffb6efbf403..4f080dad13d 100644 --- a/packages/app-check/src/providers.ts +++ b/packages/app-check/src/providers.ts @@ -18,7 +18,11 @@ import { FirebaseApp, _getProvider } from '@firebase/app'; import { Provider } from '@firebase/component'; import { issuedAtTime } from '@firebase/util'; -import { exchangeToken, getExchangeRecaptchaEnterpriseTokenRequest, getExchangeRecaptchaV3TokenRequest } from './client'; +import { + exchangeToken, + getExchangeRecaptchaEnterpriseTokenRequest, + getExchangeRecaptchaV3TokenRequest +} from './client'; import { AppCheckError, ERROR_FACTORY } from './errors'; import { CustomProviderOptions } from './public-types'; import { @@ -50,10 +54,12 @@ export class ReCaptchaV3Provider implements AppCheckProvider { async getToken(): Promise { // Top-level `getToken()` has already checked that App Check is initialized // and therefore this._app and this._platformLoggerProvider are available. - const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch(_e => { - // reCaptcha.execute() throws null which is not very descriptive. - throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); - }); + const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch( + _e => { + // reCaptcha.execute() throws null which is not very descriptive. + throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); + } + ); return exchangeToken( getExchangeRecaptchaV3TokenRequest(this._app!, attestedClaimsToken), this._platformLoggerProvider! @@ -105,12 +111,17 @@ export class ReCaptchaEnterpriseProvider implements AppCheckProvider { async getToken(): Promise { // Top-level `getToken()` has already checked that App Check is initialized // and therefore this._app and this._platformLoggerProvider are available. - const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch(_e => { - // reCaptcha.execute() throws null which is not very descriptive. - throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); - }); + const attestedClaimsToken = await getReCAPTCHAToken(this._app!).catch( + _e => { + // reCaptcha.execute() throws null which is not very descriptive. + throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); + } + ); return exchangeToken( - getExchangeRecaptchaEnterpriseTokenRequest(this._app!, attestedClaimsToken), + getExchangeRecaptchaEnterpriseTokenRequest( + this._app!, + attestedClaimsToken + ), this._platformLoggerProvider! ); } diff --git a/packages/app-check/src/recaptcha.test.ts b/packages/app-check/src/recaptcha.test.ts index 1e63e9ccbea..2716064c6c0 100644 --- a/packages/app-check/src/recaptcha.test.ts +++ b/packages/app-check/src/recaptcha.test.ts @@ -26,7 +26,12 @@ import { findgreCAPTCHAScriptsOnPage, FAKE_SITE_KEY } from '../test/util'; -import { initializeV3, initializeEnterprise, getToken, GreCAPTCHATopLevel } from './recaptcha'; +import { + initializeV3, + initializeEnterprise, + getToken, + GreCAPTCHATopLevel +} from './recaptcha'; import * as utils from './util'; import { getState } from './state'; import { Deferred } from '@firebase/util'; diff --git a/packages/app-check/src/recaptcha.ts b/packages/app-check/src/recaptcha.ts index 5e7933d8b92..bf85b67dd40 100644 --- a/packages/app-check/src/recaptcha.ts +++ b/packages/app-check/src/recaptcha.ts @@ -86,7 +86,8 @@ function queueWidgetRender( siteKey: string, grecaptcha: GreCAPTCHA, container: string, - initialized: Deferred): void { + initialized: Deferred +): void { grecaptcha.ready(() => { // Invisible widgets allow us to set a different siteKey for each widget, // so we use them to support multiple apps @@ -98,16 +99,14 @@ function queueWidgetRender( /** * Add invisible div to page. */ -function makeDiv( - app: FirebaseApp): string { - - const divId = `fire_app_check_${app.name}`; - const invisibleDiv = document.createElement('div'); - invisibleDiv.id = divId; - invisibleDiv.style.display = 'none'; - - document.body.appendChild(invisibleDiv); - return divId; +function makeDiv(app: FirebaseApp): string { + const divId = `fire_app_check_${app.name}`; + const invisibleDiv = document.createElement('div'); + invisibleDiv.id = divId; + invisibleDiv.style.display = 'none'; + + document.body.appendChild(invisibleDiv); + return divId; } export async function getToken(app: FirebaseApp): Promise { @@ -158,18 +157,14 @@ function renderInvisibleWidget( }); } -function loadReCAPTCHAV3Script( - onload: () => void -): void { +function loadReCAPTCHAV3Script(onload: () => void): void { const script = document.createElement('script'); script.src = RECAPTCHA_URL; script.onload = onload; document.head.appendChild(script); } -function loadReCAPTCHAEnterpriseScript( - onload: () => void -): void { +function loadReCAPTCHAEnterpriseScript(onload: () => void): void { const script = document.createElement('script'); script.src = RECAPTCHA_ENTERPRISE_URL; script.onload = onload; From 54b258c30f1172a15ed6d469b21361ce911fbe78 Mon Sep 17 00:00:00 2001 From: Feiyang1 Date: Mon, 1 Nov 2021 12:42:57 -0700 Subject: [PATCH 16/16] update comments --- packages/app-check/src/providers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-check/src/providers.ts b/packages/app-check/src/providers.ts index 4f080dad13d..ab1009c5148 100644 --- a/packages/app-check/src/providers.ts +++ b/packages/app-check/src/providers.ts @@ -99,8 +99,8 @@ export class ReCaptchaEnterpriseProvider implements AppCheckProvider { private _app?: FirebaseApp; private _platformLoggerProvider?: Provider<'platform-logger'>; /** - * Create a ReCaptchaV3Provider instance. - * @param siteKey - ReCAPTCHA V3 siteKey. + * Create a ReCaptchaEnterpriseProvider instance. + * @param siteKey - reCAPTCHA Enterprise score-based site key. */ constructor(private _siteKey: string) {}