From 3037b861de536af55081b611632c4be4cf7a99ab Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 8 Jan 2024 14:32:34 -0500 Subject: [PATCH 01/19] [FirebaseServerApp] Add ServerApp to feature branch (#7894) Baseline addition of the FirebaseServerApp object. --- common/api-review/app.api.md | 26 +++ config/tsconfig.base.json | 1 + docs-devsite/app.firebaseserverapp.md | 83 ++++++++++ docs-devsite/app.firebaseserverappsettings.md | 104 ++++++++++++ docs-devsite/app.md | 60 +++++++ packages/app/src/api.test.ts | 105 ++++++++++-- packages/app/src/api.ts | 135 +++++++++++++++- packages/app/src/errors.ts | 15 +- packages/app/src/firebaseApp.ts | 12 +- packages/app/src/firebaseServerApp.test.ts | 143 +++++++++++++++++ packages/app/src/firebaseServerApp.ts | 118 ++++++++++++++ packages/app/src/internal.ts | 26 ++- packages/app/src/public-types.ts | 150 ++++++++++++++++++ 13 files changed, 955 insertions(+), 23 deletions(-) create mode 100644 docs-devsite/app.firebaseserverapp.md create mode 100644 docs-devsite/app.firebaseserverappsettings.md create mode 100644 packages/app/src/firebaseServerApp.test.ts create mode 100644 packages/app/src/firebaseServerApp.ts diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 52f134dac16..266e7730610 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -71,6 +71,23 @@ export interface FirebaseOptions { storageBucket?: string; } +// @public +export interface FirebaseServerApp extends FirebaseApp { + appCheckTokenVerified: () => Promise; + authIdTokenVerified: () => Promise; + installationTokenVerified: () => Promise; + name: string; +} + +// @public +export interface FirebaseServerAppSettings extends FirebaseAppSettings { + appCheckToken?: string; + authIdToken?: string; + installationsAuthToken?: string; + name?: undefined; + releaseOnDeref?: object; +} + // @internal (undocumented) export interface _FirebaseService { // (undocumented) @@ -96,6 +113,12 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSett // @public export function initializeApp(): FirebaseApp; +// @public +export function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; + +// @internal (undocumented) +export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; + // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; @@ -111,6 +134,9 @@ export function _removeServiceInstance(app: FirebaseApp, name: T // @public export const SDK_VERSION: string; +// @public (undocumented) +export const _serverApps: Map; + // @public export function setLogLevel(logLevel: LogLevelString): void; diff --git a/config/tsconfig.base.json b/config/tsconfig.base.json index cf88a789aa2..0b585359a36 100644 --- a/config/tsconfig.base.json +++ b/config/tsconfig.base.json @@ -15,6 +15,7 @@ "es2015.core", "es2017.object", "es2017.string", + "ESNext.WeakRef", ], "module": "ES2015", "moduleResolution": "node", diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md new file mode 100644 index 00000000000..cf41ee0a633 --- /dev/null +++ b/docs-devsite/app.firebaseserverapp.md @@ -0,0 +1,83 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirebaseServerApp interface +A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments. + +Do not call this constructor directly. Instead, use to create an app. + +Signature: + +```typescript +export interface FirebaseServerApp extends FirebaseApp +``` +Extends: [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appCheckTokenVerified](./app.firebaseserverapp.md#firebaseserverappappchecktokenverified) | () => Promise<void> | Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately.It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | +| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | +| [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | +| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. | + +## FirebaseServerApp.appCheckTokenVerified + +Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately. + +It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation. + +The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +appCheckTokenVerified: () => Promise; +``` + +## FirebaseServerApp.authIdTokenVerified + +Checks to see if the verification of the authIdToken provided to has completed. + +It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK. + +The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +authIdTokenVerified: () => Promise; +``` + +## FirebaseServerApp.installationTokenVerified + +Checks to see if the verification of the installationToken provided to has completed. + +It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation. + +The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. + +Signature: + +```typescript +installationTokenVerified: () => Promise; +``` + +## FirebaseServerApp.name + +There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. + +Signature: + +```typescript +name: string; +``` diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md new file mode 100644 index 00000000000..54ba13baa11 --- /dev/null +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -0,0 +1,104 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirebaseServerAppSettings interface +Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) + +Signature: + +```typescript +export interface FirebaseServerAppSettings extends FirebaseAppSettings +``` +Extends: [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional AppCheck token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided.If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check.If the token fails verification then a warning is logged and the token will not be used. | +| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided.Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token.If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. | +| [installationsAuthToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsinstallationsauthtoken) | string | An optional Installation Auth token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs.If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations.If the token fails verification then a warning is logged and the token will not be used. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | +| [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or.The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted.If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp.If the app 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | + +## FirebaseServerAppSettings.appCheckToken + +An optional AppCheck token. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided. + +If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check. + +If the token fails verification then a warning is logged and the token will not be used. + +Signature: + +```typescript +appCheckToken?: string; +``` + +## FirebaseServerAppSettings.authIdToken + +An optional Auth ID token used to resume a signed in user session from a client runtime environment. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided. + +Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token. + +If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. + +Signature: + +```typescript +authIdToken?: string; +``` + +## FirebaseServerAppSettings.installationsAuthToken + +An optional Installation Auth token. + +If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs. + +If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations. + +If the token fails verification then a warning is logged and the token will not be used. + +Signature: + +```typescript +installationsAuthToken?: string; +``` + +## FirebaseServerAppSettings.name + +There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. + +Signature: + +```typescript +name?: undefined; +``` + +## FirebaseServerAppSettings.releaseOnDeref + +An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or. + +The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted. + +If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp. + +If the app 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. + +Signature: + +```typescript +releaseOnDeref?: object; +``` diff --git a/docs-devsite/app.md b/docs-devsite/app.md index de0611cac1a..b12c29819c9 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -34,6 +34,7 @@ This package coordinates the communication between the different Firebase compon | function(options, ...) | | [initializeApp(options, name)](./app.md#initializeapp_cb2f5e1) | Creates and initializes a [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | | [initializeApp(options, config)](./app.md#initializeapp_079e917) | Creates and initializes a FirebaseApp instance. | +| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | ## Interfaces @@ -42,11 +43,14 @@ This package coordinates the communication between the different Firebase compon | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | A [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) holds the initialization information for a collection of services.Do not call this constructor directly. Instead, use [initializeApp()](./app.md#initializeapp_cb2f5e1) to create an app. | | [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) | Configuration options given to [initializeApp()](./app.md#initializeapp_cb2f5e1) | | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | Firebase configuration object. Contains a set of parameters required by services in order to successfully communicate with Firebase server APIs and to associate client data with your Firebase project and Firebase application. Typically this object is populated by the Firebase console at project setup. See also: [Learn about the Firebase config object](https://firebase.google.com/docs/web/setup#config-object). | +| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use to create an app. | +| [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) | ## Variables | Variable | Description | | --- | --- | +| [\_serverApps](./app.md#_serverapps) | | | [SDK\_VERSION](./app.md#sdk_version) | The current SDK version. | ## function(app, ...) @@ -309,6 +313,62 @@ export declare function initializeApp(options: FirebaseOptions, config?: Firebas [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) +### initializeServerApp(options, config) {:#initializeserverapp_30ab697} + +Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance. + +The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. + +See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. + +Signature: + +```typescript +export declare function initializeServerApp(options: FirebaseOptions | FirebaseApp, config: FirebaseServerAppSettings): FirebaseServerApp; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) \| [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | Firebase.AppOptions to configure the app's services, or a a FirebaseApp instance which contains the AppOptions within. | +| config | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | FirebaseServerApp configuration. | + +Returns: + +[FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) + +The initialized FirebaseServerApp. + +### Example + + +```javascript + +// Initialize a FirebaseServerApp. +// Retrieve your own options values by adding a web app on +// https://console.firebase.google.com +initializeServerApp({ + apiKey: "AIza....", // Auth / General Use + authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect + databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database + storageBucket: "YOUR_APP.appspot.com", // Storage + messagingSenderId: "123456789" // Cloud Messaging + }, + { + authIdToken: "Your Auth ID Token" + }); + +``` + +## \_serverApps + +Signature: + +```typescript +_serverApps: Map +``` + ## SDK\_VERSION The current SDK version. diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index ab361df6ff1..7faabc1e1a7 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -20,6 +20,7 @@ import { stub, spy } from 'sinon'; import '../test/setup'; import { initializeApp, + initializeServerApp, getApps, deleteApp, getApp, @@ -28,7 +29,7 @@ import { onLog } from './api'; import { DEFAULT_ENTRY_NAME } from './constants'; -import { _FirebaseService } from './public-types'; +import { FirebaseServerAppSettings, _FirebaseService } from './public-types'; import { _clearComponents, _components, @@ -39,6 +40,7 @@ import { createTestComponent } from '../test/util'; import { Component, ComponentType } from '@firebase/component'; import { Logger } from '@firebase/logger'; import { FirebaseAppImpl } from './firebaseApp'; +import { isBrowser } from '@firebase/util'; declare module '@firebase/component' { interface NameServiceMapping { @@ -54,7 +56,7 @@ describe('API tests', () => { }); describe('initializeApp', () => { - it('creats DEFAULT App', () => { + it('creates DEFAULT App', () => { const app = initializeApp({}); expect(app.name).to.equal(DEFAULT_ENTRY_NAME); }); @@ -91,7 +93,7 @@ describe('API tests', () => { ).to.equal(app); }); - it('throws when creating duplicate DEDAULT Apps with different options', () => { + it('throws when creating duplicate DEFAULT Apps with different options', () => { initializeApp({ apiKey: 'test1' }); @@ -120,7 +122,7 @@ describe('API tests', () => { ).throws(/'MyApp'.*exists/i); }); - it('throws when creating duplicate DEDAULT Apps with different config values', () => { + it('throws when creating duplicate DEFAULT Apps with different config values', () => { initializeApp( { apiKey: 'test1' @@ -161,12 +163,6 @@ describe('API tests', () => { expect(app.name).to.equal(appName); }); - it('takes an object as the second parameter to create named App', () => { - const appName = 'MyApp'; - const app = initializeApp({}, { name: appName }); - expect(app.name).to.equal(appName); - }); - it('sets automaticDataCollectionEnabled', () => { const app = initializeApp({}, { automaticDataCollectionEnabled: true }); expect(app.automaticDataCollectionEnabled).to.be.true; @@ -187,6 +183,95 @@ describe('API tests', () => { }); }); + describe('initializeServerApp', () => { + it('creates FirebaseServerApp with options', () => { + if (isBrowser()) { + const options = { + apiKey: 'APIKEY' + }; + const serverAppSettings: FirebaseServerAppSettings = {}; + expect(() => initializeServerApp(options, serverAppSettings)).throws( + /FirebaseServerApp is not for use in browser environments./ + ); + } + }); + + it('creates FirebaseServerApp with options', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = {}; + + const app = initializeServerApp(options, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.false; + }); + + it('creates FirebaseServerApp with automaticDataCollectionEnabled', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: true + }; + + const app = initializeServerApp(options, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.true; + }); + + it('creates FirebaseServerApp with releaseOnDeref', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { apiKey: 'APIKEY' }; + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const app = initializeServerApp(options, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.automaticDataCollectionEnabled).to.be.false; + }); + + it('creates FirebaseServerApp with FirebaseApp', () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { + apiKey: 'test1' + }; + const standardApp = initializeApp(options); + expect(standardApp.name).to.equal(DEFAULT_ENTRY_NAME); + expect(standardApp.options.apiKey).to.equal('test1'); + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false + }; + + const serverApp = initializeServerApp(standardApp, serverAppSettings); + expect(serverApp).to.not.equal(null); + expect(serverApp.options.apiKey).to.equal('test1'); + }); + }); + describe('getApp', () => { it('retrieves DEFAULT App', () => { const app = initializeApp({}); diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index d43c4ac65c2..e3812fd7538 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -17,8 +17,10 @@ import { FirebaseApp, + FirebaseServerApp, FirebaseOptions, - FirebaseAppSettings + FirebaseAppSettings, + FirebaseServerAppSettings } from './public-types'; import { DEFAULT_ENTRY_NAME, PLATFORM_LOG_STRING } from './constants'; import { ERROR_FACTORY, AppError } from './errors'; @@ -30,7 +32,14 @@ import { } from '@firebase/component'; import { version } from '../../firebase/package.json'; import { FirebaseAppImpl } from './firebaseApp'; -import { _apps, _components, _registerComponent } from './internal'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; +import { + _apps, + _components, + _isFirebaseApp, + _registerComponent, + _serverApps +} from './internal'; import { logger } from './logger'; import { LogLevelString, @@ -39,7 +48,7 @@ import { LogOptions, setUserLogHandler } from '@firebase/logger'; -import { deepEqual, getDefaultAppConfig } from '@firebase/util'; +import { deepEqual, getDefaultAppConfig, isBrowser } from '@firebase/util'; export { FirebaseError } from '@firebase/util'; @@ -171,6 +180,119 @@ export function initializeApp( return newApp; } +/** + * Creates and initializes a {@link @firebase/app#FirebaseServerApp} instance. + * + * The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in + * server side rendering environments only. + * + * See + * {@link + * https://firebase.google.com/docs/web/setup#add_firebase_to_your_app + * | Add Firebase to your app} and + * {@link + * https://firebase.google.com/docs/web/setup#multiple-projects + * | Initialize multiple projects} for detailed documentation. + * + * @example + * ```javascript + * + * // Initialize a FirebaseServerApp. + * // Retrieve your own options values by adding a web app on + * // https://console.firebase.google.com + * initializeServerApp({ + * apiKey: "AIza....", // Auth / General Use + * authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect + * databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database + * storageBucket: "YOUR_APP.appspot.com", // Storage + * messagingSenderId: "123456789" // Cloud Messaging + * }, + * { + * authIdToken: "Your Auth ID Token" + * }); + * ``` + * + * @param options - Firebase.AppOptions to configure the app's services, or a + * a FirebaseApp instance which contains the AppOptions within. + * @param config - FirebaseServerApp configuration. + * + * @returns The initialized FirebaseServerApp. + * + * @public + */ +export function initializeServerApp( + options: FirebaseOptions | FirebaseApp, + config: FirebaseServerAppSettings +): FirebaseServerApp; + +export function initializeServerApp( + _options: FirebaseOptions | FirebaseApp, + _serverAppConfig: FirebaseServerAppSettings +): FirebaseServerApp { + if (isBrowser()) { + // FirebaseServerApps aren't designed to be run in browsers. + throw ERROR_FACTORY.create(AppError.INVALID_SERVER_APP_ENVIRONMENT); + } + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + ..._serverAppConfig + }; + + let appOptions: FirebaseOptions; + if (_isFirebaseApp(_options)) { + appOptions = _options.options; + } else { + appOptions = _options; + } + + const nameObj = { + authIdToken: _serverAppConfig?.authIdToken, + appCheckToken: _serverAppConfig?.appCheckToken, + installationsAuthToken: _serverAppConfig?.installationsAuthToken, + ...appOptions + }; + + if (serverAppSettings.releaseOnDeref !== undefined) { + if (typeof FinalizationRegistry === 'undefined') { + throw ERROR_FACTORY.create( + AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, + {} + ); + } + } + + // TODO: move this into util.js. + const hashCode = (s: string): number => { + return [...s].reduce( + (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, + 0 + ); + }; + + const nameString = '' + hashCode(JSON.stringify(nameObj)); + const existingApp = _serverApps.get(nameString) as FirebaseServerAppImpl; + if (existingApp) { + // TODO: + // 1: Register a new reference to finalization registry. + // 2: Incrememnt reference count. + return existingApp; + } + + const container = new ComponentContainer(nameString); + for (const component of _components.values()) { + container.addComponent(component); + } + + const newApp = new FirebaseServerAppImpl( + appOptions, + serverAppSettings, + container + ); + + return newApp; +} + /** * Retrieves a {@link @firebase/app#FirebaseApp} instance. * @@ -238,9 +360,16 @@ export function getApps(): FirebaseApp[] { * @public */ export async function deleteApp(app: FirebaseApp): Promise { + let foundApp = false; const name = app.name; if (_apps.has(name)) { + foundApp = true; _apps.delete(name); + } else if (_serverApps.has(name)) { + foundApp = true; + _serverApps.delete(name); + } + if (foundApp) { await Promise.all( (app as FirebaseAppImpl).container .getProviders() diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 25582398663..0149ef3dcb1 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -22,23 +22,27 @@ export const enum AppError { BAD_APP_NAME = 'bad-app-name', DUPLICATE_APP = 'duplicate-app', APP_DELETED = 'app-deleted', + SERVER_APP_DELETED = 'server-app-deleted', NO_OPTIONS = 'no-options', INVALID_APP_ARGUMENT = 'invalid-app-argument', INVALID_LOG_ARGUMENT = 'invalid-log-argument', IDB_OPEN = 'idb-open', IDB_GET = 'idb-get', IDB_WRITE = 'idb-set', - IDB_DELETE = 'idb-delete' + IDB_DELETE = 'idb-delete', + FINALIZATION_REGISTRY_NOT_SUPPORTED = 'finalization-registry-not-supported', + INVALID_SERVER_APP_ENVIRONMENT = 'invalid-server-app-environment' } const ERRORS: ErrorMap = { [AppError.NO_APP]: "No Firebase App '{$appName}' has been created - " + 'call initializeApp() first', - [AppError.BAD_APP_NAME]: "Illegal App name: '{$appName}", + [AppError.BAD_APP_NAME]: "Illegal App name: '{$appName}'", [AppError.DUPLICATE_APP]: "Firebase App named '{$appName}' already exists with different options or config", [AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted", + [AppError.SERVER_APP_DELETED]: 'Firebase Server App has been deleted', [AppError.NO_OPTIONS]: 'Need to provide options, when not being deployed to hosting via source.', [AppError.INVALID_APP_ARGUMENT]: @@ -53,7 +57,11 @@ const ERRORS: ErrorMap = { [AppError.IDB_WRITE]: 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.', [AppError.IDB_DELETE]: - 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.' + 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.', + [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: + 'FirebaseServerApp deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry.', + [AppError.INVALID_SERVER_APP_ENVIRONMENT]: + 'FirebaseServerApp is not for use in browser environments.' }; interface ErrorParams { @@ -66,6 +74,7 @@ interface ErrorParams { [AppError.IDB_GET]: { originalErrorMessage?: string }; [AppError.IDB_WRITE]: { originalErrorMessage?: string }; [AppError.IDB_DELETE]: { originalErrorMessage?: string }; + [AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED]: { appName?: string }; } export const ERROR_FACTORY = new ErrorFactory( diff --git a/packages/app/src/firebaseApp.ts b/packages/app/src/firebaseApp.ts index 60d3b29a3ea..70c8c8ce5b2 100644 --- a/packages/app/src/firebaseApp.ts +++ b/packages/app/src/firebaseApp.ts @@ -28,8 +28,8 @@ import { import { ERROR_FACTORY, AppError } from './errors'; export class FirebaseAppImpl implements FirebaseApp { - private readonly _options: FirebaseOptions; - private readonly _name: string; + protected readonly _options: FirebaseOptions; + protected readonly _name: string; /** * Original config values passed in as a constructor parameter. * It is only used to compare with another config object to support idempotent initializeApp(). @@ -37,9 +37,9 @@ export class FirebaseAppImpl implements FirebaseApp { * Updating automaticDataCollectionEnabled on the App instance will not change its value in _config. */ private readonly _config: Required; - private _automaticDataCollectionEnabled: boolean; - private _isDeleted = false; - private readonly _container: ComponentContainer; + protected _automaticDataCollectionEnabled: boolean; + protected _isDeleted = false; + protected readonly _container: ComponentContainer; constructor( options: FirebaseOptions, @@ -98,7 +98,7 @@ export class FirebaseAppImpl implements FirebaseApp { * This function will throw an Error if the App has already been deleted - * use before performing API actions on the App. */ - private checkDestroyed(): void { + protected checkDestroyed(): void { if (this.isDeleted) { throw ERROR_FACTORY.create(AppError.APP_DELETED, { appName: this._name }); } diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts new file mode 100644 index 00000000000..a95f7e42d8a --- /dev/null +++ b/packages/app/src/firebaseServerApp.test.ts @@ -0,0 +1,143 @@ +/** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import '../test/setup'; +import { ComponentContainer } from '@firebase/component'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; +import { FirebaseServerAppSettings } from './public-types'; + +describe('FirebaseServerApp', () => { + it('has various accessors', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; + expect(firebaseServerAppImpl.options).to.deep.equal(options); + }); + + it('deep-copies options', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.options).to.not.equal(options); + expect(firebaseServerAppImpl.options).to.deep.equal(options); + }); + + it('sets automaticDataCollectionEnabled', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const firebaseServerAppImpl = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.false; + firebaseServerAppImpl.automaticDataCollectionEnabled = true; + expect(firebaseServerAppImpl.automaticDataCollectionEnabled).to.be.true; + }); + + it('throws accessing any property after being deleted', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const app = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(() => app.options).to.not.throw(); + (app as unknown as FirebaseServerAppImpl).isDeleted = true; + + expect(() => app.options).throws('Firebase Server App has been deleted'); + + expect(() => app.automaticDataCollectionEnabled).throws( + 'Firebase Server App has been deleted' + ); + }); + + it('throws accessing any method after being deleted', () => { + const options = { + apiKey: 'APIKEY' + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const app = new FirebaseServerAppImpl( + options, + serverAppSettings, + new ComponentContainer('test') + ); + + expect(() => app.authIdTokenVerified).to.not.throw(); + (app as unknown as FirebaseServerAppImpl).isDeleted = true; + + expect(() => app.authIdTokenVerified()).throws( + 'Firebase Server App has been deleted' + ); + + expect(() => app.appCheckTokenVerified()).throws( + 'Firebase Server App has been deleted' + ); + + expect(() => app.installationTokenVerified()).throws( + 'Firebase Server App has been deleted' + ); + }); +}); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts new file mode 100644 index 00000000000..cc4264ae504 --- /dev/null +++ b/packages/app/src/firebaseServerApp.ts @@ -0,0 +1,118 @@ +/** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FirebaseAppSettings, + FirebaseServerApp, + FirebaseServerAppSettings, + FirebaseOptions +} from './public-types'; +import { deleteApp } from './api'; +import { ComponentContainer } from '@firebase/component'; +import { FirebaseAppImpl } from './firebaseApp'; +import { ERROR_FACTORY, AppError } from './errors'; + +export class FirebaseServerAppImpl + extends FirebaseAppImpl + implements FirebaseServerApp +{ + private readonly _serverConfig: FirebaseServerAppSettings; + private _finalizationRegistry: FinalizationRegistry; + + constructor( + options: FirebaseOptions | FirebaseAppImpl, + serverConfig: FirebaseServerAppSettings, + container: ComponentContainer + ) { + // Build configuration parameters for the FirebaseAppImpl base class. + const automaticDataCollectionEnabled = + serverConfig.automaticDataCollectionEnabled !== undefined + ? serverConfig.automaticDataCollectionEnabled + : false; + + // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. + const config: Required = { + name: '', + automaticDataCollectionEnabled + }; + + if ((options as FirebaseOptions).apiKey !== undefined) { + // Construct the parent FirebaseAppImp object. + super(options as FirebaseOptions, config, container); + } else { + const appImpl: FirebaseAppImpl = options as FirebaseAppImpl; + + super(appImpl.options, config, container); + } + + // Now construct the data for the FirebaseServerAppImpl. + this._serverConfig = { + automaticDataCollectionEnabled, + ...serverConfig + }; + + this._finalizationRegistry = new FinalizationRegistry( + this.automaticCleanup + ); + + if (this._serverConfig.releaseOnDeref !== undefined) { + this._finalizationRegistry.register( + this._serverConfig.releaseOnDeref, + this + ); + this._serverConfig.releaseOnDeref = undefined; // Don't keep a strong reference to the object. + } + } + + private automaticCleanup(serverApp: FirebaseServerAppImpl): void { + // TODO: implement reference counting. + void deleteApp(serverApp); + } + + get serverAppConfig(): FirebaseServerAppSettings { + this.checkDestroyed(); + return this._serverConfig; + } + + authIdTokenVerified(): Promise { + this.checkDestroyed(); + // TODO + return Promise.resolve(); + } + + appCheckTokenVerified(): Promise { + this.checkDestroyed(); + // TODO + return Promise.resolve(); + } + + installationTokenVerified(): Promise { + this.checkDestroyed(); + // TODO + return Promise.resolve(); + } + + /** + * This function will throw an Error if the App has already been deleted - + * use before performing API actions on the App. + */ + protected checkDestroyed(): void { + if (this.isDeleted) { + throw ERROR_FACTORY.create(AppError.SERVER_APP_DELETED); + } + } +} diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 9026a36b26a..94af4b9434c 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -15,16 +15,22 @@ * limitations under the License. */ -import { FirebaseApp } from './public-types'; +import { + FirebaseApp, + FirebaseOptions, + FirebaseServerApp +} from './public-types'; import { Component, Provider, Name } from '@firebase/component'; import { logger } from './logger'; import { DEFAULT_ENTRY_NAME } from './constants'; import { FirebaseAppImpl } from './firebaseApp'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; /** * @internal */ export const _apps = new Map(); +export const _serverApps = new Map(); /** * Registered components. @@ -90,6 +96,10 @@ export function _registerComponent( _addComponent(app as FirebaseAppImpl, component); } + for (const serverApp of _serverApps.values()) { + _addComponent(serverApp as FirebaseServerAppImpl, component); + } + return true; } @@ -131,6 +141,20 @@ export function _removeServiceInstance( _getProvider(app, name).clearInstance(instanceIdentifier); } +/** + * + * @param obj - an object of type FirebaseApp or FirebaseOptions. + * + * @returns true if the provide object is of type FirebaseApp. + * + * @internal + */ +export function _isFirebaseApp( + obj: FirebaseApp | FirebaseOptions +): obj is FirebaseApp { + return (obj as FirebaseApp).options !== undefined; +} + /** * Test only * diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 3ca76c47889..f179e31dac3 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -71,6 +71,69 @@ export interface FirebaseApp { automaticDataCollectionEnabled: boolean; } +/** + * A {@link @firebase/app#FirebaseServerApp} holds the initialization information + * for a collection of services running in server enviornments. + * + * Do not call this constructor directly. Instead, use + * {@link (initializeServerAppInstance:1) | initializeServerAppInstance()} to create + * an app. + * + * @public + */ +export interface FirebaseServerApp extends FirebaseApp { + /** + * Checks to see if the verification of the authIdToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise if an authIdToken was + * provided during FirebaseServerApp initialization before invoking getAuth(). If an + * instance of Auth is created before the Auth ID Token is validated, then the token + * will not be used by that instance of the Auth SDK. + * + * The returned Promise is completed immediately if the optional authIdToken parameter + * was omitted from FirebaseServerApp initialization. + */ + authIdTokenVerified: () => Promise; + + /** + * Checks to see if the verification of the appCheckToken provided to + * @initializeServerApp has completed. If the optional appCheckToken parameter was + * omitted then the returned Promise is completed immediately. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use AppCheck. The Firebase SDKs will not + * use App Check tokens that are determined to be invalid or those that have not yet + * completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. + */ + appCheckTokenVerified: () => Promise; + + /** + * Checks to see if the verification of the installationToken provided to + * @initializeServerApp has completed. + * + * It is recommend that your application awaits this promise before initializing + * any Firebase products that use Firebase Installations. The Firebase SDKs will not + * use Installation Auth tokens that are determined to be invalid or those that have + * not yet completed validation. + * + * The returned Promise is completed immediately if the optional appCheckToken + * parameter was omitted from FirebaseServerApp initialization. + */ + + installationTokenVerified: () => Promise; + + /** + * There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here + * so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this + * string will always be empty for FirebaseServerApp instances. + */ + name: string; +} + /** * @public * @@ -139,6 +202,93 @@ export interface FirebaseAppSettings { automaticDataCollectionEnabled?: boolean; } +/** + * @public + * + * Configuration options given to {@link (initializeServerApp:1) | initializeServerApp()} + */ +export interface FirebaseServerAppSettings extends FirebaseAppSettings { + /** + * An optional Auth ID token used to resume a signed in user session from a client + * runtime environment. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be queried via by the application by invoking the + * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by + * authIdTokenVerified is highly recommended if an Auth ID token is provided. + * + * Once the token has been properly verified then invoking getAuth() will attempt to + * automatically sign in a user with the provided Auth ID Token. + * + * If the token fails verification then a warning is logged and Auth SDK will not + * attempt to sign in a user upon its initalization. + */ + authIdToken?: string; + + /** + * An optional AppCheck token. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended if an AppCheck token is provided. + * + * If the token has been properly verified then the AppCheck token will be + * automatically used by Firebase SDKs that support App Check. + * + * If the token fails verification then a warning is logged and the token will not + * be used. + */ + appCheckToken?: string; + + /** + * An optional Installation Auth token. + * + * If provided, the FirebaseServerApp instance will work to validate the token. The + * result of the validation can be monitored by invoking the + * FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by + * appCheckTokenVerified is highly recommended before initalization any other Firebase + * SDKs. + * + * If the token has been properly verified then the Installation Auth token will be + * automatically used by Firebase SDKs that support Firebase Installations. + * + * If the token fails verification then a warning is logged and the token will not + * be used. + */ + installationsAuthToken?: string; + + /** + * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry + * object to monitor the Garbage Collection status of the provided object, and the + * Firebase SDK will release its refrence on the FirebaseServerApp instance when the + * provided object is collected. or. + * + * The intent of this field is to help reduce memory overhead for long-running cloud + * functions executing SSR fulfillment without the customer's app needing to + * orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp + * instances may reused if they're identical to a previously generated one that has + * yet to be deleted. + * + * If the object is not provided then the application must clean up the + * FirebaseServerApp instance through the applicationss own standard mechanisms by + * invoking deleteApp. + * + * If the app 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 the Firebase SDK will not be able + * to automatically clean up the FirebaseServerApp instance and an error will be + * thrown. + */ + releaseOnDeref?: object; + + /** + * There is no get for FirebaseServerApps, so the name is not relevant. however it's always + * a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. + */ + name?: undefined; +} + /** * @internal */ From fc44b08f75dcbc8e147e3d7e1c02f7b1d6e5fd73 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 26 Jan 2024 14:22:09 -0500 Subject: [PATCH 02/19] [ServerApp] Update feature branch Auth implementation to use the authIdToken (#7944) Add support for logging-in users with the FirebaseServerApp's authIdToken. ### Testing Local project testing client-side created users, passing idTokens to serverApps, and logging in the user. Tested with multiple users and multiple instances of FirebaseServerApps w/ Auth. CI tests (added integration tests). ### API Changes N/A --- common/api-review/app.api.md | 4 + docs-devsite/app.firebaseserverapp.md | 21 + packages/app/src/firebaseServerApp.ts | 2 +- packages/app/src/internal.ts | 14 + packages/app/src/public-types.ts | 12 + packages/auth/karma.conf.js | 3 +- packages/auth/scripts/run_node_tests.ts | 4 +- packages/auth/src/core/auth/auth_impl.ts | 12 +- packages/auth/src/core/auth/initialize.ts | 37 +- packages/auth/src/core/user/reload.ts | 2 +- .../auth/src/core/user/token_manager.test.ts | 31 +- packages/auth/src/core/user/token_manager.ts | 16 +- packages/auth/src/core/user/user_impl.ts | 61 ++- .../auth/test/helpers/integration/helpers.ts | 29 +- .../flows/firebaseserverapp.test.ts | 391 ++++++++++++++++++ 15 files changed, 618 insertions(+), 21 deletions(-) create mode 100644 packages/auth/test/integration/flows/firebaseserverapp.test.ts diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 266e7730610..087318ae27f 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -77,6 +77,7 @@ export interface FirebaseServerApp extends FirebaseApp { authIdTokenVerified: () => Promise; installationTokenVerified: () => Promise; name: string; + readonly settings: FirebaseServerAppSettings; } // @public @@ -119,6 +120,9 @@ export function initializeServerApp(options: FirebaseOptions | FirebaseApp, conf // @internal (undocumented) export function _isFirebaseApp(obj: FirebaseApp | FirebaseOptions): obj is FirebaseApp; +// @internal (undocumented) +export function _isFirebaseServerApp(obj: FirebaseApp | FirebaseServerApp): obj is FirebaseServerApp; + // @public export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index cf41ee0a633..387e27a9822 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -29,6 +29,7 @@ export interface FirebaseServerApp extends FirebaseApp | [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | | [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | | [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. | +| [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | ## FirebaseServerApp.appCheckTokenVerified @@ -81,3 +82,23 @@ There is no get for FirebaseServerApp, so the name is not relevant. However, it' ```typescript name: string; ``` + +## FirebaseServerApp.settings + +The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). + +Signature: + +```typescript +readonly settings: FirebaseServerAppSettings; +``` + +### Example + + +```javascript +const app = initializeServerApp(settings); +console.log(app.settings.authIdToken === options.authIdToken); // true + +``` + diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index cc4264ae504..7c51511d087 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -83,7 +83,7 @@ export class FirebaseServerAppImpl void deleteApp(serverApp); } - get serverAppConfig(): FirebaseServerAppSettings { + get settings(): FirebaseServerAppSettings { this.checkDestroyed(); return this._serverConfig; } diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index 94af4b9434c..da0ba3f73fc 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -155,6 +155,20 @@ export function _isFirebaseApp( return (obj as FirebaseApp).options !== undefined; } +/** + * + * @param obj - an object of type FirebaseApp. + * + * @returns true if the provided object is of type FirebaseServerAppImpl. + * + * @internal + */ +export function _isFirebaseServerApp( + obj: FirebaseApp | FirebaseServerApp +): obj is FirebaseServerApp { + return (obj as FirebaseServerApp).authIdTokenVerified !== undefined; +} + /** * Test only * diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index f179e31dac3..04ba4f0b24c 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -132,6 +132,18 @@ export interface FirebaseServerApp extends FirebaseApp { * string will always be empty for FirebaseServerApp instances. */ name: string; + + /** + * The (read-only) configuration settings for this server app. These are the original + * parameters given in {@link (initializeServerApp:1) | initializeServerApp()}. + * + * @example + * ```javascript + * const app = initializeServerApp(settings); + * console.log(app.settings.authIdToken === options.authIdToken); // true + * ``` + */ + readonly settings: FirebaseServerAppSettings; } /** diff --git a/packages/auth/karma.conf.js b/packages/auth/karma.conf.js index 6845f0bd91d..198b079a15b 100644 --- a/packages/auth/karma.conf.js +++ b/packages/auth/karma.conf.js @@ -65,7 +65,8 @@ function getTestFiles(argv) { 'src/**/*.test.ts', 'test/helpers/**/*.test.ts', 'test/integration/flows/anonymous.test.ts', - 'test/integration/flows/email.test.ts' + 'test/integration/flows/email.test.ts', + 'test/integration/flows/firebaseserverapp.test.ts' ]; } } diff --git a/packages/auth/scripts/run_node_tests.ts b/packages/auth/scripts/run_node_tests.ts index 2bfc593d8fd..ce913612f64 100644 --- a/packages/auth/scripts/run_node_tests.ts +++ b/packages/auth/scripts/run_node_tests.ts @@ -48,7 +48,9 @@ let testConfig = [ ]; if (argv.integration) { - testConfig = ['test/integration/flows/{email,anonymous}.test.ts']; + testConfig = [ + 'test/integration/flows/{email,anonymous,firebaseserverapp}.test.ts' + ]; if (argv.local) { testConfig.push('test/integration/flows/*.local.test.ts'); } diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index cd75276e006..55f474eda9e 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -import { _FirebaseService, FirebaseApp } from '@firebase/app'; +import { + _isFirebaseServerApp, + _FirebaseService, + FirebaseApp +} from '@firebase/app'; import { Provider } from '@firebase/component'; import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { @@ -167,7 +171,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } } - await this.initializeCurrentUser(popupRedirectResolver); + // Skip loading users from persistence in FirebaseServerApp Auth instances. + if (!_isFirebaseServerApp(this.app)) { + await this.initializeCurrentUser(popupRedirectResolver); + } + this.lastNotifiedUid = this.currentUser?.uid || null; if (this._deleted) { diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index c6218953508..3bb21364718 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp } from '@firebase/app'; +import { _getProvider, _isFirebaseServerApp, FirebaseApp } from '@firebase/app'; import { deepEqual } from '@firebase/util'; import { Auth, Dependencies } from '../../model/public_types'; @@ -23,7 +23,9 @@ import { AuthErrorCode } from '../errors'; import { PersistenceInternal } from '../persistence'; import { _fail } from '../util/assert'; import { _getInstance } from '../util/instantiator'; -import { AuthImpl } from './auth_impl'; +import { AuthImpl, _castAuth } from './auth_impl'; +import { UserImpl } from '../user/user_impl'; +import { getAccountInfo } from '../../api/account_management/account'; /** * Initializes an {@link Auth} instance with fine-grained control over @@ -65,9 +67,40 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { const auth = provider.initialize({ options: deps }) as AuthImpl; + if (_isFirebaseServerApp(app)) { + if (app.settings.authIdToken !== undefined) { + const idToken = app.settings.authIdToken; + // Start the auth operation in the next tick to allow a moment for the customer's app to + // attach an emulator, if desired. + setTimeout(() => void _loadUserFromIdToken(auth, idToken), 0); + } + } + return auth; } +export async function _loadUserFromIdToken( + auth: Auth, + idToken: string +): Promise { + try { + const response = await getAccountInfo(auth, { idToken }); + const authInternal = _castAuth(auth); + await authInternal._initializationPromise; + const user = await UserImpl._fromGetAccountInfoResponse( + authInternal, + response, + idToken + ); + await authInternal._updateCurrentUser(user); + } catch (err) { + console.warn( + 'FirebaseServerApp could not login user with provided authIdToken: ', + err + ); + } +} + export function _initializeAuthInstance( auth: AuthImpl, deps?: Dependencies diff --git a/packages/auth/src/core/user/reload.ts b/packages/auth/src/core/user/reload.ts index fc0a33b937a..ac9a1683e2d 100644 --- a/packages/auth/src/core/user/reload.ts +++ b/packages/auth/src/core/user/reload.ts @@ -102,7 +102,7 @@ function mergeProviderData( return [...deduped, ...newData]; } -function extractProviderData(providers: ProviderUserInfo[]): UserInfo[] { +export function extractProviderData(providers: ProviderUserInfo[]): UserInfo[] { return providers.map(({ providerId, ...provider }) => { return { providerId, diff --git a/packages/auth/src/core/user/token_manager.test.ts b/packages/auth/src/core/user/token_manager.test.ts index e1648d4eb32..b2e1609692f 100644 --- a/packages/auth/src/core/user/token_manager.test.ts +++ b/packages/auth/src/core/user/token_manager.test.ts @@ -144,8 +144,35 @@ describe('core/user/token_manager', () => { }); }); - it('returns null if the refresh token is missing', async () => { - expect(await stsTokenManager.getToken(auth)).to.be.null; + it('returns non-null if the refresh token is missing but token still valid', async () => { + Object.assign(stsTokenManager, { + accessToken: 'token', + expirationTime: now + 100_000 + }); + const tokens = await stsTokenManager.getToken(auth, false); + expect(tokens).to.eql('token'); + }); + + it('throws an error if the refresh token is missing and force refresh is true', async () => { + Object.assign(stsTokenManager, { + accessToken: 'token', + expirationTime: now + 100_000 + }); + await expect(stsTokenManager.getToken(auth, true)).to.be.rejectedWith( + FirebaseError, + "Firebase: The user's credential is no longer valid. The user must sign in again. (auth/user-token-expired)" + ); + }); + + it('throws an error if the refresh token is missing and token is no longer valid', async () => { + Object.assign(stsTokenManager, { + accessToken: 'old-access-token', + expirationTime: now - 1 + }); + await expect(stsTokenManager.getToken(auth)).to.be.rejectedWith( + FirebaseError, + "Firebase: The user's credential is no longer valid. The user must sign in again. (auth/user-token-expired)" + ); }); it('throws an error if expired but refresh token is missing', async () => { diff --git a/packages/auth/src/core/user/token_manager.ts b/packages/auth/src/core/user/token_manager.ts index 5f56f88afb6..14969005d89 100644 --- a/packages/auth/src/core/user/token_manager.ts +++ b/packages/auth/src/core/user/token_manager.ts @@ -73,20 +73,22 @@ export class StsTokenManager { ); } + updateFromIdToken(idToken: string): void { + _assert(idToken.length !== 0, AuthErrorCode.INTERNAL_ERROR); + const expiresIn = _tokenExpiresIn(idToken); + this.updateTokensAndExpiration(idToken, null, expiresIn); + } + async getToken( auth: AuthInternal, forceRefresh = false ): Promise { - _assert( - !this.accessToken || this.refreshToken, - auth, - AuthErrorCode.TOKEN_EXPIRED - ); - if (!forceRefresh && this.accessToken && !this.isExpired) { return this.accessToken; } + _assert(this.refreshToken, auth, AuthErrorCode.TOKEN_EXPIRED); + if (this.refreshToken) { await this.refresh(auth, this.refreshToken!); return this.accessToken; @@ -113,7 +115,7 @@ export class StsTokenManager { private updateTokensAndExpiration( accessToken: string, - refreshToken: string, + refreshToken: string | null, expiresInSec: number ): void { this.refreshToken = refreshToken || null; diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index 44192cc4617..0aa91861cb9 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { IdTokenResult } from '../../model/public_types'; +import { IdTokenResult, UserInfo } from '../../model/public_types'; import { NextFn } from '@firebase/util'; - import { APIUserInfo, + GetAccountInfoResponse, deleteAccount } from '../../api/account_management/account'; import { FinalizeMfaResponse } from '../../api/authentication/mfa'; @@ -36,7 +36,7 @@ import { _assert } from '../util/assert'; import { getIdTokenResult } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { ProactiveRefresh } from './proactive_refresh'; -import { _reloadWithoutSaving, reload } from './reload'; +import { extractProviderData, _reloadWithoutSaving, reload } from './reload'; import { StsTokenManager } from './token_manager'; import { UserMetadata } from './user_metadata'; import { ProviderId } from '../../model/enums'; @@ -333,4 +333,59 @@ export class UserImpl implements UserInternal { await _reloadWithoutSaving(user); return user; } + + /** + * Initialize a User from an idToken server response + * @param auth + * @param idTokenResponse + */ + static async _fromGetAccountInfoResponse( + auth: AuthInternal, + response: GetAccountInfoResponse, + idToken: string + ): Promise { + const coreAccount = response.users[0]; + _assert(coreAccount.localId !== undefined, AuthErrorCode.INTERNAL_ERROR); + + const providerData: UserInfo[] = + coreAccount.providerUserInfo !== undefined + ? extractProviderData(coreAccount.providerUserInfo) + : []; + + const isAnonymous = + !(coreAccount.email && coreAccount.passwordHash) && !providerData?.length; + + const stsTokenManager = new StsTokenManager(); + stsTokenManager.updateFromIdToken(idToken); + + // Initialize the Firebase Auth user. + const user = new UserImpl({ + uid: coreAccount.localId, + auth, + stsTokenManager, + isAnonymous + }); + + // update the user with data from the GetAccountInfo response. + const updates: Partial = { + uid: coreAccount.localId, + displayName: coreAccount.displayName || null, + photoURL: coreAccount.photoUrl || null, + email: coreAccount.email || null, + emailVerified: coreAccount.emailVerified || false, + phoneNumber: coreAccount.phoneNumber || null, + tenantId: coreAccount.tenantId || null, + providerData, + metadata: new UserMetadata( + coreAccount.createdAt, + coreAccount.lastLoginAt + ), + isAnonymous: + !(coreAccount.email && coreAccount.passwordHash) && + !providerData?.length + }; + + Object.assign(user, updates); + return user; + } } diff --git a/packages/auth/test/helpers/integration/helpers.ts b/packages/auth/test/helpers/integration/helpers.ts index 9825a8f4ba0..17865228784 100644 --- a/packages/auth/test/helpers/integration/helpers.ts +++ b/packages/auth/test/helpers/integration/helpers.ts @@ -16,7 +16,7 @@ */ import * as sinon from 'sinon'; -import { deleteApp, initializeApp } from '@firebase/app'; +import { FirebaseServerApp, deleteApp, initializeApp } from '@firebase/app'; import { Auth, User } from '@firebase/auth'; import { getAuth, connectAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env. @@ -80,6 +80,33 @@ export function getTestInstance(requireEmulator = false): Auth { return auth; } +export function getTestInstanceForServerApp( + serverApp: FirebaseServerApp, + requireEmulator = false +): Auth { + const auth = getAuth(serverApp) as IntegrationTestAuth; + auth.settings.appVerificationDisabledForTesting = true; + const emulatorUrl = getEmulatorUrl(); + + if (emulatorUrl) { + connectAuthEmulator(auth, emulatorUrl, { disableWarnings: true }); + } else if (requireEmulator) { + /* Emulator wasn't configured but test must use emulator */ + throw new Error('Test may only be run using the Auth Emulator!'); + } + + // Don't track created users on the created Auth instance like we do for Auth objects created in + // getTestInstance(...) above. FirebaseServerApp testing re-uses users created by the Auth + // instances returned by getTestInstance, so those Auth cleanup routines will suffice. + auth.cleanUp = async () => { + // If we're in an emulated environment, the emulator will clean up for us. + //if (emulatorUrl) { + // await resetEmulator(); + //} + }; + return auth; +} + export async function cleanUpTestInstance(auth: Auth): Promise { await auth.signOut(); await (auth as IntegrationTestAuth).cleanUp(); diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts new file mode 100644 index 00000000000..42b14eb2edc --- /dev/null +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -0,0 +1,391 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + Auth, + OperationType, + createUserWithEmailAndPassword, + getAdditionalUserInfo, + onAuthStateChanged, + signInAnonymously, + signOut, + updateProfile +} from '@firebase/auth'; +import { isBrowser } from '@firebase/util'; +import { initializeServerApp } from '@firebase/app'; + +import { + cleanUpTestInstance, + getTestInstance, + getTestInstanceForServerApp, + randomEmail +} from '../../helpers/integration/helpers'; + +import { getAppConfig } from '../../helpers/integration/settings'; + +use(chaiAsPromised); + +const signInWaitDuration = 200; + +describe('Integration test: Auth FirebaseServerApp tests', () => { + let auth: Auth; + let serverAppAuth: Auth | null; + + beforeEach(() => { + auth = getTestInstance(); + }); + + afterEach(async () => { + if (serverAppAuth) { + await signOut(serverAppAuth); + serverAppAuth = null; + } + await cleanUpTestInstance(auth); + }); + + it('signs in with anonymous user', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.isAnonymous).to.be.true; + expect(user.uid).to.be.a('string'); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.equal(0); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); + serverAppAuth = getTestInstanceForServerApp(serverApp); + + console.log('auth.emulatorConfig ', auth.emulatorConfig); + console.log('serverAuth.emulatorConfig ', serverAppAuth.emulatorConfig); + + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + + // Note, the serverAuthUser does not fully equal the standard Auth user + // since the serverAuthUser does not have a refresh token. + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.isAnonymous).to.be.equal(serverAuthUser.isAnonymous); + expect(user.emailVerified).to.be.equal(serverAuthUser.emailVerified); + expect(user.providerData.length).to.eq( + serverAuthUser.providerData.length + ); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + }); + + it('getToken operations fullfilled or rejected', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.isAnonymous).to.be.true; + expect(user.uid).to.be.a('string'); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAppAuth).to.not.be.null; + expect(serverAuthUser.getIdToken); + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth.currentUser).to.not.be.null; + if (serverAppAuth.currentUser) { + const idToken = await serverAppAuth.currentUser.getIdToken( + /*forceRefresh=*/ false + ); + expect(idToken).to.not.be.null; + await expect(serverAppAuth.currentUser.getIdToken(/*forceRefresh=*/ true)) + .to.be.rejected; + } + }); + + it('invalid token does not sign in user', async () => { + if (isBrowser()) { + return; + } + const authIdToken = '{ invalid token }'; + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + 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; + }); + + it('signs in with email crednetial user', async () => { + if (isBrowser()) { + return; + } + const email = randomEmail(); + const password = 'password'; + const userCred = await createUserWithEmailAndPassword( + auth, + email, + password + ); + const user = userCred.user; + expect(auth.currentUser).to.eq(userCred.user); + expect(userCred.operationType).to.eq(OperationType.SIGN_IN); + + const additionalUserInfo = getAdditionalUserInfo(userCred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('password'); + expect(user.isAnonymous).to.be.false; + expect(user.email).to.equal(email); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAuthUser.refreshToken).to.be.empty; + expect(user.isAnonymous).to.be.equal(serverAuthUser.isAnonymous); + expect(user.emailVerified).to.be.equal(serverAuthUser.emailVerified); + expect(user.providerData.length).to.eq( + serverAuthUser.providerData.length + ); + expect(user.email).to.equal(serverAuthUser.email); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(numberServerLogins).to.equal(1); + }); + + it('can reload user', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + expect(numberServerLogins).to.equal(1); + }); + + it('can update server based user profile', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.displayName).to.be.null; + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); + let numberServerLogins = 0; + const newDisplayName = 'newName'; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.displayName).to.be.null; + void updateProfile(serverAuthUser, { + displayName: newDisplayName + }); + } + }); + + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.not.be.null; + expect(serverAppAuth.currentUser?.displayName).to.not.be.null; + expect(serverAppAuth.currentUser?.displayName).to.equal(newDisplayName); + } + }); + + it('can sign out of main auth and still use server auth', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.displayName).to.be.null; + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + serverAppAuth = getTestInstanceForServerApp(serverApp); + let numberServerLogins = 0; + onAuthStateChanged(serverAppAuth, serverAuthUser => { + if (serverAuthUser) { + numberServerLogins++; + expect(serverAppAuth).to.not.be.null; + expect(user.uid).to.be.equal(serverAuthUser.uid); + expect(user.displayName).to.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.equal(serverAuthUser); + } + } + }); + + await signOut(auth); + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + + if (serverAppAuth.currentUser) { + await serverAppAuth.currentUser.reload(); + } + + expect(numberServerLogins).to.equal(1); + expect(serverAppAuth).to.not.be.null; + if (serverAppAuth) { + expect(serverAppAuth.currentUser).to.not.be.null; + } + }); +}); From 562d6196e1e829ebab27772dca9f3a60ccd5ed9b Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 29 Jan 2024 15:14:28 -0500 Subject: [PATCH 03/19] [ServerApp] Remove appCheck and installations tokens. Will ad them back later (#7989) Removed the appCheck and installations token parameters in FirebaseServerAppSettings as they won't be part of our initial launch. Additionally, update the the doxgen comments to no longer refer to these parameters, and to inform users that the User refreshToken will not work for User objects signed in with the authIdToken. --- common/api-review/app.api.md | 4 - docs-devsite/app.firebaseserverapp.md | 42 +----- docs-devsite/app.firebaseserverappsettings.md | 54 ++------ docs-devsite/app.md | 6 +- packages/app/src/api.ts | 7 +- packages/app/src/public-types.ts | 121 +++++------------- 6 files changed, 54 insertions(+), 180 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 087318ae27f..f05a07c5499 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -73,18 +73,14 @@ export interface FirebaseOptions { // @public export interface FirebaseServerApp extends FirebaseApp { - appCheckTokenVerified: () => Promise; authIdTokenVerified: () => Promise; - installationTokenVerified: () => Promise; name: string; readonly settings: FirebaseServerAppSettings; } // @public export interface FirebaseServerAppSettings extends FirebaseAppSettings { - appCheckToken?: string; authIdToken?: string; - installationsAuthToken?: string; name?: undefined; releaseOnDeref?: object; } diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index 387e27a9822..b0198ce4951 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -12,7 +12,7 @@ https://github.com/firebase/firebase-js-sdk # FirebaseServerApp interface A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments. -Do not call this constructor directly. Instead, use to create an app. +Do not call this constructor directly. Instead, use [initializeServerApp()](./app.md#initializeserverapp_30ab697) to create an app. Signature: @@ -25,31 +25,15 @@ export interface FirebaseServerApp extends FirebaseApp | Property | Type | Description | | --- | --- | --- | -| [appCheckTokenVerified](./app.firebaseserverapp.md#firebaseserverappappchecktokenverified) | () => Promise<void> | Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately.It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | -| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the verification of the authIdToken provided to has completed.It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | -| [installationTokenVerified](./app.firebaseserverapp.md#firebaseserverappinstallationtokenverified) | () => Promise<void> | Checks to see if the verification of the installationToken provided to has completed.It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation.The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. | -| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. | +| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this promise before invoking getAuth() if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | +| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no getApp operation for FirebaseServerApps, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | | [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | -## FirebaseServerApp.appCheckTokenVerified - -Checks to see if the verification of the appCheckToken provided to has completed. If the optional appCheckToken parameter was omitted then the returned Promise is completed immediately. - -It is recommend that your application awaits this promise before initializing any Firebase products that use AppCheck. The Firebase SDKs will not use App Check tokens that are determined to be invalid or those that have not yet completed validation. - -The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. - -Signature: - -```typescript -appCheckTokenVerified: () => Promise; -``` - ## FirebaseServerApp.authIdTokenVerified -Checks to see if the verification of the authIdToken provided to has completed. +Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed. -It is recommend that your application awaits this promise if an authIdToken was provided during FirebaseServerApp initialization before invoking getAuth(). If an instance of Auth is created before the Auth ID Token is validated, then the token will not be used by that instance of the Auth SDK. +It is recommend that your application awaits this promise before invoking getAuth() if an authIdToken was provided in the FirebaseServerAppSettings. The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. @@ -59,23 +43,9 @@ The returned Promise is completed immediately if the optional authIdToken parame authIdTokenVerified: () => Promise; ``` -## FirebaseServerApp.installationTokenVerified - -Checks to see if the verification of the installationToken provided to has completed. - -It is recommend that your application awaits this promise before initializing any Firebase products that use Firebase Installations. The Firebase SDKs will not use Installation Auth tokens that are determined to be invalid or those that have not yet completed validation. - -The returned Promise is completed immediately if the optional appCheckToken parameter was omitted from FirebaseServerApp initialization. - -Signature: - -```typescript -installationTokenVerified: () => Promise; -``` - ## FirebaseServerApp.name -There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this string will always be empty for FirebaseServerApp instances. +There is no getApp operation for FirebaseServerApps, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Signature: diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 54ba13baa11..4edf299fe8e 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -23,58 +23,26 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | Property | Type | Description | | --- | --- | --- | -| [appCheckToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsappchecktoken) | string | An optional AppCheck token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided.If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check.If the token fails verification then a warning is logged and the token will not be used. | -| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided.Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token.If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. | -| [installationsAuthToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsinstallationsauthtoken) | string | An optional Installation Auth token.If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs.If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations.If the token fails verification then a warning is logged and the token will not be used. | +| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token even before Auth is initialized. The result of the validation can be queried via by the application by invoking . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause 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, otherwise it will fail validation.If the token fails local verification, or if the Auth service has deemed it invalid when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user upon initalization.If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback will be invoked with the User as per standard Auth flows. However, users created via authIdTokens do not have a refresh token and any attempted refresh operation will fail. | | [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | -| [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or.The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted.If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp.If the app 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | - -## FirebaseServerAppSettings.appCheckToken - -An optional AppCheck token. - -If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended if an AppCheck token is provided. - -If the token has been properly verified then the AppCheck token will be automatically used by Firebase SDKs that support App Check. - -If the token fails verification then a warning is logged and the token will not be used. - -Signature: - -```typescript -appCheckToken?: string; -``` +| [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is garbage collected.The intent of this field is to help reduce memory overhead for long-running cloud functions. If provided, the customer's app running in a SSR pass need not worry about 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | ## FirebaseServerAppSettings.authIdToken An optional Auth ID token used to resume a signed in user session from a client runtime environment. -If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be queried via by the application by invoking the FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by authIdTokenVerified is highly recommended if an Auth ID token is provided. +If provided, the FirebaseServerApp instance will work to validate the token even before Auth is initialized. The result of the validation can be queried via by the application by invoking . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided. -Once the token has been properly verified then invoking getAuth() will attempt to automatically sign in a user with the provided Auth ID Token. +Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause 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, otherwise it will fail validation. -If the token fails verification then a warning is logged and Auth SDK will not attempt to sign in a user upon its initalization. +If the token fails local verification, or if the Auth service has deemed it invalid when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user upon initalization. -Signature: - -```typescript -authIdToken?: string; -``` - -## FirebaseServerAppSettings.installationsAuthToken - -An optional Installation Auth token. - -If provided, the FirebaseServerApp instance will work to validate the token. The result of the validation can be monitored by invoking the FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by appCheckTokenVerified is highly recommended before initalization any other Firebase SDKs. - -If the token has been properly verified then the Installation Auth token will be automatically used by Firebase SDKs that support Firebase Installations. - -If the token fails verification then a warning is logged and the token will not be used. +If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback will be invoked with the User as per standard Auth flows. However, users created via authIdTokens do not have a refresh token and any attempted refresh operation will fail. Signature: ```typescript -installationsAuthToken?: string; +authIdToken?: string; ``` ## FirebaseServerAppSettings.name @@ -89,13 +57,13 @@ name?: undefined; ## FirebaseServerAppSettings.releaseOnDeref -An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is collected. or. +An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is garbage collected. -The intent of this field is to help reduce memory overhead for long-running cloud functions executing SSR fulfillment without the customer's app needing to orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp instances may reused if they're identical to a previously generated one that has yet to be deleted. +The intent of this field is to help reduce memory overhead for long-running cloud functions. If provided, the customer's app running in a SSR pass need not worry about FirebaseServerApp cleanup, so long as the reference object is deleted (by falling out of SSR scope, for instance.) -If the object is not provided then the application must clean up the FirebaseServerApp instance through the applicationss own standard mechanisms by invoking deleteApp. +If an object is not provided then the application must clean up the FirebaseServerApp instance by invoking deleteApp. -If the app 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. +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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. Signature: diff --git a/docs-devsite/app.md b/docs-devsite/app.md index b12c29819c9..a36bdc065f7 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -34,7 +34,7 @@ This package coordinates the communication between the different Firebase compon | function(options, ...) | | [initializeApp(options, name)](./app.md#initializeapp_cb2f5e1) | Creates and initializes a [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | | [initializeApp(options, config)](./app.md#initializeapp_079e917) | Creates and initializes a FirebaseApp instance. | -| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | +| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | ## Interfaces @@ -43,7 +43,7 @@ This package coordinates the communication between the different Firebase compon | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | A [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) holds the initialization information for a collection of services.Do not call this constructor directly. Instead, use [initializeApp()](./app.md#initializeapp_cb2f5e1) to create an app. | | [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) | Configuration options given to [initializeApp()](./app.md#initializeapp_cb2f5e1) | | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | Firebase configuration object. Contains a set of parameters required by services in order to successfully communicate with Firebase server APIs and to associate client data with your Firebase project and Firebase application. Typically this object is populated by the Firebase console at project setup. See also: [Learn about the Firebase config object](https://firebase.google.com/docs/web/setup#config-object). | -| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use to create an app. | +| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use [initializeServerApp()](./app.md#initializeserverapp_30ab697) to create an app. | | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) | ## Variables @@ -317,7 +317,7 @@ export declare function initializeApp(options: FirebaseOptions, config?: Firebas Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance. -The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. +The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment. See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index e3812fd7538..c8383d06fd9 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -184,7 +184,8 @@ export function initializeApp( * Creates and initializes a {@link @firebase/app#FirebaseServerApp} instance. * * The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in - * server side rendering environments only. + * server side rendering environments only. Initialization will fail if invoked from a + * browser environment. * * See * {@link @@ -248,8 +249,6 @@ export function initializeServerApp( const nameObj = { authIdToken: _serverAppConfig?.authIdToken, - appCheckToken: _serverAppConfig?.appCheckToken, - installationsAuthToken: _serverAppConfig?.installationsAuthToken, ...appOptions }; @@ -271,7 +270,7 @@ export function initializeServerApp( }; const nameString = '' + hashCode(JSON.stringify(nameObj)); - const existingApp = _serverApps.get(nameString) as FirebaseServerAppImpl; + const existingApp = _serverApps.get(nameString) as FirebaseServerApp; if (existingApp) { // TODO: // 1: Register a new reference to finalization registry. diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 04ba4f0b24c..463bd694a05 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -76,20 +76,18 @@ export interface FirebaseApp { * for a collection of services running in server enviornments. * * Do not call this constructor directly. Instead, use - * {@link (initializeServerAppInstance:1) | initializeServerAppInstance()} to create + * {@link (initializeServerApp:1) | initializeServerApp()} to create * an app. * * @public */ export interface FirebaseServerApp extends FirebaseApp { /** - * Checks to see if the verification of the authIdToken provided to - * @initializeServerApp has completed. + * Checks to see if the local verification of the authIdToken provided to + * {@link (initializeServerApp:1) | initializeServerApp()} has completed. * - * It is recommend that your application awaits this promise if an authIdToken was - * provided during FirebaseServerApp initialization before invoking getAuth(). If an - * instance of Auth is created before the Auth ID Token is validated, then the token - * will not be used by that instance of the Auth SDK. + * It is recommend that your application awaits this promise before invoking getAuth() if an + * authIdToken was provided in the FirebaseServerAppSettings. * * The returned Promise is completed immediately if the optional authIdToken parameter * was omitted from FirebaseServerApp initialization. @@ -97,39 +95,9 @@ export interface FirebaseServerApp extends FirebaseApp { authIdTokenVerified: () => Promise; /** - * Checks to see if the verification of the appCheckToken provided to - * @initializeServerApp has completed. If the optional appCheckToken parameter was - * omitted then the returned Promise is completed immediately. - * - * It is recommend that your application awaits this promise before initializing - * any Firebase products that use AppCheck. The Firebase SDKs will not - * use App Check tokens that are determined to be invalid or those that have not yet - * completed validation. - * - * The returned Promise is completed immediately if the optional appCheckToken - * parameter was omitted from FirebaseServerApp initialization. - */ - appCheckTokenVerified: () => Promise; - - /** - * Checks to see if the verification of the installationToken provided to - * @initializeServerApp has completed. - * - * It is recommend that your application awaits this promise before initializing - * any Firebase products that use Firebase Installations. The Firebase SDKs will not - * use Installation Auth tokens that are determined to be invalid or those that have - * not yet completed validation. - * - * The returned Promise is completed immediately if the optional appCheckToken - * parameter was omitted from FirebaseServerApp initialization. - */ - - installationTokenVerified: () => Promise; - - /** - * There is no get for FirebaseServerApp, so the name is not relevant. However, it's declared here - * so that FirebaseServerApp conforms to the FirebaseApp interface declaration. Internally this - * string will always be empty for FirebaseServerApp instances. + * There is no getApp operation for FirebaseServerApps, so the name is not relevant for + * applications. However, it may be used internally, and is declared here so that + * FirebaseServerApp conforms to the FirebaseApp interface declaration. */ name: string; @@ -224,69 +192,42 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * An optional Auth ID token used to resume a signed in user session from a client * runtime environment. * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be queried via by the application by invoking the - * FirebaseServerApp.authIdTokenVerified(). Awaiting the Promise returned by - * authIdTokenVerified is highly recommended if an Auth ID token is provided. - * - * Once the token has been properly verified then invoking getAuth() will attempt to - * automatically sign in a user with the provided Auth ID Token. + * If provided, the FirebaseServerApp instance will work to validate the token even before Auth + * is initialized. The result of the validation can be queried via by the application by invoking + * {@link (FirebaseServerApp.authIdTokenVerified()}. Awaiting the Promise returned by + * {@link (FirebaseServerApp.authIdTokenVerified()} is highly recommended if an authIdToken token + * is provided. * - * If the token fails verification then a warning is logged and Auth SDK will not - * attempt to sign in a user upon its initalization. - */ - authIdToken?: string; - - /** - * An optional AppCheck token. - * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be monitored by invoking the - * FirebaseServerApp.appCheckTokenVerified(). Awaiting the Promise returned by - * appCheckTokenVerified is highly recommended if an AppCheck token is provided. - * - * If the token has been properly verified then the AppCheck token will be - * automatically used by Firebase SDKs that support App Check. + * Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause + * 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, otherwise it will fail + * validation. * - * If the token fails verification then a warning is logged and the token will not - * be used. - */ - appCheckToken?: string; - - /** - * An optional Installation Auth token. + * If the token fails local verification, or if the Auth service has deemed it invalid when + * the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not + * sign in a user upon initalization. * - * If provided, the FirebaseServerApp instance will work to validate the token. The - * result of the validation can be monitored by invoking the - * FirebaseServerApp.installationTokenVerified(). Awaiting the Promise returned by - * appCheckTokenVerified is highly recommended before initalization any other Firebase - * SDKs. - * - * If the token has been properly verified then the Installation Auth token will be - * automatically used by Firebase SDKs that support Firebase Installations. - * - * If the token fails verification then a warning is logged and the token will not - * be used. + * If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback + * will be invoked with the User as per standard Auth flows. However, users created via + * authIdTokens do not have a refresh token and any attempted refresh operation will fail. */ - installationsAuthToken?: string; + authIdToken?: string; /** * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry * object to monitor the Garbage Collection status of the provided object, and the * Firebase SDK will release its refrence on the FirebaseServerApp instance when the - * provided object is collected. or. + * provided object is garbage collected. * * The intent of this field is to help reduce memory overhead for long-running cloud - * functions executing SSR fulfillment without the customer's app needing to - * orchestrate FirebaseServerApp cleanup. Additionally, prexisting FirebaseServerApp - * instances may reused if they're identical to a previously generated one that has - * yet to be deleted. + * functions. If provided, the customer's app running in a SSR pass need not worry about + * FirebaseServerApp cleanup, so long as the reference object is deleted (by falling out of + * SSR scope, for instance.) * - * If the object is not provided then the application must clean up the - * FirebaseServerApp instance through the applicationss own standard mechanisms by - * invoking deleteApp. + * If an object is not provided then the application must clean up the FirebaseServerApp instance + * by invoking deleteApp. * - * If the app provides an object in this parameter, but the application is + * 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 the Firebase SDK will not be able * to automatically clean up the FirebaseServerApp instance and an error will be From abc9d87c79fd5fbb189822a39187affe9424b37c Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 30 Jan 2024 10:51:06 -0500 Subject: [PATCH 04/19] [ServerApp] Mangle the FirebaseServerApp name (#7993) Mangle the name of the ServerAap based on the hash of the parameters passed in. In addition, return the same instance of app when the same parameters are used. --- packages/app/src/api.test.ts | 73 +++++++++++++++++++--- packages/app/src/api.ts | 31 ++++----- packages/app/src/firebaseServerApp.test.ts | 13 ++-- packages/app/src/firebaseServerApp.ts | 16 +---- 4 files changed, 89 insertions(+), 44 deletions(-) diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 7faabc1e1a7..9a4f1fbe216 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -40,6 +40,7 @@ import { createTestComponent } from '../test/util'; import { Component, ComponentType } from '@firebase/component'; import { Logger } from '@firebase/logger'; import { FirebaseAppImpl } from './firebaseApp'; +import { FirebaseServerAppImpl } from './firebaseServerApp'; import { isBrowser } from '@firebase/util'; declare module '@firebase/component' { @@ -184,7 +185,7 @@ describe('API tests', () => { }); describe('initializeServerApp', () => { - it('creates FirebaseServerApp with options', () => { + it('creates FirebaseServerApp fails in browsers.', () => { if (isBrowser()) { const options = { apiKey: 'APIKEY' @@ -196,7 +197,7 @@ describe('API tests', () => { } }); - it('creates FirebaseServerApp with options', () => { + it('creates FirebaseServerApp with options', async () => { if (isBrowser()) { // FirebaseServerApp isn't supported for execution in browser enviornments. return; @@ -211,9 +212,10 @@ describe('API tests', () => { const app = initializeServerApp(options, serverAppSettings); expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.false; + await deleteApp(app); }); - it('creates FirebaseServerApp with automaticDataCollectionEnabled', () => { + it('creates FirebaseServerApp with automaticDataCollectionEnabled', async () => { if (isBrowser()) { // FirebaseServerApp isn't supported for execution in browser enviornments. return; @@ -230,9 +232,10 @@ describe('API tests', () => { const app = initializeServerApp(options, serverAppSettings); expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.true; + await deleteApp(app); }); - it('creates FirebaseServerApp with releaseOnDeref', () => { + it('creates FirebaseServerApp with releaseOnDeref', async () => { if (isBrowser()) { // FirebaseServerApp isn't supported for execution in browser enviornments. return; @@ -247,9 +250,10 @@ describe('API tests', () => { const app = initializeServerApp(options, serverAppSettings); expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.false; + await deleteApp(app); }); - it('creates FirebaseServerApp with FirebaseApp', () => { + it('creates FirebaseServerApp with FirebaseApp', async () => { if (isBrowser()) { // FirebaseServerApp isn't supported for execution in browser enviornments. return; @@ -266,12 +270,65 @@ describe('API tests', () => { automaticDataCollectionEnabled: false }; - const serverApp = initializeServerApp(standardApp, serverAppSettings); - expect(serverApp).to.not.equal(null); - expect(serverApp.options.apiKey).to.equal('test1'); + const app = initializeServerApp(standardApp, serverAppSettings); + expect(app).to.not.equal(null); + expect(app.options.apiKey).to.equal('test1'); + await deleteApp(app); }); }); + it('create similar FirebaseServerApps does not return the same object', async () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { apiKey: 'APIKEY' }; + const serverAppSettingsOne: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const serverAppSettingsTwo: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false + }; + + const appOne = initializeServerApp(options, serverAppSettingsOne); + expect(appOne).to.not.equal(null); + expect(appOne.automaticDataCollectionEnabled).to.be.false; + const appTwo = initializeServerApp(options, serverAppSettingsTwo); + expect(appTwo).to.not.equal(null); + expect(appTwo).to.not.equal(appOne); + await deleteApp(appOne); + await deleteApp(appTwo); + }); + + it('create duplicate FirebaseServerApps returns the same object', async () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { apiKey: 'APIKEY' }; + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const appOne = initializeServerApp(options, serverAppSettings); + expect(appOne).to.not.equal(null); + expect(appOne.automaticDataCollectionEnabled).to.be.false; + const appTwo = initializeServerApp(options, serverAppSettings); + expect(appTwo).to.not.equal(null); + expect(appTwo).to.equal(appOne); + await deleteApp(appOne); + + // TODO: When Reference Counting works, update test. The following line should be false + // until and the app should be deleted a second time. + expect((appOne as FirebaseServerAppImpl).isDeleted).to.be.true; + // await deleteApp(appTwo); + }); + describe('getApp', () => { it('retrieves DEFAULT App', () => { const app = initializeApp({}); diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index c8383d06fd9..246c06b144f 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -235,11 +235,6 @@ export function initializeServerApp( throw ERROR_FACTORY.create(AppError.INVALID_SERVER_APP_ENVIRONMENT); } - const serverAppSettings: FirebaseServerAppSettings = { - automaticDataCollectionEnabled: false, - ..._serverAppConfig - }; - let appOptions: FirebaseOptions; if (_isFirebaseApp(_options)) { appOptions = _options.options; @@ -247,10 +242,23 @@ export function initializeServerApp( appOptions = _options; } + // Mangle the ap name based on a hash of the FirebaseServerAppSettings, and FirebaseOptions + // objects and the authIdToken, if provided. const nameObj = { - authIdToken: _serverAppConfig?.authIdToken, + _serverAppConfig, ...appOptions }; + const hashCode = (s: string): number => { + return [...s].reduce( + (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, + 0 + ); + }; + + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + ..._serverAppConfig + }; if (serverAppSettings.releaseOnDeref !== undefined) { if (typeof FinalizationRegistry === 'undefined') { @@ -261,14 +269,6 @@ export function initializeServerApp( } } - // TODO: move this into util.js. - const hashCode = (s: string): number => { - return [...s].reduce( - (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, - 0 - ); - }; - const nameString = '' + hashCode(JSON.stringify(nameObj)); const existingApp = _serverApps.get(nameString) as FirebaseServerApp; if (existingApp) { @@ -286,9 +286,12 @@ export function initializeServerApp( const newApp = new FirebaseServerAppImpl( appOptions, serverAppSettings, + nameString, container ); + _serverApps.set(nameString, newApp); + return newApp; } diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index a95f7e42d8a..c31d603b0e1 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -35,6 +35,7 @@ describe('FirebaseServerApp', () => { const firebaseServerAppImpl = new FirebaseServerAppImpl( options, serverAppSettings, + 'testName', new ComponentContainer('test') ); @@ -55,6 +56,7 @@ describe('FirebaseServerApp', () => { const firebaseServerAppImpl = new FirebaseServerAppImpl( options, serverAppSettings, + 'testName', new ComponentContainer('test') ); @@ -75,6 +77,7 @@ describe('FirebaseServerApp', () => { const firebaseServerAppImpl = new FirebaseServerAppImpl( options, serverAppSettings, + 'testName', new ComponentContainer('test') ); @@ -96,6 +99,7 @@ describe('FirebaseServerApp', () => { const app = new FirebaseServerAppImpl( options, serverAppSettings, + 'testName', new ComponentContainer('test') ); @@ -122,6 +126,7 @@ describe('FirebaseServerApp', () => { const app = new FirebaseServerAppImpl( options, serverAppSettings, + 'testName', new ComponentContainer('test') ); @@ -131,13 +136,5 @@ describe('FirebaseServerApp', () => { expect(() => app.authIdTokenVerified()).throws( 'Firebase Server App has been deleted' ); - - expect(() => app.appCheckTokenVerified()).throws( - 'Firebase Server App has been deleted' - ); - - expect(() => app.installationTokenVerified()).throws( - 'Firebase Server App has been deleted' - ); }); }); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 7c51511d087..0135ff8897a 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -36,6 +36,7 @@ export class FirebaseServerAppImpl constructor( options: FirebaseOptions | FirebaseAppImpl, serverConfig: FirebaseServerAppSettings, + name: string, container: ComponentContainer ) { // Build configuration parameters for the FirebaseAppImpl base class. @@ -46,7 +47,7 @@ export class FirebaseServerAppImpl // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. const config: Required = { - name: '', + name, automaticDataCollectionEnabled }; @@ -55,7 +56,6 @@ export class FirebaseServerAppImpl super(options as FirebaseOptions, config, container); } else { const appImpl: FirebaseAppImpl = options as FirebaseAppImpl; - super(appImpl.options, config, container); } @@ -94,18 +94,6 @@ export class FirebaseServerAppImpl return Promise.resolve(); } - appCheckTokenVerified(): Promise { - this.checkDestroyed(); - // TODO - return Promise.resolve(); - } - - installationTokenVerified(): Promise { - this.checkDestroyed(); - // TODO - return Promise.resolve(); - } - /** * This function will throw an Error if the App has already been deleted - * use before performing API actions on the App. From 6df26bb6aad94d551827725d8cae195f7be85fc8 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 2 Feb 2024 10:40:55 -0500 Subject: [PATCH 05/19] [ServerApp] RefCount FirebaseServerApps (#8000) Update the `FirebaseServerApp` creation to return the same object if an existing object exists with the same configuration. However, the `deleteOnDeref` field is ignored when detecting duplicate apps, since that object reference could vary across multiple SSR rendering passes. The hope is that a `FirebaseServerApp` instance awaiting deletion from a the `deleteOnDeref` feature maybe be reused if another SSR pass occurs in rapid succession, there by speeding up the SSR code. --- packages/app/src/api.test.ts | 74 ++++++++++++++++++++++++--- packages/app/src/api.ts | 45 +++++++++------- packages/app/src/firebaseServerApp.ts | 51 +++++++++++++----- 3 files changed, 134 insertions(+), 36 deletions(-) diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 9a4f1fbe216..6dd12b82798 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -213,6 +213,7 @@ describe('API tests', () => { expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.false; await deleteApp(app); + expect((app as FirebaseServerAppImpl).isDeleted).to.be.true; }); it('creates FirebaseServerApp with automaticDataCollectionEnabled', async () => { @@ -233,6 +234,7 @@ describe('API tests', () => { expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.true; await deleteApp(app); + expect((app as FirebaseServerAppImpl).isDeleted).to.be.true; }); it('creates FirebaseServerApp with releaseOnDeref', async () => { @@ -251,6 +253,7 @@ describe('API tests', () => { expect(app).to.not.equal(null); expect(app.automaticDataCollectionEnabled).to.be.false; await deleteApp(app); + expect((app as FirebaseServerAppImpl).isDeleted).to.be.true; }); it('creates FirebaseServerApp with FirebaseApp', async () => { @@ -274,6 +277,7 @@ describe('API tests', () => { expect(app).to.not.equal(null); expect(app.options.apiKey).to.equal('test1'); await deleteApp(app); + expect((app as FirebaseServerAppImpl).isDeleted).to.be.true; }); }); @@ -285,8 +289,7 @@ describe('API tests', () => { const options = { apiKey: 'APIKEY' }; const serverAppSettingsOne: FirebaseServerAppSettings = { - automaticDataCollectionEnabled: false, - releaseOnDeref: options + automaticDataCollectionEnabled: true }; const serverAppSettingsTwo: FirebaseServerAppSettings = { @@ -295,12 +298,42 @@ describe('API tests', () => { const appOne = initializeServerApp(options, serverAppSettingsOne); expect(appOne).to.not.equal(null); - expect(appOne.automaticDataCollectionEnabled).to.be.false; + expect(appOne.automaticDataCollectionEnabled).to.be.true; const appTwo = initializeServerApp(options, serverAppSettingsTwo); expect(appTwo).to.not.equal(null); + expect(appTwo.automaticDataCollectionEnabled).to.be.false; expect(appTwo).to.not.equal(appOne); await deleteApp(appOne); await deleteApp(appTwo); + expect((appOne as FirebaseServerAppImpl).isDeleted).to.be.true; + expect((appTwo as FirebaseServerAppImpl).isDeleted).to.be.true; + }); + + it('create FirebaseServerApps with varying deleteOnDeref, and they still return same object ', async () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { apiKey: 'APIKEY' }; + const serverAppSettingsOne: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false + }; + + const serverAppSettingsTwo: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const appOne = initializeServerApp(options, serverAppSettingsOne); + expect(appOne).to.not.equal(null); + expect(appOne.automaticDataCollectionEnabled).to.be.false; + const appTwo = initializeServerApp(options, serverAppSettingsTwo); + expect(appTwo).to.not.equal(null); + expect(appTwo.automaticDataCollectionEnabled).to.be.false; + expect(appTwo).to.equal(appOne); + await deleteApp(appOne); + await deleteApp(appTwo); }); it('create duplicate FirebaseServerApps returns the same object', async () => { @@ -322,11 +355,40 @@ describe('API tests', () => { expect(appTwo).to.not.equal(null); expect(appTwo).to.equal(appOne); await deleteApp(appOne); + await deleteApp(appTwo); + }); - // TODO: When Reference Counting works, update test. The following line should be false - // until and the app should be deleted a second time. + it('deleting FirebaseServerApps is ref counted', async () => { + if (isBrowser()) { + // FirebaseServerApp isn't supported for execution in browser enviornments. + return; + } + + const options = { apiKey: 'APIKEY' }; + const serverAppSettings: FirebaseServerAppSettings = { + automaticDataCollectionEnabled: false, + releaseOnDeref: options + }; + + const appOne = initializeServerApp(options, serverAppSettings); + expect((appOne as FirebaseServerAppImpl).refCount).to.equal(1); + + const appTwo = initializeServerApp(options, serverAppSettings); + expect(appTwo).to.equal(appOne); + expect((appOne as FirebaseServerAppImpl).refCount).to.equal(2); + expect((appTwo as FirebaseServerAppImpl).refCount).to.equal(2); + + await deleteApp(appOne); + expect((appOne as FirebaseServerAppImpl).refCount).to.equal(1); + expect((appTwo as FirebaseServerAppImpl).refCount).to.equal(1); + expect((appOne as FirebaseServerAppImpl).isDeleted).to.be.false; + expect((appTwo as FirebaseServerAppImpl).isDeleted).to.be.false; + + await deleteApp(appTwo); + expect((appOne as FirebaseServerAppImpl).refCount).to.equal(0); + expect((appTwo as FirebaseServerAppImpl).refCount).to.equal(0); expect((appOne as FirebaseServerAppImpl).isDeleted).to.be.true; - // await deleteApp(appTwo); + expect((appTwo as FirebaseServerAppImpl).isDeleted).to.be.true; }); describe('getApp', () => { diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index 246c06b144f..e4b1a6db341 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -235,6 +235,10 @@ export function initializeServerApp( throw ERROR_FACTORY.create(AppError.INVALID_SERVER_APP_ENVIRONMENT); } + if (_serverAppConfig.automaticDataCollectionEnabled === undefined) { + _serverAppConfig.automaticDataCollectionEnabled = false; + } + let appOptions: FirebaseOptions; if (_isFirebaseApp(_options)) { appOptions = _options.options; @@ -242,12 +246,18 @@ export function initializeServerApp( appOptions = _options; } - // Mangle the ap name based on a hash of the FirebaseServerAppSettings, and FirebaseOptions - // objects and the authIdToken, if provided. + // Build an app name based on a hash of the configuration options. const nameObj = { - _serverAppConfig, + ..._serverAppConfig, ...appOptions }; + + // However, Do not mangle the name based on releaseOnDeref, since it will vary between the + // construction of FirebaseServerApp instances. For example, if the object is the request headers. + if (nameObj.releaseOnDeref !== undefined) { + delete nameObj.releaseOnDeref; + } + const hashCode = (s: string): number => { return [...s].reduce( (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, @@ -255,12 +265,7 @@ export function initializeServerApp( ); }; - const serverAppSettings: FirebaseServerAppSettings = { - automaticDataCollectionEnabled: false, - ..._serverAppConfig - }; - - if (serverAppSettings.releaseOnDeref !== undefined) { + if (_serverAppConfig.releaseOnDeref !== undefined) { if (typeof FinalizationRegistry === 'undefined') { throw ERROR_FACTORY.create( AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED, @@ -272,9 +277,9 @@ export function initializeServerApp( const nameString = '' + hashCode(JSON.stringify(nameObj)); const existingApp = _serverApps.get(nameString) as FirebaseServerApp; if (existingApp) { - // TODO: - // 1: Register a new reference to finalization registry. - // 2: Incrememnt reference count. + (existingApp as FirebaseServerAppImpl).incRefCount( + _serverAppConfig.releaseOnDeref + ); return existingApp; } @@ -285,7 +290,7 @@ export function initializeServerApp( const newApp = new FirebaseServerAppImpl( appOptions, - serverAppSettings, + _serverAppConfig, nameString, container ); @@ -362,16 +367,20 @@ export function getApps(): FirebaseApp[] { * @public */ export async function deleteApp(app: FirebaseApp): Promise { - let foundApp = false; + let cleanupProviders = false; const name = app.name; if (_apps.has(name)) { - foundApp = true; + cleanupProviders = true; _apps.delete(name); } else if (_serverApps.has(name)) { - foundApp = true; - _serverApps.delete(name); + const firebaseServerApp = app as FirebaseServerAppImpl; + if (firebaseServerApp.decRefCount() <= 0) { + _serverApps.delete(name); + cleanupProviders = true; + } } - if (foundApp) { + + if (cleanupProviders) { await Promise.all( (app as FirebaseAppImpl).container .getProviders() diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 0135ff8897a..a94dc7bd760 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -32,6 +32,7 @@ export class FirebaseServerAppImpl { private readonly _serverConfig: FirebaseServerAppSettings; private _finalizationRegistry: FinalizationRegistry; + private _refCount: number; constructor( options: FirebaseOptions | FirebaseAppImpl, @@ -65,22 +66,48 @@ export class FirebaseServerAppImpl ...serverConfig }; - this._finalizationRegistry = new FinalizationRegistry( - this.automaticCleanup - ); + this._finalizationRegistry = new FinalizationRegistry(() => { + this.automaticCleanup(); + }); - if (this._serverConfig.releaseOnDeref !== undefined) { - this._finalizationRegistry.register( - this._serverConfig.releaseOnDeref, - this - ); - this._serverConfig.releaseOnDeref = undefined; // Don't keep a strong reference to the object. + this._refCount = 0; + this.incRefCount(this._serverConfig.releaseOnDeref); + + // Do not retain a hard reference to the dref object, otherwise the FinalizationRegisry + // will never trigger. + this._serverConfig.releaseOnDeref = undefined; + serverConfig.releaseOnDeref = undefined; + } + + get refCount(): number { + return this._refCount; + } + + // Increment the reference count of this server app. If an object is provided, register it + // with the finalization registry. + incRefCount(obj: object | undefined): void { + if (this.isDeleted) { + return; + } + this._refCount++; + if (obj !== undefined) { + this._finalizationRegistry.register(obj, this); + } + } + + // Decrement the reference count. + decRefCount(): number { + if (this.isDeleted) { + return 0; } + return --this._refCount; } - private automaticCleanup(serverApp: FirebaseServerAppImpl): void { - // TODO: implement reference counting. - void deleteApp(serverApp); + // Invoked by the FinalizationRegistry callback to note that this app should go through its + // reference counts and delete itself if no reference count remain. The coordinating logic that + // handles this is in deleteApp(...). + private automaticCleanup(): void { + void deleteApp(this); } get settings(): FirebaseServerAppSettings { From 92c655a48b8e5af3edf8a1cb4b3fc7c02cd335b6 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 6 Feb 2024 09:57:29 -0500 Subject: [PATCH 06/19] revert two FirebaseServerApps members to private --- packages/app/src/firebaseApp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/firebaseApp.ts b/packages/app/src/firebaseApp.ts index 70c8c8ce5b2..4f36e818f96 100644 --- a/packages/app/src/firebaseApp.ts +++ b/packages/app/src/firebaseApp.ts @@ -37,9 +37,9 @@ export class FirebaseAppImpl implements FirebaseApp { * Updating automaticDataCollectionEnabled on the App instance will not change its value in _config. */ private readonly _config: Required; - protected _automaticDataCollectionEnabled: boolean; + private _automaticDataCollectionEnabled: boolean; protected _isDeleted = false; - protected readonly _container: ComponentContainer; + private readonly _container: ComponentContainer; constructor( options: FirebaseOptions, From 5b7d7efb4e25e4258c7f56ccb7853a5610e04d34 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 13 Feb 2024 09:55:25 -0500 Subject: [PATCH 07/19] [SeverApp] Restrict certain auth operations (#8015) Updates FirebaseServerApp implementation in Auth to prevent operations that would change the currently logged in user. The user should be that of the authIdToken provided to FirebaseServerApp only. Note: some of the method implementations currently reside in browser-only files. I added safe guards to these methods even though FirebaseServerApp is not supported in browser enviornments. These guards protect us in case the methods are later adapted to other environments and/or migrated to other files that are not browser-only. The changes to the browser implementations produce little overhead, so I thought that safety first was the correct call here. --- docs-devsite/auth.auth.md | 2 + docs-devsite/auth.md | 40 +++-- docs-devsite/auth.user.md | 2 + packages/auth/src/core/auth/auth_impl.ts | 17 +- packages/auth/src/core/index.ts | 10 +- .../auth/src/core/strategies/anonymous.ts | 11 ++ .../auth/src/core/strategies/credential.ts | 14 ++ .../auth/src/core/strategies/custom_token.ts | 12 +- .../src/core/strategies/email_and_password.ts | 20 ++- .../auth/src/core/strategies/email_link.ts | 11 ++ packages/auth/src/core/user/account_info.ts | 18 +- packages/auth/src/core/user/reauthenticate.ts | 7 + packages/auth/src/core/user/user_impl.ts | 8 +- packages/auth/src/model/public_types.ts | 7 + .../src/platform_browser/strategies/phone.ts | 30 +++- .../src/platform_browser/strategies/popup.ts | 17 +- .../platform_browser/strategies/redirect.ts | 31 +++- .../flows/firebaseserverapp.test.ts | 167 ++++++++++++++++-- 18 files changed, 377 insertions(+), 47 deletions(-) diff --git a/docs-devsite/auth.auth.md b/docs-devsite/auth.auth.md index b089a9b09bd..cbbc7a9ceb0 100644 --- a/docs-devsite/auth.auth.md +++ b/docs-devsite/auth.auth.md @@ -265,6 +265,8 @@ auth.setPersistence(browserSessionPersistence); Signs out the current user. This does not automatically revoke the user's ID token. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript diff --git a/docs-devsite/auth.md b/docs-devsite/auth.md index 4449c8dc1a4..10f153f53fe 100644 --- a/docs-devsite/auth.md +++ b/docs-devsite/auth.md @@ -381,6 +381,8 @@ On successful creation of the user account, this user will also be signed in to User account creation can fail if the account already exists or the password is invalid. +This method is not supported on [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Note: The email address acts as a unique identifier for the user and enables an email-based password reset. This function will create a new user account and set the initial user password. Signature: @@ -451,7 +453,7 @@ Returns a [UserCredential](./auth.usercredential.md#usercredential_interface) fr If sign-in succeeded, returns the signed in user. If sign-in was unsuccessful, fails with an error. If no redirect operation was called, returns `null`. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -728,7 +730,7 @@ Changes the type of persistence on the [Auth](./auth.auth.md#auth_interface) ins This makes it easy for a user signing in to specify whether their session should be remembered or not. It also makes it easier to never persist the `Auth` state for applications that are shared by other users or have sensitive data. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -763,6 +765,8 @@ Asynchronously signs in as an anonymous user. If there is already an anonymous user signed in, that user will be returned; otherwise, a new anonymous user identity will be created and returned. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -785,6 +789,8 @@ Asynchronously signs in with the given credentials. An [AuthProvider](./auth.authprovider.md#authprovider_interface) can be used to generate the credential. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -810,6 +816,8 @@ Custom tokens are used to integrate Firebase Auth with existing auth systems, an Fails with an error if the token is invalid, expired, or not accepted by the Firebase Auth service. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -833,6 +841,8 @@ Asynchronously signs in using an email and password. Fails with an error if the email address and password do not match. When \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, this method fails with "auth/invalid-credential" in case of an invalid email/password. +This method is not supported on [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Note: The user's password is NOT the password used to access the user's email account. The email address serves as a unique identifier for the user, and the password is used to access the user's account in your Firebase project. See also: [createUserWithEmailAndPassword()](./auth.md#createuserwithemailandpassword_21ad33b). Signature: @@ -861,6 +871,8 @@ If no link is passed, the link is inferred from the current URL. Fails with an error if the email address is invalid or OTP in email link expires. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Note: Confirm the link is a sign-in email link before calling this method firebase.auth.Auth.isSignInWithEmailLink. Signature: @@ -913,7 +925,7 @@ This method sends a code via SMS to the given phone number, and returns a [Confi For abuse prevention, this method also requires a [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface). This SDK includes a reCAPTCHA-based implementation, [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class). This function can work on other platforms that do not support the [RecaptchaVerifier](./auth.recaptchaverifier.md#recaptchaverifier_class) (like React Native), but you need to use a third-party [ApplicationVerifier](./auth.applicationverifier.md#applicationverifier_interface) implementation. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -951,7 +963,7 @@ Authenticates a Firebase client using a popup-based OAuth authentication flow. If succeeds, returns the signed in user along with the provider's credential. If sign in was unsuccessful, returns an error object containing additional information about the error. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -993,7 +1005,7 @@ Authenticates a Firebase client using a full-page redirect flow. To handle the results and errors for this operation, refer to [getRedirectResult()](./auth.md#getredirectresult_c35dc1f). Follow the [best practices](https://firebase.google.com/docs/auth/web/redirect-best-practices) when using [signInWithRedirect()](./auth.md#signinwithredirect_770f816). -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -1045,6 +1057,8 @@ const operationType = result.operationType; Signs out the current user. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -1071,6 +1085,8 @@ This will trigger [onAuthStateChanged()](./auth.md#onauthstatechanged_b0d07ab) a The operation fails with an error if the user to be updated belongs to a different Firebase project. +This method is not supported by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -1347,7 +1363,7 @@ Links the [OAuthProvider](./auth.oauthprovider.md#oauthprovider_class) to the us To handle the results and errors for this operation, refer to [getRedirectResult()](./auth.md#getredirectresult_c35dc1f). Follow the [best practices](https://firebase.google.com/docs/auth/web/redirect-best-practices) when using [linkWithRedirect()](./auth.md#linkwithredirect_41c0b31). -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -1411,6 +1427,8 @@ Re-authenticates a user using a fresh credential. Use before operations such as [updatePassword()](./auth.md#updatepassword_6df673e) that require tokens from recent sign-in attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error or a `TOKEN_EXPIRED` error. +This method is not supported on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript @@ -1434,7 +1452,7 @@ Re-authenticates a user using a fresh phone credential. Use before operations such as [updatePassword()](./auth.md#updatepassword_6df673e) that require tokens from recent sign-in attempts. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -1460,7 +1478,7 @@ Reauthenticates the current user with the specified [OAuthProvider](./auth.oauth If the reauthentication is successful, the returned result will contain the user and the provider's credential. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -1498,7 +1516,7 @@ Reauthenticates the current user with the specified [OAuthProvider](./auth.oauth To handle the results and errors for this operation, refer to [getRedirectResult()](./auth.md#getredirectresult_c35dc1f). Follow the [best practices](https://firebase.google.com/docs/auth/web/redirect-best-practices) when using [reauthenticateWithRedirect()](./auth.md#reauthenticatewithredirect_41c0b31). -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or with [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: @@ -1630,6 +1648,8 @@ Updates the user's email address. An email will be sent to the original email address (if it was set) that allows to revoke the email address change, in order to protect them from account hijacking. +This method is not supported on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Important: this is a security sensitive operation that requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call [reauthenticateWithCredential()](./auth.md#reauthenticatewithcredential_60f8043). Signature: @@ -1676,7 +1696,7 @@ Promise<void> Updates the user's phone number. -This method does not work in a Node.js environment. +This method does not work in a Node.js environment or on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). Signature: diff --git a/docs-devsite/auth.user.md b/docs-devsite/auth.user.md index a774c914f34..f28c7eeb3bc 100644 --- a/docs-devsite/auth.user.md +++ b/docs-devsite/auth.user.md @@ -121,6 +121,8 @@ Deletes and signs out the user. Important: this is a security-sensitive operation that requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call one of the reauthentication methods like [reauthenticateWithCredential()](./auth.md#reauthenticatewithcredential_60f8043). +This method is not supported on any [User](./auth.user.md#user_interface) signed in by [Auth](./auth.auth.md#auth_interface) instances created with a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface). + Signature: ```typescript diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 55f474eda9e..893854852d8 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -62,7 +62,7 @@ import { PersistenceUserManager } from '../persistence/persistence_user_manager'; import { _reloadWithoutSaving } from '../user/reload'; -import { _assert } from '../util/assert'; +import { _assert, _createError } from '../util/assert'; import { _getInstance } from '../util/instantiator'; import { _getUserLanguage } from '../util/navigator'; import { _getClientVersion } from '../util/version'; @@ -354,6 +354,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } async updateCurrentUser(userExtern: User | null): Promise { + if (_isFirebaseServerApp(this.app)) { + return Promise.reject( + _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } // The public updateCurrentUser method needs to make a copy of the user, // and also check that the project matches const user = userExtern @@ -395,6 +400,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } async signOut(): Promise { + if (_isFirebaseServerApp(this.app)) { + return Promise.reject( + _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } // Run first, to block _setRedirectUser() if any callbacks fail. await this.beforeStateQueue.runMiddleware(null); // Clear the redirect user when signOut is called @@ -408,6 +418,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } setPersistence(persistence: Persistence): Promise { + if (_isFirebaseServerApp(this.app)) { + return Promise.reject( + _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } return this.queue(async () => { await this.assertedPersistence.setPersistence(_getInstance(persistence)); }); diff --git a/packages/auth/src/core/index.ts b/packages/auth/src/core/index.ts index 506d7623f25..43b1adb4bb9 100644 --- a/packages/auth/src/core/index.ts +++ b/packages/auth/src/core/index.ts @@ -46,7 +46,8 @@ export { * remembered or not. It also makes it easier to never persist the `Auth` state for applications * that are shared by other users or have sensitive data. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -223,6 +224,9 @@ export function useDeviceLanguage(auth: Auth): void { * The operation fails with an error if the user to be updated belongs to a different Firebase * project. * + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * @param auth - The {@link Auth} instance. * @param user - The new {@link User}. * @@ -237,6 +241,10 @@ export function updateCurrentUser( /** * Signs out the current user. * + * @remarks + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * @param auth - The {@link Auth} instance. * * @public diff --git a/packages/auth/src/core/strategies/anonymous.ts b/packages/auth/src/core/strategies/anonymous.ts index cd2a40f8240..e15b738d4d6 100644 --- a/packages/auth/src/core/strategies/anonymous.ts +++ b/packages/auth/src/core/strategies/anonymous.ts @@ -21,6 +21,9 @@ import { UserInternal } from '../../model/user'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; import { OperationType } from '../../model/enums'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; /** * Asynchronously signs in as an anonymous user. @@ -29,11 +32,19 @@ import { OperationType } from '../../model/enums'; * If there is already an anonymous user signed in, that user will be returned; otherwise, a * new anonymous user identity will be created and returned. * + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * @param auth - The {@link Auth} instance. * * @public */ export async function signInAnonymously(auth: Auth): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); await authInternal._initializationPromise; if (authInternal.currentUser?.isAnonymous) { diff --git a/packages/auth/src/core/strategies/credential.ts b/packages/auth/src/core/strategies/credential.ts index 00aa919f047..d429d3eb11b 100644 --- a/packages/auth/src/core/strategies/credential.ts +++ b/packages/auth/src/core/strategies/credential.ts @@ -27,12 +27,20 @@ import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; import { getModularInstance } from '@firebase/util'; import { OperationType } from '../../model/enums'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; export async function _signInWithCredential( auth: AuthInternal, credential: AuthCredential, bypassAuthState = false ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const operationType = OperationType.SIGN_IN; const response = await _processCredentialSavingMfaContextIfNecessary( auth, @@ -57,6 +65,9 @@ export async function _signInWithCredential( * @remarks * An {@link AuthProvider} can be used to generate the credential. * + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * @param auth - The {@link Auth} instance. * @param credential - The auth credential. * @@ -99,6 +110,9 @@ export async function linkWithCredential( * attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error * or a `TOKEN_EXPIRED` error. * + * This method is not supported on any {@link User} signed in by {@link Auth} instances + * created with a {@link @firebase/app#FirebaseServerApp}. + * * @param user - The user. * @param credential - The auth credential. * diff --git a/packages/auth/src/core/strategies/custom_token.ts b/packages/auth/src/core/strategies/custom_token.ts index 6d1e1b36fe0..56dc6741453 100644 --- a/packages/auth/src/core/strategies/custom_token.ts +++ b/packages/auth/src/core/strategies/custom_token.ts @@ -22,7 +22,9 @@ import { IdTokenResponse } from '../../model/id_token'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; import { OperationType } from '../../model/enums'; - +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; /** * Asynchronously signs in using a custom token. * @@ -34,6 +36,9 @@ import { OperationType } from '../../model/enums'; * * Fails with an error if the token is invalid, expired, or not accepted by the Firebase Auth service. * + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * @param auth - The {@link Auth} instance. * @param customToken - The custom token to sign in with. * @@ -43,6 +48,11 @@ export async function signInWithCustomToken( auth: Auth, customToken: string ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); const response: IdTokenResponse = await getIdTokenResponse(authInternal, { token: customToken, diff --git a/packages/auth/src/core/strategies/email_and_password.ts b/packages/auth/src/core/strategies/email_and_password.ts index 86855dfa8b2..4e3d1304507 100644 --- a/packages/auth/src/core/strategies/email_and_password.ts +++ b/packages/auth/src/core/strategies/email_and_password.ts @@ -29,7 +29,7 @@ import { signUp, SignUpRequest } from '../../api/authentication/sign_up'; import { MultiFactorInfoImpl } from '../../mfa/mfa_info'; import { EmailAuthProvider } from '../providers/email'; import { UserCredentialImpl } from '../user/user_credential_impl'; -import { _assert } from '../util/assert'; +import { _assert, _createError } from '../util/assert'; import { _setActionCodeSettingsOnRequest } from './action_code_settings'; import { signInWithCredential } from './credential'; import { _castAuth } from '../auth/auth_impl'; @@ -39,6 +39,7 @@ import { OperationType } from '../../model/enums'; import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { IdTokenResponse } from '../../model/id_token'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; +import { _isFirebaseServerApp } from '@firebase/app'; /** * Updates the password policy cached in the {@link Auth} instance if a policy is already @@ -253,6 +254,9 @@ export async function verifyPasswordResetCode( * * User account creation can fail if the account already exists or the password is invalid. * + * This method is not supported on {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * Note: The email address acts as a unique identifier for the user and enables an email-based * password reset. This function will create a new user account and set the initial user password. * @@ -267,6 +271,11 @@ export async function createUserWithEmailAndPassword( email: string, password: string ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); const request: SignUpRequest = { returnSecureToken: true, @@ -308,10 +317,14 @@ export async function createUserWithEmailAndPassword( * When [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, * this method fails with "auth/invalid-credential" in case of an invalid email/password. * + * This method is not supported on {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * Note: The user's password is NOT the password used to access the user's email account. The * email address serves as a unique identifier for the user, and the password is used to access * the user's account in your Firebase project. See also: {@link createUserWithEmailAndPassword}. * + * * @param auth - The {@link Auth} instance. * @param email - The users email address. * @param password - The users password. @@ -323,6 +336,11 @@ export function signInWithEmailAndPassword( email: string, password: string ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } return signInWithCredential( getModularInstance(auth), EmailAuthProvider.credential(email, password) diff --git a/packages/auth/src/core/strategies/email_link.ts b/packages/auth/src/core/strategies/email_link.ts index 55f95226656..f9dd75bbe26 100644 --- a/packages/auth/src/core/strategies/email_link.ts +++ b/packages/auth/src/core/strategies/email_link.ts @@ -34,6 +34,8 @@ import { getModularInstance } from '@firebase/util'; import { _castAuth } from '../auth/auth_impl'; import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; /** * Sends a sign-in email link to the user with the specified email. @@ -131,6 +133,9 @@ export function isSignInWithEmailLink(auth: Auth, emailLink: string): boolean { * * Fails with an error if the email address is invalid or OTP in email link expires. * + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. + * * Note: Confirm the link is a sign-in email link before calling this method firebase.auth.Auth.isSignInWithEmailLink. * * @example @@ -154,6 +159,7 @@ export function isSignInWithEmailLink(auth: Auth, emailLink: string): boolean { * } * ``` * + * * @param auth - The {@link Auth} instance. * @param email - The user's email address. * @param emailLink - The link sent to the user's email address. @@ -165,6 +171,11 @@ export async function signInWithEmailLink( email: string, emailLink?: string ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authModular = getModularInstance(auth); const credential = EmailAuthProvider.credentialWithLink( email, diff --git a/packages/auth/src/core/user/account_info.ts b/packages/auth/src/core/user/account_info.ts index 3f061630435..d59669f153c 100644 --- a/packages/auth/src/core/user/account_info.ts +++ b/packages/auth/src/core/user/account_info.ts @@ -26,6 +26,9 @@ import { UserInternal } from '../../model/user'; import { _logoutIfInvalidated } from './invalidation'; import { getModularInstance } from '@firebase/util'; import { ProviderId } from '../../model/enums'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; /** * Updates a user's profile data. @@ -81,6 +84,9 @@ export async function updateProfile( * An email will be sent to the original email address (if it was set) that allows to revoke the * email address change, in order to protect them from account hijacking. * + * This method is not supported on any {@link User} signed in by {@link Auth} instances + * created with a {@link @firebase/app#FirebaseServerApp}. + * * Important: this is a security sensitive operation that requires the user to have recently signed * in. If this requirement isn't met, ask the user to authenticate again and then call * {@link reauthenticateWithCredential}. @@ -94,11 +100,13 @@ export async function updateProfile( * @public */ export function updateEmail(user: User, newEmail: string): Promise { - return updateEmailOrPassword( - getModularInstance(user) as UserInternal, - newEmail, - null - ); + const userInternal = getModularInstance(user) as UserInternal; + if (_isFirebaseServerApp(userInternal.auth.app)) { + return Promise.reject( + _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } + return updateEmailOrPassword(userInternal, newEmail, null); } /** diff --git a/packages/auth/src/core/user/reauthenticate.ts b/packages/auth/src/core/user/reauthenticate.ts index a88b9479b51..eb2a6c5b57d 100644 --- a/packages/auth/src/core/user/reauthenticate.ts +++ b/packages/auth/src/core/user/reauthenticate.ts @@ -25,6 +25,8 @@ import { _assert, _fail } from '../util/assert'; import { _parseToken } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { UserCredentialImpl } from './user_credential_impl'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { _createError } from '../../core/util/assert'; export async function _reauthenticate( user: UserInternal, @@ -32,6 +34,11 @@ export async function _reauthenticate( bypassAuthState = false ): Promise { const { auth } = user; + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const operationType = OperationType.REAUTHENTICATE; try { diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index 0aa91861cb9..468bdc163ce 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -32,7 +32,7 @@ import { } from '../../model/user'; import { AuthErrorCode } from '../errors'; import { PersistedBlob } from '../persistence'; -import { _assert } from '../util/assert'; +import { _assert, _createError } from '../util/assert'; import { getIdTokenResult } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { ProactiveRefresh } from './proactive_refresh'; @@ -40,6 +40,7 @@ import { extractProviderData, _reloadWithoutSaving, reload } from './reload'; import { StsTokenManager } from './token_manager'; import { UserMetadata } from './user_metadata'; import { ProviderId } from '../../model/enums'; +import { _isFirebaseServerApp } from '@firebase/app'; function assertStringOrUndefined( assertion: unknown, @@ -200,6 +201,11 @@ export class UserImpl implements UserInternal { } async delete(): Promise { + if (_isFirebaseServerApp(this.auth.app)) { + return Promise.reject( + _createError(this.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const idToken = await this.getIdToken(); await _logoutIfInvalidated(this, deleteAccount(this.auth, { idToken })); this.stsTokenManager.clearRefreshToken(); diff --git a/packages/auth/src/model/public_types.ts b/packages/auth/src/model/public_types.ts index 0390ba5e30d..ea9b9e0c213 100644 --- a/packages/auth/src/model/public_types.ts +++ b/packages/auth/src/model/public_types.ts @@ -322,6 +322,10 @@ export interface Auth { useDeviceLanguage(): void; /** * Signs out the current user. This does not automatically revoke the user's ID token. + * + * @remarks + * This method is not supported by {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. */ signOut(): Promise; } @@ -1004,6 +1008,9 @@ export interface User extends UserInfo { * Important: this is a security-sensitive operation that requires the user to have recently * signed in. If this requirement isn't met, ask the user to authenticate again and then call * one of the reauthentication methods like {@link reauthenticateWithCredential}. + * + * This method is not supported on any {@link User} signed in by {@link Auth} instances + * created with a {@link @firebase/app#FirebaseServerApp}. */ delete(): Promise; /** diff --git a/packages/auth/src/platform_browser/strategies/phone.ts b/packages/auth/src/platform_browser/strategies/phone.ts index 745385c2db9..5e027284275 100644 --- a/packages/auth/src/platform_browser/strategies/phone.ts +++ b/packages/auth/src/platform_browser/strategies/phone.ts @@ -31,7 +31,7 @@ import { ApplicationVerifierInternal } from '../../model/application_verifier'; import { PhoneAuthCredential } from '../../core/credentials/phone'; import { AuthErrorCode } from '../../core/errors'; import { _assertLinkedStatus, _link } from '../../core/user/link_unlink'; -import { _assert } from '../../core/util/assert'; +import { _assert, _createError } from '../../core/util/assert'; import { AuthInternal } from '../../model/auth'; import { linkWithCredential, @@ -47,6 +47,7 @@ import { RECAPTCHA_VERIFIER_TYPE } from '../recaptcha/recaptcha_verifier'; import { _castAuth } from '../../core/auth/auth_impl'; import { getModularInstance } from '@firebase/util'; import { ProviderId } from '../../model/enums'; +import { _isFirebaseServerApp } from '@firebase/app'; interface OnConfirmationCallback { (credential: PhoneAuthCredential): Promise; @@ -82,7 +83,8 @@ class ConfirmationResultImpl implements ConfirmationResult { * {@link RecaptchaVerifier} (like React Native), but you need to use a * third-party {@link ApplicationVerifier} implementation. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -104,6 +106,11 @@ export async function signInWithPhoneNumber( phoneNumber: string, appVerifier: ApplicationVerifier ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); const verificationId = await _verifyPhoneNumber( authInternal, @@ -150,7 +157,8 @@ export async function linkWithPhoneNumber( * @remarks * Use before operations such as {@link updatePassword} that require tokens from recent sign-in attempts. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or on any {@link User} signed in by + * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). @@ -164,6 +172,11 @@ export async function reauthenticateWithPhoneNumber( appVerifier: ApplicationVerifier ): Promise { const userInternal = getModularInstance(user) as UserInternal; + if (_isFirebaseServerApp(userInternal.auth.app)) { + return Promise.reject( + _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const verificationId = await _verifyPhoneNumber( userInternal.auth, phoneNumber, @@ -259,7 +272,8 @@ export async function _verifyPhoneNumber( * Updates the user's phone number. * * @remarks - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or on any {@link User} signed in by + * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @example * ``` @@ -281,5 +295,11 @@ export async function updatePhoneNumber( user: User, credential: PhoneAuthCredential ): Promise { - await _link(getModularInstance(user) as UserInternal, credential); + const userInternal = getModularInstance(user) as UserInternal; + if (_isFirebaseServerApp(userInternal.auth.app)) { + return Promise.reject( + _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } + await _link(userInternal, credential); } diff --git a/packages/auth/src/platform_browser/strategies/popup.ts b/packages/auth/src/platform_browser/strategies/popup.ts index e47e03cd3f7..12ba128aca9 100644 --- a/packages/auth/src/platform_browser/strategies/popup.ts +++ b/packages/auth/src/platform_browser/strategies/popup.ts @@ -44,6 +44,7 @@ import { AuthPopup } from '../util/popup'; import { AbstractPopupRedirectOperation } from '../../core/strategies/abstract_popup_redirect_operation'; import { FederatedAuthProvider } from '../../core/providers/federated'; import { getModularInstance } from '@firebase/util'; +import { _isFirebaseServerApp } from '@firebase/app'; /* * The event timeout is the same on mobile and desktop, no need for Delay. Set this to 8s since @@ -63,7 +64,8 @@ export const _POLL_WINDOW_CLOSE_TIMEOUT = new Delay(2000, 10000); * If succeeds, returns the signed in user along with the provider's credential. If sign in was * unsuccessful, returns an error object containing additional information about the error. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -91,6 +93,11 @@ export async function signInWithPopup( provider: AuthProvider, resolver?: PopupRedirectResolver ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); _assertInstanceOf(auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(authInternal, resolver); @@ -111,7 +118,8 @@ export async function signInWithPopup( * If the reauthentication is successful, the returned result will contain the user and the * provider's credential. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or on any {@link User} signed in by + * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -136,6 +144,11 @@ export async function reauthenticateWithPopup( resolver?: PopupRedirectResolver ): Promise { const userInternal = getModularInstance(user) as UserInternal; + if (_isFirebaseServerApp(userInternal.auth.app)) { + return Promise.reject( + _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation( diff --git a/packages/auth/src/platform_browser/strategies/redirect.ts b/packages/auth/src/platform_browser/strategies/redirect.ts index 8586ea589d2..0c4abaf82f8 100644 --- a/packages/auth/src/platform_browser/strategies/redirect.ts +++ b/packages/auth/src/platform_browser/strategies/redirect.ts @@ -25,7 +25,7 @@ import { import { _castAuth } from '../../core/auth/auth_impl'; import { _assertLinkedStatus } from '../../core/user/link_unlink'; -import { _assertInstanceOf } from '../../core/util/assert'; +import { _assertInstanceOf, _createError } from '../../core/util/assert'; import { _generateEventId } from '../../core/util/event_id'; import { AuthEventType } from '../../model/popup_redirect'; import { UserInternal } from '../../model/user'; @@ -36,6 +36,8 @@ import { } from '../../core/strategies/redirect'; import { FederatedAuthProvider } from '../../core/providers/federated'; import { getModularInstance } from '@firebase/util'; +import { _isFirebaseServerApp } from '@firebase/app'; +import { AuthErrorCode } from '../../core/errors'; /** * Authenticates a Firebase client using a full-page redirect flow. @@ -45,7 +47,8 @@ import { getModularInstance } from '@firebase/util'; * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link signInWithRedirect}. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -93,6 +96,11 @@ export async function _signInWithRedirect( provider: AuthProvider, resolver?: PopupRedirectResolver ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); _assertInstanceOf(auth, provider, FederatedAuthProvider); // Wait for auth initialization to complete, this will process pending redirects and clear the @@ -116,7 +124,8 @@ export async function _signInWithRedirect( * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link reauthenticateWithRedirect}. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances + * created with a {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -161,6 +170,11 @@ export async function _reauthenticateWithRedirect( ): Promise { const userInternal = getModularInstance(user) as UserInternal; _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); + if (_isFirebaseServerApp(userInternal.auth.app)) { + return Promise.reject( + _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } // Wait for auth initialization to complete, this will process pending redirects and clear the // PENDING_REDIRECT_KEY in persistence. This should be completed before starting a new // redirect and creating a PENDING_REDIRECT_KEY entry. @@ -185,7 +199,8 @@ export async function _reauthenticateWithRedirect( * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link linkWithRedirect}. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances + * created with a {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -247,7 +262,8 @@ export async function _linkWithRedirect( * If sign-in succeeded, returns the signed in user. If sign-in was unsuccessful, fails with an * error. If no redirect operation was called, returns `null`. * - * This method does not work in a Node.js environment. + * This method does not work in a Node.js environment or with {@link Auth} instances created with a + * {@link @firebase/app#FirebaseServerApp}. * * @example * ```javascript @@ -293,6 +309,11 @@ export async function _getRedirectResult( resolverExtern?: PopupRedirectResolver, bypassAuthState = false ): Promise { + if (_isFirebaseServerApp(auth.app)) { + return Promise.reject( + _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + ); + } const authInternal = _castAuth(auth); const resolver = _withDefaultResolver(authInternal, resolverExtern); const action = new RedirectAction(authInternal, resolver, bypassAuthState); diff --git a/packages/auth/test/integration/flows/firebaseserverapp.test.ts b/packages/auth/test/integration/flows/firebaseserverapp.test.ts index 42b14eb2edc..917430089c4 100644 --- a/packages/auth/test/integration/flows/firebaseserverapp.test.ts +++ b/packages/auth/test/integration/flows/firebaseserverapp.test.ts @@ -21,16 +21,27 @@ import chaiAsPromised from 'chai-as-promised'; // eslint-disable-next-line import/no-extraneous-dependencies import { Auth, - OperationType, createUserWithEmailAndPassword, + EmailAuthProvider, getAdditionalUserInfo, + getRedirectResult, + GoogleAuthProvider, onAuthStateChanged, + OperationType, + reauthenticateWithCredential, signInAnonymously, + signInWithCredential, + signInWithCustomToken, + signInWithEmailAndPassword, + signInWithEmailLink, + signInWithRedirect, signOut, + updateCurrentUser, + updateEmail, updateProfile } from '@firebase/auth'; -import { isBrowser } from '@firebase/util'; -import { initializeServerApp } from '@firebase/app'; +import { isBrowser, FirebaseError } from '@firebase/util'; +import { initializeServerApp, deleteApp } from '@firebase/app'; import { cleanUpTestInstance, @@ -47,17 +58,12 @@ const signInWaitDuration = 200; describe('Integration test: Auth FirebaseServerApp tests', () => { let auth: Auth; - let serverAppAuth: Auth | null; beforeEach(() => { auth = getTestInstance(); }); afterEach(async () => { - if (serverAppAuth) { - await signOut(serverAppAuth); - serverAppAuth = null; - } await cleanUpTestInstance(auth); }); @@ -79,7 +85,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { const firebaseServerAppSettings = { authIdToken }; const serverApp = initializeServerApp(auth.app, firebaseServerAppSettings); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); console.log('auth.emulatorConfig ', auth.emulatorConfig); console.log('serverAuth.emulatorConfig ', serverAppAuth.emulatorConfig); @@ -105,6 +111,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { }); expect(numberServerLogins).to.equal(1); + + await deleteApp(serverApp); }); it('getToken operations fullfilled or rejected', async () => { @@ -126,7 +134,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -154,6 +162,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { await expect(serverAppAuth.currentUser.getIdToken(/*forceRefresh=*/ true)) .to.be.rejected; } + + await deleteApp(serverApp); }); it('invalid token does not sign in user', async () => { @@ -167,7 +177,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); expect(serverAppAuth.currentUser).to.be.null; let numberServerLogins = 0; @@ -183,6 +193,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { expect(numberServerLogins).to.equal(0); expect(serverAppAuth.currentUser).to.be.null; + + await deleteApp(serverApp); }); it('signs in with email crednetial user', async () => { @@ -213,7 +225,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -238,6 +250,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { }); expect(numberServerLogins).to.equal(1); + + await deleteApp(serverApp); }); it('can reload user', async () => { @@ -258,7 +272,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -280,6 +294,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { await serverAppAuth.currentUser.reload(); } expect(numberServerLogins).to.equal(1); + + await deleteApp(serverApp); }); it('can update server based user profile', async () => { @@ -301,7 +317,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; const newDisplayName = 'newName'; onAuthStateChanged(serverAppAuth, serverAuthUser => { @@ -336,6 +352,8 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { expect(serverAppAuth.currentUser?.displayName).to.not.be.null; expect(serverAppAuth.currentUser?.displayName).to.equal(newDisplayName); } + + await deleteApp(serverApp); }); it('can sign out of main auth and still use server auth', async () => { @@ -357,7 +375,7 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { getAppConfig(), firebaseServerAppSettings ); - serverAppAuth = getTestInstanceForServerApp(serverApp); + const serverAppAuth = getTestInstanceForServerApp(serverApp); let numberServerLogins = 0; onAuthStateChanged(serverAppAuth, serverAuthUser => { if (serverAuthUser) { @@ -387,5 +405,124 @@ describe('Integration test: Auth FirebaseServerApp tests', () => { if (serverAppAuth) { expect(serverAppAuth.currentUser).to.not.be.null; } + + await deleteApp(serverApp); + }); + + it('auth operations fail correctly on FirebaseServerApp instances', async () => { + if (isBrowser()) { + return; + } + const userCred = await signInAnonymously(auth); + expect(auth.currentUser).to.eq(userCred.user); + + const user = userCred.user; + expect(user).to.equal(auth.currentUser); + expect(user.uid).to.be.a('string'); + + const authIdToken = await user.getIdToken(); + const firebaseServerAppSettings = { authIdToken }; + + const serverApp = initializeServerApp( + getAppConfig(), + firebaseServerAppSettings + ); + + const serverAppAuth = getTestInstanceForServerApp(serverApp); + await new Promise(resolve => { + setTimeout(resolve, signInWaitDuration); + }); + + expect(serverAppAuth.currentUser).to.not.be.null; + const email = randomEmail(); + const password = 'password'; + + // Auth tests: + await expect( + createUserWithEmailAndPassword(serverAppAuth, email, password) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect( + signInWithRedirect(serverAppAuth, new GoogleAuthProvider()) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect(getRedirectResult(serverAppAuth)).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect(signInAnonymously(serverAppAuth)).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + + const credential = EmailAuthProvider.credential(email, password); + await expect( + signInWithCredential(serverAppAuth, credential) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + + await expect( + signInWithCustomToken(serverAppAuth, 'custom token') + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect( + signInWithEmailAndPassword(serverAppAuth, email, password) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect( + signInWithEmailLink(serverAppAuth, email, 'email link') + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect( + updateCurrentUser(serverAppAuth, serverAppAuth.currentUser) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect( + updateCurrentUser(serverAppAuth, serverAppAuth.currentUser) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + await expect(signOut(serverAppAuth)).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + + if (serverAppAuth.currentUser !== null) { + await expect( + reauthenticateWithCredential(serverAppAuth.currentUser, credential) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + + await expect(serverAppAuth.currentUser.delete()).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + + await expect( + updateEmail(serverAppAuth.currentUser, email) + ).to.be.rejectedWith( + FirebaseError, + 'operation-not-supported-in-this-environment' + ); + } + + await deleteApp(serverApp); }); }); From 64f48f607a028cc08ac2bfdcb19a99c7a3ecc85a Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 13 Feb 2024 11:14:51 -0500 Subject: [PATCH 08/19] Added a changeset --- .changeset/afraid-fishes-repair.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/afraid-fishes-repair.md diff --git a/.changeset/afraid-fishes-repair.md b/.changeset/afraid-fishes-repair.md new file mode 100644 index 00000000000..ee9e74614f4 --- /dev/null +++ b/.changeset/afraid-fishes-repair.md @@ -0,0 +1,6 @@ +--- +'@firebase/auth': minor +'@firebase/app': minor +--- + +Added a new app type `FirebaseServerApp` which is intended to bridge state data between Client and Server runtime environments. From 7ba816a607504f363878a6401c23a9b0a74618a5 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 13 Feb 2024 13:58:51 -0500 Subject: [PATCH 09/19] add firebase entry to the changeset --- .changeset/afraid-fishes-repair.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/afraid-fishes-repair.md b/.changeset/afraid-fishes-repair.md index ee9e74614f4..8322e666325 100644 --- a/.changeset/afraid-fishes-repair.md +++ b/.changeset/afraid-fishes-repair.md @@ -1,6 +1,7 @@ --- '@firebase/auth': minor '@firebase/app': minor +'firebase': minor --- Added a new app type `FirebaseServerApp` which is intended to bridge state data between Client and Server runtime environments. From 345d4bdc448316b8fae4395cdd93e2219afb32b7 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 14 Feb 2024 17:16:27 -0500 Subject: [PATCH 10/19] Documentation updates. --- .changeset/afraid-fishes-repair.md | 3 +- docs-devsite/app.firebaseserverapp.md | 12 ++-- docs-devsite/app.firebaseserverappsettings.md | 24 +++---- docs-devsite/app.md | 12 ++-- packages/app/src/api.ts | 14 ++-- packages/app/src/public-types.ts | 71 +++++++++---------- 6 files changed, 68 insertions(+), 68 deletions(-) diff --git a/.changeset/afraid-fishes-repair.md b/.changeset/afraid-fishes-repair.md index 8322e666325..578018145f9 100644 --- a/.changeset/afraid-fishes-repair.md +++ b/.changeset/afraid-fishes-repair.md @@ -4,4 +4,5 @@ 'firebase': minor --- -Added a new app type `FirebaseServerApp` which is intended to bridge state data between Client and Server runtime environments. +Added a new interface `FirebaseServerApp` which extends FirebaseApp and is intended to bridge state +data between client and server runtime environments. diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index b0198ce4951..54837269964 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -25,17 +25,17 @@ export interface FirebaseServerApp extends FirebaseApp | Property | Type | Description | | --- | --- | --- | -| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this promise before invoking getAuth() if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | -| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no getApp operation for FirebaseServerApps, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | +| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this Promise before invoking getAuth if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | +| [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | | [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | ## FirebaseServerApp.authIdTokenVerified -Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed. +Checks to see if the local verification of the `authIdToken` provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed. -It is recommend that your application awaits this promise before invoking getAuth() if an authIdToken was provided in the FirebaseServerAppSettings. +It is recommend that your application awaits this `Promise` before invoking `getAuth` if an `authIdToken` was provided in the `FirebaseServerAppSettings`. -The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. +The returned `Promise` is completed immediately if the optional authIdToken parameter was omitted from `FirebaseServerApp` initialization. Signature: @@ -45,7 +45,7 @@ authIdTokenVerified: () => Promise; ## FirebaseServerApp.name -There is no getApp operation for FirebaseServerApps, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface declaration. +There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for applications. However, it may be used internally, and is declared here so that `FirebaseServerApp` conforms to the `FirebaseApp` interface. Signature: diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 4edf299fe8e..78ae05910ba 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -23,21 +23,21 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | Property | Type | Description | | --- | --- | --- | -| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp instance will work to validate the token even before Auth is initialized. The result of the validation can be queried via by the application by invoking . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause 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, otherwise it will fail validation.If the token fails local verification, or if the Auth service has deemed it invalid when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user upon initalization.If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback will be invoked with the User as per standard Auth flows. However, users created via authIdTokens do not have a refresh token and any attempted refresh operation will fail. | -| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. | -| [releaseOnDeref](./app.firebaseserverappsettings.md#firebaseserverappsettingsreleaseonderef) | object | An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is garbage collected.The intent of this field is to help reduce memory overhead for long-running cloud functions. If provided, the customer's app running in a SSR pass need not worry about 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. | +| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp works to validate the token even before Auth is initialized. The result of the validation can be queried via . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.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 initalization.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. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApp, so the name is not relevant. However it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp\` interface declaration. | +| [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 refrence on the FirebaseServerApp instance when the provided releaseOnDeref object is garbage collected.The intent of this field is to help reduce memory overhead for cloud functions. If provided, the customer's app running in a SSR pass need not worry about 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.authIdToken An optional Auth ID token used to resume a signed in user session from a client runtime environment. -If provided, the FirebaseServerApp instance will work to validate the token even before Auth is initialized. The result of the validation can be queried via by the application by invoking . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided. +If provided, the `FirebaseServerApp` works to validate the token even before `Auth` is initialized. The result of the validation can be queried via . Awaiting the `Promise` returned by is highly recommended if an `authIdToken` token is provided. -Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause 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, otherwise it will fail validation. +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 deemed it invalid when the Auth SDK is initialized, then a warning is logged to the console and the Auth SDK will not sign in a user upon initalization. +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 initalization. -If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback will be invoked with the User as per standard Auth flows. However, users created via authIdTokens do not have a refresh token and any attempted refresh operation will fail. +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. Signature: @@ -47,7 +47,7 @@ authIdToken?: string; ## FirebaseServerAppSettings.name -There is no get for FirebaseServerApps, so the name is not relevant. however it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. +There is no get for `FirebaseServerApp`, so the name is not relevant. However it's always a blank string so that `FirebaseServerAp`p` conforms to the `FirebaseApp\` interface declaration. Signature: @@ -57,13 +57,13 @@ name?: undefined; ## FirebaseServerAppSettings.releaseOnDeref -An optional object. If provided, the Firebase SDK will use a FinalizationRegistry object to monitor the Garbage Collection status of the provided object, and the Firebase SDK will release its refrence on the FirebaseServerApp instance when the provided object is garbage collected. +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 refrence on the `FirebaseServerApp` instance when the provided `releaseOnDeref` object is garbage collected. -The intent of this field is to help reduce memory overhead for long-running cloud functions. If provided, the customer's app running in a SSR pass need not worry about FirebaseServerApp cleanup, so long as the reference object is deleted (by falling out of SSR scope, for instance.) +The intent of this field is to help reduce memory overhead for cloud functions. If provided, the customer's app running in a SSR pass need not worry about `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 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 the Firebase SDK will not be able to automatically clean up the FirebaseServerApp instance and an error will be thrown. +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. Signature: diff --git a/docs-devsite/app.md b/docs-devsite/app.md index a36bdc065f7..b9eb27bae75 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -34,7 +34,7 @@ This package coordinates the communication between the different Firebase compon | function(options, ...) | | [initializeApp(options, name)](./app.md#initializeapp_cb2f5e1) | Creates and initializes a [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | | [initializeApp(options, config)](./app.md#initializeapp_079e917) | Creates and initializes a FirebaseApp instance. | -| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | +| [initializeServerApp(options, config)](./app.md#initializeserverapp_30ab697) | Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance.The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | ## Interfaces @@ -317,7 +317,7 @@ export declare function initializeApp(options: FirebaseOptions, config?: Firebas Creates and initializes a [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) instance. -The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment. +The `FirebaseServerApp` is similar to `FirebaseApp`, but is intended for execution in server side rendering environments only. Initialization will fail if invoked from a browser environment. See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. @@ -331,21 +331,21 @@ export declare function initializeServerApp(options: FirebaseOptions | FirebaseA | Parameter | Type | Description | | --- | --- | --- | -| options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) \| [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | Firebase.AppOptions to configure the app's services, or a a FirebaseApp instance which contains the AppOptions within. | -| config | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | FirebaseServerApp configuration. | +| options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) \| [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | Firebase.AppOptions to configure the app's services, or a a FirebaseApp instance which contains the AppOptions within. | +| config | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | FirebaseServerApp configuration. | Returns: [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) -The initialized FirebaseServerApp. +The initialized `FirebaseServerApp`. ### Example ```javascript -// Initialize a FirebaseServerApp. +// Initialize an instance of `FirebaseServerApp`. // Retrieve your own options values by adding a web app on // https://console.firebase.google.com initializeServerApp({ diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index e4b1a6db341..5928fd737a8 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -183,7 +183,7 @@ export function initializeApp( /** * Creates and initializes a {@link @firebase/app#FirebaseServerApp} instance. * - * The FirebaseServerApp is similar to FirebaseApp, but is intended for execution in + * The `FirebaseServerApp` is similar to `FirebaseApp`, but is intended for execution in * server side rendering environments only. Initialization will fail if invoked from a * browser environment. * @@ -198,7 +198,7 @@ export function initializeApp( * @example * ```javascript * - * // Initialize a FirebaseServerApp. + * // Initialize an instance of `FirebaseServerApp`. * // Retrieve your own options values by adding a web app on * // https://console.firebase.google.com * initializeServerApp({ @@ -213,11 +213,11 @@ export function initializeApp( * }); * ``` * - * @param options - Firebase.AppOptions to configure the app's services, or a - * a FirebaseApp instance which contains the AppOptions within. - * @param config - FirebaseServerApp configuration. + * @param options - `Firebase.AppOptions` to configure the app's services, or a + * a `FirebaseApp` instance which contains the `AppOptions` within. + * @param config - `FirebaseServerApp` configuration. * - * @returns The initialized FirebaseServerApp. + * @returns The initialized `FirebaseServerApp`. * * @public */ @@ -231,7 +231,7 @@ export function initializeServerApp( _serverAppConfig: FirebaseServerAppSettings ): FirebaseServerApp { if (isBrowser()) { - // FirebaseServerApps aren't designed to be run in browsers. + // FirebaseServerApp isn't designed to be run in browsers. throw ERROR_FACTORY.create(AppError.INVALID_SERVER_APP_ENVIRONMENT); } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 463bd694a05..6a3ff10f8c4 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -83,21 +83,21 @@ export interface FirebaseApp { */ export interface FirebaseServerApp extends FirebaseApp { /** - * Checks to see if the local verification of the authIdToken provided to + * Checks to see if the local verification of the `authIdToken` provided to * {@link (initializeServerApp:1) | initializeServerApp()} has completed. * - * It is recommend that your application awaits this promise before invoking getAuth() if an - * authIdToken was provided in the FirebaseServerAppSettings. + * It is recommend that your application awaits this `Promise` before invoking `getAuth` if an + * `authIdToken` was provided in the `FirebaseServerAppSettings`. * - * The returned Promise is completed immediately if the optional authIdToken parameter - * was omitted from FirebaseServerApp initialization. + * The returned `Promise` is completed immediately if the optional authIdToken parameter + * was omitted from `FirebaseServerApp` initialization. */ authIdTokenVerified: () => Promise; /** - * There is no getApp operation for FirebaseServerApps, so the name is not relevant for + * There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for * applications. However, it may be used internally, and is declared here so that - * FirebaseServerApp conforms to the FirebaseApp interface declaration. + * `FirebaseServerApp` conforms to the `FirebaseApp` interface. */ name: string; @@ -192,52 +192,51 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * An optional Auth ID token used to resume a signed in user session from a client * runtime environment. * - * If provided, the FirebaseServerApp instance will work to validate the token even before Auth - * is initialized. The result of the validation can be queried via by the application by invoking - * {@link (FirebaseServerApp.authIdTokenVerified()}. Awaiting the Promise returned by - * {@link (FirebaseServerApp.authIdTokenVerified()} is highly recommended if an authIdToken token - * is provided. + * If provided, the `FirebaseServerApp` works to validate the token even before + * `Auth` is initialized. The result of the validation can be queried via + * {@link (FirebaseServerApp.authIdTokenVerified()}. Awaiting the `Promise` returned by + * {@link (FirebaseServerApp.authIdTokenVerified()} is highly recommended if an `authIdToken` + * token is provided. * - * Invoking getAuth() with a FirebaseServerApp configured with a validated authIdToken will cause - * 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, otherwise it will fail - * validation. + * 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 deemed it invalid when + * 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 upon initalization. + * sign in a user on initalization. * - * If a user is successfully signed-in, then the Auth instance's onAuthStateChanged callback - * will be invoked with the User as per standard Auth flows. However, users created via - * authIdTokens do not have a refresh token and any attempted refresh operation will fail. + * 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. */ authIdToken?: string; /** - * An optional object. If provided, the Firebase SDK will use a FinalizationRegistry - * object to monitor the Garbage Collection status of the provided object, and the - * Firebase SDK will release its refrence on the FirebaseServerApp instance when the - * provided object is garbage collected. + * 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 refrence on the `FirebaseServerApp` instance when the + * provided `releaseOnDeref` object is garbage collected. * - * The intent of this field is to help reduce memory overhead for long-running cloud - * functions. If provided, the customer's app running in a SSR pass need not worry about - * FirebaseServerApp cleanup, so long as the reference object is deleted (by falling out of + * The intent of this field is to help reduce memory overhead for cloud functions. + * If provided, the customer's app running in a SSR pass need not worry about + * `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 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 the Firebase SDK will not be able - * to automatically clean up the FirebaseServerApp instance and an error will be - * thrown. + * 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. */ releaseOnDeref?: object; /** - * There is no get for FirebaseServerApps, so the name is not relevant. however it's always - * a blank string so that FirebaseServerApp conforms to the FirebaseApp interface declaration. + * There is no get for `FirebaseServerApp`, so the name is not relevant. However it's always + * a blank string so that `FirebaseServerAp`p` conforms to the `FirebaseApp` interface declaration. */ name?: undefined; } From 31b10e190fc9c7bf811f495ca1a6bd0aa1d385d8 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 26 Feb 2024 11:52:58 -0500 Subject: [PATCH 11/19] marked _serverApps as internal --- common/api-review/app.api.md | 2 +- docs-devsite/app.md | 9 --------- packages/app/src/internal.ts | 4 ++++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index f05a07c5499..538adc36471 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -134,7 +134,7 @@ export function _removeServiceInstance(app: FirebaseApp, name: T // @public export const SDK_VERSION: string; -// @public (undocumented) +// @internal (undocumented) export const _serverApps: Map; // @public diff --git a/docs-devsite/app.md b/docs-devsite/app.md index b9eb27bae75..9b5e4b0b03e 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -50,7 +50,6 @@ This package coordinates the communication between the different Firebase compon | Variable | Description | | --- | --- | -| [\_serverApps](./app.md#_serverapps) | | | [SDK\_VERSION](./app.md#sdk_version) | The current SDK version. | ## function(app, ...) @@ -361,14 +360,6 @@ initializeServerApp({ ``` -## \_serverApps - -Signature: - -```typescript -_serverApps: Map -``` - ## SDK\_VERSION The current SDK version. diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index da0ba3f73fc..f266de95166 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -30,6 +30,10 @@ import { FirebaseServerAppImpl } from './firebaseServerApp'; * @internal */ export const _apps = new Map(); + +/** + * @internal + */ export const _serverApps = new Map(); /** From e583b805ec26d5f1d7b1be53ea8ea4730ac71b5c Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 27 Feb 2024 15:01:18 -0500 Subject: [PATCH 12/19] Copy fixes for documentation. --- .changeset/afraid-fishes-repair.md | 4 ++-- docs-devsite/app.firebaseserverapp.md | 6 +++--- docs-devsite/app.firebaseserverappsettings.md | 8 ++++---- docs-devsite/app.md | 2 +- packages/app/src/api.test.ts | 16 ++++++++-------- packages/app/src/public-types.ts | 13 +++++++------ 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/.changeset/afraid-fishes-repair.md b/.changeset/afraid-fishes-repair.md index 578018145f9..0db8e8ffe22 100644 --- a/.changeset/afraid-fishes-repair.md +++ b/.changeset/afraid-fishes-repair.md @@ -4,5 +4,5 @@ 'firebase': minor --- -Added a new interface `FirebaseServerApp` which extends FirebaseApp and is intended to bridge state -data between client and server runtime environments. +Added the new `FirebaseServerApp` interface to bridge state +data between client and server runtime environments. This interface extends `FirebaseApp`. diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index 54837269964..ba8680883c2 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -10,7 +10,7 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # FirebaseServerApp interface -A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments. +A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server environments. Do not call this constructor directly. Instead, use [initializeServerApp()](./app.md#initializeserverapp_30ab697) to create an app. @@ -25,7 +25,7 @@ export interface FirebaseServerApp extends FirebaseApp | Property | Type | Description | | --- | --- | --- | -| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this Promise before invoking getAuth if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | +| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this Promise before invoking getAuth if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | | [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | | [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | @@ -35,7 +35,7 @@ Checks to see if the local verification of the `authIdToken` provided to [initia It is recommend that your application awaits this `Promise` before invoking `getAuth` if an `authIdToken` was provided in the `FirebaseServerAppSettings`. -The returned `Promise` is completed immediately if the optional authIdToken parameter was omitted from `FirebaseServerApp` initialization. +The returned `Promise` is completed immediately if the optional `authIdToken` parameter was omitted from `FirebaseServerApp` initialization. Signature: diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 78ae05910ba..c187166435d 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -24,8 +24,8 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | Property | Type | Description | | --- | --- | --- | | [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp works to validate the token even before Auth is initialized. The result of the validation can be queried via . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.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 initalization.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. | -| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no get for FirebaseServerApp, so the name is not relevant. However it's always a blank string so that FirebaseServerApp conforms to the FirebaseApp\` interface declaration. | -| [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 refrence on the FirebaseServerApp instance when the provided releaseOnDeref object is garbage collected.The intent of this field is to help reduce memory overhead for cloud functions. If provided, the customer's app running in a SSR pass need not worry about 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. | +| [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | +| [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 refrence 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.authIdToken @@ -47,7 +47,7 @@ authIdToken?: string; ## FirebaseServerAppSettings.name -There is no get for `FirebaseServerApp`, so the name is not relevant. However it's always a blank string so that `FirebaseServerAp`p` conforms to the `FirebaseApp\` interface declaration. +There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for applications. However, it may be used internally, and is declared here so that `FirebaseServerApp` conforms to the `FirebaseApp` interface. Signature: @@ -59,7 +59,7 @@ name?: undefined; 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 refrence on the `FirebaseServerApp` instance when the provided `releaseOnDeref` object is garbage collected. -The intent of this field is to help reduce memory overhead for cloud functions. If provided, the customer's app running in a SSR pass need not worry about `FirebaseServerApp` cleanup, so long as the reference object is deleted (by falling out of SSR scope, for instance.) +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`. diff --git a/docs-devsite/app.md b/docs-devsite/app.md index 9b5e4b0b03e..9c3b322aaaf 100644 --- a/docs-devsite/app.md +++ b/docs-devsite/app.md @@ -43,7 +43,7 @@ This package coordinates the communication between the different Firebase compon | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | A [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) holds the initialization information for a collection of services.Do not call this constructor directly. Instead, use [initializeApp()](./app.md#initializeapp_cb2f5e1) to create an app. | | [FirebaseAppSettings](./app.firebaseappsettings.md#firebaseappsettings_interface) | Configuration options given to [initializeApp()](./app.md#initializeapp_cb2f5e1) | | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | Firebase configuration object. Contains a set of parameters required by services in order to successfully communicate with Firebase server APIs and to associate client data with your Firebase project and Firebase application. Typically this object is populated by the Firebase console at project setup. See also: [Learn about the Firebase config object](https://firebase.google.com/docs/web/setup#config-object). | -| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server enviornments.Do not call this constructor directly. Instead, use [initializeServerApp()](./app.md#initializeserverapp_30ab697) to create an app. | +| [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) | A [FirebaseServerApp](./app.firebaseserverapp.md#firebaseserverapp_interface) holds the initialization information for a collection of services running in server environments.Do not call this constructor directly. Instead, use [initializeServerApp()](./app.md#initializeserverapp_30ab697) to create an app. | | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | Configuration options given to [initializeServerApp()](./app.md#initializeserverapp_30ab697) | ## Variables diff --git a/packages/app/src/api.test.ts b/packages/app/src/api.test.ts index 6dd12b82798..f5577d092e2 100644 --- a/packages/app/src/api.test.ts +++ b/packages/app/src/api.test.ts @@ -199,7 +199,7 @@ describe('API tests', () => { it('creates FirebaseServerApp with options', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -218,7 +218,7 @@ describe('API tests', () => { it('creates FirebaseServerApp with automaticDataCollectionEnabled', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -239,7 +239,7 @@ describe('API tests', () => { it('creates FirebaseServerApp with releaseOnDeref', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -258,7 +258,7 @@ describe('API tests', () => { it('creates FirebaseServerApp with FirebaseApp', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -283,7 +283,7 @@ describe('API tests', () => { it('create similar FirebaseServerApps does not return the same object', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -311,7 +311,7 @@ describe('API tests', () => { it('create FirebaseServerApps with varying deleteOnDeref, and they still return same object ', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -338,7 +338,7 @@ describe('API tests', () => { it('create duplicate FirebaseServerApps returns the same object', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } @@ -360,7 +360,7 @@ describe('API tests', () => { it('deleting FirebaseServerApps is ref counted', async () => { if (isBrowser()) { - // FirebaseServerApp isn't supported for execution in browser enviornments. + // FirebaseServerApp isn't supported for execution in browser environments. return; } diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 6a3ff10f8c4..372d1e7e17a 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -73,7 +73,7 @@ export interface FirebaseApp { /** * A {@link @firebase/app#FirebaseServerApp} holds the initialization information - * for a collection of services running in server enviornments. + * for a collection of services running in server environments. * * Do not call this constructor directly. Instead, use * {@link (initializeServerApp:1) | initializeServerApp()} to create @@ -89,7 +89,7 @@ export interface FirebaseServerApp extends FirebaseApp { * It is recommend that your application awaits this `Promise` before invoking `getAuth` if an * `authIdToken` was provided in the `FirebaseServerAppSettings`. * - * The returned `Promise` is completed immediately if the optional authIdToken parameter + * The returned `Promise` is completed immediately if the optional `authIdToken` parameter * was omitted from `FirebaseServerApp` initialization. */ authIdTokenVerified: () => Promise; @@ -219,8 +219,8 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * Firebase SDK releases its refrence on the `FirebaseServerApp` instance when the * provided `releaseOnDeref` object is garbage collected. * - * The intent of this field is to help reduce memory overhead for cloud functions. - * If provided, the customer's app running in a SSR pass need not worry about + * 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.) * @@ -235,8 +235,9 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { releaseOnDeref?: object; /** - * There is no get for `FirebaseServerApp`, so the name is not relevant. However it's always - * a blank string so that `FirebaseServerAp`p` conforms to the `FirebaseApp` interface declaration. + * There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for + * applications. However, it may be used internally, and is declared here so that + * `FirebaseServerApp` conforms to the `FirebaseApp` interface. */ name?: undefined; } From 8e5221b09f81716c2d2d39d7d8890ad0324e6feb Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 28 Feb 2024 15:30:56 -0500 Subject: [PATCH 13/19] reference type --- docs-devsite/app.firebaseserverappsettings.md | 4 ++-- packages/app/src/public-types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index c187166435d..32e74fa31e2 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -25,7 +25,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | --- | --- | --- | | [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp works to validate the token even before Auth is initialized. The result of the validation can be queried via . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.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 initalization.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. | | [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | -| [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 refrence 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. | +| [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.authIdToken @@ -57,7 +57,7 @@ name?: undefined; ## FirebaseServerAppSettings.releaseOnDeref -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 refrence on the `FirebaseServerApp` instance when the provided `releaseOnDeref` object is garbage collected. +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.) diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index 372d1e7e17a..a1c2aed69a9 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -216,7 +216,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { /** * 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 refrence on the `FirebaseServerApp` instance when 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. From 725f8434da42e1df7df146558c1ca4992ca23238 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Thu, 7 Mar 2024 09:52:19 -0500 Subject: [PATCH 14/19] Remove requireEmulator from getTestInstanceForServerApp in auth iTest helpers --- packages/auth/test/helpers/integration/helpers.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/auth/test/helpers/integration/helpers.ts b/packages/auth/test/helpers/integration/helpers.ts index 17865228784..540c926dd99 100644 --- a/packages/auth/test/helpers/integration/helpers.ts +++ b/packages/auth/test/helpers/integration/helpers.ts @@ -81,8 +81,7 @@ export function getTestInstance(requireEmulator = false): Auth { } export function getTestInstanceForServerApp( - serverApp: FirebaseServerApp, - requireEmulator = false + serverApp: FirebaseServerApp ): Auth { const auth = getAuth(serverApp) as IntegrationTestAuth; auth.settings.appVerificationDisabledForTesting = true; @@ -90,9 +89,6 @@ export function getTestInstanceForServerApp( if (emulatorUrl) { connectAuthEmulator(auth, emulatorUrl, { disableWarnings: true }); - } else if (requireEmulator) { - /* Emulator wasn't configured but test must use emulator */ - throw new Error('Test may only be run using the Auth Emulator!'); } // Don't track created users on the created Auth instance like we do for Auth objects created in From f9ca3b798535f8cd3940d46a79eb53eb8d79fc80 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 8 Mar 2024 08:30:38 -0500 Subject: [PATCH 15/19] Descriptive errors when changing the current user. --- packages/auth/src/core/auth/auth_impl.ts | 11 +++++++---- packages/auth/src/core/strategies/anonymous.ts | 4 ++-- packages/auth/src/core/strategies/credential.ts | 4 ++-- packages/auth/src/core/strategies/custom_token.ts | 5 ++--- .../auth/src/core/strategies/email_and_password.ts | 9 ++++++--- packages/auth/src/core/strategies/email_link.ts | 4 ++-- packages/auth/src/core/user/account_info.ts | 5 ++--- packages/auth/src/core/user/reauthenticate.ts | 4 ++-- packages/auth/src/core/user/user_impl.ts | 7 +++++-- packages/auth/src/core/util/assert.ts | 10 ++++++++++ .../auth/src/platform_browser/strategies/phone.ts | 11 +++++++---- .../auth/src/platform_browser/strategies/redirect.ts | 11 +++++++---- 12 files changed, 54 insertions(+), 31 deletions(-) diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 893854852d8..2103306bf5a 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -62,7 +62,10 @@ import { PersistenceUserManager } from '../persistence/persistence_user_manager'; import { _reloadWithoutSaving } from '../user/reload'; -import { _assert, _createError } from '../util/assert'; +import { + _assert, + _serverAppCurrentUserOperationNotSupportedError +} from '../util/assert'; import { _getInstance } from '../util/instantiator'; import { _getUserLanguage } from '../util/navigator'; import { _getClientVersion } from '../util/version'; @@ -356,7 +359,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { async updateCurrentUser(userExtern: User | null): Promise { if (_isFirebaseServerApp(this.app)) { return Promise.reject( - _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(this) ); } // The public updateCurrentUser method needs to make a copy of the user, @@ -402,7 +405,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { async signOut(): Promise { if (_isFirebaseServerApp(this.app)) { return Promise.reject( - _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(this) ); } // Run first, to block _setRedirectUser() if any callbacks fail. @@ -420,7 +423,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { setPersistence(persistence: Persistence): Promise { if (_isFirebaseServerApp(this.app)) { return Promise.reject( - _createError(this, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(this) ); } return this.queue(async () => { diff --git a/packages/auth/src/core/strategies/anonymous.ts b/packages/auth/src/core/strategies/anonymous.ts index e15b738d4d6..1b903dd7c82 100644 --- a/packages/auth/src/core/strategies/anonymous.ts +++ b/packages/auth/src/core/strategies/anonymous.ts @@ -22,7 +22,7 @@ import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; import { OperationType } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; import { AuthErrorCode } from '../../core/errors'; /** @@ -42,7 +42,7 @@ import { AuthErrorCode } from '../../core/errors'; export async function signInAnonymously(auth: Auth): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); diff --git a/packages/auth/src/core/strategies/credential.ts b/packages/auth/src/core/strategies/credential.ts index d429d3eb11b..b713db05198 100644 --- a/packages/auth/src/core/strategies/credential.ts +++ b/packages/auth/src/core/strategies/credential.ts @@ -28,7 +28,7 @@ import { _castAuth } from '../auth/auth_impl'; import { getModularInstance } from '@firebase/util'; import { OperationType } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; import { AuthErrorCode } from '../../core/errors'; export async function _signInWithCredential( @@ -38,7 +38,7 @@ export async function _signInWithCredential( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const operationType = OperationType.SIGN_IN; diff --git a/packages/auth/src/core/strategies/custom_token.ts b/packages/auth/src/core/strategies/custom_token.ts index 56dc6741453..83c66d5edd6 100644 --- a/packages/auth/src/core/strategies/custom_token.ts +++ b/packages/auth/src/core/strategies/custom_token.ts @@ -23,8 +23,7 @@ import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; import { OperationType } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; -import { AuthErrorCode } from '../../core/errors'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; /** * Asynchronously signs in using a custom token. * @@ -50,7 +49,7 @@ export async function signInWithCustomToken( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); diff --git a/packages/auth/src/core/strategies/email_and_password.ts b/packages/auth/src/core/strategies/email_and_password.ts index 4e3d1304507..473b3800eac 100644 --- a/packages/auth/src/core/strategies/email_and_password.ts +++ b/packages/auth/src/core/strategies/email_and_password.ts @@ -29,7 +29,10 @@ import { signUp, SignUpRequest } from '../../api/authentication/sign_up'; import { MultiFactorInfoImpl } from '../../mfa/mfa_info'; import { EmailAuthProvider } from '../providers/email'; import { UserCredentialImpl } from '../user/user_credential_impl'; -import { _assert, _createError } from '../util/assert'; +import { + _assert, + _serverAppCurrentUserOperationNotSupportedError +} from '../util/assert'; import { _setActionCodeSettingsOnRequest } from './action_code_settings'; import { signInWithCredential } from './credential'; import { _castAuth } from '../auth/auth_impl'; @@ -273,7 +276,7 @@ export async function createUserWithEmailAndPassword( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); @@ -338,7 +341,7 @@ export function signInWithEmailAndPassword( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } return signInWithCredential( diff --git a/packages/auth/src/core/strategies/email_link.ts b/packages/auth/src/core/strategies/email_link.ts index f9dd75bbe26..351583a6bb5 100644 --- a/packages/auth/src/core/strategies/email_link.ts +++ b/packages/auth/src/core/strategies/email_link.ts @@ -35,7 +35,7 @@ import { _castAuth } from '../auth/auth_impl'; import { handleRecaptchaFlow } from '../../platform_browser/recaptcha/recaptcha_enterprise_verifier'; import { RecaptchaActionName, RecaptchaClientType } from '../../api'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; /** * Sends a sign-in email link to the user with the specified email. @@ -173,7 +173,7 @@ export async function signInWithEmailLink( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authModular = getModularInstance(auth); diff --git a/packages/auth/src/core/user/account_info.ts b/packages/auth/src/core/user/account_info.ts index d59669f153c..72e2cbc7d2a 100644 --- a/packages/auth/src/core/user/account_info.ts +++ b/packages/auth/src/core/user/account_info.ts @@ -27,8 +27,7 @@ import { _logoutIfInvalidated } from './invalidation'; import { getModularInstance } from '@firebase/util'; import { ProviderId } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; -import { AuthErrorCode } from '../../core/errors'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; /** * Updates a user's profile data. @@ -103,7 +102,7 @@ export function updateEmail(user: User, newEmail: string): Promise { const userInternal = getModularInstance(user) as UserInternal; if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject( - _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(userInternal.auth) ); } return updateEmailOrPassword(userInternal, newEmail, null); diff --git a/packages/auth/src/core/user/reauthenticate.ts b/packages/auth/src/core/user/reauthenticate.ts index eb2a6c5b57d..7e888c2c1d7 100644 --- a/packages/auth/src/core/user/reauthenticate.ts +++ b/packages/auth/src/core/user/reauthenticate.ts @@ -26,7 +26,7 @@ import { _parseToken } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { UserCredentialImpl } from './user_credential_impl'; import { _isFirebaseServerApp } from '@firebase/app'; -import { _createError } from '../../core/util/assert'; +import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; export async function _reauthenticate( user: UserInternal, @@ -36,7 +36,7 @@ export async function _reauthenticate( const { auth } = user; if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const operationType = OperationType.REAUTHENTICATE; diff --git a/packages/auth/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts index 468bdc163ce..aeb031ab855 100644 --- a/packages/auth/src/core/user/user_impl.ts +++ b/packages/auth/src/core/user/user_impl.ts @@ -32,7 +32,10 @@ import { } from '../../model/user'; import { AuthErrorCode } from '../errors'; import { PersistedBlob } from '../persistence'; -import { _assert, _createError } from '../util/assert'; +import { + _assert, + _serverAppCurrentUserOperationNotSupportedError +} from '../util/assert'; import { getIdTokenResult } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { ProactiveRefresh } from './proactive_refresh'; @@ -203,7 +206,7 @@ export class UserImpl implements UserInternal { async delete(): Promise { if (_isFirebaseServerApp(this.auth.app)) { return Promise.reject( - _createError(this.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(this.auth) ); } const idToken = await this.getIdToken(); diff --git a/packages/auth/src/core/util/assert.ts b/packages/auth/src/core/util/assert.ts index adb391b033a..51dff0793e2 100644 --- a/packages/auth/src/core/util/assert.ts +++ b/packages/auth/src/core/util/assert.ts @@ -102,6 +102,16 @@ export function _errorWithCustomMessage( }); } +export function _serverAppCurrentUserOperationNotSupportedError( + auth: Auth +): FirebaseError { + return _errorWithCustomMessage( + auth, + AuthErrorCode.OPERATION_NOT_SUPPORTED, + 'Operations that alter the current user are not supported in conjunction with FirebaseServerApp' + ); +} + export function _assertInstanceOf( auth: Auth, object: object, diff --git a/packages/auth/src/platform_browser/strategies/phone.ts b/packages/auth/src/platform_browser/strategies/phone.ts index 5e027284275..9e0c34d7058 100644 --- a/packages/auth/src/platform_browser/strategies/phone.ts +++ b/packages/auth/src/platform_browser/strategies/phone.ts @@ -31,7 +31,10 @@ import { ApplicationVerifierInternal } from '../../model/application_verifier'; import { PhoneAuthCredential } from '../../core/credentials/phone'; import { AuthErrorCode } from '../../core/errors'; import { _assertLinkedStatus, _link } from '../../core/user/link_unlink'; -import { _assert, _createError } from '../../core/util/assert'; +import { + _assert, + _serverAppCurrentUserOperationNotSupportedError +} from '../../core/util/assert'; import { AuthInternal } from '../../model/auth'; import { linkWithCredential, @@ -108,7 +111,7 @@ export async function signInWithPhoneNumber( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); @@ -174,7 +177,7 @@ export async function reauthenticateWithPhoneNumber( const userInternal = getModularInstance(user) as UserInternal; if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject( - _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(userInternal.auth) ); } const verificationId = await _verifyPhoneNumber( @@ -298,7 +301,7 @@ export async function updatePhoneNumber( const userInternal = getModularInstance(user) as UserInternal; if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject( - _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(userInternal.auth) ); } await _link(userInternal, credential); diff --git a/packages/auth/src/platform_browser/strategies/redirect.ts b/packages/auth/src/platform_browser/strategies/redirect.ts index 0c4abaf82f8..48cf5c075b2 100644 --- a/packages/auth/src/platform_browser/strategies/redirect.ts +++ b/packages/auth/src/platform_browser/strategies/redirect.ts @@ -25,7 +25,10 @@ import { import { _castAuth } from '../../core/auth/auth_impl'; import { _assertLinkedStatus } from '../../core/user/link_unlink'; -import { _assertInstanceOf, _createError } from '../../core/util/assert'; +import { + _assertInstanceOf, + _serverAppCurrentUserOperationNotSupportedError +} from '../../core/util/assert'; import { _generateEventId } from '../../core/util/event_id'; import { AuthEventType } from '../../model/popup_redirect'; import { UserInternal } from '../../model/user'; @@ -98,7 +101,7 @@ export async function _signInWithRedirect( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); @@ -172,7 +175,7 @@ export async function _reauthenticateWithRedirect( _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); if (_isFirebaseServerApp(userInternal.auth.app)) { return Promise.reject( - _createError(userInternal.auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(userInternal.auth) ); } // Wait for auth initialization to complete, this will process pending redirects and clear the @@ -311,7 +314,7 @@ export async function _getRedirectResult( ): Promise { if (_isFirebaseServerApp(auth.app)) { return Promise.reject( - _createError(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED) + _serverAppCurrentUserOperationNotSupportedError(auth) ); } const authInternal = _castAuth(auth); From 01161ef9f85e07420fd28a3e840c8295d17760ca Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 8 Mar 2024 09:08:17 -0500 Subject: [PATCH 16/19] lint fixes --- packages/auth/src/core/strategies/anonymous.ts | 1 - packages/auth/src/core/strategies/credential.ts | 1 - packages/auth/src/platform_browser/strategies/redirect.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/auth/src/core/strategies/anonymous.ts b/packages/auth/src/core/strategies/anonymous.ts index 1b903dd7c82..ee537fa8eb9 100644 --- a/packages/auth/src/core/strategies/anonymous.ts +++ b/packages/auth/src/core/strategies/anonymous.ts @@ -23,7 +23,6 @@ import { _castAuth } from '../auth/auth_impl'; import { OperationType } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; -import { AuthErrorCode } from '../../core/errors'; /** * Asynchronously signs in as an anonymous user. diff --git a/packages/auth/src/core/strategies/credential.ts b/packages/auth/src/core/strategies/credential.ts index b713db05198..476cf888095 100644 --- a/packages/auth/src/core/strategies/credential.ts +++ b/packages/auth/src/core/strategies/credential.ts @@ -29,7 +29,6 @@ import { getModularInstance } from '@firebase/util'; import { OperationType } from '../../model/enums'; import { _isFirebaseServerApp } from '@firebase/app'; import { _serverAppCurrentUserOperationNotSupportedError } from '../../core/util/assert'; -import { AuthErrorCode } from '../../core/errors'; export async function _signInWithCredential( auth: AuthInternal, diff --git a/packages/auth/src/platform_browser/strategies/redirect.ts b/packages/auth/src/platform_browser/strategies/redirect.ts index 48cf5c075b2..1084fab1b6f 100644 --- a/packages/auth/src/platform_browser/strategies/redirect.ts +++ b/packages/auth/src/platform_browser/strategies/redirect.ts @@ -40,7 +40,6 @@ import { import { FederatedAuthProvider } from '../../core/providers/federated'; import { getModularInstance } from '@firebase/util'; import { _isFirebaseServerApp } from '@firebase/app'; -import { AuthErrorCode } from '../../core/errors'; /** * Authenticates a Firebase client using a full-page redirect flow. From eabee1a2a7bd0a62e2f4089519ddcd5e4b4e268d Mon Sep 17 00:00:00 2001 From: James Daniels Date: Wed, 20 Mar 2024 17:35:31 -0400 Subject: [PATCH 17/19] Fix for authStateReady for FirebaseServerApp (#8085) It turns out the FirebaseServerApp implementation in #8005 was not blocking auth initialization, so when testing end-to-end we were seeing race conditions with auth state. In this PR I address by awaiting the user fetch and moving the implementation into AuthImpl#initializeCurrentUser for cleanliness. --- packages/auth/src/core/auth/auth_impl.ts | 45 +++++++++++++++++++++-- packages/auth/src/core/auth/initialize.ts | 37 +------------------ 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 2103306bf5a..eb7e2a322c8 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -81,6 +81,8 @@ import { _logWarn } from '../util/log'; import { _getPasswordPolicy } from '../../api/password_policy/get_password_policy'; import { PasswordPolicyInternal } from '../../model/password_policy'; import { PasswordPolicyImpl } from './password_policy_impl'; +import { getAccountInfo } from '../../api/account_management/account'; +import { UserImpl } from '../user/user_impl'; interface AsyncAction { (): Promise; @@ -174,10 +176,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } } - // Skip loading users from persistence in FirebaseServerApp Auth instances. - if (!_isFirebaseServerApp(this.app)) { - await this.initializeCurrentUser(popupRedirectResolver); - } + await this.initializeCurrentUser(popupRedirectResolver); this.lastNotifiedUid = this.currentUser?.uid || null; @@ -221,9 +220,47 @@ export class AuthImpl implements AuthInternal, _FirebaseService { await this._updateCurrentUser(user, /* skipBeforeStateCallbacks */ true); } + private async initializeCurrentUserFromIdToken( + idToken: string + ): Promise { + try { + const response = await getAccountInfo(this, { idToken }); + const user = await UserImpl._fromGetAccountInfoResponse( + this, + response, + idToken + ); + await this.directlySetCurrentUser(user); + } catch (err) { + console.warn( + 'FirebaseServerApp could not login user with provided authIdToken: ', + err + ); + await this.directlySetCurrentUser(null); + } + } + private async initializeCurrentUser( popupRedirectResolver?: PopupRedirectResolver ): Promise { + if (_isFirebaseServerApp(this.app)) { + const idToken = this.app.settings.authIdToken; + if (idToken) { + // Start the auth operation in the next tick to allow a moment for the customer's app to + // attach an emulator, if desired. + return new Promise(resolve => { + setTimeout(() => + this.initializeCurrentUserFromIdToken(idToken).then( + resolve, + resolve + ) + ); + }); + } else { + return this.directlySetCurrentUser(null); + } + } + // First check to see if we have a pending redirect event. const previouslyStoredUser = (await this.assertedPersistence.getCurrentUser()) as UserInternal | null; diff --git a/packages/auth/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts index 3bb21364718..c6218953508 100644 --- a/packages/auth/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, _isFirebaseServerApp, FirebaseApp } from '@firebase/app'; +import { _getProvider, FirebaseApp } from '@firebase/app'; import { deepEqual } from '@firebase/util'; import { Auth, Dependencies } from '../../model/public_types'; @@ -23,9 +23,7 @@ import { AuthErrorCode } from '../errors'; import { PersistenceInternal } from '../persistence'; import { _fail } from '../util/assert'; import { _getInstance } from '../util/instantiator'; -import { AuthImpl, _castAuth } from './auth_impl'; -import { UserImpl } from '../user/user_impl'; -import { getAccountInfo } from '../../api/account_management/account'; +import { AuthImpl } from './auth_impl'; /** * Initializes an {@link Auth} instance with fine-grained control over @@ -67,40 +65,9 @@ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { const auth = provider.initialize({ options: deps }) as AuthImpl; - if (_isFirebaseServerApp(app)) { - if (app.settings.authIdToken !== undefined) { - const idToken = app.settings.authIdToken; - // Start the auth operation in the next tick to allow a moment for the customer's app to - // attach an emulator, if desired. - setTimeout(() => void _loadUserFromIdToken(auth, idToken), 0); - } - } - return auth; } -export async function _loadUserFromIdToken( - auth: Auth, - idToken: string -): Promise { - try { - const response = await getAccountInfo(auth, { idToken }); - const authInternal = _castAuth(auth); - await authInternal._initializationPromise; - const user = await UserImpl._fromGetAccountInfoResponse( - authInternal, - response, - idToken - ); - await authInternal._updateCurrentUser(user); - } catch (err) { - console.warn( - 'FirebaseServerApp could not login user with provided authIdToken: ', - err - ); - } -} - export function _initializeAuthInstance( auth: AuthImpl, deps?: Dependencies From 9f74ac90aa2af89c120240785df09fbc481f2d0f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 22 Mar 2024 13:50:29 -0400 Subject: [PATCH 18/19] Remove authIDTokenVerified (#8089) We're rethinking how authIDTokenVerified should be used. It might reside in its own function. Remove the method for now. --- common/api-review/app.api.md | 1 - docs-devsite/app.firebaseserverapp.md | 15 --------------- docs-devsite/app.firebaseserverappsettings.md | 4 +--- packages/app/src/firebaseServerApp.test.ts | 6 ++---- packages/app/src/firebaseServerApp.ts | 6 ------ packages/app/src/internal.ts | 2 +- packages/app/src/public-types.ts | 18 ------------------ 7 files changed, 4 insertions(+), 48 deletions(-) diff --git a/common/api-review/app.api.md b/common/api-review/app.api.md index 538adc36471..e226940ef1b 100644 --- a/common/api-review/app.api.md +++ b/common/api-review/app.api.md @@ -73,7 +73,6 @@ export interface FirebaseOptions { // @public export interface FirebaseServerApp extends FirebaseApp { - authIdTokenVerified: () => Promise; name: string; readonly settings: FirebaseServerAppSettings; } diff --git a/docs-devsite/app.firebaseserverapp.md b/docs-devsite/app.firebaseserverapp.md index ba8680883c2..66b51c45fb2 100644 --- a/docs-devsite/app.firebaseserverapp.md +++ b/docs-devsite/app.firebaseserverapp.md @@ -25,24 +25,9 @@ export interface FirebaseServerApp extends FirebaseApp | Property | Type | Description | | --- | --- | --- | -| [authIdTokenVerified](./app.firebaseserverapp.md#firebaseserverappauthidtokenverified) | () => Promise<void> | Checks to see if the local verification of the authIdToken provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed.It is recommend that your application awaits this Promise before invoking getAuth if an authIdToken was provided in the FirebaseServerAppSettings.The returned Promise is completed immediately if the optional authIdToken parameter was omitted from FirebaseServerApp initialization. | | [name](./app.firebaseserverapp.md#firebaseserverappname) | string | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | | [settings](./app.firebaseserverapp.md#firebaseserverappsettings) | [FirebaseServerAppSettings](./app.firebaseserverappsettings.md#firebaseserverappsettings_interface) | The (read-only) configuration settings for this server app. These are the original parameters given in [initializeServerApp()](./app.md#initializeserverapp_30ab697). | -## FirebaseServerApp.authIdTokenVerified - -Checks to see if the local verification of the `authIdToken` provided to [initializeServerApp()](./app.md#initializeserverapp_30ab697) has completed. - -It is recommend that your application awaits this `Promise` before invoking `getAuth` if an `authIdToken` was provided in the `FirebaseServerAppSettings`. - -The returned `Promise` is completed immediately if the optional `authIdToken` parameter was omitted from `FirebaseServerApp` initialization. - -Signature: - -```typescript -authIdTokenVerified: () => Promise; -``` - ## FirebaseServerApp.name There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for applications. However, it may be used internally, and is declared here so that `FirebaseServerApp` conforms to the `FirebaseApp` interface. diff --git a/docs-devsite/app.firebaseserverappsettings.md b/docs-devsite/app.firebaseserverappsettings.md index 32e74fa31e2..20c335b04a6 100644 --- a/docs-devsite/app.firebaseserverappsettings.md +++ b/docs-devsite/app.firebaseserverappsettings.md @@ -23,7 +23,7 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings | Property | Type | Description | | --- | --- | --- | -| [authIdToken](./app.firebaseserverappsettings.md#firebaseserverappsettingsauthidtoken) | string | An optional Auth ID token used to resume a signed in user session from a client runtime environment.If provided, the FirebaseServerApp works to validate the token even before Auth is initialized. The result of the validation can be queried via . Awaiting the Promise returned by is highly recommended if an authIdToken token is provided.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 initalization.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. | +| [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, 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 initalization.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. | | [name](./app.firebaseserverappsettings.md#firebaseserverappsettingsname) | undefined | There is no getApp() operation for FirebaseServerApp, so the name is not relevant for applications. However, it may be used internally, and is declared here so that FirebaseServerApp conforms to the FirebaseApp interface. | | [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. | @@ -31,8 +31,6 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings An optional Auth ID token used to resume a signed in user session from a client runtime environment. -If provided, the `FirebaseServerApp` works to validate the token even before `Auth` is initialized. The result of the validation can be queried via . Awaiting the `Promise` returned by is highly recommended if an `authIdToken` token is provided. - 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 initalization. diff --git a/packages/app/src/firebaseServerApp.test.ts b/packages/app/src/firebaseServerApp.test.ts index c31d603b0e1..408b5f09922 100644 --- a/packages/app/src/firebaseServerApp.test.ts +++ b/packages/app/src/firebaseServerApp.test.ts @@ -130,11 +130,9 @@ describe('FirebaseServerApp', () => { new ComponentContainer('test') ); - expect(() => app.authIdTokenVerified).to.not.throw(); + expect(() => app.settings).to.not.throw(); (app as unknown as FirebaseServerAppImpl).isDeleted = true; - expect(() => app.authIdTokenVerified()).throws( - 'Firebase Server App has been deleted' - ); + expect(() => app.settings).throws('Firebase Server App has been deleted'); }); }); diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index a94dc7bd760..6f72b10fea0 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -115,12 +115,6 @@ export class FirebaseServerAppImpl return this._serverConfig; } - authIdTokenVerified(): Promise { - this.checkDestroyed(); - // TODO - return Promise.resolve(); - } - /** * This function will throw an Error if the App has already been deleted - * use before performing API actions on the App. diff --git a/packages/app/src/internal.ts b/packages/app/src/internal.ts index f266de95166..7e0c1545962 100644 --- a/packages/app/src/internal.ts +++ b/packages/app/src/internal.ts @@ -170,7 +170,7 @@ export function _isFirebaseApp( export function _isFirebaseServerApp( obj: FirebaseApp | FirebaseServerApp ): obj is FirebaseServerApp { - return (obj as FirebaseServerApp).authIdTokenVerified !== undefined; + return (obj as FirebaseServerApp).settings !== undefined; } /** diff --git a/packages/app/src/public-types.ts b/packages/app/src/public-types.ts index a1c2aed69a9..ad2b441933a 100644 --- a/packages/app/src/public-types.ts +++ b/packages/app/src/public-types.ts @@ -82,18 +82,6 @@ export interface FirebaseApp { * @public */ export interface FirebaseServerApp extends FirebaseApp { - /** - * Checks to see if the local verification of the `authIdToken` provided to - * {@link (initializeServerApp:1) | initializeServerApp()} has completed. - * - * It is recommend that your application awaits this `Promise` before invoking `getAuth` if an - * `authIdToken` was provided in the `FirebaseServerAppSettings`. - * - * The returned `Promise` is completed immediately if the optional `authIdToken` parameter - * was omitted from `FirebaseServerApp` initialization. - */ - authIdTokenVerified: () => Promise; - /** * There is no `getApp()` operation for `FirebaseServerApp`, so the name is not relevant for * applications. However, it may be used internally, and is declared here so that @@ -192,12 +180,6 @@ export interface FirebaseServerAppSettings extends FirebaseAppSettings { * An optional Auth ID token used to resume a signed in user session from a client * runtime environment. * - * If provided, the `FirebaseServerApp` works to validate the token even before - * `Auth` is initialized. The result of the validation can be queried via - * {@link (FirebaseServerApp.authIdTokenVerified()}. Awaiting the `Promise` returned by - * {@link (FirebaseServerApp.authIdTokenVerified()} is highly recommended if an `authIdToken` - * token is provided. - * * 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. From db244036473ae2c8d6a6b21ae7c06d3216e6ea9f Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 22 Mar 2024 20:12:13 -0400 Subject: [PATCH 19/19] [FirebaseServerApp] add a new serverapp type for usage tracking. (#8094) Add a new serverapp string so that register version to track FirebaseServerApp usage. --- packages/app/src/firebaseServerApp.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app/src/firebaseServerApp.ts b/packages/app/src/firebaseServerApp.ts index 6f72b10fea0..b40471c903f 100644 --- a/packages/app/src/firebaseServerApp.ts +++ b/packages/app/src/firebaseServerApp.ts @@ -21,10 +21,11 @@ import { FirebaseServerAppSettings, FirebaseOptions } from './public-types'; -import { deleteApp } from './api'; +import { deleteApp, registerVersion } from './api'; import { ComponentContainer } from '@firebase/component'; import { FirebaseAppImpl } from './firebaseApp'; import { ERROR_FACTORY, AppError } from './errors'; +import { name as packageName, version } from '../package.json'; export class FirebaseServerAppImpl extends FirebaseAppImpl @@ -77,6 +78,8 @@ export class FirebaseServerAppImpl // will never trigger. this._serverConfig.releaseOnDeref = undefined; serverConfig.releaseOnDeref = undefined; + + registerVersion(packageName, version, 'serverapp'); } get refCount(): number {