diff --git a/.changeset/kind-pets-sin.md b/.changeset/kind-pets-sin.md new file mode 100644 index 00000000000..7debbf4b220 --- /dev/null +++ b/.changeset/kind-pets-sin.md @@ -0,0 +1,15 @@ +--- +'@firebase/app': minor +'firebase': minor +'@firebase/data-connect': patch +'@firebase/firestore': patch +'@firebase/functions': patch +'@firebase/database': patch +'@firebase/vertexai': patch +'@firebase/storage': patch +'@firebase/auth': patch +--- + +`FirebaseServerApp` can now be initalized with an App Check token instead of invoking the App Check +`getToken` method. This should unblock the use of App Check enforced products in SSR environments +where the App Check SDK cannot be initialized. diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index bdfb2a681f1..4e93f1ae87f 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -79,6 +79,7 @@ export interface FirebaseServerApp extends FirebaseApp { // @public export interface FirebaseServerAppSettings extends Omit { + appCheckToken?: string; authIdToken?: string; releaseOnDeref?: object; } @@ -115,7 +116,7 @@ export function initializeServerApp(options: FirebaseOptions | FirebaseApp, conf export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; // @internal (undocumented) -export function _isFirebaseServerApp(obj: FirebaseApp | FirebaseServerApp): obj is FirebaseServerApp; +export function _isFirebaseServerApp(obj: FirebaseApp | FirebaseServerApp | null | undefined): obj is FirebaseServerApp; // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index bc46c5292d0..79d4fbe022a 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -23,16 +23,31 @@ export interface FirebaseServerAppSettings extends OmitInvoking getAuth with a FirebaseServerApp configured with a validated authIdToken causes an automatic attempt to sign in the user that the authIdToken represents. The token needs to have been recently minted for this operation to succeed.If the token fails local verification, or if the Auth service has failed to validate it when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user on initialization.If a user is successfully signed in, then the Auth instance's onAuthStateChanged callback is invoked with the User object as per standard Auth flows. However, User objects created via an authIdToken do not have a refresh token. Attempted refreshToken operations fail. | +| [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional App Check token. If provided, the Firebase SDKs that use App Check will utilize this App Check token in place of requiring an instance of App Check to be initialized.If the token fails local verification due to expiration or parsing errors, then a console error is logged at the time of initialization of the FirebaseServerApp instance. | +| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.Invoking getAuth with a FirebaseServerApp configured with a validated authIdToken causes an automatic attempt to sign in the user that the authIdToken represents. The token needs to have been recently minted for this operation to succeed.If the token fails local verification due to expiration or parsing errors, then a console error is logged at the time of initialization of the FirebaseServerApp instance.If the Auth service has failed to validate the token when the Auth SDK is initialized, then an warning is logged to the console and the Auth SDK will not sign in a user on initialization.If a user is successfully signed in, then the Auth instance's onAuthStateChanged callback is invoked with the User object as per standard Auth flows. However, User objects created via an authIdToken do not have a refresh token. Attempted refreshToken operations fail. | | [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK uses a FinalizationRegistry object to monitor the garbage collection status of the provided object. The Firebase SDK releases its reference on the FirebaseServerApp instance when the provided releaseOnDeref object is garbage collected.You can use this field to reduce memory management overhead for your application. If provided, an app running in a SSR pass does not need to perform FirebaseServerApp cleanup, so long as the reference object is deleted (by falling out of SSR scope, for instance.)If an object is not provided then the application must clean up the FirebaseServerApp instance by invoking deleteApp.If the application provides an object in this parameter, but the application is executed in a JavaScript engine that predates the support of FinalizationRegistry (introduced in node v14.6.0, for instance), then an error is thrown at FirebaseServerApp initialization. | +## FirebaseServerAppSettings.appCheckToken + +An optional App Check token. If provided, the Firebase SDKs that use App Check will utilize this App Check token in place of requiring an instance of App Check to be initialized. + +If the token fails local verification due to expiration or parsing errors, then a console error is logged at the time of initialization of the `FirebaseServerApp` instance. + +Signature: + +```typescript +appCheckToken?: string; +``` + ## FirebaseServerAppSettings.authIdToken An optional Auth ID token used to resume a signed in user session from a client runtime environment. Invoking `getAuth` with a `FirebaseServerApp` configured with a validated `authIdToken` causes an automatic attempt to sign in the user that the `authIdToken` represents. The token needs to have been recently minted for this operation to succeed. -If the token fails local verification, or if the Auth service has failed to validate it when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user on initialization. +If the token fails local verification due to expiration or parsing errors, then a console error is logged at the time of initialization of the `FirebaseServerApp` instance. + +If the Auth service has failed to validate the token when the Auth SDK is initialized, then an warning is logged to the console and the Auth SDK will not sign in a user on initialization. If a user is successfully signed in, then the Auth instance's `onAuthStateChanged` callback is invoked with the `User` object as per standard Auth flows. However, `User` objects created via an `authIdToken` do not have a refresh token. Attempted `refreshToken` operations fail. diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index bf2da5c06d5..de00687948e 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -20,6 +20,21 @@ import '../test/setup'; import { ComponentContainer } from '@firebase/component'; import { FirebaseServerAppImpl } from './firebaseServerApp'; import { FirebaseServerAppSettings } from './public-types'; +import { base64Encode } from '@firebase/util'; + +const BASE64_DUMMY = base64Encode('dummystrings'); // encodes to ZHVtbXlzdHJpbmdz + +// Creates a three part dummy token with an expiration claim in the second part. The expration +// time is based on the date offset provided. +function createServerAppTokenWithOffset(daysOffset: number): string { + const timeInSeconds = Math.trunc( + new Date().setDate(new Date().getDate() + daysOffset) / 1000 + ); + const secondPart = JSON.stringify({ exp: timeInSeconds }); + const token = + BASE64_DUMMY + '.' + base64Encode(secondPart) + '.' + BASE64_DUMMY; + return token; +} describe('FirebaseServerApp', () => { it('has various accessors', () => { @@ -155,4 +170,48 @@ describe('FirebaseServerApp', () => { expect(JSON.stringify(app)).to.eql(undefined); }); + + it('accepts a valid authIdToken expiration', () => { + const options = { apiKey: 'APIKEY' }; + const authIdToken = createServerAppTokenWithOffset(/*daysOffset=*/ 1); + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options, + authIdToken + }; + let encounteredError = false; + try { + new FirebaseServerAppImpl( + options, + serverAppSettings, + 'testName', + new ComponentContainer('test') + ); + } catch (e) { + encounteredError = true; + } + expect(encounteredError).to.be.false; + }); + + it('accepts a valid appCheckToken expiration', () => { + const options = { apiKey: 'APIKEY' }; + const appCheckToken = createServerAppTokenWithOffset(/*daysOffset=*/ 1); + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options, + appCheckToken + }; + let encounteredError = false; + try { + new FirebaseServerAppImpl( + options, + serverAppSettings, + 'testName', + new ComponentContainer('test') + ); + } catch (e) { + encounteredError = true; + } + expect(encounteredError).to.be.false; + }); }); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 0c41d4cd607..21232869c3c 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -26,6 +26,35 @@ import { ComponentContainer } from '@firebase/component'; import { FirebaseAppImpl } from './firebaseApp'; import { ERROR_FACTORY, AppError } from './errors'; import { name as packageName, version } from '../package.json'; +import { base64Decode } from '@firebase/util'; + +// Parse the token and check to see if the `exp` claim is in the future. +// Reports an error to the console if the token or claim could not be parsed, or if `exp` is in +// the past. +function validateTokenTTL(base64Token: string, tokenName: string): void { + const secondPart = base64Decode(base64Token.split('.')[1]); + if (secondPart === null) { + console.error( + `FirebaseServerApp ${tokenName} is invalid: second part could not be parsed.` + ); + return; + } + const expClaim = JSON.parse(secondPart).exp; + if (expClaim === undefined) { + console.error( + `FirebaseServerApp ${tokenName} is invalid: expiration claim could not be parsed` + ); + return; + } + const exp = JSON.parse(secondPart).exp * 1000; + const now = new Date().getTime(); + const diff = exp - now; + if (diff <= 0) { + console.error( + `FirebaseServerApp ${tokenName} is invalid: the token has expired.` + ); + } +} export class FirebaseServerAppImpl extends FirebaseAppImpl @@ -67,6 +96,16 @@ export class FirebaseServerAppImpl ...serverConfig }; + // Ensure that the current time is within the `authIdtoken` window of validity. + if (this._serverConfig.authIdToken) { + validateTokenTTL(this._serverConfig.authIdToken, 'authIdToken'); + } + + // Ensure that the current time is within the `appCheckToken` window of validity. + if (this._serverConfig.appCheckToken) { + validateTokenTTL(this._serverConfig.appCheckToken, 'appCheckToken'); + } + this._finalizationRegistry = null; if (typeof FinalizationRegistry !== 'undefined') { this._finalizationRegistry = new FinalizationRegistry(() => { diff --git a/packages/app/src/internal.test.ts b/packages/app/src/internal.test.ts index 47ea2e80c40..78baf400b8c 100644 --- a/packages/app/src/internal.test.ts +++ b/packages/app/src/internal.test.ts @@ -19,7 +19,7 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import '../test/setup'; import { createTestComponent, TestService } from '../test/util'; -import { initializeApp, getApps, deleteApp } from './api'; +import { initializeApp, initializeServerApp, getApps, deleteApp } from './api'; import { FirebaseAppImpl } from './firebaseApp'; import { _addComponent, @@ -28,9 +28,11 @@ import { _components, _clearComponents, _getProvider, - _removeServiceInstance + _removeServiceInstance, + _isFirebaseServerApp } from './internal'; import { logger } from './logger'; +import { isBrowser } from '@firebase/util'; declare module '@firebase/component' { interface NameServiceMapping { @@ -161,4 +163,25 @@ describe('Internal API tests', () => { expect(instance1).to.not.equal(instance2); }); }); + + describe('_isFirebaseServerApp', () => { + it('detects a valid FirebaseServerApp', () => { + if (!isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser environments. + const app = initializeServerApp({}, {}); + expect(_isFirebaseServerApp(app)).to.be.true; + } + }); + it('a standard FirebaseApp returns false', () => { + const app = initializeApp({}); + expect(_isFirebaseServerApp(app)).to.be.false; + }); + it('a null object returns false', () => { + expect(_isFirebaseServerApp(null)).to.be.false; + }); + it('undefined returns false', () => { + let app: undefined; + expect(_isFirebaseServerApp(app)).to.be.false; + }); + }); }); diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 7e0c1545962..cbcdcb26501 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -168,8 +168,11 @@ export function _isFirebaseApp( * @internal */ export function _isFirebaseServerApp( - obj: FirebaseApp | FirebaseServerApp + obj: FirebaseApp | FirebaseServerApp | null | undefined ): obj is FirebaseServerApp { + if (obj === null || obj === undefined) { + return false; + } return (obj as FirebaseServerApp).settings !== undefined; } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index ff25de93a46..ea68579a7e9 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -185,9 +185,11 @@ export interface FirebaseServerAppSettings * causes an automatic attempt to sign in the user that the `authIdToken` represents. The token * needs to have been recently minted for this operation to succeed. * - * If the token fails local verification, or if the Auth service has failed to validate it when - * the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not - * sign in a user on initialization. + * If the token fails local verification due to expiration or parsing errors, then a console error + * is logged at the time of initialization of the `FirebaseServerApp` instance. + * + * If the Auth service has failed to validate the token when the Auth SDK is initialized, then an + * warning is logged to the console and the Auth SDK will not sign in a user on initialization. * * If a user is successfully signed in, then the Auth instance's `onAuthStateChanged` callback * is invoked with the `User` object as per standard Auth flows. However, `User` objects @@ -196,6 +198,15 @@ export interface FirebaseServerAppSettings */ authIdToken?: string; + /** + * An optional App Check token. If provided, the Firebase SDKs that use App Check will utilize + * this App Check token in place of requiring an instance of App Check to be initialized. + * + * If the token fails local verification due to expiration or parsing errors, then a console error + * is logged at the time of initialization of the `FirebaseServerApp` instance. + */ + appCheckToken?: string; + /** * An optional object. If provided, the Firebase SDK uses a `FinalizationRegistry` * object to monitor the garbage collection status of the provided object. The diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index fd6f1a82a76..45a2c99ea0b 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -845,6 +845,9 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } async _getAppCheckToken(): Promise { + if (_isFirebaseServerApp(this.app) && this.app.settings.appCheckToken) { + return this.app.settings.appCheckToken; + } const appCheckTokenResult = await this.appCheckServiceProvider .getImmediate({ optional: true }) ?.getToken(); diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts index fc61bf561bf..71c64b8554a 100644 --- a/packages/auth/test/integration/flows/firebaseserverapp.test.ts +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -166,37 +166,6 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { await deleteApp(serverApp); }); - it('invalid token does not sign in user', async () => { - if (isBrowser()) { - return; - } - const authIdToken = '{ invalid token }'; - const firebaseServerAppSettings = { authIdToken }; - - const serverApp = initializeServerApp( - getAppConfig(), - firebaseServerAppSettings - ); - const serverAppAuth = getTestInstanceForServerApp(serverApp); - expect(serverAppAuth.currentUser).to.be.null; - - let numberServerLogins = 0; - onAuthStateChanged(serverAppAuth, serverAuthUser => { - if (serverAuthUser) { - numberServerLogins++; - } - }); - - await new Promise(resolve => { - setTimeout(resolve, signInWaitDuration); - }); - - expect(numberServerLogins).to.equal(0); - expect(serverAppAuth.currentUser).to.be.null; - - await deleteApp(serverApp); - }); - it('signs in with email credentials user', async () => { if (isBrowser()) { return; diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 30ec344f3ef..53eb0c97ed5 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -151,7 +151,7 @@ export class DataConnect { } if (this._appCheckProvider) { this._appCheckTokenProvider = new AppCheckTokenProvider( - this.app.name, + this.app, this._appCheckProvider ); } diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index d9cdaeb6f39..4b49a8f674a 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { FirebaseApp, _isFirebaseServerApp } from '@firebase/app'; import { AppCheckInternalComponentName, AppCheckTokenListener, @@ -29,10 +30,14 @@ import { Provider } from '@firebase/component'; */ export class AppCheckTokenProvider { private appCheck?: FirebaseAppCheckInternal; + private serverAppAppCheckToken?: string; constructor( - private appName_: string, + app: FirebaseApp, private appCheckProvider?: Provider ) { + if (_isFirebaseServerApp(app) && app.settings.appCheckToken) { + this.serverAppAppCheckToken = app.settings.appCheckToken; + } this.appCheck = appCheckProvider?.getImmediate({ optional: true }); if (!this.appCheck) { void appCheckProvider @@ -42,7 +47,11 @@ export class AppCheckTokenProvider { } } - getToken(forceRefresh?: boolean): Promise { + getToken(): Promise { + if (this.serverAppAppCheckToken) { + return Promise.resolve({ token: this.serverAppAppCheckToken }); + } + if (!this.appCheck) { return new Promise((resolve, reject) => { // Support delayed initialization of FirebaseAppCheck. This allows our @@ -51,14 +60,14 @@ export class AppCheckTokenProvider { // becomes available before the timoeout below expires. setTimeout(() => { if (this.appCheck) { - this.getToken(forceRefresh).then(resolve, reject); + this.getToken().then(resolve, reject); } else { resolve(null); } }, 0); }); } - return this.appCheck.getToken(forceRefresh); + return this.appCheck.getToken(); } addTokenChangeListener(listener: AppCheckTokenListener): void { diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index 3182365dda0..72ae85c08a1 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -164,7 +164,7 @@ export function repoManagerDatabaseFromApp( repoInfo, app, authTokenProvider, - new AppCheckTokenProvider(app.name, appCheckProvider) + new AppCheckTokenProvider(app, appCheckProvider) ); return new Database(repo, app); } diff --git a/packages/database/src/core/AppCheckTokenProvider.ts b/packages/database/src/core/AppCheckTokenProvider.ts index 2bdd8fdadac..612cb902e5f 100644 --- a/packages/database/src/core/AppCheckTokenProvider.ts +++ b/packages/database/src/core/AppCheckTokenProvider.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { FirebaseApp, _isFirebaseServerApp } from '@firebase/app'; // eslint-disable-line import/no-extraneous-dependencies import { AppCheckInternalComponentName, AppCheckTokenListener, @@ -30,10 +31,16 @@ import { warn } from './util/util'; */ export class AppCheckTokenProvider { private appCheck?: FirebaseAppCheckInternal; + private serverAppAppCheckToken?: string; + private appName: string; constructor( - private appName_: string, + app: FirebaseApp, private appCheckProvider?: Provider ) { + this.appName = app.name; + if (_isFirebaseServerApp(app) && app.settings.appCheckToken) { + this.serverAppAppCheckToken = app.settings.appCheckToken; + } this.appCheck = appCheckProvider?.getImmediate({ optional: true }); if (!this.appCheck) { appCheckProvider?.get().then(appCheck => (this.appCheck = appCheck)); @@ -41,6 +48,14 @@ export class AppCheckTokenProvider { } getToken(forceRefresh?: boolean): Promise { + if (this.serverAppAppCheckToken) { + if (forceRefresh) { + throw new Error( + 'Attempted reuse of `FirebaseServerApp.appCheckToken` after previous usage failed.' + ); + } + return Promise.resolve({ token: this.serverAppAppCheckToken }); + } if (!this.appCheck) { return new Promise((resolve, reject) => { // Support delayed initialization of FirebaseAppCheck. This allows our @@ -67,7 +82,7 @@ export class AppCheckTokenProvider { notifyForInvalidToken(): void { warn( - `Provided AppCheck credentials for the app named "${this.appName_}" ` + + `Provided AppCheck credentials for the app named "${this.appName}" ` + 'are invalid. This usually indicates your app was not initialized correctly.' ); } diff --git a/packages/firestore/lite/register.ts b/packages/firestore/lite/register.ts index 9bd7b014fa2..300f9d5ec94 100644 --- a/packages/firestore/lite/register.ts +++ b/packages/firestore/lite/register.ts @@ -49,6 +49,7 @@ export function registerFirestore(): void { container.getProvider('auth-internal') ), new LiteAppCheckTokenProvider( + app, container.getProvider('app-check-internal') ), databaseIdFromApp(app, databaseId), diff --git a/packages/firestore/src/api/credentials.ts b/packages/firestore/src/api/credentials.ts index 5da9a32209b..b542ec80b91 100644 --- a/packages/firestore/src/api/credentials.ts +++ b/packages/firestore/src/api/credentials.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { FirebaseApp, _isFirebaseServerApp } from '@firebase/app'; import { AppCheckInternalComponentName, AppCheckTokenListener, @@ -495,10 +496,16 @@ export class FirebaseAppCheckTokenProvider private forceRefresh = false; private appCheck: FirebaseAppCheckInternal | null = null; private latestAppCheckToken: string | null = null; + private serverAppAppCheckToken: string | null = null; constructor( + app: FirebaseApp, private appCheckProvider: Provider - ) {} + ) { + if (_isFirebaseServerApp(app) && app.settings.appCheckToken) { + this.serverAppAppCheckToken = app.settings.appCheckToken; + } + } start( asyncQueue: AsyncQueue, @@ -562,6 +569,9 @@ export class FirebaseAppCheckTokenProvider } getToken(): Promise { + if (this.serverAppAppCheckToken) { + return Promise.resolve(new AppCheckToken(this.serverAppAppCheckToken)); + } debugAssert( this.tokenListener != null, 'FirebaseAppCheckTokenProvider not started.' @@ -622,16 +632,25 @@ export class EmptyAppCheckTokenProvider implements CredentialsProvider { /** AppCheck token provider for the Lite SDK. */ export class LiteAppCheckTokenProvider implements CredentialsProvider { private appCheck: FirebaseAppCheckInternal | null = null; + private serverAppAppCheckToken: string | null = null; constructor( + app: FirebaseApp, private appCheckProvider: Provider ) { + if (_isFirebaseServerApp(app) && app.settings.appCheckToken) { + this.serverAppAppCheckToken = app.settings.appCheckToken; + } appCheckProvider.onInit(appCheck => { this.appCheck = appCheck; }); } getToken(): Promise { + if (this.serverAppAppCheckToken) { + return Promise.resolve(new AppCheckToken(this.serverAppAppCheckToken)); + } + if (!this.appCheck) { return Promise.resolve(null); } diff --git a/packages/firestore/src/register.ts b/packages/firestore/src/register.ts index 573ac6f2020..82b450b3834 100644 --- a/packages/firestore/src/register.ts +++ b/packages/firestore/src/register.ts @@ -47,6 +47,7 @@ export function registerFirestore( container.getProvider('auth-internal') ), new FirebaseAppCheckTokenProvider( + app, container.getProvider('app-check-internal') ), databaseIdFromApp(app, databaseId), diff --git a/packages/functions/src/context.ts b/packages/functions/src/context.ts index 0013e2c54f6..4ac4bbe2cb9 100644 --- a/packages/functions/src/context.ts +++ b/packages/functions/src/context.ts @@ -16,6 +16,7 @@ */ import { Provider } from '@firebase/component'; +import { _isFirebaseServerApp, FirebaseApp } from '@firebase/app'; import { AppCheckInternalComponentName, FirebaseAppCheckInternal @@ -47,11 +48,16 @@ export class ContextProvider { private auth: FirebaseAuthInternal | null = null; private messaging: MessagingInternal | null = null; private appCheck: FirebaseAppCheckInternal | null = null; + private serverAppAppCheckToken: string | null = null; constructor( + readonly app: FirebaseApp, authProvider: Provider, messagingProvider: Provider, appCheckProvider: Provider ) { + if (_isFirebaseServerApp(app) && app.settings.appCheckToken) { + this.serverAppAppCheckToken = app.settings.appCheckToken; + } this.auth = authProvider.getImmediate({ optional: true }); this.messaging = messagingProvider.getImmediate({ optional: true @@ -76,7 +82,7 @@ export class ContextProvider { } if (!this.appCheck) { - appCheckProvider.get().then( + appCheckProvider?.get().then( appCheck => (this.appCheck = appCheck), () => { /* get() never rejects */ @@ -122,6 +128,9 @@ export class ContextProvider { async getAppCheckToken( limitedUseAppCheckTokens?: boolean ): Promise { + if (this.serverAppAppCheckToken) { + return this.serverAppAppCheckToken; + } if (this.appCheck) { const result = limitedUseAppCheckTokens ? await this.appCheck.getLimitedUseToken() diff --git a/packages/functions/src/service.ts b/packages/functions/src/service.ts index ec459472b5a..34cb732bf71 100644 --- a/packages/functions/src/service.ts +++ b/packages/functions/src/service.ts @@ -112,6 +112,7 @@ export class FunctionsService implements _FirebaseService { readonly fetchImpl: typeof fetch = (...args) => fetch(...args) ) { this.contextProvider = new ContextProvider( + app, authProvider, messagingProvider, appCheckProvider diff --git a/packages/storage/src/service.ts b/packages/storage/src/service.ts index 6777cb7b659..422e3e1a188 100644 --- a/packages/storage/src/service.ts +++ b/packages/storage/src/service.ts @@ -24,7 +24,11 @@ import { Provider } from '@firebase/component'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; // eslint-disable-next-line import/no-extraneous-dependencies -import { FirebaseApp, FirebaseOptions } from '@firebase/app'; +import { + FirebaseApp, + FirebaseOptions, + _isFirebaseServerApp +} from '@firebase/app'; import { CONFIG_STORAGE_BUCKET_KEY, DEFAULT_HOST, @@ -262,6 +266,9 @@ export class FirebaseStorageImpl implements FirebaseStorage { } async _getAppCheckToken(): Promise { + if (_isFirebaseServerApp(this.app) && this.app.settings.appCheckToken) { + return this.app.settings.appCheckToken; + } const appCheck = this._appCheckProvider.getImmediate({ optional: true }); if (appCheck) { const result = await appCheck.getToken(); diff --git a/packages/vertexai/src/models/generative-model.ts b/packages/vertexai/src/models/generative-model.ts index e719529967c..b44dc1131a3 100644 --- a/packages/vertexai/src/models/generative-model.ts +++ b/packages/vertexai/src/models/generative-model.ts @@ -46,6 +46,7 @@ import { import { VertexAI } from '../public-types'; import { ApiSettings } from '../types/internal'; import { VertexAIService } from '../service'; +import { _isFirebaseServerApp } from '@firebase/app'; /** * Class for generative model APIs. @@ -82,7 +83,16 @@ export class GenerativeModel { project: vertexAI.app.options.projectId, location: vertexAI.location }; - if ((vertexAI as VertexAIService).appCheck) { + + if ( + _isFirebaseServerApp(vertexAI.app) && + vertexAI.app.settings.appCheckToken + ) { + const token = vertexAI.app.settings.appCheckToken; + this._apiSettings.getAppCheckToken = () => { + return Promise.resolve({ token }); + }; + } else if ((vertexAI as VertexAIService).appCheck) { this._apiSettings.getAppCheckToken = () => (vertexAI as VertexAIService).appCheck!.getToken(); }