Skip to content

Commit

Permalink
Implement ReCaptchaEnterprise for App Check (#5595)
Browse files Browse the repository at this point in the history
* Add recaptcha enterprise provider

* start on app-check-compat

* Correct Enterprise URL

* prettier

* Add changeset

* start on app-check-compat

* Add enterprise endpoint

* Fix tests

* update index.d.ts

* Add more tests

* Update .changeset/ten-impalas-wink.md

Co-authored-by: Feiyang <feiyangc@google.com>

* Address PR comments

* Update doc comment

* Address doc-related PR comments

* Formatting pass

* update comments

Co-authored-by: Feiyang <feiyangc@google.com>
Co-authored-by: Feiyang1 <plane1113@gmail.com>
  • Loading branch information
3 people committed Nov 2, 2021
1 parent 31bd6f2 commit 6160497
Show file tree
Hide file tree
Showing 17 changed files with 536 additions and 180 deletions.
7 changes: 7 additions & 0 deletions .changeset/ten-impalas-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@firebase/app-check': minor
'@firebase/app-check-compat': minor
'firebase': minor
---

Add ReCAPTCHA Enterprise as an attestation option for App Check.
199 changes: 105 additions & 94 deletions common/api-review/app-check.api.md
Original file line number Diff line number Diff line change
@@ -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<AppCheckTokenInternal>;
// @internal (undocumented)
initialize(app: FirebaseApp): void;
// @internal (undocumented)
isEqual(otherProvider: unknown): boolean;
}

// @public
export interface CustomProviderOptions {
getToken: () => Promise<AppCheckToken>;
}

// @public
export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise<AppCheckTokenResult>;

// @public
export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck;

// @public
export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver<AppCheckTokenResult>): 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<AppCheckTokenInternal>;
// @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<AppCheckTokenInternal>;
// @internal (undocumented)
initialize(app: FirebaseApp): void;
// @internal (undocumented)
isEqual(otherProvider: unknown): boolean;
}

// @public
export interface CustomProviderOptions {
getToken: () => Promise<AppCheckToken>;
}

// @public
export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise<AppCheckTokenResult>;

// @public
export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck;

// @public
export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver<AppCheckTokenResult>): 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<AppCheckTokenInternal>;
// @internal (undocumented)
initialize(app: FirebaseApp): void;
// @internal (undocumented)
isEqual(otherProvider: unknown): boolean;
}

// @public
export class ReCaptchaV3Provider implements AppCheckProvider {
constructor(_siteKey: string);
// @internal
getToken(): Promise<AppCheckTokenInternal>;
// @internal (undocumented)
initialize(app: FirebaseApp): void;
// @internal (undocumented)
isEqual(otherProvider: unknown): boolean;
}

// @public
export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void;

export { Unsubscribe }


```
7 changes: 6 additions & 1 deletion packages/app-check-compat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,6 +50,7 @@ export function registerAppCheck(): void {
factory,
ComponentType.PUBLIC
).setServiceProps({
ReCaptchaEnterpriseProvider,
ReCaptchaV3Provider,
CustomProvider
})
Expand Down
7 changes: 6 additions & 1 deletion packages/app-check-compat/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
CustomProvider,
initializeAppCheck,
ReCaptchaV3Provider,
ReCaptchaEnterpriseProvider,
setTokenAutoRefreshEnabled as setTokenAutoRefreshEnabledExp,
getToken as getTokenExp,
onTokenChanged as onTokenChangedExp
Expand All @@ -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
) {
Expand Down
41 changes: 39 additions & 2 deletions packages/app-check/src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ReCaptchaV3Provider } from './providers';
import {
CustomProvider,
ReCaptchaEnterpriseProvider,
ReCaptchaV3Provider
} from './providers';
import { AppCheckService } from './factory';
import { AppCheckToken } from './public-types';
import { getDebugToken } from './debug';
Expand Down Expand Up @@ -83,6 +87,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({
Expand All @@ -107,6 +121,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({
Expand Down Expand Up @@ -166,7 +190,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, {
Expand All @@ -178,6 +202,19 @@ describe('api', () => {
);
});

it('initialize reCAPTCHA when a ReCaptchaEnterpriseProvider is provided', () => {
const initReCAPTCHAStub = stub(reCAPTCHA, 'initializeEnterprise').returns(
Promise.resolve({} as any)
);
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
});
expect(initReCAPTCHAStub).to.have.been.calledWithExactly(
app,
FAKE_SITE_KEY
);
});

it('sets activated to true', () => {
expect(getState(app).activated).to.equal(false);
initializeAppCheck(app, {
Expand Down
6 changes: 5 additions & 1 deletion packages/app-check/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 6160497

Please sign in to comment.