From 217872780250e4ff097ab3e2d996d25881c54e9e Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 25 May 2021 17:23:32 -0400 Subject: [PATCH 1/2] feat(fac): Expose FAC APIs from firebase-admin/app-check entry point --- entrypoints.json | 4 + etc/firebase-admin.api.md | 34 ++-- etc/firebase-admin.app-check.api.md | 47 ++++++ package-lock.json | 30 ++-- package.json | 7 + .../app-check-api-client-internal.ts | 22 ++- src/app-check/app-check-api.ts | 98 ++++++++++++ src/app-check/app-check-namespace.ts | 74 +++++++++ src/app-check/app-check.ts | 25 +-- src/app-check/index.ts | 150 ++++-------------- src/app-check/token-generator.ts | 2 +- src/app-check/token-verifier.ts | 7 +- src/app/firebase-app.ts | 8 +- src/auth/token-generator.ts | 4 +- src/firebase-namespace-api.ts | 4 +- src/utils/crypto-signer.ts | 5 +- .../integration/postcheck/esm/example.test.js | 6 + .../typescript/example-modular.test.ts | 6 + test/unit/app-check/token-generator.spec.ts | 4 +- test/unit/app/firebase-namespace.spec.ts | 2 +- 20 files changed, 334 insertions(+), 205 deletions(-) create mode 100644 etc/firebase-admin.app-check.api.md create mode 100644 src/app-check/app-check-api.ts create mode 100644 src/app-check/app-check-namespace.ts diff --git a/entrypoints.json b/entrypoints.json index 3c2d6c1e0c..d35b7b8edf 100644 --- a/entrypoints.json +++ b/entrypoints.json @@ -8,6 +8,10 @@ "typings": "./lib/app/index.d.ts", "dist": "./lib/app/index.js" }, + "firebase-admin/app-check": { + "typings": "./lib/app-check/index.d.ts", + "dist": "./lib/app-check/index.js" + }, "firebase-admin/auth": { "typings": "./lib/auth/index.d.ts", "dist": "./lib/auth/index.js" diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index a434fbc62a..c82698b5a3 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -44,34 +44,18 @@ export namespace app { } // @public -export function appCheck(app?: app.App): appCheck.AppCheck; +export function appCheck(app?: App): appCheck.AppCheck; // @public (undocumented) export namespace appCheck { - export interface AppCheck { - // (undocumented) - app: app.App; - createToken(appId: string): Promise; - verifyToken(appCheckToken: string): Promise; - } - export interface AppCheckToken { - token: string; - ttlMillis: number; - } - export interface DecodedAppCheckToken { - // (undocumented) - [key: string]: any; - app_id: string; - aud: string[]; - exp: number; - iat: number; - iss: string; - sub: string; - } - export interface VerifyAppCheckTokenResponse { - appId: string; - token: appCheck.DecodedAppCheckToken; - } + // Warning: (ae-forgotten-export) The symbol "AppCheck" needs to be exported by the entry point default-namespace.d.ts + export type AppCheck = AppCheck; + // Warning: (ae-forgotten-export) The symbol "AppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type AppCheckToken = AppCheckToken; + // Warning: (ae-forgotten-export) The symbol "DecodedAppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type DecodedAppCheckToken = DecodedAppCheckToken; + // Warning: (ae-forgotten-export) The symbol "VerifyAppCheckTokenResponse" needs to be exported by the entry point default-namespace.d.ts + export type VerifyAppCheckTokenResponse = VerifyAppCheckTokenResponse; } // @public diff --git a/etc/firebase-admin.app-check.api.md b/etc/firebase-admin.app-check.api.md new file mode 100644 index 0000000000..7455692bec --- /dev/null +++ b/etc/firebase-admin.app-check.api.md @@ -0,0 +1,47 @@ +## API Report File for "firebase-admin.app-check" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public +export class AppCheck { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly app: App; + createToken(appId: string): Promise; + verifyToken(appCheckToken: string): Promise; +} + +// @public +export interface AppCheckToken { + token: string; + ttlMillis: number; +} + +// @public +export interface DecodedAppCheckToken { + // (undocumented) + [key: string]: any; + app_id: string; + aud: string[]; + exp: number; + iat: number; + iss: string; + sub: string; +} + +// @public +export function getAppCheck(app?: App): AppCheck; + +// @public +export interface VerifyAppCheckTokenResponse { + appId: string; + token: DecodedAppCheckToken; +} + + +``` diff --git a/package-lock.json b/package-lock.json index 4fcc7c284c..d1097b9d83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1058,9 +1058,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.20.tgz", - "integrity": "sha512-8qqFN4W53IEWa9bdmuVrUcVkFemQWnt5DKPQ/oa8xKDYgtjCr2OO6NX5TIK49NLFr3mPYU2cLh92DQquC3oWWQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -1141,9 +1141,9 @@ } }, "@types/node": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz", - "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==" + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.0.tgz", + "integrity": "sha512-+aHJvoCsVhO2ZCuT4o5JtcPrCPyDE3+1nvbDprYes+pPkEsbjH7AGUCNtjMOXS0fqH14t+B7yLzaqSz92FPWyw==" }, "@types/qs": { "version": "6.9.6", @@ -2033,9 +2033,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001232", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001232.tgz", - "integrity": "sha512-e4Gyp7P8vqC2qV2iHA+cJNf/yqUKOShXQOJHQt81OHxlIZl/j/j3soEA0adAQi8CPUQgvOdDENyQ5kd6a6mNSg==", + "version": "1.0.30001233", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001233.tgz", + "integrity": "sha512-BmkbxLfStqiPA7IEzQpIk0UFZFf3A4E6fzjPJ6OR+bFC2L8ES9J8zGA/asoi47p8XDVkev+WJo2I2Nc8c/34Yg==", "dev": true }, "caseless": { @@ -2804,9 +2804,9 @@ } }, "electron-to-chromium": { - "version": "1.3.743", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.743.tgz", - "integrity": "sha512-K2wXfo9iZQzNJNx67+Pld0DRF+9bYinj62gXCdgPhcu1vidwVuLPHQPPFnCdO55njWigXXpfBiT90jGUPbw8Zg==", + "version": "1.3.746", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.746.tgz", + "integrity": "sha512-3ffyGODL38apwSsIgXaWnAKNXChsjXhAmBTjbqCbrv1fBbVltuNLWh0zdrQbwK/oxPQ/Gss/kYfFAPPGu9mszQ==", "dev": true }, "emoji-regex": { @@ -4118,9 +4118,9 @@ } }, "google-auth-library": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.0.tgz", - "integrity": "sha512-X+gbkGjnLN3HUZP2W3KBREuA603BXd80ITvL0PeS0QpyDNYz/u0pIZ7aRuGnrSuUc0grk/qxEgtVTFt1ogbP+A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.1.tgz", + "integrity": "sha512-+Q1linq/To3DYLyPz4UTEkQ0v5EOXadMM/S+taLV3W9611hq9zqg8kgGApqbTQnggtwdO9yU1y2YT7+83wdTRg==", "optional": true, "requires": { "arrify": "^2.0.0", diff --git a/package.json b/package.json index f26c11e9c6..a9e5f9a153 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,9 @@ "app": [ "lib/app" ], + "app-check": [ + "lib/app-check" + ], "auth": [ "lib/auth" ], @@ -103,6 +106,10 @@ "require": "./lib/app/index.js", "import": "./lib/esm/app/index.js" }, + "./app-check": { + "require": "./lib/app-check/index.js", + "import": "./lib/esm/app-check/index.js" + }, "./auth": { "require": "./lib/auth/index.js", "import": "./lib/esm/auth/index.js" diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts index 640acb72aa..a57fa0a8e4 100644 --- a/src/app-check/app-check-api-client-internal.ts +++ b/src/app-check/app-check-api-client-internal.ts @@ -15,17 +15,15 @@ * limitations under the License. */ -import { appCheck } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; -import { FirebaseApp } from '../app/firebase-app'; import { PrefixedFirebaseError } from '../utils/error'; - import * as utils from '../utils/index'; import * as validator from '../utils/validator'; - -import AppCheckToken = appCheck.AppCheckToken; +import { AppCheckToken } from './app-check-api' // App Check backend constants const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1beta/projects/{projectId}/apps/{appId}:exchangeCustomToken'; @@ -43,13 +41,13 @@ export class AppCheckApiClient { private readonly httpClient: HttpClient; private projectId?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseAppCheckError( 'invalid-argument', 'First argument passed to admin.appCheck() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** @@ -57,7 +55,7 @@ export class AppCheckApiClient { * * @param customToken The custom token to be exchanged. * @param appId The mobile App ID. - * @return A promise that fulfills with a `AppCheckToken`. + * @returns A promise that fulfills with a `AppCheckToken`. */ public exchangeToken(customToken: string, appId: string): Promise { if (!validator.isNonEmptyString(appId)) { @@ -143,7 +141,7 @@ export class AppCheckApiClient { * Creates an AppCheckToken from the API response. * * @param resp API response object. - * @return An AppCheckToken instance. + * @returns An AppCheckToken instance. */ private toAppCheckToken(resp: HttpResponse): AppCheckToken { const token = resp.data.attestationToken; @@ -164,7 +162,7 @@ export class AppCheckApiClient { * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", * and 3 seconds and 1 microsecond is expressed as "3.000001s". * - * @return The duration in milliseconds. + * @returns The duration in milliseconds. */ private stringToMilliseconds(duration: string): number { if (!validator.isNonEmptyString(duration) || !duration.endsWith('s')) { @@ -211,8 +209,8 @@ export type AppCheckErrorCode = /** * Firebase App Check error code structure. This extends PrefixedFirebaseError. * - * @param {AppCheckErrorCode} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseAppCheckError extends PrefixedFirebaseError { diff --git a/src/app-check/app-check-api.ts b/src/app-check/app-check-api.ts new file mode 100644 index 0000000000..30ee4f46ab --- /dev/null +++ b/src/app-check/app-check-api.ts @@ -0,0 +1,98 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * 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. + */ + +/** + * Interface representing an App Check token. + */ +export interface AppCheckToken { + /** + * The Firebase App Check token. + */ + token: string; + + /** + * The time-to-live duration of the token in milliseconds. + */ + ttlMillis: number; +} + +/** + * Interface representing a decoded Firebase App Check token, returned from the + * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. + */ +export interface DecodedAppCheckToken { + /** + * The issuer identifier for the issuer of the response. + * + * This value is a URL with the format + * `https://firebaseappcheck.googleapis.com/`, where `` is the + * same project number specified in the [`aud`](#aud) property. + */ + iss: string; + + /** + * The Firebase App ID corresponding to the app the token belonged to. + * + * As a convenience, this value is copied over to the [`app_id`](#app_id) property. + */ + sub: string; + + /** + * The audience for which this token is intended. + * + * This value is a JSON array of two strings, the first is the project number of your + * Firebase project, and the second is the project ID of the same project. + */ + aud: string[]; + + /** + * The App Check token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this App Check token expires and should no longer be considered valid. + */ + exp: number; + + /** + * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this App Check token was issued and should start to be considered + * valid. + */ + iat: number; + + /** + * The App ID corresponding to the App the App Check token belonged to. + * + * This value is not actually one of the JWT token claims. It is added as a + * convenience, and is set as the value of the [`sub`](#sub) property. + */ + app_id: string; + [key: string]: any; +} + +/** + * Interface representing a verified App Check token response. + */ +export interface VerifyAppCheckTokenResponse { + /** + * The App ID corresponding to the App the App Check token belonged to. + */ + appId: string; + + /** + * The decoded Firebase App Check token. + */ + token: DecodedAppCheckToken; +} diff --git a/src/app-check/app-check-namespace.ts b/src/app-check/app-check-namespace.ts new file mode 100644 index 0000000000..4e5de379b2 --- /dev/null +++ b/src/app-check/app-check-namespace.ts @@ -0,0 +1,74 @@ +/*! + * Copyright 2021 Google Inc. + * + * 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 { App } from '../app'; +import { + AppCheckToken as TAppCheckToken, + DecodedAppCheckToken as TDecodedAppCheckToken, + VerifyAppCheckTokenResponse as TVerifyAppCheckTokenResponse, +} from './app-check-api'; +import { AppCheck as TAppCheck } from './app-check'; + +/** + * Gets the {@link firebase-admin.app-check#AppCheck} service for the default app or a given app. + * + * `admin.appCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `admin.appCheck(app)` to access the + * `AppCheck` service associated with a specific app. + * + * @example + * ```javascript + * // Get the `AppCheck` service for the default app + * var defaultAppCheck = admin.appCheck(); + * ``` + * + * @example + * ```javascript + * // Get the `AppCheck` service for a given app + * var otherAppCheck = admin.appCheck(otherApp); + * ``` + * + * @param app Optional app for which to return the `AppCheck` service. + * If not provided, the default `AppCheck` service is returned. + * + * @returns The default `AppCheck` service if no + * app is provided, or the `AppCheck` service associated with the provided + * app. + */ +export declare function appCheck(app?: App): appCheck.AppCheck; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace appCheck { + /** + * Type alias to {@link firebase-admin.app-check#AppCheck}. + */ + export type AppCheck = TAppCheck; + + /** + * Type alias to {@link firebase-admin.app-check#AppCheckToken}. + */ + export type AppCheckToken = TAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#DecodedAppCheckToken}. + */ + export type DecodedAppCheckToken = TDecodedAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#VerifyAppCheckTokenResponse}. + */ + export type VerifyAppCheckTokenResponse = TVerifyAppCheckTokenResponse; +} diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts index 1e79980820..815ab7bf8d 100644 --- a/src/app-check/app-check.ts +++ b/src/app-check/app-check.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { appCheck } from './index'; +import { App } from '../app'; import { AppCheckApiClient } from './app-check-api-client-internal'; import { appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator @@ -23,15 +23,15 @@ import { import { AppCheckTokenVerifier } from './token-verifier'; import { cryptoSignerFromApp } from '../utils/crypto-signer'; -import AppCheckInterface = appCheck.AppCheck; -import AppCheckToken = appCheck.AppCheckToken; -import VerifyAppCheckTokenResponse = appCheck.VerifyAppCheckTokenResponse; -import { FirebaseApp } from '../app/firebase-app'; +import { + AppCheckToken, + VerifyAppCheckTokenResponse, +} from './app-check-api'; /** - * AppCheck service bound to the provided app. + * The Firebase `AppCheck` service interface. */ -export class AppCheck implements AppCheckInterface { +export class AppCheck { private readonly client: AppCheckApiClient; private readonly tokenGenerator: AppCheckTokenGenerator; @@ -40,8 +40,9 @@ export class AppCheck implements AppCheckInterface { /** * @param app The app for this AppCheck service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new AppCheckApiClient(app); try { this.tokenGenerator = new AppCheckTokenGenerator(cryptoSignerFromApp(app)); @@ -67,12 +68,14 @@ export class AppCheck implements AppCheckInterface { } /** - * Verifies an App Check token. + * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. * * @param appCheckToken The App Check token to verify. * - * @returns A promise that fulfills with a `VerifyAppCheckTokenResponse` on successful - * verification. + * @returns A promise fulfilled with the token's decoded claims + * if the App Check token is valid; otherwise, a rejected promise. */ public verifyToken(appCheckToken: string): Promise { return this.appCheckTokenVerifier.verifyToken(appCheckToken) diff --git a/src/app-check/index.ts b/src/app-check/index.ts index ef347be57f..cf057ad7ab 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -15,28 +15,40 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase App Check. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { AppCheck } from './app-check'; + +export { + AppCheckToken, + DecodedAppCheckToken, + VerifyAppCheckTokenResponse, +} from './app-check-api'; +export { AppCheck } from './app-check'; /** - * Gets the {@link appCheck.AppCheck `AppCheck`} service for the - * default app or a given app. + * Gets the {@link AppCheck} service for the default app or a given app. * - * You can call `admin.appCheck()` with no arguments to access the default - * app's {@link appCheck.AppCheck `AppCheck`} service or as - * `admin.appCheck(app)` to access the - * {@link appCheck.AppCheck `AppCheck`} service associated with a - * specific app. + * `getAppCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `getAppCheck(app)` to access the + * `AppCheck` service associated with a specific app. * * @example * ```javascript * // Get the `AppCheck` service for the default app - * var defaultAppCheck = admin.appCheck(); + * const defaultAppCheck = getAppCheck(); * ``` * * @example * ```javascript * // Get the `AppCheck` service for a given app - * var otherAppCheck = admin.appCheck(otherApp); + * const otherAppCheck = getAppCheck(otherApp); * ``` * * @param app Optional app for which to return the `AppCheck` service. @@ -46,119 +58,11 @@ import { app } from '../firebase-namespace-api'; * app is provided, or the `AppCheck` service associated with the provided * app. */ -export declare function appCheck(app?: app.App): appCheck.AppCheck; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace appCheck { - /** - * The Firebase `AppCheck` service interface. - */ - export interface AppCheck { - app: app.App; - - /** - * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent - * back to a client. - * - * @param appId The App ID of the Firebase App the token belongs to. - * - * @returns A promise that fulfills with a `AppCheckToken`. - */ - createToken(appId: string): Promise; - - /** - * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is - * fulfilled with the token's decoded claims; otherwise, the promise is - * rejected. - * - * @param appCheckToken The App Check token to verify. - * - * @returns A promise fulfilled with the - * token's decoded claims if the App Check token is valid; otherwise, a rejected - * promise. - */ - verifyToken(appCheckToken: string): Promise; - } - - /** - * Interface representing an App Check token. - */ - export interface AppCheckToken { - /** - * The Firebase App Check token. - */ - token: string; - - /** - * The time-to-live duration of the token in milliseconds. - */ - ttlMillis: number; - } - - /** - * Interface representing a decoded Firebase App Check token, returned from the - * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. - */ - export interface DecodedAppCheckToken { - /** - * The issuer identifier for the issuer of the response. - * - * This value is a URL with the format - * `https://firebaseappcheck.googleapis.com/`, where `` is the - * same project number specified in the [`aud`](#aud) property. - */ - iss: string; - - /** - * The Firebase App ID corresponding to the app the token belonged to. - * - * As a convenience, this value is copied over to the [`app_id`](#app_id) property. - */ - sub: string; - - /** - * The audience for which this token is intended. - * - * This value is a JSON array of two strings, the first is the project number of your - * Firebase project, and the second is the project ID of the same project. - */ - aud: string[]; - - /** - * The App Check token's expiration time, in seconds since the Unix epoch. That is, the - * time at which this App Check token expires and should no longer be considered valid. - */ - exp: number; - - /** - * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the - * time at which this App Check token was issued and should start to be considered - * valid. - */ - iat: number; - - /** - * The App ID corresponding to the App the App Check token belonged to. - * - * This value is not actually one of the JWT token claims. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. - */ - app_id: string; - [key: string]: any; +export function getAppCheck(app?: App): AppCheck { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Interface representing a verified App Check token response. - */ - export interface VerifyAppCheckTokenResponse { - /** - * The App ID corresponding to the App the App Check token belonged to. - */ - appId: string; - - /** - * The decoded Firebase App Check token. - */ - token: appCheck.DecodedAppCheckToken; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('appCheck', (app) => new AppCheck(app)); } diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index 1b557438bb..0ebe9196eb 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -123,7 +123,7 @@ export function appCheckErrorFromCryptoSignerError(err: Error): Error { code = APP_CHECK_ERROR_CODE_MAPPING[status]; } return new FirebaseAppCheckError(code, - `Error returned from server while siging a custom token: ${description}` + `Error returned from server while signing a custom token: ${description}` ); } return new FirebaseAppCheckError('internal-error', diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index cf37b46ce6..9924f62b3f 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -14,17 +14,16 @@ * limitations under the License. */ -import { appCheck } from '.'; import * as validator from '../utils/validator'; import * as util from '../utils/index'; import { FirebaseAppCheckError } from './app-check-api-client-internal'; -import { FirebaseApp } from '../app/firebase-app'; import { ALGORITHM_RS256, DecodedToken, decodeJwt, JwtError, JwtErrorCode, PublicKeySignatureVerifier, SignatureVerifier } from '../utils/jwt'; -import DecodedAppCheckToken = appCheck.DecodedAppCheckToken; +import { DecodedAppCheckToken } from './app-check-api' +import { App } from '../app'; const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'; const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; @@ -37,7 +36,7 @@ const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; export class AppCheckTokenVerifier { private readonly signatureVerifier: SignatureVerifier; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { this.signatureVerifier = PublicKeySignatureVerifier.withJwksUrl(JWKS_URL); } diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 68cfe47adb..0fdabacbbd 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -24,7 +24,7 @@ import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; -import { AppCheck } from '../app-check/app-check'; +import { AppCheck } from '../app-check/index'; import { MachineLearning } from '../machine-learning/index'; import { Messaging } from '../messaging/index'; import { Storage } from '../storage/index'; @@ -202,10 +202,8 @@ export class FirebaseApp implements app.App { * @returns The AppCheck service instance of this app. */ public appCheck(): AppCheck { - return this.ensureService_('appCheck', () => { - const appCheckService: typeof AppCheck = require('../app-check/app-check').AppCheck; - return new appCheckService(this); - }); + const fn = require('../app-check/index').getAppCheck; + return fn(this); } /** diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index c2e5b8ab90..71da2390b1 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -187,8 +187,8 @@ export class FirebaseTokenGenerator { /** * Returns whether or not the provided developer claims are valid. * - * @param {object} [developerClaims] Optional developer claims to validate. - * @returns {boolean} True if the provided claims are valid; otherwise, false. + * @param developerClaims Optional developer claims to validate. + * @returns True if the provided claims are valid; otherwise, false. */ private isDeveloperClaimsValid_(developerClaims?: object): boolean { if (typeof developerClaims === 'undefined') { diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index d75fb9a17e..d1bea3d509 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { appCheck } from './app-check/index'; +import { appCheck } from './app-check/app-check-namespace'; import { auth } from './auth/auth-namespace'; import { database } from './database/database-namespace'; import { firestore } from './firestore/firestore-namespace'; @@ -77,8 +77,8 @@ export namespace app { } export * from './credential/index'; +export { appCheck } from './app-check/app-check-namespace'; export { auth } from './auth/auth-namespace'; -export { appCheck } from './app-check/index'; export { database } from './database/database-namespace'; export { firestore } from './firestore/firestore-namespace'; export { instanceId } from './instance-id/instance-id-namespace'; diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts index 569d5212f7..d34ace3feb 100644 --- a/src/utils/crypto-signer.ts +++ b/src/utils/crypto-signer.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { ServiceAccountCredential } from '../app/credential-internal'; import { AuthorizedHttpClient, HttpRequestConfig, HttpClient, HttpError } from './api-request'; @@ -190,13 +191,13 @@ export class IAMSigner implements CryptoSigner { * @param app A FirebaseApp instance. * @returns A CryptoSigner instance. */ -export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { +export function cryptoSignerFromApp(app: App): CryptoSigner { const credential = app.options.credential; if (credential instanceof ServiceAccountCredential) { return new ServiceAccountSigner(credential); } - return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); + return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); } /** diff --git a/test/integration/postcheck/esm/example.test.js b/test/integration/postcheck/esm/example.test.js index 323b226982..c731577f66 100644 --- a/test/integration/postcheck/esm/example.test.js +++ b/test/integration/postcheck/esm/example.test.js @@ -18,6 +18,7 @@ import { expect } from 'chai'; import { cert, deleteApp, initializeApp } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; import { getAuth, Auth } from 'firebase-admin/auth'; import { getDatabase, getDatabaseWithUrl, ServerValue } from 'firebase-admin/database'; import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; @@ -47,6 +48,11 @@ describe('ESM entry points', () => { expect(app.name).to.equal('TestApp'); }); + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + it('Should return an Auth client', () => { const client = getAuth(app); expect(client).to.be.instanceOf(Auth); diff --git a/test/integration/postcheck/typescript/example-modular.test.ts b/test/integration/postcheck/typescript/example-modular.test.ts index 8f607e448a..0d67abade4 100644 --- a/test/integration/postcheck/typescript/example-modular.test.ts +++ b/test/integration/postcheck/typescript/example-modular.test.ts @@ -18,6 +18,7 @@ import { expect } from 'chai'; import { cert, deleteApp, initializeApp, App } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; import { getAuth, Auth } from 'firebase-admin/auth'; import { getDatabase, getDatabaseWithUrl, Database, ServerValue } from 'firebase-admin/database'; import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; @@ -53,6 +54,11 @@ describe('Modular API', () => { expect(app.name).to.equal('TestApp'); }); + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + it('Should return an Auth client', () => { const client = getAuth(app); expect(client).to.be.instanceOf(Auth); diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index 2a7431f9cd..ba47850f30 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -225,7 +225,7 @@ describe('AppCheckTokenGenerator', () => { expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); expect(appCheckError).to.have.property('message', - 'Error returned from server while siging a custom token: server error.'); + 'Error returned from server while signing a custom token: server error.'); }); it('should convert CryptoSignerError HttpError with no error.message to FirebaseAppCheckError', () => { @@ -240,7 +240,7 @@ describe('AppCheckTokenGenerator', () => { expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); expect(appCheckError).to.have.property('message', - 'Error returned from server while siging a custom token: '+ + 'Error returned from server while signing a custom token: '+ '{"status":500,"headers":{},"data":{"error":{}},"text":"{\\"error\\":{}}"}'); }); diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index b1f3854f16..220d76579b 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -51,8 +51,8 @@ import { app, auth, messaging, machineLearning, storage, firestore, database, instanceId, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; -import { Auth as AuthImpl } from '../../../src/auth/auth'; import { AppCheck as AppCheckImpl } from '../../../src/app-check/app-check'; +import { Auth as AuthImpl } from '../../../src/auth/auth'; import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; From e9f08b605a6d4722c10ced44780db55aec2db93f Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 3 Jun 2021 17:57:17 -0400 Subject: [PATCH 2/2] Fix tsdoc syntax --- src/app-check/app-check-api.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app-check/app-check-api.ts b/src/app-check/app-check-api.ts index 30ee4f46ab..4eeaa1bce5 100644 --- a/src/app-check/app-check-api.ts +++ b/src/app-check/app-check-api.ts @@ -32,28 +32,25 @@ export interface AppCheckToken { /** * Interface representing a decoded Firebase App Check token, returned from the - * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. + * {@link AppCheck.verifyToken} method. */ export interface DecodedAppCheckToken { /** * The issuer identifier for the issuer of the response. - * * This value is a URL with the format * `https://firebaseappcheck.googleapis.com/`, where `` is the - * same project number specified in the [`aud`](#aud) property. + * same project number specified in the {@link DecodedAppCheckToken.aud | aud} property. */ iss: string; /** * The Firebase App ID corresponding to the app the token belonged to. - * - * As a convenience, this value is copied over to the [`app_id`](#app_id) property. + * As a convenience, this value is copied over to the {@link DecodedAppCheckToken.app_id | app_id} property. */ sub: string; /** * The audience for which this token is intended. - * * This value is a JSON array of two strings, the first is the project number of your * Firebase project, and the second is the project ID of the same project. */ @@ -74,9 +71,8 @@ export interface DecodedAppCheckToken { /** * The App ID corresponding to the App the App Check token belonged to. - * * This value is not actually one of the JWT token claims. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. + * convenience, and is set as the value of the {@link DecodedAppCheckToken.sub | sub} property. */ app_id: string; [key: string]: any;