From c51716731ca5f21e53f939f03ee5999d06e6bcf4 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 9 Nov 2020 14:34:53 +0000 Subject: [PATCH 1/9] Use 'owner' token when communicating with Auth emulator --- src/auth/auth-api-request.ts | 23 ++++++++++++------ src/auth/auth.ts | 10 +++++--- src/utils/api-request.ts | 47 ++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index f57fdc3fcc..af22c66696 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -23,7 +23,7 @@ import { import { FirebaseApp } from '../firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { - ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, + ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpClient, EmulatorHttpClient, } from '../utils/api-request'; import { UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, @@ -926,7 +926,7 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') */ export abstract class AbstractAuthRequestHandler { - protected readonly httpClient: AuthorizedHttpClient; + protected readonly httpClient: HttpClient; private authUrlBuilder: AuthResourceUrlBuilder; private projectConfigUrlBuilder: AuthResourceUrlBuilder; @@ -981,9 +981,10 @@ export abstract class AbstractAuthRequestHandler { /** * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param {HttpClient} httpClient Optional HttpClient override. * @constructor */ - constructor(protected readonly app: FirebaseApp) { + constructor(protected readonly app: FirebaseApp, httpClient?: HttpClient) { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -991,7 +992,11 @@ export abstract class AbstractAuthRequestHandler { ); } - this.httpClient = new AuthorizedHttpClient(app); + if (httpClient) { + this.httpClient = httpClient; + } else { + this.httpClient = new AuthorizedHttpClient(app); + } } /** @@ -1899,10 +1904,11 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { * The FirebaseAuthRequestHandler constructor used to initialize an instance using a FirebaseApp. * * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param {HttpClient} httpClient Optional HttpClient override. * @constructor. */ - constructor(app: FirebaseApp) { - super(app); + constructor(app: FirebaseApp, httpClient?: HttpClient) { + super(app, httpClient); this.tenantMgmtResourceBuilder = new AuthResourceUrlBuilder(app, 'v2'); } @@ -2045,10 +2051,11 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. * @param {string} tenantId The request handler's tenant ID. + * @param {HttpClient} httpClient Optional HttpClient override. * @constructor */ - constructor(app: FirebaseApp, private readonly tenantId: string) { - super(app); + constructor(app: FirebaseApp, private readonly tenantId: string, httpClient?: HttpClient) { + super(app, httpClient); } /** diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 816333e3b2..c5051e7b05 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -35,6 +35,7 @@ import { SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, } from './auth-config'; import { TenantManager } from './tenant-manager'; +import { EmulatorHttpClient } from '../utils/api-request'; import UserIdentifier = auth.UserIdentifier; import CreateRequest = auth.CreateRequest; @@ -733,9 +734,10 @@ export class TenantAwareAuth * @constructor */ constructor(app: FirebaseApp, tenantId: string) { + const httpClient = useEmulator() ? new EmulatorHttpClient(app) : undefined; const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); - super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); + super(app, new TenantAwareAuthRequestHandler(app, tenantId, httpClient), tokenGenerator); utils.addReadonlyGetter(this, 'tenantId', tenantId); } @@ -822,7 +824,7 @@ export class TenantAwareAuth export class Auth extends BaseAuth implements FirebaseServiceInterface, AuthInterface { - public INTERNAL: AuthInternals = new AuthInternals(); + public INTERNAL: AuthInternals; private readonly tenantManager_: TenantManager; private readonly app_: FirebaseApp; @@ -831,7 +833,9 @@ export class Auth extends BaseAuth * @constructor */ constructor(app: FirebaseApp) { - super(app, new AuthRequestHandler(app)); + const httpClient = useEmulator() ? new EmulatorHttpClient(app) : undefined; + super(app, new AuthRequestHandler(app, httpClient)); + this.INTERNAL = new AuthInternals(); this.app_ = app; this.tenantManager_ = new TenantManager(app); } diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 3b83221d5f..fb1a029adc 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -808,26 +808,57 @@ class HttpRequestConfigImpl implements HttpRequestConfig { } } -export class AuthorizedHttpClient extends HttpClient { - constructor(private readonly app: FirebaseApp) { - super(); - } +export abstract class BaseAuthorizedHttpClient extends HttpClient { + + public abstract getToken(): Promise; + public abstract getHttpAgent(): http.Agent | undefined; public send(request: HttpRequestConfig): Promise { - return this.app.INTERNAL.getToken().then((accessTokenObj) => { + return this.getToken().then((token) => { const requestCopy = Object.assign({}, request); requestCopy.headers = Object.assign({}, request.headers); const authHeader = 'Authorization'; - requestCopy.headers[authHeader] = `Bearer ${accessTokenObj.accessToken}`; + requestCopy.headers[authHeader] = `Bearer ${token}`; - if (!requestCopy.httpAgent && this.app.options.httpAgent) { - requestCopy.httpAgent = this.app.options.httpAgent; + const agent = this.getHttpAgent(); + if (!requestCopy.httpAgent && agent) { + requestCopy.httpAgent = agent; } return super.send(requestCopy); }); } } +export class AuthorizedHttpClient extends BaseAuthorizedHttpClient { + constructor(private readonly app: FirebaseApp) { + super(); + } + + public getToken(): Promise { + return this.app.INTERNAL.getToken() + .then((accessTokenObj) => { + return accessTokenObj.accessToken; + }) + } + + public getHttpAgent(): http.Agent | undefined { + return this.app.options.httpAgent; + } +} + +export class EmulatorHttpClient extends BaseAuthorizedHttpClient { + constructor(private readonly app: FirebaseApp) { + super(); + } + + public getToken(): Promise { + return Promise.resolve("owner"); + } + public getHttpAgent(): http.Agent | undefined { + return this.app.options.httpAgent; + } +} + /** * Class that defines all the settings for the backend API endpoint. * From cc6d9c7c7cc3b374918c854e6e9ca3f6033241fc Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 9 Nov 2020 14:38:34 +0000 Subject: [PATCH 2/9] Unused import --- src/auth/auth-api-request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index af22c66696..0b5f233120 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -23,7 +23,7 @@ import { import { FirebaseApp } from '../firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { - ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpClient, EmulatorHttpClient, + ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpClient } from '../utils/api-request'; import { UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, From c2192bafcb5123fe2e561d85084b40d96ee658f7 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 9 Nov 2020 14:42:22 +0000 Subject: [PATCH 3/9] Single quote --- src/utils/api-request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index fb1a029adc..43c2cf50af 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -852,7 +852,7 @@ export class EmulatorHttpClient extends BaseAuthorizedHttpClient { } public getToken(): Promise { - return Promise.resolve("owner"); + return Promise.resolve('owner'); } public getHttpAgent(): http.Agent | undefined { return this.app.options.httpAgent; From 4d2c34f188f28bf8bb693ed7393f7cd601b89a5a Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 10 Nov 2020 11:12:39 +0000 Subject: [PATCH 4/9] Simplify to address code review --- src/auth/auth.ts | 26 +++++++++++++++++++------- src/utils/api-request.ts | 38 +++++++------------------------------- 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/auth/auth.ts b/src/auth/auth.ts index c5051e7b05..2b354d5edf 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -35,7 +35,6 @@ import { SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, } from './auth-config'; import { TenantManager } from './tenant-manager'; -import { EmulatorHttpClient } from '../utils/api-request'; import UserIdentifier = auth.UserIdentifier; import CreateRequest = auth.CreateRequest; @@ -58,6 +57,7 @@ import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; import BaseAuthInterface = auth.BaseAuth; import AuthInterface = auth.Auth; import TenantAwareAuthInterface = auth.TenantAwareAuth; +import { AuthorizedHttpClient } from '../utils/api-request'; /** * Internals of an Auth instance. @@ -74,6 +74,21 @@ class AuthInternals implements FirebaseServiceInternalsInterface { } } +/** + * Auth-specific HTTP client which uses the special "owner" token + * when communicating with the Auth Emulator. + */ +class AuthHttpClient extends AuthorizedHttpClient { + + protected getToken(): Promise { + if (useEmulator()) { + return Promise.resolve('owner'); + } + + return super.getToken(); + } + +} /** * Base Auth class. Mainly used for user management APIs. @@ -734,10 +749,9 @@ export class TenantAwareAuth * @constructor */ constructor(app: FirebaseApp, tenantId: string) { - const httpClient = useEmulator() ? new EmulatorHttpClient(app) : undefined; const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); - super(app, new TenantAwareAuthRequestHandler(app, tenantId, httpClient), tokenGenerator); + super(app, new TenantAwareAuthRequestHandler(app, tenantId, new AuthHttpClient(app)), tokenGenerator); utils.addReadonlyGetter(this, 'tenantId', tenantId); } @@ -824,7 +838,7 @@ export class TenantAwareAuth export class Auth extends BaseAuth implements FirebaseServiceInterface, AuthInterface { - public INTERNAL: AuthInternals; + public INTERNAL: AuthInternals = new AuthInternals(); private readonly tenantManager_: TenantManager; private readonly app_: FirebaseApp; @@ -833,9 +847,7 @@ export class Auth extends BaseAuth * @constructor */ constructor(app: FirebaseApp) { - const httpClient = useEmulator() ? new EmulatorHttpClient(app) : undefined; - super(app, new AuthRequestHandler(app, httpClient)); - this.INTERNAL = new AuthInternals(); + super(app, new AuthRequestHandler(app, new AuthHttpClient(app))); this.app_ = app; this.tenantManager_ = new TenantManager(app); } diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 43c2cf50af..1b44e73021 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -808,10 +808,10 @@ class HttpRequestConfigImpl implements HttpRequestConfig { } } -export abstract class BaseAuthorizedHttpClient extends HttpClient { - - public abstract getToken(): Promise; - public abstract getHttpAgent(): http.Agent | undefined; +export class AuthorizedHttpClient extends HttpClient { + constructor(private readonly app: FirebaseApp) { + super(); + } public send(request: HttpRequestConfig): Promise { return this.getToken().then((token) => { @@ -820,43 +820,19 @@ export abstract class BaseAuthorizedHttpClient extends HttpClient { const authHeader = 'Authorization'; requestCopy.headers[authHeader] = `Bearer ${token}`; - const agent = this.getHttpAgent(); - if (!requestCopy.httpAgent && agent) { - requestCopy.httpAgent = agent; + if (!requestCopy.httpAgent && this.app.options.httpAgent) { + requestCopy.httpAgent = this.app.options.httpAgent; } return super.send(requestCopy); }); } -} - -export class AuthorizedHttpClient extends BaseAuthorizedHttpClient { - constructor(private readonly app: FirebaseApp) { - super(); - } - public getToken(): Promise { + protected getToken(): Promise { return this.app.INTERNAL.getToken() .then((accessTokenObj) => { return accessTokenObj.accessToken; }) } - - public getHttpAgent(): http.Agent | undefined { - return this.app.options.httpAgent; - } -} - -export class EmulatorHttpClient extends BaseAuthorizedHttpClient { - constructor(private readonly app: FirebaseApp) { - super(); - } - - public getToken(): Promise { - return Promise.resolve('owner'); - } - public getHttpAgent(): http.Agent | undefined { - return this.app.options.httpAgent; - } } /** From 6fd7e47383256bd328d0445845fac626c40674ce Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 10 Nov 2020 11:20:29 +0000 Subject: [PATCH 5/9] Further simplify --- src/auth/auth-api-request.ts | 35 ++++++++++++++++++++++------------- src/auth/auth.ts | 23 +++-------------------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 599e3f09d8..86c75ef4c8 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -36,6 +36,7 @@ import { } from './auth-config'; import { Tenant, TenantServerResponse } from './tenant'; import { auth } from './index'; +import { useEmulator } from './auth'; import CreateRequest = auth.CreateRequest; import UpdateRequest = auth.UpdateRequest; @@ -236,6 +237,21 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { } } +/** + * Auth-specific HTTP client which uses the special "owner" token + * when communicating with the Auth Emulator. + */ +class AuthHttpClient extends AuthorizedHttpClient { + + protected getToken(): Promise { + if (useEmulator()) { + return Promise.resolve('owner'); + } + + return super.getToken(); + } + +} /** * Validates an AuthFactorInfo object. All unsupported parameters @@ -981,10 +997,9 @@ export abstract class AbstractAuthRequestHandler { /** * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. - * @param {HttpClient} httpClient Optional HttpClient override. * @constructor */ - constructor(protected readonly app: FirebaseApp, httpClient?: HttpClient) { + constructor(protected readonly app: FirebaseApp) { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -992,11 +1007,7 @@ export abstract class AbstractAuthRequestHandler { ); } - if (httpClient) { - this.httpClient = httpClient; - } else { - this.httpClient = new AuthorizedHttpClient(app); - } + this.httpClient = new AuthHttpClient(app); } /** @@ -1904,11 +1915,10 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { * The FirebaseAuthRequestHandler constructor used to initialize an instance using a FirebaseApp. * * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. - * @param {HttpClient} httpClient Optional HttpClient override. * @constructor. */ - constructor(app: FirebaseApp, httpClient?: HttpClient) { - super(app, httpClient); + constructor(app: FirebaseApp) { + super(app); this.tenantMgmtResourceBuilder = new AuthResourceUrlBuilder(app, 'v2'); } @@ -2051,11 +2061,10 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. * @param {string} tenantId The request handler's tenant ID. - * @param {HttpClient} httpClient Optional HttpClient override. * @constructor */ - constructor(app: FirebaseApp, private readonly tenantId: string, httpClient?: HttpClient) { - super(app, httpClient); + constructor(app: FirebaseApp, private readonly tenantId: string) { + super(app); } /** diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 2b354d5edf..d57f182bc2 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -57,7 +57,6 @@ import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; import BaseAuthInterface = auth.BaseAuth; import AuthInterface = auth.Auth; import TenantAwareAuthInterface = auth.TenantAwareAuth; -import { AuthorizedHttpClient } from '../utils/api-request'; /** * Internals of an Auth instance. @@ -74,22 +73,6 @@ class AuthInternals implements FirebaseServiceInternalsInterface { } } -/** - * Auth-specific HTTP client which uses the special "owner" token - * when communicating with the Auth Emulator. - */ -class AuthHttpClient extends AuthorizedHttpClient { - - protected getToken(): Promise { - if (useEmulator()) { - return Promise.resolve('owner'); - } - - return super.getToken(); - } - -} - /** * Base Auth class. Mainly used for user management APIs. */ @@ -751,7 +734,7 @@ export class TenantAwareAuth constructor(app: FirebaseApp, tenantId: string) { const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); - super(app, new TenantAwareAuthRequestHandler(app, tenantId, new AuthHttpClient(app)), tokenGenerator); + super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); utils.addReadonlyGetter(this, 'tenantId', tenantId); } @@ -847,7 +830,7 @@ export class Auth extends BaseAuth * @constructor */ constructor(app: FirebaseApp) { - super(app, new AuthRequestHandler(app, new AuthHttpClient(app))); + super(app, new AuthRequestHandler(app)); this.app_ = app; this.tenantManager_ = new TenantManager(app); } @@ -875,6 +858,6 @@ export class Auth extends BaseAuth * For security reasons that must be explicitly disabled through * setJwtVerificationEnabled(false); */ -function useEmulator(): boolean { +export function useEmulator(): boolean { return !!process.env.FIREBASE_AUTH_EMULATOR_HOST; } From bfa539decf13bf8940bced3f0ee23bd85725cbe5 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 10 Nov 2020 11:21:43 +0000 Subject: [PATCH 6/9] Reduce diff --- src/auth/auth-api-request.ts | 4 ++-- src/auth/auth.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 86c75ef4c8..643531de66 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -23,7 +23,7 @@ import { import { FirebaseApp } from '../firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { - ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpClient + ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, @@ -942,7 +942,7 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') */ export abstract class AbstractAuthRequestHandler { - protected readonly httpClient: HttpClient; + protected readonly httpClient: AuthorizedHttpClient; private authUrlBuilder: AuthResourceUrlBuilder; private projectConfigUrlBuilder: AuthResourceUrlBuilder; diff --git a/src/auth/auth.ts b/src/auth/auth.ts index d57f182bc2..689ed99cd4 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -73,6 +73,7 @@ class AuthInternals implements FirebaseServiceInternalsInterface { } } + /** * Base Auth class. Mainly used for user management APIs. */ From 116cb4a20370d82df37bcdc313ba510b59729306 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 10 Nov 2020 11:22:35 +0000 Subject: [PATCH 7/9] Stray comma --- src/auth/auth-api-request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 643531de66..613a392f84 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -23,7 +23,7 @@ import { import { FirebaseApp } from '../firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { - ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError + ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, } from '../utils/api-request'; import { UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, From c1804edbecbbf6281e9c10425da961137d177cf0 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 11 Nov 2020 13:59:04 +0000 Subject: [PATCH 8/9] Remove circular import, add unit test --- src/auth/auth-api-request.ts | 27 +++++++++--- src/auth/auth.ts | 14 +----- src/utils/api-request.ts | 2 +- test/unit/auth/auth-api-request.spec.ts | 57 +++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 613a392f84..3b8e243a5d 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -36,7 +36,6 @@ import { } from './auth-config'; import { Tenant, TenantServerResponse } from './tenant'; import { auth } from './index'; -import { useEmulator } from './auth'; import CreateRequest = auth.CreateRequest; import UpdateRequest = auth.UpdateRequest; @@ -144,10 +143,9 @@ class AuthResourceUrlBuilder { * @constructor */ constructor(protected app: FirebaseApp, protected version: string = 'v1') { - const emulatorHost = process.env.FIREBASE_AUTH_EMULATOR_HOST; - if (emulatorHost) { + if (emulatorHost()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT, { - host: emulatorHost + host: emulatorHost() }); } else { this.urlFormat = FIREBASE_AUTH_BASE_URL_FORMAT; @@ -211,10 +209,9 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { */ constructor(protected app: FirebaseApp, protected version: string, protected tenantId: string) { super(app, version); - const emulatorHost = process.env.FIREBASE_AUTH_EMULATOR_HOST - if (emulatorHost) { + if (emulatorHost()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT, { - host: emulatorHost + host: emulatorHost() }); } else { this.urlFormat = FIREBASE_AUTH_TENANT_URL_FORMAT; @@ -2111,3 +2108,19 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { return super.uploadAccount(users, options); } } + +function emulatorHost(): string | undefined { + return process.env.FIREBASE_AUTH_EMULATOR_HOST +} + +/** + * When true the SDK should communicate with the Auth Emulator for all API + * calls and also produce unsigned tokens. + * + * This alone does NOT short-circuit ID Token verification. + * For security reasons that must be explicitly disabled through + * setJwtVerificationEnabled(false); + */ +export function useEmulator(): boolean { + return !!emulatorHost(); +} diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 689ed99cd4..03d1c4e666 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -21,7 +21,7 @@ import { import { FirebaseApp } from '../firebase-app'; import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; import { - AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, + AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, } from './auth-api-request'; import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; @@ -850,15 +850,3 @@ export class Auth extends BaseAuth return this.tenantManager_; } } - -/** - * When true the SDK should communicate with the Auth Emulator for all API - * calls and also produce unsigned tokens. - * - * This alone does NOT short-circuit ID Token verification. - * For security reasons that must be explicitly disabled through - * setJwtVerificationEnabled(false); - */ -export function useEmulator(): boolean { - return !!process.env.FIREBASE_AUTH_EMULATOR_HOST; -} diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 1b44e73021..d079b44557 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -831,7 +831,7 @@ export class AuthorizedHttpClient extends HttpClient { return this.app.INTERNAL.getToken() .then((accessTokenObj) => { return accessTokenObj.accessToken; - }) + }); } } diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 0a86231e53..13d5fcfc33 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -865,6 +865,10 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { 'X-Client-Version': `Node/Admin/${getSdkVersion()}`, 'Authorization': 'Bearer ' + mockAccessToken, }; + const expectedHeadersEmulator: {[key: string]: string} = { + 'X-Client-Version': `Node/Admin/${getSdkVersion()}`, + 'Authorization': 'Bearer owner', + }; const callParams = (path: string, method: any, data: any): HttpRequestConfig => { return { method, @@ -902,6 +906,59 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); }); + describe('Emulator Support', () => { + const method = 'POST'; + const path = handler.path('v1', '/accounts:lookup', 'project_id'); + const expectedResult = utils.responseFrom({ + users : [ + { localId: 'uid' }, + ], + }); + const data = { localId: ['uid'] }; + + after(() => { + delete process.env.FIREBASE_AUTH_EMULATOR_HOST; + }) + + it('should call a prod URL with a real token when emulator is not running', () => { + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + + return requestHandler.getAccountInfoByUid('uid') + .then(() => { + expect(stub).to.have.been.calledOnce.and.calledWith({ + method, + url: `https://${host}${path}`, + data, + headers: expectedHeaders, + timeout, + }); + }); + }); + + it('should call a local URL with a mock token when the emulator is running', () => { + const emulatorHost = 'localhost:9099'; + process.env.FIREBASE_AUTH_EMULATOR_HOST = emulatorHost; + + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + return requestHandler.getAccountInfoByUid('uid') + .then(() => { + expect(stub).to.have.been.calledOnce.and.calledWith({ + method, + url: `http://${emulatorHost}/identitytoolkit.googleapis.com${path}`, + data, + headers: expectedHeadersEmulator, + timeout, + }); + }); + }); + }); + describe('createSessionCookie', () => { const durationInMs = 24 * 60 * 60 * 1000; const path = handler.path('v1', ':createSessionCookie', 'project_id'); From f28aa79d077c9ddd3194675581cd09137f2bd382 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Wed, 11 Nov 2020 13:23:00 -0500 Subject: [PATCH 9/9] Final review feedback --- src/auth/auth-api-request.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 3b8e243a5d..740d173356 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -143,7 +143,7 @@ class AuthResourceUrlBuilder { * @constructor */ constructor(protected app: FirebaseApp, protected version: string = 'v1') { - if (emulatorHost()) { + if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT, { host: emulatorHost() }); @@ -209,7 +209,7 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { */ constructor(protected app: FirebaseApp, protected version: string, protected tenantId: string) { super(app, version); - if (emulatorHost()) { + if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT, { host: emulatorHost() });