From 4f7f84055a40a2e38ea0aba82cbb9877885868c3 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 14 Apr 2021 17:19:33 -0700 Subject: [PATCH 01/18] Add mockUserToken support for database emulator. --- .../database/src/core/AuthTokenProvider.ts | 13 +- packages/database/src/exp/Database.ts | 43 +++++- packages/util/index.node.ts | 1 + packages/util/index.ts | 1 + packages/util/src/emulator.ts | 140 ++++++++++++++++++ packages/util/test/emulator.test.ts | 58 ++++++++ 6 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 packages/util/src/emulator.ts create mode 100644 packages/util/test/emulator.test.ts diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index 1662eea2770..0feb61975a8 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -109,20 +109,23 @@ export class FirebaseAuthTokenProvider implements AuthTokenProvider { } } -/* Auth token provider that the Admin SDK uses to connect to the Emulator. */ -export class EmulatorAdminTokenProvider implements AuthTokenProvider { - private static EMULATOR_AUTH_TOKEN = 'owner'; +/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */ +export class EmulatorTokenProvider implements AuthTokenProvider { + /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */ + static OWNER = 'owner'; + + constructor(private accessToken: string) {} getToken(forceRefresh: boolean): Promise { return Promise.resolve({ - accessToken: EmulatorAdminTokenProvider.EMULATOR_AUTH_TOKEN + accessToken: this.accessToken }); } addTokenChangeListener(listener: (token: string | null) => void): void { // Invoke the listener immediately to match the behavior in Firebase Auth // (see packages/auth/src/auth.js#L1807) - listener(EmulatorAdminTokenProvider.EMULATOR_AUTH_TOKEN); + listener(this.accessToken); } removeTokenChangeListener(listener: (token: string | null) => void): void {} diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts index 5f0ac58c144..1715cc7253c 100644 --- a/packages/database/src/exp/Database.ts +++ b/packages/database/src/exp/Database.ts @@ -24,11 +24,15 @@ import { } from '@firebase/app-exp'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; -import { getModularInstance } from '@firebase/util'; +import { + getModularInstance, + createMockUserToken, + FirebaseIdToken +} from '@firebase/util'; import { AuthTokenProvider, - EmulatorAdminTokenProvider, + EmulatorTokenProvider, FirebaseAuthTokenProvider } from '../core/AuthTokenProvider'; import { Repo, repoInterrupt, repoResume, repoStart } from '../core/Repo'; @@ -74,7 +78,8 @@ let useRestClient = false; function repoManagerApplyEmulatorSettings( repo: Repo, host: string, - port: number + port: number, + tokenProvider?: AuthTokenProvider ): void { repo.repoInfo_ = new RepoInfo( `${host}:${port}`, @@ -86,8 +91,8 @@ function repoManagerApplyEmulatorSettings( repo.repoInfo_.includeNamespaceInQueryParams ); - if (repo.repoInfo_.nodeAdmin) { - repo.authTokenProvider_ = new EmulatorAdminTokenProvider(); + if (tokenProvider) { + repo.authTokenProvider_ = tokenProvider; } } @@ -135,7 +140,7 @@ export function repoManagerDatabaseFromApp( const authTokenProvider = nodeAdmin && isEmulator - ? new EmulatorAdminTokenProvider() + ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) : new FirebaseAuthTokenProvider(app.name, app.options, authProvider); validateUrl('Invalid Firebase Database URL', parsedUrl); @@ -286,11 +291,15 @@ export function getDatabase( * @param db - The instance to modify. * @param host - The emulator host (ex: localhost) * @param port - The emulator port (ex: 8080) + * @param options.mockUserToken - Optional: The mock token to use (for unit testing Security Rules) */ export function useDatabaseEmulator( db: FirebaseDatabase, host: string, - port: number + port: number, + options: { + mockUserToken?: Partial; + } = {} ): void { db = getModularInstance(db); db._checkNotDeleted('useEmulator'); @@ -299,8 +308,26 @@ export function useDatabaseEmulator( 'Cannot call useEmulator() after instance has already been initialized.' ); } + + const repo = db._repo; + let tokenProvider: EmulatorTokenProvider | undefined = undefined; + if (repo.repoInfo_.nodeAdmin) { + if (options.mockUserToken) { + fatal( + 'mockUserToken is not supported on the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".' + ); + } + tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER); + } else if (options.mockUserToken) { + const token = createMockUserToken( + options.mockUserToken, + db.app.options.projectId + ); + tokenProvider = new EmulatorTokenProvider(token); + } + // Modify the repo to apply emulator settings - repoManagerApplyEmulatorSettings(db._repo, host, port); + repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider); } /** diff --git a/packages/util/index.node.ts b/packages/util/index.node.ts index a603393d987..8dace3b8e1e 100644 --- a/packages/util/index.node.ts +++ b/packages/util/index.node.ts @@ -25,6 +25,7 @@ export * from './src/crypt'; export * from './src/constants'; export * from './src/deepCopy'; export * from './src/deferred'; +export * from './src/emulator'; export * from './src/environment'; export * from './src/errors'; export * from './src/json'; diff --git a/packages/util/index.ts b/packages/util/index.ts index d2da4426c4a..00d661734b8 100644 --- a/packages/util/index.ts +++ b/packages/util/index.ts @@ -20,6 +20,7 @@ export * from './src/crypt'; export * from './src/constants'; export * from './src/deepCopy'; export * from './src/deferred'; +export * from './src/emulator'; export * from './src/environment'; export * from './src/errors'; export * from './src/json'; diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts new file mode 100644 index 00000000000..cdd24bc4e87 --- /dev/null +++ b/packages/util/src/emulator.ts @@ -0,0 +1,140 @@ +/** + * @license + * Copyright 2021 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 { base64 } from './crypt'; + +// Firebase Auth tokens contain snake_case claims following the JWT standard / convention. +/* eslint-disable camelcase */ + +export type FirebaseSignInProvider = + | 'custom' + | 'email' + | 'password' + | 'phone' + | 'anonymous' + | 'google.com' + | 'facebook.com' + | 'github.com' + | 'twitter.com' + | 'microsoft.com' + | 'apple.com'; + +export interface FirebaseIdToken { + // Always set to https://securetoken.google.com/PROJECT_ID + iss: string; + + // Always set to PROJECT_ID + aud: string; + + // The user's unique id + sub: string; + + // The token issue time, in seconds since epoch + iat: number; + + // The token expiry time, normally 'iat' + 3600 + exp: number; + + // The user's unique id, must be equal to 'sub' + user_id: string; + + // The time the user authenticated, normally 'iat' + auth_time: number; + + // The sign in provider, only set when the provider is 'anonymous' + provider_id?: 'anonymous'; + + // The user's primary email + email?: string; + + // The user's email verification status + email_verified?: boolean; + + // The user's primary phone number + phone_number?: string; + + // The user's display name + name?: string; + + // The user's profile photo URL + picture?: string; + + // Information on all identities linked to this user + firebase: { + // The primary sign-in provider + sign_in_provider: FirebaseSignInProvider; + + // A map of providers to the user's list of unique identifiers from + // each provider + identities?: { [provider in FirebaseSignInProvider]?: string[] }; + }; + + // Custom claims set by the developer + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [claim: string]: any; + + uid?: never; // Try to catch a common mistake of "uid" (should be "sub" instead). +} + +export function createMockUserToken( + token: Partial, + projectId?: string +): string { + if (token.uid) { + throw new Error( + 'Invalid Firebase token field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + ); + } + // Unsecured JWTs use "none" as the algorithm. + const header = { + alg: 'none', + type: 'JWT' + }; + + const project = projectId || 'fake-project'; + const iat = token.iat || 0; + const uid = token.uid || token.user_id; + if (!uid) { + throw new Error("Auth must contain 'sub' or 'user_id' field!"); + } + + const payload: FirebaseIdToken = { + // Set all required fields to decent defaults + iss: `https://securetoken.google.com/${project}`, + aud: project, + iat, + exp: iat + 3600, + auth_time: iat, + sub: uid, + user_id: uid, + firebase: { + sign_in_provider: 'custom', + identities: {} + }, + + // Override with user options + ...token + }; + + // Unsecured JWTs use the empty string as a signature. + const signature = ''; + return [ + base64.encodeString(JSON.stringify(header), /*webSafe=*/ false), + base64.encodeString(JSON.stringify(payload), /*webSafe=*/ false), + signature + ].join('.'); +} diff --git a/packages/util/test/emulator.test.ts b/packages/util/test/emulator.test.ts new file mode 100644 index 00000000000..5eeb09ff7b8 --- /dev/null +++ b/packages/util/test/emulator.test.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2017 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 { base64 } from '../src/crypt'; +import { createMockUserToken, FirebaseIdToken } from '../src/emulator'; + +// Firebase Auth tokens contain snake_case claims following the JWT standard / convention. +/* eslint-disable camelcase */ + +describe('createMockUserToken()', () => { + it('creates a well-formed JWT', () => { + const projectId = 'my-project'; + const options = { user_id: 'alice' }; + + const token = createMockUserToken(options, projectId); + const claims = JSON.parse( + base64.decodeString(token.split('.')[1], /*webSafe=*/ false) + ); + // We add an 'iat' field. + expect(claims).to.deep.equal({ + iss: 'https://securetoken.google.com/' + projectId, + aud: projectId, + iat: 0, + exp: 3600, + auth_time: 0, + sub: 'alice', + user_id: 'alice', + firebase: { + sign_in_provider: 'custom', + identities: {} + } + }); + }); + + it('rejects "uid" field with error', () => { + const options = { uid: 'alice' }; + + expect(() => + createMockUserToken((options as unknown) as Partial) + ).to.throw( + 'Invalid Firebase token field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + ); + }); +}); From 21374c2676d5c0ff6caac25fb347e8f477edee2c Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 14 Apr 2021 17:21:28 -0700 Subject: [PATCH 02/18] Add compact API. --- packages/database/src/api/Database.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index 0282070c616..31b14ab42a5 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -18,7 +18,7 @@ import { FirebaseApp } from '@firebase/app-types'; import { FirebaseService } from '@firebase/app-types/private'; -import { validateArgCount, Compat } from '@firebase/util'; +import { validateArgCount, Compat, FirebaseIdToken } from '@firebase/util'; import { FirebaseDatabase as ExpDatabase, @@ -58,9 +58,16 @@ export class Database implements FirebaseService, Compat { * * @param host - the emulator host (ex: localhost) * @param port - the emulator port (ex: 8080) + * @param options.mockUserToken - Optional: The mock token to use (for unit testing Security Rules) */ - useEmulator(host: string, port: number): void { - useDatabaseEmulator(this._delegate, host, port); + useEmulator( + host: string, + port: number, + options: { + mockUserToken?: Partial; + } = {} + ): void { + useDatabaseEmulator(this._delegate, host, port, options); } /** From 5fd82c76d7641d5895ce1255e73b65a8bfe1da21 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 15 Apr 2021 16:58:49 -0700 Subject: [PATCH 03/18] Add generated API file. --- common/api-review/database.api.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/api-review/database.api.md b/common/api-review/database.api.md index 7bdcbe08dae..3be16c59c31 100644 --- a/common/api-review/database.api.md +++ b/common/api-review/database.api.md @@ -5,6 +5,7 @@ ```ts import { FirebaseApp } from '@firebase/app'; +import { FirebaseIdToken } from '@firebase/util'; // @public (undocumented) export function child(parent: Reference, path: string): Reference; @@ -229,7 +230,9 @@ export type Unsubscribe = () => void; export function update(ref: Reference, values: object): Promise; // @public -export function useDatabaseEmulator(db: FirebaseDatabase, host: string, port: number): void; +export function useDatabaseEmulator(db: FirebaseDatabase, host: string, port: number, options?: { + mockUserToken?: Partial; +}): void; ``` From 4d8b954b58722634933b48f780ed677295553468 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 20 Apr 2021 15:56:10 -0700 Subject: [PATCH 04/18] Change default project ID to demo-project. --- packages/util/src/emulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index cdd24bc4e87..eba4b28c035 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -105,7 +105,7 @@ export function createMockUserToken( type: 'JWT' }; - const project = projectId || 'fake-project'; + const project = projectId || 'demo-project'; const iat = token.iat || 0; const uid = token.uid || token.user_id; if (!uid) { From 9f0b20344d03afc085b782a5d271a7e3288302d0 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 23 Apr 2021 09:55:50 -0600 Subject: [PATCH 05/18] Fix db.useEmulator --- packages/database/src/core/PersistentConnection.ts | 13 ++++++------- packages/database/src/exp/Database.ts | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/database/src/core/PersistentConnection.ts b/packages/database/src/core/PersistentConnection.ts index e3cfa4c5322..0c0762a8d22 100644 --- a/packages/database/src/core/PersistentConnection.ts +++ b/packages/database/src/core/PersistentConnection.ts @@ -773,7 +773,6 @@ export class PersistentConnection extends ServerActions { const onReady = this.onReady_.bind(this); const onDisconnect = this.onRealtimeDisconnect_.bind(this); const connId = this.id + ':' + PersistentConnection.nextConnectionId_++; - const self = this; const lastSessionId = this.lastSessionId; let canceled = false; let connection: Connection | null = null; @@ -807,17 +806,17 @@ export class PersistentConnection extends ServerActions { .then(result => { if (!canceled) { log('getToken() completed. Creating connection.'); - self.authToken_ = result && result.accessToken; + this.authToken_ = result && result.accessToken; connection = new Connection( connId, - self.repoInfo_, - self.applicationId_, + this.repoInfo_, + this.applicationId_, onDataMessage, onReady, onDisconnect, /* onKill= */ reason => { - warn(reason + ' (' + self.repoInfo_.toString() + ')'); - self.interrupt(SERVER_KILL_INTERRUPT_REASON); + warn(reason + ' (' + this.repoInfo_.toString() + ')'); + this.interrupt(SERVER_KILL_INTERRUPT_REASON); }, lastSessionId ); @@ -826,7 +825,7 @@ export class PersistentConnection extends ServerActions { } }) .then(null, error => { - self.log_('Failed to get token: ' + error); + this.log_('Failed to get token: ' + error); if (!canceled) { if (this.repoInfo_.nodeAdmin) { // This may be a critical error for the Admin Node.js SDK, so log a warning. diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts index 5f0ac58c144..f70d797be3e 100644 --- a/packages/database/src/exp/Database.ts +++ b/packages/database/src/exp/Database.ts @@ -217,7 +217,7 @@ export class FirebaseDatabase implements _FirebaseService { /** @hideconstructor */ constructor( - private _repoInternal: Repo, + public _repoInternal: Repo, /** The FirebaseApp associated with this Realtime Database instance. */ readonly app: FirebaseApp ) {} @@ -300,7 +300,7 @@ export function useDatabaseEmulator( ); } // Modify the repo to apply emulator settings - repoManagerApplyEmulatorSettings(db._repo, host, port); + repoManagerApplyEmulatorSettings(db._repoInternal, host, port); } /** From a8293985684c4d082791a15118d770d3f55824c5 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 23 Apr 2021 09:58:17 -0600 Subject: [PATCH 06/18] Create sweet-monkeys-warn.md --- .changeset/sweet-monkeys-warn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sweet-monkeys-warn.md diff --git a/.changeset/sweet-monkeys-warn.md b/.changeset/sweet-monkeys-warn.md new file mode 100644 index 00000000000..6b1ac7a7f98 --- /dev/null +++ b/.changeset/sweet-monkeys-warn.md @@ -0,0 +1,5 @@ +--- +"@firebase/database": patch +--- + +Fixes a regression introduced with 8.4.1 that broke `useEmulator()`. From efe6d17eb37447e12037acfce76f09dab1828505 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 09:23:45 -0700 Subject: [PATCH 07/18] Update packages/database/src/exp/Database.ts Co-authored-by: Sebastian Schmidt --- packages/database/src/exp/Database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts index 1715cc7253c..e8af1db88ee 100644 --- a/packages/database/src/exp/Database.ts +++ b/packages/database/src/exp/Database.ts @@ -314,7 +314,7 @@ export function useDatabaseEmulator( if (repo.repoInfo_.nodeAdmin) { if (options.mockUserToken) { fatal( - 'mockUserToken is not supported on the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".' + 'mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".' ); } tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER); From f0b11f0ff149ed2585f1cf416a8234601d1e2f4d Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 09:23:52 -0700 Subject: [PATCH 08/18] Update packages/util/test/emulator.test.ts Co-authored-by: Sebastian Schmidt --- packages/util/test/emulator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/util/test/emulator.test.ts b/packages/util/test/emulator.test.ts index 5eeb09ff7b8..6605285cac5 100644 --- a/packages/util/test/emulator.test.ts +++ b/packages/util/test/emulator.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From ff24362a2a1d73bd4d6230aff549ef679ca49170 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 19:05:11 -0700 Subject: [PATCH 09/18] Fix sub field name. --- packages/util/src/emulator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index eba4b28c035..54de6a29015 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -107,8 +107,8 @@ export function createMockUserToken( const project = projectId || 'demo-project'; const iat = token.iat || 0; - const uid = token.uid || token.user_id; - if (!uid) { + const sub = token.sub || token.user_id; + if (!sub) { throw new Error("Auth must contain 'sub' or 'user_id' field!"); } @@ -119,8 +119,8 @@ export function createMockUserToken( iat, exp: iat + 3600, auth_time: iat, - sub: uid, - user_id: uid, + sub, + user_id: sub, firebase: { sign_in_provider: 'custom', identities: {} From 05c383e0900ddf60df2d06ab0f502431ec69bf6a Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 19:10:08 -0700 Subject: [PATCH 10/18] Remove optional in jsdocs. --- packages/database/src/api/Database.ts | 2 +- packages/database/src/exp/Database.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index 31b14ab42a5..fe375036bcf 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -58,7 +58,7 @@ export class Database implements FirebaseService, Compat { * * @param host - the emulator host (ex: localhost) * @param port - the emulator port (ex: 8080) - * @param options.mockUserToken - Optional: The mock token to use (for unit testing Security Rules) + * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules */ useEmulator( host: string, diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts index 9e95acba907..9a38e1cb99c 100644 --- a/packages/database/src/exp/Database.ts +++ b/packages/database/src/exp/Database.ts @@ -291,7 +291,7 @@ export function getDatabase( * @param db - The instance to modify. * @param host - The emulator host (ex: localhost) * @param port - The emulator port (ex: 8080) - * @param options.mockUserToken - Optional: The mock token to use (for unit testing Security Rules) + * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules */ export function useDatabaseEmulator( db: FirebaseDatabase, From 9d99437411410c50976b71d5b375d9a27f6765fe Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 19:13:49 -0700 Subject: [PATCH 11/18] Create loud-feet-jump.md --- .changeset/loud-feet-jump.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/loud-feet-jump.md diff --git a/.changeset/loud-feet-jump.md b/.changeset/loud-feet-jump.md new file mode 100644 index 00000000000..1586b89cb83 --- /dev/null +++ b/.changeset/loud-feet-jump.md @@ -0,0 +1,6 @@ +--- +"@firebase/database": patch +"@firebase/util": patch +--- + +Add mockUserToken support for database emulator. From f3aebe068a1fc9743df45e6bbe99032941c77eec Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Fri, 23 Apr 2021 19:14:20 -0700 Subject: [PATCH 12/18] Update loud-feet-jump.md --- .changeset/loud-feet-jump.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/loud-feet-jump.md b/.changeset/loud-feet-jump.md index 1586b89cb83..6a6176a3b07 100644 --- a/.changeset/loud-feet-jump.md +++ b/.changeset/loud-feet-jump.md @@ -1,6 +1,6 @@ --- -"@firebase/database": patch -"@firebase/util": patch +"@firebase/database": minor +"@firebase/util": minor --- Add mockUserToken support for database emulator. From a6842199b63526cc9c7f7356a60d94ca0e0e24a3 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Mon, 26 Apr 2021 18:33:47 -0700 Subject: [PATCH 13/18] Make sub/user_id required in typing. --- packages/database/src/api/Database.ts | 8 ++++++-- packages/database/src/exp/Database.ts | 4 ++-- packages/util/src/emulator.ts | 7 +++++-- packages/util/test/emulator.test.ts | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index fe375036bcf..b3c34f4c869 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -18,7 +18,11 @@ import { FirebaseApp } from '@firebase/app-types'; import { FirebaseService } from '@firebase/app-types/private'; -import { validateArgCount, Compat, FirebaseIdToken } from '@firebase/util'; +import { + validateArgCount, + Compat, + EmulatorMockTokenOptions +} from '@firebase/util'; import { FirebaseDatabase as ExpDatabase, @@ -64,7 +68,7 @@ export class Database implements FirebaseService, Compat { host: string, port: number, options: { - mockUserToken?: Partial; + mockUserToken?: EmulatorMockTokenOptions; } = {} ): void { useDatabaseEmulator(this._delegate, host, port, options); diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts index 9a38e1cb99c..455eb108068 100644 --- a/packages/database/src/exp/Database.ts +++ b/packages/database/src/exp/Database.ts @@ -27,7 +27,7 @@ import { Provider } from '@firebase/component'; import { getModularInstance, createMockUserToken, - FirebaseIdToken + EmulatorMockTokenOptions } from '@firebase/util'; import { @@ -298,7 +298,7 @@ export function useDatabaseEmulator( host: string, port: number, options: { - mockUserToken?: Partial; + mockUserToken?: EmulatorMockTokenOptions; } = {} ): void { db = getModularInstance(db); diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index 54de6a29015..a68ad34f671 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -33,7 +33,7 @@ export type FirebaseSignInProvider = | 'microsoft.com' | 'apple.com'; -export interface FirebaseIdToken { +interface FirebaseIdToken { // Always set to https://securetoken.google.com/PROJECT_ID iss: string; @@ -90,8 +90,11 @@ export interface FirebaseIdToken { uid?: never; // Try to catch a common mistake of "uid" (should be "sub" instead). } +export type EmulatorMockTokenOptions = ({ user_id: string } | { sub: string }) & + Partial; + export function createMockUserToken( - token: Partial, + token: EmulatorMockTokenOptions, projectId?: string ): string { if (token.uid) { diff --git a/packages/util/test/emulator.test.ts b/packages/util/test/emulator.test.ts index 6605285cac5..5ef6090c839 100644 --- a/packages/util/test/emulator.test.ts +++ b/packages/util/test/emulator.test.ts @@ -16,7 +16,7 @@ */ import { expect } from 'chai'; import { base64 } from '../src/crypt'; -import { createMockUserToken, FirebaseIdToken } from '../src/emulator'; +import { createMockUserToken, EmulatorMockTokenOptions } from '../src/emulator'; // Firebase Auth tokens contain snake_case claims following the JWT standard / convention. /* eslint-disable camelcase */ @@ -50,7 +50,7 @@ describe('createMockUserToken()', () => { const options = { uid: 'alice' }; expect(() => - createMockUserToken((options as unknown) as Partial) + createMockUserToken((options as unknown) as EmulatorMockTokenOptions) ).to.throw( 'Invalid Firebase token field "uid". Did you mean "sub" (for Firebase Auth User ID)?' ); From 6238b8b064e7e89c9d67cb8ccb117f1a28fcb66c Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Mon, 26 Apr 2021 18:46:04 -0700 Subject: [PATCH 14/18] Update error messages to contain mockUserToken. --- packages/util/src/emulator.ts | 4 ++-- packages/util/test/emulator.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index a68ad34f671..cd002f06111 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -99,7 +99,7 @@ export function createMockUserToken( ): string { if (token.uid) { throw new Error( - 'Invalid Firebase token field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + 'Invalid mockUserToken field "uid". Did you mean "sub" (for Firebase Auth User ID)?' ); } // Unsecured JWTs use "none" as the algorithm. @@ -112,7 +112,7 @@ export function createMockUserToken( const iat = token.iat || 0; const sub = token.sub || token.user_id; if (!sub) { - throw new Error("Auth must contain 'sub' or 'user_id' field!"); + throw new Error("mockUserToken must contain 'sub' or 'user_id' field!"); } const payload: FirebaseIdToken = { diff --git a/packages/util/test/emulator.test.ts b/packages/util/test/emulator.test.ts index 5ef6090c839..1d95c0343d7 100644 --- a/packages/util/test/emulator.test.ts +++ b/packages/util/test/emulator.test.ts @@ -52,7 +52,7 @@ describe('createMockUserToken()', () => { expect(() => createMockUserToken((options as unknown) as EmulatorMockTokenOptions) ).to.throw( - 'Invalid Firebase token field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + 'Invalid mockUserToken field "uid". Did you mean "sub" (for Firebase Auth User ID)?' ); }); }); From 3a1cd9825287a04c0eace0ff8e0e90a653bc86c1 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 27 Apr 2021 09:37:24 -0700 Subject: [PATCH 15/18] Add API changes in md. --- common/api-review/database.api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/api-review/database.api.md b/common/api-review/database.api.md index 3be16c59c31..ad8490ac286 100644 --- a/common/api-review/database.api.md +++ b/common/api-review/database.api.md @@ -4,10 +4,10 @@ ```ts +import { EmulatorMockTokenOptions } from '@firebase/util'; import { FirebaseApp } from '@firebase/app'; -import { FirebaseIdToken } from '@firebase/util'; -// @public (undocumented) +// @public export function child(parent: Reference, path: string): Reference; // @public @@ -231,7 +231,7 @@ export function update(ref: Reference, values: object): Promise; // @public export function useDatabaseEmulator(db: FirebaseDatabase, host: string, port: number, options?: { - mockUserToken?: Partial; + mockUserToken?: EmulatorMockTokenOptions; }): void; From ea53f0c644b86344f237d5c5c2b5b32d3a0f2af3 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 28 Apr 2021 10:41:58 -0700 Subject: [PATCH 16/18] Update error message for uid field. --- packages/util/src/emulator.ts | 2 +- packages/util/test/emulator.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index cd002f06111..e669b6260a2 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -99,7 +99,7 @@ export function createMockUserToken( ): string { if (token.uid) { throw new Error( - 'Invalid mockUserToken field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + 'The "uid" field is no longer supported by mockUserToken. Please use "sub" instead for Firebase Auth User ID.' ); } // Unsecured JWTs use "none" as the algorithm. diff --git a/packages/util/test/emulator.test.ts b/packages/util/test/emulator.test.ts index 1d95c0343d7..2f1122dcc9f 100644 --- a/packages/util/test/emulator.test.ts +++ b/packages/util/test/emulator.test.ts @@ -52,7 +52,7 @@ describe('createMockUserToken()', () => { expect(() => createMockUserToken((options as unknown) as EmulatorMockTokenOptions) ).to.throw( - 'Invalid mockUserToken field "uid". Did you mean "sub" (for Firebase Auth User ID)?' + 'The "uid" field is no longer supported by mockUserToken. Please use "sub" instead for Firebase Auth User ID.' ); }); }); From c0b51f88d7b650ea1154d1fbd5ff34655d4e1272 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 28 Apr 2021 10:53:18 -0700 Subject: [PATCH 17/18] Update loud-feet-jump.md --- .changeset/loud-feet-jump.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/loud-feet-jump.md b/.changeset/loud-feet-jump.md index 6a6176a3b07..c24fea7298a 100644 --- a/.changeset/loud-feet-jump.md +++ b/.changeset/loud-feet-jump.md @@ -1,5 +1,6 @@ --- "@firebase/database": minor +"firebase": minor "@firebase/util": minor --- From dc72577717ad9577b86af684f4effd73df495899 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 29 Apr 2021 13:35:07 -0700 Subject: [PATCH 18/18] Change custom claim typing to unknown. --- packages/util/src/emulator.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/util/src/emulator.ts b/packages/util/src/emulator.ts index e669b6260a2..6f5a9dbacf8 100644 --- a/packages/util/src/emulator.ts +++ b/packages/util/src/emulator.ts @@ -84,8 +84,7 @@ interface FirebaseIdToken { }; // Custom claims set by the developer - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [claim: string]: any; + [claim: string]: unknown; uid?: never; // Try to catch a common mistake of "uid" (should be "sub" instead). }