diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index 088facef70f..4515bb46fb7 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -306,6 +306,8 @@ toc: path: /docs/reference/js/auth.samlauthprovider.md - title: TenantConfig path: /docs/reference/js/auth.tenantconfig.md + - title: TokenResponse + path: /docs/reference/js/auth.tokenresponse.md - title: TotpMultiFactorAssertion path: /docs/reference/js/auth.totpmultifactorassertion.md - title: TotpMultiFactorGenerator diff --git a/docs-devsite/auth.auth.md b/docs-devsite/auth.auth.md index 170447bc43c..502cf53628e 100644 --- a/docs-devsite/auth.auth.md +++ b/docs-devsite/auth.auth.md @@ -33,6 +33,7 @@ export interface Auth | [settings](./auth.auth.md#authsettings) | [AuthSettings](./auth.authsettings.md#authsettings_interface) | The [Auth](./auth.auth.md#auth_interface) instance's settings. | | [tenantConfig](./auth.auth.md#authtenantconfig) | [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) | The [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) used to initialize a Regional Auth. This is only present if regional auth is initialized and DefaultConfig.REGIONAL_API_HOST backend endpoint is used. | | [tenantId](./auth.auth.md#authtenantid) | string \| null | The [Auth](./auth.auth.md#auth_interface) instance's tenant ID. | +| [tokenResponse](./auth.auth.md#authtokenresponse) | [TokenResponse](./auth.tokenresponse.md#tokenresponse_interface) \| null | The token response initialized via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. | ## Methods @@ -156,6 +157,18 @@ const result = await signInWithEmailAndPassword(auth, email, password); ``` +## Auth.tokenResponse + +The token response initialized via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. + +This field is only supported for [Auth](./auth.auth.md#auth_interface) instance that have defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface). + +Signature: + +```typescript +readonly tokenResponse: TokenResponse | null; +``` + ## Auth.authStateReady() returns a promise that resolves immediately when the initial auth state is settled. When the promise resolves, the current user might be a valid user or `null` if the user signed out. diff --git a/docs-devsite/auth.md b/docs-devsite/auth.md index ab460d58e51..4e357662166 100644 --- a/docs-devsite/auth.md +++ b/docs-devsite/auth.md @@ -139,6 +139,7 @@ Firebase Authentication | [ReactNativeAsyncStorage](./auth.reactnativeasyncstorage.md#reactnativeasyncstorage_interface) | Interface for a supplied AsyncStorage. | | [RecaptchaParameters](./auth.recaptchaparameters.md#recaptchaparameters_interface) | Interface representing reCAPTCHA parameters.See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) for the list of accepted parameters. All parameters are accepted except for sitekey: Firebase Auth provisions a reCAPTCHA for each project and will configure the site key upon rendering.For an invisible reCAPTCHA, set the size key to invisible. | | [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) | The tenant config that can be used to initialize a Regional [Auth](./auth.auth.md#auth_interface) instance. | +| [TokenResponse](./auth.tokenresponse.md#tokenresponse_interface) | Interface for TokenRespone returned via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. This is expected to be returned only if [Auth](./auth.auth.md#auth_interface) object initialized has defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface). | | [TotpMultiFactorAssertion](./auth.totpmultifactorassertion.md#totpmultifactorassertion_interface) | The class for asserting ownership of a TOTP second factor. Provided by [TotpMultiFactorGenerator.assertionForEnrollment()](./auth.totpmultifactorgenerator.md#totpmultifactorgeneratorassertionforenrollment) and [TotpMultiFactorGenerator.assertionForSignIn()](./auth.totpmultifactorgenerator.md#totpmultifactorgeneratorassertionforsignin). | | [TotpMultiFactorInfo](./auth.totpmultifactorinfo.md#totpmultifactorinfo_interface) | The subclass of the [MultiFactorInfo](./auth.multifactorinfo.md#multifactorinfo_interface) interface for TOTP second factors. The factorId of this second factor is [FactorId](./auth.md#factorid).TOTP. | | [User](./auth.user.md#user_interface) | A user account. | diff --git a/docs-devsite/auth.tokenresponse.md b/docs-devsite/auth.tokenresponse.md new file mode 100644 index 00000000000..540b529cd7d --- /dev/null +++ b/docs-devsite/auth.tokenresponse.md @@ -0,0 +1,42 @@ +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 %} + +# TokenResponse interface +Interface for TokenRespone returned via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. This is expected to be returned only if [Auth](./auth.auth.md#auth_interface) object initialized has defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface). + +Signature: + +```typescript +export interface TokenResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [expiresIn](./auth.tokenresponse.md#tokenresponseexpiresin) | string | | +| [token](./auth.tokenresponse.md#tokenresponsetoken) | string | | + +## TokenResponse.expiresIn + +Signature: + +```typescript +readonly expiresIn: string; +``` + +## TokenResponse.token + +Signature: + +```typescript +readonly token: string; +``` diff --git a/packages/auth/src/api/authentication/exchange_token.ts b/packages/auth/src/api/authentication/exchange_token.ts index dde1b8f9b2f..88fc32af54a 100644 --- a/packages/auth/src/api/authentication/exchange_token.ts +++ b/packages/auth/src/api/authentication/exchange_token.ts @@ -28,7 +28,7 @@ export interface ExchangeTokenRequest { export interface ExchangeTokenRespose { accessToken: string; - expiresIn?: string; + expiresInSec: string; } export async function exchangeToken( diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts index 8aac4fb12f4..5fcaf2eb1ca 100644 --- a/packages/auth/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -37,7 +37,8 @@ import { NextFn, Unsubscribe, PasswordValidationStatus, - TenantConfig + TenantConfig, + TokenResponse } from '../../model/public_types'; import { createSubscribe, @@ -99,6 +100,7 @@ export const enum DefaultConfig { export class AuthImpl implements AuthInternal, _FirebaseService { currentUser: User | null = null; emulatorConfig: EmulatorConfig | null = null; + tokenResponse: TokenResponse | null = null; private operations = Promise.resolve(); private persistenceManager?: PersistenceUserManager; private redirectPersistenceManager?: PersistenceUserManager; @@ -454,6 +456,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService { }); } + async _updateTokenResponse(tokenResponse: TokenResponse): Promise { + if (tokenResponse) { + this.tokenResponse = tokenResponse; + } + } + async signOut(): Promise { if (_isFirebaseServerApp(this.app)) { return Promise.reject( @@ -621,6 +629,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService { } } + async getTokenForRegionalAuth(): + Promise { + if (Date.now() > this.tokenResponse?.expiresIn) + } + toJSON(): object { return { apiKey: this.config.apiKey, diff --git a/packages/auth/src/core/auth/firebase_internal.ts b/packages/auth/src/core/auth/firebase_internal.ts index 4fad0754375..9c267d9cc33 100644 --- a/packages/auth/src/core/auth/firebase_internal.ts +++ b/packages/auth/src/core/auth/firebase_internal.ts @@ -43,6 +43,9 @@ export class AuthInterop implements FirebaseAuthInternal { ): Promise<{ accessToken: string } | null> { this.assertAuthConfigured(); await this.auth._initializationPromise; + if (this.auth.tenantConfig) { + return this.getTokenRegionalAuth(); + } if (!this.auth.currentUser) { return null; } @@ -51,6 +54,23 @@ export class AuthInterop implements FirebaseAuthInternal { return { accessToken }; } + async getTokenRegionalAuth() : + Promise<{ accessToken: string } | null> { + this.assertRegionalAuthConfigured(); + if (!this.auth.tokenResponse) { + return null; + } + + if (!this.auth.tokenResponse.expirationTime || + Date.now() > this.auth.tokenResponse.expirationTime) { + await this.auth._updateTokenResponse(null); + return null; + } + + const accessToken = await this.auth.tokenResponse.token; + return { accessToken }; + } + addAuthTokenListener(listener: TokenListener): void { this.assertAuthConfigured(); if (this.internalListeners.has(listener)) { @@ -85,6 +105,10 @@ export class AuthInterop implements FirebaseAuthInternal { ); } + private assertRegionalAuthConfigured(): void { + _assert(this.auth.tenantConfig, AuthErrorCode.OPERATION_NOT_ALLOWED); + } + private updateProactiveRefresh(): void { if (this.internalListeners.size > 0) { this.auth._startProactiveRefresh(); diff --git a/packages/auth/src/core/strategies/exchange_token.test.ts b/packages/auth/src/core/strategies/exchange_token.test.ts index 60166034b64..1548735de2d 100644 --- a/packages/auth/src/core/strategies/exchange_token.test.ts +++ b/packages/auth/src/core/strategies/exchange_token.test.ts @@ -61,6 +61,8 @@ describe('core/strategies/exchangeToken', () => { 'projects/test-project-id/locations/us/tenants/tenant-1/idpConfigs/idp-config', token: 'custom-token' }); + expect(regionalAuth.tokenResponse?.token).to.equal('outbound-token'); + expect(regionalAuth.tokenResponse?.expiresIn).to.equal('1000'); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages/auth/src/core/strategies/exhange_token.ts b/packages/auth/src/core/strategies/exhange_token.ts index 8b2c37a2b93..c5fb88816a2 100644 --- a/packages/auth/src/core/strategies/exhange_token.ts +++ b/packages/auth/src/core/strategies/exhange_token.ts @@ -54,7 +54,12 @@ export async function exchangeToken( parent: buildParent(auth, idpConfigId), token: customToken }); - // TODO(sammansi): Write token to the Auth object passed. + if (token) { + await authInternal._updateTokenResponse({ + token: token.accessToken, + expirationTime: Date.now() + Number(token.expiresInSec) * 1000 + }); + } return token.accessToken; } diff --git a/packages/auth/src/model/auth.ts b/packages/auth/src/model/auth.ts index 60faedef4e6..4928c17ad5a 100644 --- a/packages/auth/src/model/auth.ts +++ b/packages/auth/src/model/auth.ts @@ -24,6 +24,7 @@ import { PasswordValidationStatus, PopupRedirectResolver, TenantConfig, + TokenResponse, User } from './public_types'; import { ErrorFactory } from '@firebase/util'; @@ -66,6 +67,7 @@ export interface ConfigInternal extends Config { export interface AuthInternal extends Auth { currentUser: User | null; emulatorConfig: EmulatorConfig | null; + tokenResponse: TokenResponse | null; _agentRecaptchaConfig: RecaptchaConfig | null; _tenantRecaptchaConfigs: Record; _projectPasswordPolicy: PasswordPolicy | null; @@ -75,6 +77,7 @@ export interface AuthInternal extends Auth { _initializationPromise: Promise | null; _persistenceManagerAvailable: Promise; _updateCurrentUser(user: UserInternal | null): Promise; + _updateTokenResponse(tokenResponse: TokenResponse | null): Promise; _onStorageEvent(): void; diff --git a/packages/auth/src/model/public_types.ts b/packages/auth/src/model/public_types.ts index e7275aeee4c..99fec4e9a5e 100644 --- a/packages/auth/src/model/public_types.ts +++ b/packages/auth/src/model/public_types.ts @@ -334,6 +334,14 @@ export interface Auth { * {@link @firebase/app#FirebaseServerApp}. */ signOut(): Promise; + /** + * The token response initialized via {@link exchangeToken} endpoint. + * + * @remarks + * This field is only supported for {@link Auth} instance that have defined + * {@link TenantConfig}. + */ + readonly tokenResponse: TokenResponse | null; } /** @@ -966,6 +974,18 @@ export interface ReactNativeAsyncStorage { removeItem(key: string): Promise; } +/** + * Interface for TokenRespone returned via {@link exchangeToken} endpoint. + * This is expected to be returned only if {@link Auth} object initialized + * has defined {@link TenantConfig}. + */ +export interface TokenResponse { + // The firebase access token (JWT signed by Firebase Auth). + readonly token: string; + // The time when the access token expires. + readonly expirationTime: number; +} + /** * A user account. * diff --git a/packages/auth/test/helpers/api/helper.ts b/packages/auth/test/helpers/api/helper.ts index 1877371bf2e..00680be3341 100644 --- a/packages/auth/test/helpers/api/helper.ts +++ b/packages/auth/test/helpers/api/helper.ts @@ -63,6 +63,5 @@ export function mockRegionalEndpointWithParent( status = 200 ): Route { const url = `${TEST_SCHEME}://${TEST_HOST}${parent}${endpoint}`; - console.log('here ', url); return mock(url, response, status); }