Skip to content

Commit 37c1a01

Browse files
authored
Finish off the emulator hooks (#3732)
* Finish emulator implementation * Formatting * Add appVerificationDisabledForTesting setting and the type to auth-types-exp * Formatting
1 parent 603cd87 commit 37c1a01

File tree

13 files changed

+196
-99
lines changed

13 files changed

+196
-99
lines changed

packages-exp/auth-exp/src/api/index.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import { mockEndpoint } from '../../test/helpers/api/helper';
2525
import { testAuth, TestAuth } from '../../test/helpers/mock_auth';
2626
import * as mockFetch from '../../test/helpers/mock_fetch';
2727
import { AuthErrorCode } from '../core/errors';
28+
import { ConfigInternal } from '../model/auth';
2829
import {
30+
_getFinalTarget,
2931
_performApiRequest,
3032
DEFAULT_API_TIMEOUT_MS,
3133
Endpoint,
@@ -327,4 +329,22 @@ describe('api/_performApiRequest', () => {
327329
}
328330
});
329331
});
332+
333+
context('_getFinalTarget', () => {
334+
it('works properly with a non-emulated environment', () => {
335+
expect(_getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
336+
'mock://host/path?query=test'
337+
);
338+
});
339+
340+
it('works properly with an emulated environment', () => {
341+
(auth.config as ConfigInternal).emulator = {
342+
hostname: 'localhost',
343+
port: 5000
344+
};
345+
expect(_getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
346+
'http://localhost:5000/host/path?query=test'
347+
);
348+
});
349+
});
330350
});

packages-exp/auth-exp/src/api/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '../core/errors';
2525
import { fail } from '../core/util/assert';
2626
import { Delay } from '../core/util/delay';
27+
import { _emulatorUrl } from '../core/util/emulator';
2728
import { FetchProvider } from '../core/util/fetch_provider';
2829
import { Auth, AuthCore } from '../model/auth';
2930
import { IdTokenResponse, TaggedWithTokenResponse } from '../model/id_token';
@@ -191,14 +192,13 @@ export function _getFinalTarget(
191192
path: string,
192193
query: string
193194
): string {
194-
const { emulator } = auth.config;
195195
const base = `${host}${path}?${query}`;
196196

197-
if (!emulator) {
197+
if (!auth.config.emulator) {
198198
return `${auth.config.apiScheme}://${base}`;
199199
}
200200

201-
return `http://${emulator.hostname}:${emulator.port}/${base}`;
201+
return _emulatorUrl(auth.config, base);
202202
}
203203

204204
function makeNetworkTimeout<T>(appName: string): Promise<T> {

packages-exp/auth-exp/src/core/auth/auth_impl.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import * as sinonChai from 'sinon-chai';
2323
import { FirebaseApp } from '@firebase/app-types-exp';
2424
import { FirebaseError } from '@firebase/util';
2525

26-
import { testUser } from '../../../test/helpers/mock_auth';
26+
import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper';
27+
import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth';
28+
import * as fetch from '../../../test/helpers/mock_fetch';
29+
import { Endpoint } from '../../api';
2730
import { Auth } from '../../model/auth';
2831
import { User } from '../../model/user';
2932
import { Persistence } from '../persistence';
@@ -388,3 +391,47 @@ describe('core/auth/auth_impl', () => {
388391
});
389392
});
390393
});
394+
395+
// These tests are separate because they are using a different auth with
396+
// separate setup and config
397+
describe('core/auth/auth_impl useEmulator', () => {
398+
let auth: TestAuth;
399+
let user: User;
400+
let normalEndpoint: fetch.Route;
401+
let emulatorEndpoint: fetch.Route;
402+
403+
beforeEach(async () => {
404+
auth = await testAuth();
405+
user = testUser(_castAuth(auth), 'uid', 'email', true);
406+
fetch.setUp();
407+
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
408+
emulatorEndpoint = fetch.mock(
409+
`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(
410+
/^.*:\/\//,
411+
''
412+
)}`,
413+
{}
414+
);
415+
});
416+
417+
afterEach(() => {
418+
fetch.tearDown();
419+
});
420+
421+
context('useEmulator', () => {
422+
it('fails if a network request has already been made', async () => {
423+
await user.delete();
424+
expect(() => auth.useEmulator('localhost', 2020)).to.throw(
425+
FirebaseError,
426+
'auth/emulator-config-failed'
427+
);
428+
});
429+
430+
it('updates the endpoint appropriately', async () => {
431+
auth.useEmulator('localhost', 2020);
432+
await user.delete();
433+
expect(normalEndpoint.calls.length).to.eq(0);
434+
expect(emulatorEndpoint.calls.length).to.eq(1);
435+
});
436+
});
437+
});

packages-exp/auth-exp/src/core/auth/auth_impl.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
Unsubscribe
2828
} from '@firebase/util';
2929

