From b04f0408139f75c69b6f6eea396f3e961f658bd1 Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Mon, 10 Apr 2023 16:37:53 -0700 Subject: [PATCH] Added Firebase App Check support to Firebase Auth. (#7191) --- .changeset/brave-ducks-relax.md | 6 + packages/auth-compat/src/auth.test.ts | 6 +- .../auth-compat/src/popup_redirect.test.ts | 16 ++- packages/auth-compat/test/helpers/helpers.ts | 7 ++ packages/auth/src/api/index.ts | 3 +- packages/auth/src/core/auth/auth_impl.test.ts | 32 +++++ packages/auth/src/core/auth/auth_impl.ts | 26 ++++ packages/auth/src/core/auth/register.ts | 56 ++++----- packages/auth/src/core/util/handler.ts | 23 +++- packages/auth/src/core/util/log.ts | 6 + packages/auth/src/model/auth.ts | 1 + .../auth/src/platform_browser/auth.test.ts | 26 ++-- .../platform_browser/popup_redirect.test.ts | 113 +++++++++++++++++- .../src/platform_browser/popup_redirect.ts | 11 +- packages/auth/test/helpers/mock_auth.ts | 15 +++ 15 files changed, 296 insertions(+), 51 deletions(-) create mode 100644 .changeset/brave-ducks-relax.md diff --git a/.changeset/brave-ducks-relax.md b/.changeset/brave-ducks-relax.md new file mode 100644 index 00000000000..e23674343dd --- /dev/null +++ b/.changeset/brave-ducks-relax.md @@ -0,0 +1,6 @@ +--- +'@firebase/auth': minor +'@firebase/auth-compat': minor +--- + +[feature] Added Firebase App Check support to Firebase Auth. diff --git a/packages/auth-compat/src/auth.test.ts b/packages/auth-compat/src/auth.test.ts index af49682afed..2cab2890697 100644 --- a/packages/auth-compat/src/auth.test.ts +++ b/packages/auth-compat/src/auth.test.ts @@ -24,7 +24,10 @@ import sinonChai from 'sinon-chai'; import { Auth } from './auth'; import { CompatPopupRedirectResolver } from './popup_redirect'; import * as platform from './platform'; -import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers'; +import { + FAKE_APP_CHECK_CONTROLLER_PROVIDER, + FAKE_HEARTBEAT_CONTROLLER_PROVIDER +} from '../test/helpers/helpers'; use(sinonChai); @@ -45,6 +48,7 @@ describe('auth compat', () => { underlyingAuth = new exp.AuthImpl( app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: 'api-key' } as exp.ConfigInternal diff --git a/packages/auth-compat/src/popup_redirect.test.ts b/packages/auth-compat/src/popup_redirect.test.ts index e7686193e57..9426d928076 100644 --- a/packages/auth-compat/src/popup_redirect.test.ts +++ b/packages/auth-compat/src/popup_redirect.test.ts @@ -22,7 +22,10 @@ import * as exp from '@firebase/auth/internal'; import * as platform from './platform'; import { CompatPopupRedirectResolver } from './popup_redirect'; import { FirebaseApp } from '@firebase/app-compat'; -import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers'; +import { + FAKE_APP_CHECK_CONTROLLER_PROVIDER, + FAKE_HEARTBEAT_CONTROLLER_PROVIDER +} from '../test/helpers/helpers'; use(sinonChai); @@ -42,9 +45,14 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => { beforeEach(() => { compatResolver = new CompatPopupRedirectResolver(); const app = { options: { apiKey: 'api-key' } } as FirebaseApp; - auth = new exp.AuthImpl(app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, { - apiKey: 'api-key' - } as exp.ConfigInternal); + auth = new exp.AuthImpl( + app, + FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, + { + apiKey: 'api-key' + } as exp.ConfigInternal + ); }); afterEach(() => { diff --git a/packages/auth-compat/test/helpers/helpers.ts b/packages/auth-compat/test/helpers/helpers.ts index aa5069461c6..e689b77eb67 100644 --- a/packages/auth-compat/test/helpers/helpers.ts +++ b/packages/auth-compat/test/helpers/helpers.ts @@ -34,6 +34,13 @@ export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER = { } } as unknown as Provider<'heartbeat'>; +// App Check is fully tested in core auth impl +export const FAKE_APP_CHECK_CONTROLLER_PROVIDER = { + getImmediate(): undefined { + return undefined; + } +} as unknown as Provider<'app-check-internal'>; + export function initializeTestInstance(): void { firebase.initializeApp(getAppConfig()); const stub = stubConsoleToSilenceEmulatorWarnings(); diff --git a/packages/auth/src/api/index.ts b/packages/auth/src/api/index.ts index 6c9d775d243..4b07fa6e18a 100644 --- a/packages/auth/src/api/index.ts +++ b/packages/auth/src/api/index.ts @@ -42,7 +42,8 @@ export const enum HttpHeader { X_FIREBASE_LOCALE = 'X-Firebase-Locale', X_CLIENT_VERSION = 'X-Client-Version', X_FIREBASE_GMPID = 'X-Firebase-gmpid', - X_FIREBASE_CLIENT = 'X-Firebase-Client' + X_FIREBASE_CLIENT = 'X-Firebase-Client', + X_FIREBASE_APP_CHECK = 'X-Firebase-AppCheck' } export const enum Endpoint { diff --git a/packages/auth/src/core/auth/auth_impl.test.ts b/packages/auth/src/core/auth/auth_impl.test.ts index 6167de7b62b..514a61d25e4 100644 --- a/packages/auth/src/core/auth/auth_impl.test.ts +++ b/packages/auth/src/core/auth/auth_impl.test.ts @@ -24,6 +24,8 @@ import { FirebaseApp } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; import { + FAKE_APP_CHECK_CONTROLLER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, FAKE_HEARTBEAT_CONTROLLER, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, testAuth, @@ -62,6 +64,7 @@ describe('core/auth/auth_impl', () => { const authImpl = new AuthImpl( FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: FAKE_APP.options.apiKey!, apiHost: DefaultConfig.API_HOST, @@ -582,6 +585,7 @@ describe('core/auth/auth_impl', () => { const authImpl = new AuthImpl( FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: FAKE_APP.options.apiKey!, apiHost: DefaultConfig.API_HOST, @@ -656,5 +660,33 @@ describe('core/auth/auth_impl', () => { 'X-Client-Version': 'v' }); }); + + it('adds the App Check token if available', async () => { + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: 'fake-token' })); + expect(await auth._getAdditionalHeaders()).to.eql({ + 'X-Client-Version': 'v', + 'X-Firebase-AppCheck': 'fake-token' + }); + }); + + it('does not add the App Check token if none returned', async () => { + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: '' })); + expect(await auth._getAdditionalHeaders()).to.eql({ + 'X-Client-Version': 'v' + }); + }); + + it('does not add the App Check token if controller unavailable', async () => { + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(undefined as any); + expect(await auth._getAdditionalHeaders()).to.eql({ + 'X-Client-Version': 'v' + }); + }); }); }); diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 92467933da7..89a85c3cac4 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -17,6 +17,7 @@ import { _FirebaseService, FirebaseApp } from '@firebase/app'; import { Provider } from '@firebase/component'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { Auth, AuthErrorMap, @@ -62,6 +63,7 @@ import { _getUserLanguage } from '../util/navigator'; import { _getClientVersion } from '../util/version'; import { HttpHeader } from '../../api'; import { AuthMiddlewareQueue } from './middleware'; +import { _logWarn } from '../util/log'; interface AsyncAction { (): Promise; @@ -108,6 +110,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { constructor( public readonly app: FirebaseApp, private readonly heartbeatServiceProvider: Provider<'heartbeat'>, + private readonly appCheckServiceProvider: Provider, public readonly config: ConfigInternal ) { this.name = app.name; @@ -645,8 +648,31 @@ export class AuthImpl implements AuthInternal, _FirebaseService { if (heartbeatsHeader) { headers[HttpHeader.X_FIREBASE_CLIENT] = heartbeatsHeader; } + + // If the App Check service exists, add the App Check token in the headers + const appCheckToken = await this._getAppCheckToken(); + if (appCheckToken) { + headers[HttpHeader.X_FIREBASE_APP_CHECK] = appCheckToken; + } + return headers; } + + async _getAppCheckToken(): Promise { + const appCheckTokenResult = await this.appCheckServiceProvider + .getImmediate({ optional: true }) + ?.getToken(); + if (appCheckTokenResult?.error) { + // Context: appCheck.getToken() will never throw even if an error happened. + // In the error case, a dummy token will be returned along with an error field describing + // the error. In general, we shouldn't care about the error condition and just use + // the token (actual or dummy) to send requests. + _logWarn( + `Error while retrieving App Check token: ${appCheckTokenResult.error}` + ); + } + return appCheckTokenResult?.token; + } } /** diff --git a/packages/auth/src/core/auth/register.ts b/packages/auth/src/core/auth/register.ts index 10b061ee15a..1d779421c89 100644 --- a/packages/auth/src/core/auth/register.ts +++ b/packages/auth/src/core/auth/register.ts @@ -63,36 +63,38 @@ export function registerAuth(clientPlatform: ClientPlatform): void { const app = container.getProvider('app').getImmediate()!; const heartbeatServiceProvider = container.getProvider<'heartbeat'>('heartbeat'); + const appCheckServiceProvider = + container.getProvider<'app-check-internal'>('app-check-internal'); const { apiKey, authDomain } = app.options; - return ((app, heartbeatServiceProvider) => { - _assert( - apiKey && !apiKey.includes(':'), - AuthErrorCode.INVALID_API_KEY, - { appName: app.name } - ); - // Auth domain is optional if IdP sign in isn't being used - _assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, { - appName: app.name - }); - const config: ConfigInternal = { - apiKey, - authDomain, - clientPlatform, - apiHost: DefaultConfig.API_HOST, - tokenApiHost: DefaultConfig.TOKEN_API_HOST, - apiScheme: DefaultConfig.API_SCHEME, - sdkClientVersion: _getClientVersion(clientPlatform) - }; - const authInstance = new AuthImpl( - app, - heartbeatServiceProvider, - config - ); - _initializeAuthInstance(authInstance, deps); + _assert( + apiKey && !apiKey.includes(':'), + AuthErrorCode.INVALID_API_KEY, + { appName: app.name } + ); + // Auth domain is optional if IdP sign in isn't being used + _assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, { + appName: app.name + }); + const config: ConfigInternal = { + apiKey, + authDomain, + clientPlatform, + apiHost: DefaultConfig.API_HOST, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + sdkClientVersion: _getClientVersion(clientPlatform) + }; + + const authInstance = new AuthImpl( + app, + heartbeatServiceProvider, + appCheckServiceProvider, + config + ); + _initializeAuthInstance(authInstance, deps); - return authInstance; - })(app, heartbeatServiceProvider); + return authInstance; }, ComponentType.PUBLIC ) diff --git a/packages/auth/src/core/util/handler.ts b/packages/auth/src/core/util/handler.ts index 4d21b6cdd7b..363e04a16d5 100644 --- a/packages/auth/src/core/util/handler.ts +++ b/packages/auth/src/core/util/handler.ts @@ -40,6 +40,13 @@ const WIDGET_PATH = '__/auth/handler'; */ const EMULATOR_WIDGET_PATH = 'emulator/auth/handler'; +/** + * Fragment name for the App Check token that gets passed to the widget + * + * @internal + */ +const FIREBASE_APP_CHECK_FRAGMENT_ID = encodeURIComponent('fac'); + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions type WidgetParams = { apiKey: ApiKey; @@ -54,14 +61,14 @@ type WidgetParams = { tid?: string; } & { [key: string]: string | undefined }; -export function _getRedirectUrl( +export async function _getRedirectUrl( auth: AuthInternal, provider: AuthProvider, authType: AuthEventType, redirectUrl?: string, eventId?: string, additionalParams?: Record -): string { +): Promise { _assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN); _assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY); @@ -107,7 +114,17 @@ export function _getRedirectUrl( delete paramsDict[key]; } } - return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}`; + + // Sets the App Check token to pass to the widget + const appCheckToken = await auth._getAppCheckToken(); + const appCheckTokenFragment = appCheckToken + ? `#${FIREBASE_APP_CHECK_FRAGMENT_ID}=${encodeURIComponent(appCheckToken)}` + : ''; + + // Start at index 1 to skip the leading '&' in the query string + return `${getHandlerBase(auth)}?${querystring(paramsDict).slice( + 1 + )}${appCheckTokenFragment}`; } function getHandlerBase({ config }: AuthInternal): string { diff --git a/packages/auth/src/core/util/log.ts b/packages/auth/src/core/util/log.ts index 649c35d4317..5d7ba4a780c 100644 --- a/packages/auth/src/core/util/log.ts +++ b/packages/auth/src/core/util/log.ts @@ -37,6 +37,12 @@ export function _logDebug(msg: string, ...args: string[]): void { } } +export function _logWarn(msg: string, ...args: string[]): void { + if (logClient.logLevel <= LogLevel.WARN) { + logClient.warn(`Auth (${SDK_VERSION}): ${msg}`, ...args); + } +} + export function _logError(msg: string, ...args: string[]): void { if (logClient.logLevel <= LogLevel.ERROR) { logClient.error(`Auth (${SDK_VERSION}): ${msg}`, ...args); diff --git a/packages/auth/src/model/auth.ts b/packages/auth/src/model/auth.ts index 6aaa6c6781f..f0584368c94 100644 --- a/packages/auth/src/model/auth.ts +++ b/packages/auth/src/model/auth.ts @@ -82,6 +82,7 @@ export interface AuthInternal extends Auth { _logFramework(framework: string): void; _getFrameworks(): readonly string[]; _getAdditionalHeaders(): Promise>; + _getAppCheckToken(): Promise; readonly name: AppName; readonly config: ConfigInternal; diff --git a/packages/auth/src/platform_browser/auth.test.ts b/packages/auth/src/platform_browser/auth.test.ts index 13c78ee8d3d..44570d2ca20 100644 --- a/packages/auth/src/platform_browser/auth.test.ts +++ b/packages/auth/src/platform_browser/auth.test.ts @@ -29,6 +29,7 @@ import { import { OperationType } from '../model/enums'; import { + FAKE_APP_CHECK_CONTROLLER_PROVIDER, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, testAuth, testUser @@ -73,6 +74,7 @@ describe('core/auth/auth_impl', () => { const authImpl = new AuthImpl( FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: FAKE_APP.options.apiKey!, apiHost: DefaultConfig.API_HOST, @@ -141,15 +143,20 @@ describe('core/auth/initializeAuth', () => { authDomain = FAKE_APP.options.authDomain, blockMiddleware = false ): Promise { - const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, { - apiKey: FAKE_APP.options.apiKey!, - apiHost: DefaultConfig.API_HOST, - apiScheme: DefaultConfig.API_SCHEME, - tokenApiHost: DefaultConfig.TOKEN_API_HOST, - authDomain, - clientPlatform: ClientPlatform.BROWSER, - sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER) - }); + const auth = new AuthImpl( + FAKE_APP, + FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, + { + apiKey: FAKE_APP.options.apiKey!, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + authDomain, + clientPlatform: ClientPlatform.BROWSER, + sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER) + } + ); _initializeAuthInstance(auth, { persistence, @@ -378,6 +385,7 @@ describe('core/auth/initializeAuth', () => { const auth = new AuthImpl( FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: FAKE_APP.options.apiKey!, apiHost: DefaultConfig.API_HOST, diff --git a/packages/auth/src/platform_browser/popup_redirect.test.ts b/packages/auth/src/platform_browser/popup_redirect.test.ts index be8078f3c1b..7c81dd139b1 100644 --- a/packages/auth/src/platform_browser/popup_redirect.test.ts +++ b/packages/auth/src/platform_browser/popup_redirect.test.ts @@ -30,7 +30,8 @@ import { TEST_AUTH_DOMAIN, TEST_KEY, testAuth, - TestAuth + TestAuth, + FAKE_APP_CHECK_CONTROLLER } from '../../test/helpers/mock_auth'; import { AuthEventManager } from '../core/auth/auth_event_manager'; import { OAuthProvider } from '../core/providers/oauth'; @@ -125,6 +126,49 @@ describe('platform_browser/popup_redirect', () => { ); }); + it('includes the App Check token in the url fragment if present', async () => { + await resolver._initialize(auth); + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: 'fake-token' })); + + await resolver._openPopup(auth, provider, event); + + const matches = (popupUrl as string).match(/.*?#(.*)/); + expect(matches).not.to.be.null; + const fragment = matches![1]; + expect(fragment).to.include('fac=fake-token'); + }); + + it('does not add the App Check token in the url fragment if none returned', async () => { + await resolver._initialize(auth); + // Redundant, already set in mock_auth.ts but adding here for clarity + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: '' })); + + await resolver._openPopup(auth, provider, event); + + const matches = (popupUrl as string).match(/.*?#(.*)/); + // The '#' character will not be included when the url fragment is not attached, + // so the url will not match the pattern + expect(matches).to.be.null; + }); + + it('does not add the App Check token in the url fragment if controller unavailable', async () => { + await resolver._initialize(auth); + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(undefined as any); + + await resolver._openPopup(auth, provider, event); + + const matches = (popupUrl as string).match(/.*?#(.*)/); + // The '#' character will not be included when the url fragment is not attached, + // so the url will not match the pattern + expect(matches).to.be.null; + }); + it('throws an error if apiKey is unspecified', async () => { delete (auth.config as Partial).apiKey; await resolver._initialize(auth); @@ -157,8 +201,10 @@ describe('platform_browser/popup_redirect', () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises resolver._openRedirect(auth, provider, event); - // Delay one tick - await Promise.resolve(); + // Wait a bit so the _openRedirect() call completes + await new Promise((resolve): void => { + setTimeout(resolve, 100); + }); expect(newWindowLocation).to.include( `https://${TEST_AUTH_DOMAIN}/__/auth/handler` @@ -177,6 +223,67 @@ describe('platform_browser/popup_redirect', () => { ); }); + it('includes the App Check token in the url fragment if present', async () => { + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: 'fake-token' })); + + // This promise will never resolve on purpose + // eslint-disable-next-line @typescript-eslint/no-floating-promises + resolver._openRedirect(auth, provider, event); + + // Wait a bit so the _openRedirect() call completes + await new Promise((resolve): void => { + setTimeout(resolve, 100); + }); + + const matches = newWindowLocation.match(/.*?#(.*)/); + expect(matches).not.to.be.null; + const fragment = matches![1]; + expect(fragment).to.include('fac=fake-token'); + }); + + it('does not add the App Check token in the url fragment if none returned', async () => { + // Redundant, already set in mock_auth.ts but adding here for clarity + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(Promise.resolve({ token: '' })); + + // This promise will never resolve on purpose + // eslint-disable-next-line @typescript-eslint/no-floating-promises + resolver._openRedirect(auth, provider, event); + + // Wait a bit so the _openRedirect() call completes + await new Promise((resolve): void => { + setTimeout(resolve, 100); + }); + + const matches = newWindowLocation.match(/.*?#(.*)/); + // The '#' character will not be included when the url fragment is not attached, + // so the url will not match the pattern + expect(matches).to.be.null; + }); + + it('does not add the App Check token in the url fragment if controller unavailable', async () => { + sinon + .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken') + .returns(undefined as any); + + // This promise will never resolve on purpose + // eslint-disable-next-line @typescript-eslint/no-floating-promises + resolver._openRedirect(auth, provider, event); + + // Wait a bit so the _openRedirect() call completes + await new Promise((resolve): void => { + setTimeout(resolve, 100); + }); + + const matches = newWindowLocation.match(/.*?#(.*)/); + // The '#' character will not be included when the url fragment is not attached, + // so the url will not match the pattern + expect(matches).to.be.null; + }); + it('throws an error if authDomain is unspecified', async () => { delete auth.config.authDomain; diff --git a/packages/auth/src/platform_browser/popup_redirect.ts b/packages/auth/src/platform_browser/popup_redirect.ts index f17d142f3a3..36c3805c4ec 100644 --- a/packages/auth/src/platform_browser/popup_redirect.ts +++ b/packages/auth/src/platform_browser/popup_redirect.ts @@ -75,7 +75,7 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal { '_initialize() not called before _openPopup()' ); - const url = _getRedirectUrl( + const url = await _getRedirectUrl( auth, provider, authType, @@ -92,9 +92,14 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal { eventId?: string ): Promise { await this._originValidation(auth); - _setWindowLocation( - _getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId) + const url = await _getRedirectUrl( + auth, + provider, + authType, + _getCurrentUrl(), + eventId ); + _setWindowLocation(url); return new Promise(() => {}); } diff --git a/packages/auth/test/helpers/mock_auth.ts b/packages/auth/test/helpers/mock_auth.ts index 4923ee78e87..e5e30fa1384 100644 --- a/packages/auth/test/helpers/mock_auth.ts +++ b/packages/auth/test/helpers/mock_auth.ts @@ -17,6 +17,7 @@ import { FirebaseApp } from '@firebase/app'; import { Provider } from '@firebase/component'; +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; import { PopupRedirectResolver } from '../../src/model/public_types'; import { debugErrorMap } from '../../src'; @@ -55,6 +56,19 @@ export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER: Provider<'heartbeat'> = { } } as unknown as Provider<'heartbeat'>; +export const FAKE_APP_CHECK_CONTROLLER = { + getToken: async () => { + return { token: '' } as AppCheckTokenResult; + } +}; + +export const FAKE_APP_CHECK_CONTROLLER_PROVIDER: Provider<'app-check-internal'> = + { + getImmediate(): typeof FAKE_APP_CHECK_CONTROLLER { + return FAKE_APP_CHECK_CONTROLLER; + } + } as unknown as Provider<'app-check-internal'>; + export class MockPersistenceLayer extends InMemoryPersistence { lastObjectSet: PersistedBlob | null = null; @@ -77,6 +91,7 @@ export async function testAuth( const auth: TestAuth = new AuthImpl( FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, + FAKE_APP_CHECK_CONTROLLER_PROVIDER, { apiKey: TEST_KEY, authDomain: TEST_AUTH_DOMAIN,