Skip to content

Commit

Permalink
Use Admin AuthTokenProvider when targeting Emulator (#3228)
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian authored Jul 14, 2020
1 parent 29afa33 commit c2b737b
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 11 deletions.
8 changes: 8 additions & 0 deletions .changeset/smooth-poets-leave.md
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 32 additions & 4 deletions packages/database/src/core/AuthTokenProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FirebaseAuthTokenData>;
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,
Expand Down Expand Up @@ -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_) {
Expand All @@ -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 +
Expand All @@ -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<FirebaseAuthTokenData> {
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 {}
}
4 changes: 1 addition & 3 deletions packages/database/src/core/Repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,8 @@ export class Repo {
public repoInfo_: RepoInfo,
forceRestClient: boolean,
public app: FirebaseApp,
authProvider: Provider<FirebaseAuthInternalName>
authTokenProvider: AuthTokenProvider
) {
const authTokenProvider = new AuthTokenProvider(app, authProvider);

this.stats_ = StatsManager.getCollection(repoInfo_);

if (forceRestClient || beingCrawled()) {
Expand Down
24 changes: 20 additions & 4 deletions packages/database/src/core/RepoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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(
Expand All @@ -126,7 +142,7 @@ export class RepoManager {
);
}

const repo = this.createRepo(repoInfo, app, authProvider);
const repo = this.createRepo(repoInfo, app, authTokenProvider);

return repo.database;
}
Expand Down Expand Up @@ -159,7 +175,7 @@ export class RepoManager {
createRepo(
repoInfo: RepoInfo,
app: FirebaseApp,
authProvider: Provider<FirebaseAuthInternalName>
authTokenProvider: AuthTokenProvider
): Repo {
let appRepos = safeGet(this.repos_, app.name);

Expand All @@ -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;
Expand Down

0 comments on commit c2b737b

Please sign in to comment.