30-
import { Auth, AuthCore } from '../../model/auth';
30+
import { Auth, AuthCore, ConfigInternal } from '../../model/auth';
3131
import { PopupRedirectResolver } from '../../model/popup_redirect';
3232
import { User, UserParameters } from '../../model/user';
3333
import { AuthErrorCode } from '../errors';
@@ -82,7 +82,7 @@ export class AuthImplCompat<T extends User> implements Auth, _FirebaseService {
8282

8383
constructor(
8484
public readonly app: FirebaseApp,
85-
public readonly config: externs.Config,
85+
public readonly config: ConfigInternal,
8686
private readonly _userProvider: UserProvider<T>
8787
) {
8888
this.name = app.name;
@@ -189,6 +189,19 @@ export class AuthImplCompat<T extends User> implements Auth, _FirebaseService {
189189
this.languageCode = _getUserLanguage();
190190
}
191191

192+
useEmulator(hostname: string, port: number): void {
193+
assert(this._canInitEmulator, AuthErrorCode.EMULATOR_CONFIG_FAILED, {
194+
appName: this.name
195+
});
196+
197+
this.config.emulator = {
198+
hostname,
199+
port
200+
};
201+
202+
this.settings.appVerificationDisabledForTesting = true;
203+
}
204+
192205
async _delete(): Promise<void> {
193206
// TODO: Determine what we want to do in this case
194207
}

packages-exp/auth-exp/src/core/auth/initialize.test.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

packages-exp/auth-exp/src/core/auth/initialize.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ import { FirebaseApp } from '@firebase/app-types-exp';
2020
import * as externs from '@firebase/auth-types-exp';
2121

2222
import { Dependencies } from '../../model/auth';
23-
import { AuthErrorCode } from '../errors';
2423
import { Persistence } from '../persistence';
25-
import { assert } from '../util/assert';
2624
import { _getInstance } from '../util/instantiator';
2725
import { _castAuth, AuthImpl } from './auth_impl';
2826

@@ -36,22 +34,6 @@ export function initializeAuth(
3634
return auth;
3735
}
3836

39-
export function useEmulator(
40-
authExtern: externs.Auth,
41-
hostname: string,
42-
port: number
43-
): void {
44-
const auth = _castAuth(authExtern);
45-
assert(auth._canInitEmulator, AuthErrorCode.EMULATOR_CONFIG_FAILED, {
46-
appName: auth.name
47-
});
48-
49-
auth.config.emulator = {
50-
hostname,
51-
port
52-
};
53-
}
54-
5537
export function _initializeAuthInstance(
5638
auth: AuthImpl,
5739
deps?: Dependencies
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
20+
import { ConfigInternal } from '../../model/auth';
21+
import { _emulatorUrl } from './emulator';
22+
23+
describe('core/util/emulator', () => {
24+
const config: ConfigInternal = {
25+
emulator: {
26+
hostname: 'localhost',
27+
port: 4000
28+
}
29+
} as ConfigInternal;
30+
31+
it('builds the proper URL with no path', () => {
32+
expect(_emulatorUrl(config)).to.eq('http://localhost:4000');
33+
});
34+
35+
it('builds the proper URL with a path', () => {
36+
expect(_emulatorUrl(config, '/test/path')).to.eq(
37+
'http://localhost:4000/test/path'
38+
);
39+
});
40+
41+
it('builds the proper URL with a path missing separator', () => {
42+
expect(_emulatorUrl(config, 'test/path')).to.eq(
43+
'http://localhost:4000/test/path'
44+
);
45+
});
46+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { ConfigInternal } from '../../model/auth';
19+
import { debugAssert } from './assert';
20+
21+
export function _emulatorUrl(config: ConfigInternal, path?: string): string {
22+
debugAssert(config.emulator, 'Emulator should always be set here');
23+
const { hostname, port } = config.emulator;
24+
25+
const base = `http://${hostname}:${port}`;
26+
if (!path) {
27+
return base;
28+
}
29+
30+
const sep = path.startsWith('/') ? '' : '/';
31+
return `${base}${sep}${path}`;
32+
}

packages-exp/auth-exp/src/core/util/validate_origin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const IP_ADDRESS_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
2525
const HTTP_REGEX = /^https?/;
2626

2727
export async function _validateOrigin(auth: Auth): Promise<void> {
28+
// Skip origin validation if we are in an emulated environment
29+
if (auth.config.emulator) {
30+
return;
31+
}
32+
2833
const { authorizedDomains } = await _getProjectConfig(auth);
2934

3035
for (const domain of authorizedDomains) {

packages-exp/auth-exp/src/model/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export type AppName = string;
2525
export type ApiKey = string;
2626
export type AuthDomain = string;
2727

28-
interface ConfigInternal extends externs.Config {
28+
export interface ConfigInternal extends externs.Config {
2929
emulator?: {
3030
hostname: string;
3131
port: number;

packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ import { querystring } from '@firebase/util';
2121
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors';
2222
import { assert } from '../../core/util/assert';
2323
import { Delay } from '../../core/util/delay';
24+
import { _emulatorUrl } from '../../core/util/emulator';
2425
import { AuthCore } from '../../model/auth';
2526
import { _window } from '../auth_window';
2627
import * as gapiLoader from './gapi';
2728

2829
const PING_TIMEOUT = new Delay(5000, 15000);
30+
const IFRAME_PATH = '__/auth/iframe';
31+
const EMULATED_IFRAME_PATH = 'emulator/auth/iframe';
32+
2933
const IFRAME_ATTRIBUTES = {
3034
style: {
3135
position: 'absolute',
@@ -36,10 +40,13 @@ const IFRAME_ATTRIBUTES = {
3640
};
3741

3842
function getIframeUrl(auth: AuthCore): string {
39-
const url = `https://${auth.config.authDomain!}/__/auth/iframe`;
43+
const config = auth.config;
44+
const url = config.emulator
45+
? _emulatorUrl(config, EMULATED_IFRAME_PATH)
46+
: `https://${auth.config.authDomain!}/${IFRAME_PATH}`;
4047

4148
const params = {
42-
apiKey: auth.config.apiKey,
49+
apiKey: config.apiKey,
4350
appName: auth.name,
4451
v: SDK_VERSION
4552
};

0 commit comments

Comments
 (0)