diff --git a/.changeset/smooth-poets-leave.md b/.changeset/smooth-poets-leave.md new file mode 100644 index 00000000000..606fc5ee440 --- /dev/null +++ b/.changeset/smooth-poets-leave.md @@ -0,0 +1,8 @@ +--- +"firebase": patch +"@firebase/database": patch +--- + +[fix] Instead of using production auth, the SDK will use test credentials +to connect to the Emulator when the RTDB SDK is used via the Firebase +Admin SDK. diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index 81928c11020..5e18d856554 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -24,10 +24,17 @@ import { Provider } from '@firebase/component'; import { log, warn } from './util/util'; import { FirebaseApp } from '@firebase/app-types'; +export interface AuthTokenProvider { + getToken(forceRefresh: boolean): Promise; + addTokenChangeListener(listener: (token: string | null) => void): void; + removeTokenChangeListener(listener: (token: string | null) => void): void; + notifyForInvalidToken(): void; +} + /** * Abstraction around FirebaseApp's token fetching capabilities. */ -export class AuthTokenProvider { +export class FirebaseAuthTokenProvider implements AuthTokenProvider { private auth_: FirebaseAuthInternal | null = null; constructor( private app_: FirebaseApp, @@ -60,7 +67,7 @@ export class AuthTokenProvider { }); } - addTokenChangeListener(listener: (token: string | null) => void) { + addTokenChangeListener(listener: (token: string | null) => void): void { // TODO: We might want to wrap the listener and call it with no args to // avoid a leaky abstraction, but that makes removing the listener harder. if (this.auth_) { @@ -73,13 +80,13 @@ export class AuthTokenProvider { } } - removeTokenChangeListener(listener: (token: string | null) => void) { + removeTokenChangeListener(listener: (token: string | null) => void): void { this.authProvider_ .get() .then(auth => auth.removeAuthTokenListener(listener)); } - notifyForInvalidToken() { + notifyForInvalidToken(): void { let errorMessage = 'Provided authentication credentials for the app named "' + this.app_.name + @@ -104,3 +111,24 @@ export class AuthTokenProvider { warn(errorMessage); } } + +/* Auth token provider that the Admin SDK uses to connect to the Emulator. */ +export class EmulatorAdminTokenProvider implements AuthTokenProvider { + private static EMULATOR_AUTH_TOKEN = 'owner'; + + getToken(forceRefresh: boolean): Promise { + return Promise.resolve({ + accessToken: EmulatorAdminTokenProvider.EMULATOR_AUTH_TOKEN + }); + } + + 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); + } + + removeTokenChangeListener(listener: (token: string | null) => void): void {} + + notifyForInvalidToken(): void {} +} diff --git a/packages/database/src/core/Repo.ts b/packages/database/src/core/Repo.ts index 9b95aa89214..95a96954626 100644 --- a/packages/database/src/core/Repo.ts +++ b/packages/database/src/core/Repo.ts @@ -83,10 +83,8 @@ export class Repo { public repoInfo_: RepoInfo, forceRestClient: boolean, public app: FirebaseApp, - authProvider: Provider + authTokenProvider: AuthTokenProvider ) { - const authTokenProvider = new AuthTokenProvider(app, authProvider); - this.stats_ = StatsManager.getCollection(repoInfo_); if (forceRestClient || beingCrawled()) { diff --git a/packages/database/src/core/RepoManager.ts b/packages/database/src/core/RepoManager.ts index 8555b317931..5ec5e7bc753 100644 --- a/packages/database/src/core/RepoManager.ts +++ b/packages/database/src/core/RepoManager.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp } from '@firebase/app-types'; -import { safeGet } from '@firebase/util'; +import { safeGet, CONSTANTS } from '@firebase/util'; import { Repo } from './Repo'; import { fatal } from './util/util'; import { parseRepoInfo } from './util/libs/parser'; @@ -26,6 +26,11 @@ import { Database } from '../api/Database'; import { RepoInfo } from './RepoInfo'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { + AuthTokenProvider, + EmulatorAdminTokenProvider, + FirebaseAuthTokenProvider +} from './AuthTokenProvider'; /** @const {string} */ const DATABASE_URL_OPTION = 'databaseURL'; @@ -108,16 +113,27 @@ export class RepoManager { let parsedUrl = parseRepoInfo(dbUrl); let repoInfo = parsedUrl.repoInfo; + let isEmulator: boolean; + let dbEmulatorHost: string | undefined = undefined; if (typeof process !== 'undefined') { dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR]; } + if (dbEmulatorHost) { + isEmulator = true; dbUrl = `http://${dbEmulatorHost}?ns=${repoInfo.namespace}`; parsedUrl = parseRepoInfo(dbUrl); repoInfo = parsedUrl.repoInfo; + } else { + isEmulator = !parsedUrl.repoInfo.secure; } + const authTokenProvider = + CONSTANTS.NODE_ADMIN && isEmulator + ? new EmulatorAdminTokenProvider() + : new FirebaseAuthTokenProvider(app, authProvider); + validateUrl('Invalid Firebase Database URL', 1, parsedUrl); if (!parsedUrl.path.isEmpty()) { fatal( @@ -126,7 +142,7 @@ export class RepoManager { ); } - const repo = this.createRepo(repoInfo, app, authProvider); + const repo = this.createRepo(repoInfo, app, authTokenProvider); return repo.database; } @@ -159,7 +175,7 @@ export class RepoManager { createRepo( repoInfo: RepoInfo, app: FirebaseApp, - authProvider: Provider + authTokenProvider: AuthTokenProvider ): Repo { let appRepos = safeGet(this.repos_, app.name); @@ -174,7 +190,7 @@ export class RepoManager { 'Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.' ); } - repo = new Repo(repoInfo, this.useRestClient_, app, authProvider); + repo = new Repo(repoInfo, this.useRestClient_, app, authTokenProvider); appRepos[repoInfo.toURLString()] = repo; return repo;