UNPKG

voluptasmollitia

Version:
1,550 lines (1,444 loc) 435 kB
/** * @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. */ /** * @fileoverview Tests for auth.js */ goog.provide('fireauth.AuthTest'); goog.require('fireauth.ActionCodeInfo'); goog.require('fireauth.ActionCodeSettings'); goog.require('fireauth.Auth'); goog.require('fireauth.AuthCredential'); goog.require('fireauth.AuthError'); goog.require('fireauth.AuthErrorWithCredential'); goog.require('fireauth.AuthEvent'); goog.require('fireauth.AuthEventManager'); goog.require('fireauth.AuthSettings'); goog.require('fireauth.AuthUser'); goog.require('fireauth.EmailAuthProvider'); goog.require('fireauth.GoogleAuthProvider'); goog.require('fireauth.MultiFactorAssertion'); goog.require('fireauth.MultiFactorInfo'); goog.require('fireauth.MultiFactorSession'); goog.require('fireauth.PhoneAuthProvider'); goog.require('fireauth.RpcHandler'); goog.require('fireauth.SAMLAuthProvider'); goog.require('fireauth.StsTokenManager'); goog.require('fireauth.UserEventType'); goog.require('fireauth.authStorage'); goog.require('fireauth.authenum.Error'); goog.require('fireauth.common.testHelper'); goog.require('fireauth.constants'); goog.require('fireauth.deprecation'); /** @suppress {extraRequire} Needed for firebase.app().auth() */ goog.require('fireauth.exports'); goog.require('fireauth.idp'); goog.require('fireauth.iframeclient.IfcHandler'); goog.require('fireauth.object'); goog.require('fireauth.storage.MockStorage'); goog.require('fireauth.storage.PendingRedirectManager'); goog.require('fireauth.storage.RedirectUserManager'); goog.require('fireauth.storage.UserManager'); goog.require('fireauth.util'); goog.require('goog.Promise'); goog.require('goog.Timer'); goog.require('goog.Uri'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.testing.AsyncTestCase'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.events'); goog.require('goog.testing.events.Event'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.recordFunction'); goog.setTestOnly('fireauth.AuthTest'); var appId1 = 'appId1'; var appId2 = 'appId2'; var auth1 = null; var auth2 = null; var authInternal1 = null; var authInternal2 = null; var app1 = null; var app2 = null; var authUi1 = null; var authUi2 = null; var config1 = null; var config2 = null; var config3 = null; var rpcHandler = null; var token = null; var accountInfo = { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'emailVerified': true }; var accountInfoWithTenantId = { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'emailVerified': true, 'tenantId': '123456789012' }; // accountInfo in the format of a getAccountInfo response. var getAccountInfoResponse = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'providerUserInfo': [], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false }] }; // A sample JWT, along with its decoded contents. var idTokenGmail = { data: { sub: '679', aud: '204241631686', provider_id: 'gmail.com', email: 'test123456@gmail.com', federated_id: 'https://www.google.com/accounts/123456789' } }; var expectedTokenResponse; var expectedTokenResponse2; var expectedTokenResponse3; var expectedTokenResponse4; var expectedTokenResponseWithIdPData; var expectedAdditionalUserInfo; var expectedGoogleCredential; var expectedSamlTokenResponseWithIdPData; var expectedSamlAdditionalUserInfo; var now = Date.now(); var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); var mockControl; var ignoreArgument; var stubs = new goog.testing.PropertyReplacer(); var angular = {}; var currentUserStorageManager; var redirectUserStorageManager; var timeoutDelay = 30000; var clock; var actionCodeSettings = { 'url': 'https://www.example.com/?state=abc', 'iOS': { 'bundleId': 'com.example.ios' }, 'android': { 'packageName': 'com.example.android', 'installApp': true, 'minimumVersion': '12' }, 'handleCodeInApp': true, 'dynamicLinkDomain': 'example.page.link' }; var mockLocalStorage; var mockSessionStorage; var jwt1; var jwt2; var jwt3; var multiFactorErrorServerResponse; var multiFactorTokenResponse; var multiFactorGetAccountInfoResponse; function setUp() { // Create new mock storages for persistent and temporary storage before each // test. mockLocalStorage = new fireauth.storage.MockStorage(); mockSessionStorage = new fireauth.storage.MockStorage(); // Disable Auth event manager for testing unless needed. fireauth.AuthEventManager.ENABLED = false; // Assume origin is a valid one. simulateWhitelistedOrigin(); // Simulate tab can run in background. stubs.replace( fireauth.util, 'runsInBackground', function() { return true; }); // In case the tests are run from an iframe. stubs.replace( fireauth.util, 'isIframe', function() { return false; }); // Called on init state when a user is logged in. stubs.replace( fireauth.AuthUser.prototype, 'reload', function() { return goog.Promise.resolve(); }); stubs.replace( Date, 'now', function() { return now; }); stubs.replace( fireauth.util, 'getCurrentUrl', function() {return 'http://localhost';}); initializeMockStorage(); jwt1 = fireauth.common.testHelper.createMockJwt( {'group': '1'}, now + 3600 * 1000); jwt2 = fireauth.common.testHelper.createMockJwt( {'group': '2'}, now + 3600 * 1000); jwt3 = fireauth.common.testHelper.createMockJwt( {'group': '3'}, now + 3600 * 1000); idTokenGmail.data.exp = now / 1000 + 3600; idTokenGmail.jwt = fireauth.common.testHelper.createMockJwt(idTokenGmail.data); // Initialize App and Auth instances. config1 = { apiKey: 'apiKey1' }; config2 = { apiKey: 'apiKey2' }; config3 = { 'apiKey': 'API_KEY', 'authDomain': 'subdomain.firebaseapp.com', 'appName': 'appId1' }; // Same as config3 but with a different authDomain. config4 = { 'apiKey': 'API_KEY', 'authDomain': 'subdomain2.firebaseapp.com', 'appName': 'appId1' }; expectedTokenResponse = { 'idToken': jwt1, 'refreshToken': 'REFRESH_TOKEN', 'expiresIn': '3600' }; expectedTokenResponse2 = { 'idToken': jwt2, 'refreshToken': 'REFRESH_TOKEN2', 'expiresIn': '3600' }; expectedTokenResponse3 = { 'idToken': jwt3, 'refreshToken': 'REFRESH_TOKEN3', 'expiresIn': '3600' }; expectedTokenResponse4 = { // Sample ID token with provider password and email user@example.com. 'idToken': fireauth.common.testHelper.createMockJwt({ 'iss': 'https://securetoken.google.com/12345678', 'picture': 'https://plus.google.com/abcdefghijklmnopqrstu', 'aud': '12345678', 'auth_time': 1510357622, 'sub': 'abcdefghijklmnopqrstu', 'iat': 1510357622, 'exp': now / 1000 + 3600, 'email': 'user@example.com', 'email_verified': true, 'firebase': { 'identities': { 'email': [ 'user@example.com' ] }, 'sign_in_provider': 'password' } }), 'refreshToken': 'REFRESH_TOKEN4', 'expiresIn': '3600' }; expectedTokenResponseWithIdPData = { 'idToken': jwt1, 'refreshToken': 'REFRESH_TOKEN', 'expiresIn': '3600', // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + 'me":{"givenName":"John","familyName":"Doe"}}' }; expectedAdditionalUserInfo = { 'profile': { 'kind': 'plus#person', 'displayName': 'John Doe', 'name': { 'givenName': 'John', 'familyName': 'Doe' } }, 'providerId': 'google.com', 'isNewUser': false }; expectedGoogleCredential = fireauth.GoogleAuthProvider.credential( 'googleIdToken', 'googleAccessToken'); expectedSamlTokenResponseWithIdPData = { 'idToken': jwt1, 'refreshToken': 'REFRESH_TOKEN', 'expiresIn': '3600', 'providerId': 'saml.provider', // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + 'me":{"givenName":"John","familyName":"Doe"}}' }; expectedSamlAdditionalUserInfo = { 'profile': { 'kind': 'plus#person', 'displayName': 'John Doe', 'name': { 'givenName': 'John', 'familyName': 'Doe' } }, 'providerId': 'saml.provider', 'isNewUser': false }; multiFactorTokenResponse = { 'idToken': fireauth.common.testHelper.createMockJwt({ 'sub': 'defaultUserId', 'firebase': { 'sign_in_provider': 'password', 'sign_in_second_factor': 'phone' } }), 'refreshToken': 'MULTI_FACTOR_REFRESH_TOKEN' }; multiFactorErrorServerResponse = { 'mfaInfo': [ { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+*******1234' }, { 'mfaEnrollmentId': 'ENROLLMENT_UID2', 'displayName': 'Spouse phone number', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+*******6789' } ], 'mfaPendingCredential': 'PENDING_CREDENTIAL', // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + '"name":{"givenName":"John","familyName":"Doe"}}' }; multiFactorGetAccountInfoResponse = { 'users': [{ 'localId': 'defaultUserId', 'email': 'user@default.com', 'emailVerified': true, 'displayName': 'defaultDisplayName', 'providerUserInfo': [], 'photoUrl': 'https://www.default.com/default/default.png', 'passwordUpdatedAt': 0.0, 'disabled': false, 'lastLoginAt': '1506050282000', 'createdAt': '1506050282000', 'mfaInfo': [ { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505551234' }, { 'mfaEnrollmentId': 'ENROLLMENT_UID2', 'displayName': 'Spouse phone number', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505556789' } ] }] }; rpcHandler = new fireauth.RpcHandler('apiKey1'); token = new fireauth.StsTokenManager(rpcHandler); token.setRefreshToken('refreshToken'); token.setAccessToken(jwt1); token.setExpiresIn(3600); ignoreArgument = goog.testing.mockmatchers.ignoreArgument; mockControl = new goog.testing.MockControl(); mockControl.$resetAll(); } function tearDown() { // Delete all Firebase apps created var promises = []; for (var i = 0; i < firebase.apps.length; i++) { promises.push(firebase.apps[i].delete()); } if (promises.length) { // Wait for all Firebase apps to be deleted. asyncTestCase.waitForSignals(1); goog.Promise.all(promises).then(function() { // Dispose clock then. Disposing before will throw an error in IE 11. goog.dispose(clock); asyncTestCase.signal(); }); if (clock) { // Some IE browsers like IE 11, native promise hangs if this is not called // when clock is mocked. // app.delete() will hang (it uses the native Promise). clock.tick(); } } else if (clock) { // No Firebase apps created, dispose clock immediately. goog.dispose(clock); } fireauth.AuthEventManager.manager_ = {}; window.localStorage.clear(); window.sessionStorage.clear(); rpcHandler = null; token = null; if (auth1) { auth1.delete(); } auth1 = null; if (auth2) { auth2.delete(); } auth2 = null; app1 = null; app2 = null; config1 = null; config2 = null; config3 = null; multiFactorErrorServerResponse = null; multiFactorTokenResponse = null; multiFactorGetAccountInfoResponse = null; stubs.reset(); try { mockControl.$verifyAll(); } finally { mockControl.$tearDown(); } fireauth.authStorage.Manager.clear(); currentUserStorageManager = null; redirectUserStorageManager = null; if (goog.global.document) { fireauth.util.onDomReady().then(function () { var el = goog.global.document.querySelector('.firebase-emulator-warning'); if (el) { el.parentNode.removeChild(el); } }); } } function testInitializeApp_noApiKey() { var expectedError = new fireauth.AuthError( fireauth.authenum.Error.INVALID_API_KEY); // Initialize app without an API key. var appWithoutApiKey = firebase.initializeApp({}, 'appWithoutApiKey'); // Initialization without API key should not throw an Auth error. // Only on Auth explicit initialization will the error be visibly thrown. try { appWithoutApiKey.auth(); fail('Auth initialization should fail due to missing api key.'); } catch (e) { fireauth.common.testHelper.assertErrorEquals(expectedError, e); } } /** * Assert the Auth token listener is called once. * @param {!fireauth.Auth} auth The Auth instance. */ function assertAuthTokenListenerCalledOnce(auth) { var calls = 0; asyncTestCase.waitForSignals(1); auth.addAuthTokenListener(function(token) { // Should only trigger once after init state. calls++; assertEquals(1, calls); asyncTestCase.signal(); }); } /** Initializes mock storages. */ function initializeMockStorage() { // Simulate tab can run in background. stubs.replace( fireauth.util, 'runsInBackground', function() { return true; }); fireauth.common.testHelper.installMockStorages( stubs, mockLocalStorage, mockSessionStorage); } /** * Simulates current origin is whitelisted for popups and redirects. */ function simulateWhitelistedOrigin() { // Assume origin is a valid one. stubs.replace( fireauth.RpcHandler.prototype, 'getAuthorizedDomains', function() { var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); var domain = uri.getDomain(); return goog.Promise.resolve([domain]); }); } /** * Asserts that two users are equivalent. Plain assertObjectEquals may not work * as the expiration time may sometimes be off by a second. This takes that into * account. * @param {!fireauth.AuthUser} expected * @param {!fireauth.AuthUser} actual */ function assertUserEquals(expected, actual) { fireauth.common.testHelper.assertUserEqualsInWithDiffApikey(expected, actual); } function testAuth_noApiKey() { try { app1 = firebase.initializeApp({}, appId1); app1.auth(); fail('Should have thrown an error!'); } catch (e) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY), e); } } function testToJson_noUser() { // Test toJSON with no user signed in. app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); var authPlainObject = { 'apiKey': config1['apiKey'], 'authDomain': config1['authDomain'], 'appName': appId1, 'currentUser': null }; assertObjectEquals(authPlainObject, auth1.toJSON()); // Make sure JSON.stringify works and uses underlying toJSON. assertEquals(JSON.stringify(auth1), JSON.stringify(auth1.toJSON())); } function testToJson_withUser() { // Test toJSON with a user signed in. stubs.reset(); fireauth.AuthEventManager.ENABLED = false; initializeMockStorage(); // Simulate available token. stubs.replace( fireauth.AuthUser.prototype, 'reload', function() { // Reload will be called on init. return goog.Promise.resolve(); }); stubs.replace( Date, 'now', function() { return now; }); asyncTestCase.waitForSignals(1); app1 = firebase.initializeApp(config1, appId1); var user1 = new fireauth.AuthUser( config3, expectedTokenResponse, accountInfo); var storageKey = fireauth.util.createStorageKey(config1['apiKey'], appId1); var authPlainObject = { 'apiKey': config1['apiKey'], 'authDomain': config1['authDomain'], 'appName': appId1, 'currentUser': user1.toJSON() }; currentUserStorageManager = new fireauth.storage.UserManager(storageKey); // Simulate logged in user, save to storage, it will be picked up on init // Auth state. currentUserStorageManager.setCurrentUser(user1).then(function() { auth1 = app1.auth(); auth1.onIdTokenChanged(function(user) { assertObjectEquals(authPlainObject, auth1.toJSON()); // Make sure JSON.stringify works and uses underlying toJSON. assertEquals(JSON.stringify(auth1), JSON.stringify(auth1.toJSON())); asyncTestCase.signal(); }); }); } function testGetStorageKey() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); assertEquals(config1['apiKey'] + ':' + appId1, auth1.getStorageKey()); } function testAuth_initListeners_disabled() { // Test with init listener disabled. app1 = firebase.initializeApp(config1, appId1); app2 = firebase.initializeApp(config2, appId2); auth1 = app1.auth(); auth2 = app2.auth(); assertObjectEquals(app1, auth1.app_()); assertObjectEquals(app2, auth2.app_()); } function testApp() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); assertObjectEquals(app1, auth1.app_()); } function testGetRpcHandler() { app1 = firebase.initializeApp(config1, appId1); app2 = firebase.initializeApp(config2, appId2); auth1 = app1.auth(); auth2 = app2.auth(); assertNotNull(auth1.getRpcHandler()); assertNotNull(auth2.getRpcHandler()); assertEquals('apiKey1', auth1.getRpcHandler().getApiKey()); assertEquals('apiKey2', auth2.getRpcHandler().getApiKey()); } function testAuth_rpcHandlerEndpoints() { // Confirm expected endpoint config passed to underlying RPC handler. var endpoint = fireauth.constants.Endpoint.STAGING; var endpointConfig = { 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, 'secureTokenEndpoint': endpoint.secureTokenEndpoint }; stubs.replace( fireauth.constants, 'getEndpointConfig', function(opt_id) { return endpointConfig; }); var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); var rpcHandlerConstructor = mockControl.createConstructorMock( fireauth, 'RpcHandler'); rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) .$returns(rpcHandler); mockControl.$replayAll(); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); } function testAuth_rpcHandlerEndpoints_tenantId() { // Confirm expected endpoint config passed to underlying RPC handler. var endpoint = fireauth.constants.Endpoint.STAGING; var endpointConfig = { 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, 'secureTokenEndpoint': endpoint.secureTokenEndpoint }; stubs.replace( fireauth.constants, 'getEndpointConfig', function(opt_id) { return endpointConfig; }); var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); var rpcHandlerConstructor = mockControl.createConstructorMock( fireauth, 'RpcHandler'); rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) .$returns(rpcHandler); // Tenant ID of RPC handler should be updated. rpcHandler.updateTenantId('TENANT_ID').$once(); mockControl.$replayAll(); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Sets the tenant ID on Auth instance. auth1.tenantId = 'TENANT_ID'; } function testCurrentUser() { app1 = firebase.initializeApp(config3, appId1); auth1 = app1.auth(); var user = new fireauth.AuthUser( config3, expectedTokenResponse, accountInfo); auth1.setCurrentUser_(user); assertUserEquals(user, auth1['currentUser']); } function testAuthSettings() { app1 = firebase.initializeApp(config3, appId1); auth1 = app1.auth(); assertTrue(auth1['settings'] instanceof fireauth.AuthSettings); assertFalse(auth1['settings']['appVerificationDisabledForTesting']); auth1['settings']['appVerificationDisabledForTesting'] = true; assertTrue(auth1['settings']['appVerificationDisabledForTesting']); // Confirm validation is applied. var error = assertThrows(function() { auth1['settings']['appVerificationDisabledForTesting'] = 'invalid'; }); var expectedError = new fireauth.AuthError( fireauth.authenum.Error.ARGUMENT_ERROR, 'appVerificationDisabledForTesting failed: ' + '"appVerificationDisabledForTesting" must be a boolean.'); fireauth.common.testHelper.assertErrorEquals(expectedError, error); // Confirm settings is a read-only property. auth1['settings'] = null; assertTrue(auth1['settings'] instanceof fireauth.AuthSettings); } function testLogFramework() { // Helper function to get the client version for the test. var getVersion = function(frameworks) { return fireauth.util.getClientVersion( fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, frameworks); }; // Pipe through all framework IDs. stubs.replace( fireauth.util, 'getFrameworkIds', function(providedFrameworks) { return providedFrameworks; }); // Listen to all client version update calls on RpcHandler. stubs.replace( fireauth.RpcHandler.prototype, 'updateClientVersion', goog.testing.recordFunction()); var handler = goog.testing.recordFunction(); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Listen to all frameworkChanged events dispatched by the Auth instance. goog.events.listen( auth1, fireauth.constants.AuthEventType.FRAMEWORK_CHANGED, handler); assertArrayEquals([], auth1.getFramework()); assertEquals(0, handler.getCallCount()); assertEquals( 0, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); // Add version and confirm event triggered and client version updated in // RpcHandler. auth1.logFramework('angularfire'); assertArrayEquals(['angularfire'], auth1.getFramework()); assertEquals(1, handler.getCallCount()); assertArrayEquals( ['angularfire'], handler.getLastCall().getArgument(0).frameworks); assertEquals( 1, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); assertEquals( getVersion(['angularfire']), fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() .getArgument(0)); // Add another version and confirm event triggered and client version updated // in RpcHandler. auth1.logFramework('firebaseui'); assertArrayEquals(['angularfire', 'firebaseui'], auth1.getFramework()); assertEquals(2, handler.getCallCount()); assertArrayEquals( ['angularfire', 'firebaseui'], handler.getLastCall().getArgument(0).frameworks); assertEquals( 2, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); assertEquals( getVersion(['angularfire', 'firebaseui']), fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() .getArgument(0)); } function testInternalLogFramework() { // Record all calls to logFramework. stubs.replace( fireauth.Auth.prototype, 'logFramework', goog.testing.recordFunction()); // Confirm INTERNAL.logFramework calls logFramework. app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); assertEquals(0, fireauth.Auth.prototype.logFramework.getCallCount()); auth1.INTERNAL.logFramework('firebaseui'); assertEquals(1, fireauth.Auth.prototype.logFramework.getCallCount()); assertEquals( 'firebaseui', fireauth.Auth.prototype.logFramework.getLastCall().getArgument(0)); } function testUseDeviceLanguage() { // Listen to all custom locale header calls on RpcHandler. stubs.replace( fireauth.RpcHandler.prototype, 'updateCustomLocaleHeader', goog.testing.recordFunction()); var handler = goog.testing.recordFunction(); stubs.replace(fireauth.util, 'getUserLanguage', function() { return 'de'; }); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Listen to all languageCodeChanged events dispatched by the Auth instance. goog.events.listen( auth1, fireauth.constants.AuthEventType.LANGUAGE_CODE_CHANGED, handler); assertNull(auth1.languageCode); assertEquals(0, handler.getCallCount()); assertEquals( 0, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); // Update to English and confirm event triggered and custom locale updated in // RpcHandler. auth1.languageCode = 'en'; assertEquals('en', auth1.languageCode); assertEquals(1, handler.getCallCount()); assertEquals('en', handler.getLastCall().getArgument(0).languageCode); assertEquals( 1, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); assertEquals( 'en', fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() .getArgument(0)); // Update to device language and confirm event triggered and custom locale // updated in RpcHandler. auth1.useDeviceLanguage(); assertEquals('de', auth1.languageCode); assertEquals(2, handler.getCallCount()); assertEquals('de', handler.getLastCall().getArgument(0).languageCode); assertEquals( 2, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); assertEquals( 'de', fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() .getArgument(0)); // Developer should still be able to set the language code. // Update to French and confirm event triggered and custom locale updated in // RpcHandler. auth1.languageCode = 'fr'; assertEquals('fr', auth1.languageCode); assertEquals(3, handler.getCallCount()); assertEquals('fr', handler.getLastCall().getArgument(0).languageCode); assertEquals( 3, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); assertEquals( 'fr', fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() .getArgument(0)); // Switch back to device language. auth1.useDeviceLanguage(); assertEquals('de', auth1.languageCode); assertEquals(4, handler.getCallCount()); assertEquals('de', handler.getLastCall().getArgument(0).languageCode); assertEquals( 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); assertEquals( 'de', fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() .getArgument(0)); // Changing to the same language should not trigger any change. auth1.languageCode = 'de'; assertEquals(4, handler.getCallCount()); assertEquals( 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); // Update to null and confirm event triggered and custom locale updated in // RpcHandler. auth1.languageCode = null; assertNull(auth1.languageCode); assertEquals(5, handler.getCallCount()); assertNull(handler.getLastCall().getArgument(0).languageCode); assertEquals( 5, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); assertNull( fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() .getArgument(0)); } function testUseEmulator() { // Listen to emulator config calls on RpcHandler. stubs.replace( fireauth.RpcHandler.prototype, 'updateEmulatorConfig', goog.testing.recordFunction()); stubs.replace( fireauth.util, 'consoleInfo', goog.testing.recordFunction()); var handler = goog.testing.recordFunction(); stubs.replace( fireauth.AuthSettings.prototype, 'setAppVerificationDisabledForTesting', goog.testing.recordFunction()); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Listen to all emulatorConfigChange events dispatched by the Auth instance. goog.events.listen( auth1, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, handler); assertUndefined(fireauth.constants.emulatorConfig); assertEquals(0, handler.getCallCount()); assertEquals( 0, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertEquals(0, fireauth.util.consoleInfo.getCallCount()); assertEquals( 0, fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting. getCallCount()); // Update the emulator config. auth1.useEmulator('http://emulator.test.domain:1234'); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: 1234, options: {disableWarnings: false}, }, auth1.emulatorConfig); // Should notify the RPC handler. assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertObjectEquals( { url: 'http://emulator.test.domain:1234', disableWarnings: false, }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0) ); // Should emit a console warning and a banner. assertEquals(1, fireauth.util.consoleInfo.getCallCount()); if (goog.global.document) { asyncTestCase.waitForSignals(1); fireauth.util.onDomReady().then(() => { const el = goog.global.document.querySelector('.firebase-emulator-warning'); assertNotNull(el); asyncTestCase.signal(); }); } // Should disable App verification. assertEquals( true, fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting. getLastCall().getArgument(0)); // Update to the same config should not trigger event again. auth1.useEmulator('http://emulator.test.domain:1234'); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: 1234, options: {disableWarnings: false}, }, auth1.emulatorConfig); assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertEquals(1, fireauth.util.consoleInfo.getCallCount()); // Updating to different config should still not trigger event. auth1.useEmulator('http://emulator.other.domain:9876'); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: 1234, options: {disableWarnings: false}, }, auth1.emulatorConfig); assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); } function testUseEmulator_withDisableWarnings() { // Listen to emulator config calls on RpcHandler. stubs.replace( fireauth.RpcHandler.prototype, 'updateEmulatorConfig', goog.testing.recordFunction()); stubs.replace(fireauth.util, 'consoleInfo', goog.testing.recordFunction()); const handler = goog.testing.recordFunction(); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Listen to all emulatorConfigChange events dispatched by the Auth instance. goog.events.listen( auth1, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, handler); assertUndefined(fireauth.constants.emulatorConfig); assertEquals(0, handler.getCallCount()); assertEquals( 0, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertEquals(0, fireauth.util.consoleInfo.getCallCount()); // Update the emulator config. auth1.useEmulator( 'http://emulator.test.domain:1234', {disableWarnings: true}); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: 1234, options: {disableWarnings: true}, }, auth1.emulatorConfig); // Should notify the RPC handler. assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertObjectEquals( { url: 'http://emulator.test.domain:1234', disableWarnings: true, }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0)); // Should emit a console info but not a banner. assertEquals(1, fireauth.util.consoleInfo.getCallCount()); if (goog.global.document) { asyncTestCase.waitForSignals(1); fireauth.util.onDomReady().then(() => { const el = goog.global.document.querySelector('.firebase-emulator-warning'); assertNull(el); asyncTestCase.signal(); }); } } function testEmulatorConfig() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Update the emulator config. auth1.useEmulator( 'http://emulator.test.domain:1234', {disableWarnings: true}); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: 1234, options: {disableWarnings: true}, }, auth1.emulatorConfig); } /** * Asserts that the port is correctly set to null if no port supplied. */ function testEmulatorConfig_noPortSpecified() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Update the emulator config. auth1.useEmulator('http://emulator.test.domain'); assertObjectEquals( { protocol: 'http', host: 'emulator.test.domain', port: null, options: {disableWarnings: false}, }, auth1.emulatorConfig); } /** * Asserts that the port is correctly assigned 0 if specifically set to 0 for * some reason. Also checks https protocol. */ function testEmulatorConfig_portZeroAndHttpsSpecified() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Update the emulator config. auth1.useEmulator('https://emulator.test.domain:0'); assertObjectEquals( { protocol: 'https', host: 'emulator.test.domain', port: 0, options: {disableWarnings: false}, }, auth1.emulatorConfig); } /** * Asserts that the function returns null if useEmulator is not called. */ function testEmulatorConfig_nullIfNoEmulatorConfig() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); assertNull(auth1.emulatorConfig); } function testGetSetTenantId() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); // Tenant ID should be initialized to null. assertNull(auth1.tenantId); assertNull(auth1.getRpcHandler().getTenantId()); // Updating tenant ID on Auth should also update the tenant ID of RPC handler. auth1.tenantId = 'TENANT_ID1'; assertEquals('TENANT_ID1', auth1.tenantId); assertEquals('TENANT_ID1', auth1.getRpcHandler().getTenantId()); // Reset tenant ID to null. auth1.tenantId = null; assertNull(auth1.tenantId); assertNull(auth1.getRpcHandler().getTenantId()); // Test getter and setter. auth1.setTenantId('TENANT_ID2'); assertEquals('TENANT_ID2', auth1.getTenantId()); assertEquals('TENANT_ID2', auth1.tenantId); auth1.tenantId = null; assertNull(auth1.getTenantId()); assertNull(auth1.tenantId); } /** * Test Auth state listeners triggered on listener add even when initial state * is null. However it will only first trigger when state is resolved. */ function testAddAuthTokenListener_initialNullState() { var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo); stubs.reset(); // Simulate no state returned. stubs.replace( fireauth.storage.UserManager.prototype, 'getCurrentUser', function() { return goog.Promise.resolve(null); }); initializeMockStorage(); // Suppress addStateChangeListener. stubs.replace( fireauth.storage.UserManager.prototype, 'addCurrentUserChangeListener', function(listener) {}); stubs.replace( fireauth.StsTokenManager.prototype, 'getToken', function(opt_forceRefresh) { // Generate new token on next call to trigger listeners. return goog.Promise.resolve({ accessToken: jwt2, refreshToken: 'refreshToken' }); }); var listener1 = mockControl.createFunctionMock('listener1'); var listener2 = mockControl.createFunctionMock('listener2'); app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); listener1(null).$does(function() { // Should be triggered after state is resolved. assertEquals(0, marker); // Increment marker. marker++; auth1.addAuthTokenListener(listener2); }); listener2(null).$does(function() { // Should be triggered after listener2 is added. assertEquals(1, marker); // Increment marker. marker++; // Auth state change notification should also trigger immediately now. // Simulate Auth event to trigger both listeners. auth1.setCurrentUser_(user); user.getIdToken(); }); listener1(jwt2).$does(function() { // Marker should confirm listener triggered after notifyAuthListeners_. assertEquals(2, marker); asyncTestCase.signal(); }); listener2(jwt2).$does(function() { // Marker should confirm listener triggered after notifyAuthListeners_. assertEquals(2, marker); asyncTestCase.signal(); }); mockControl.$replayAll(); // Wait for last 2 expected listener calls. asyncTestCase.waitForSignals(2); // Keep track of what is triggering the events. var marker = 0; // Test listeners called when state first determined. auth1.addAuthTokenListener(listener1); } /** * Test Auth state listeners triggered on listener add even when initial state * is not null (signed in user). However it will only first trigger when state * is resolved. */ function testAddAuthTokenListener_initialValidState() { var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo); stubs.reset(); // Simulate valid state returned. stubs.replace( fireauth.storage.UserManager.prototype, 'getCurrentUser', function() { return goog.Promise.resolve(user); }); initializeMockStorage(); // Suppress addStateChangeListener. stubs.replace( fireauth.storage.UserManager.prototype, 'addCurrentUserChangeListener', function(listener) {}); // Simulate available token. stubs.replace( fireauth.AuthUser.prototype, 'reload', function() { // Internally calls Auth user listeners. return goog.Promise.resolve(); }); var currentAccessToken = jwt1; stubs.replace( fireauth.StsTokenManager.prototype, 'getToken', function(opt_forceRefresh) { // Generate new token on next call to trigger listeners. return goog.Promise.resolve({ accessToken: currentAccessToken, refreshToken: 'refreshToken' }); }); // Keep track of what is triggering the events. var marker = 0; var listener1 = mockControl.createFunctionMock('listener1'); var listener2 = mockControl.createFunctionMock('listener2'); app1 = firebase.initializeApp(config3, appId1); auth1 = app1.auth(); listener1(jwt1).$does(function() { // Should be triggered after state is resolved. assertEquals(0, marker); marker++; // Now that state is determined, adding a new listener should resolve // immediately. auth1.addAuthTokenListener(listener2); }); listener2(jwt1).$does(function() { // Should be triggered after listener2 is added. assertEquals(1, marker); // Increment marker. marker++; // Auth state change notification should also trigger immediately now. // Simulate Auth event via getIdToken refresh to trigger both listeners. currentAccessToken = 'newAccessToken'; user.getIdToken(); }); listener1('newAccessToken').$does(function() { // Marker should confirm listener triggered after notifyAuthListeners_. assertEquals(2, marker); asyncTestCase.signal(); }); listener2('newAccessToken').$does(function() { // Marker should confirm listener triggered after notifyAuthListeners_. assertEquals(2, marker); asyncTestCase.signal(); }); mockControl.$replayAll(); // Wait for last 2 expected listener calls. asyncTestCase.waitForSignals(2); // Test listeners called when state first determined. auth1.addAuthTokenListener(listener1); } function testGetUid_userSignedIn() { // Test getUid() on Auth instance and app instance with user previously // signed in. var accountInfo1 = {'uid': '1234'}; asyncTestCase.waitForSignals(1); // Get current user storage manager. var storageKey = fireauth.util.createStorageKey(config1['apiKey'], appId1); currentUserStorageManager = new fireauth.storage.UserManager(storageKey); // Create test user instance. var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo1); // Save test user. This will be loaded on Auth init. currentUserStorageManager.setCurrentUser(user).then(function() { // Initialize App and Auth. app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); // Initially getUid() should return null; assertNull(auth1.getUid()); assertNull(authInternal1.getUid()); // Listen to Auth changes. var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { // Unsubscribe of Auth state change listener. unsubscribe(); // Logged in test user should be detected. // Confirm getUid() returns expected UID. assertEquals(accountInfo1['uid'], auth1.getUid()); assertEquals(accountInfo1['uid'], authInternal1.getUid()); goog.Timer.promise(10).then(function() { // Sign out. return auth1.signOut(); }).then(function() { return goog.Timer.promise(10); }).then(function() { // getUid() should return null. assertNull(auth1.getUid()); assertNull(authInternal1.getUid()); asyncTestCase.signal(); }); }); }); } function testGetUid_noUserSignedIn() { // Test getUid() on Auth instance and App instance with no user previously // signed in and new user signs in. var accountInfo1 = {'uid': '1234'}; stubs.replace( fireauth.AuthUser, 'initializeFromIdTokenResponse', function(options, idTokenResponse) { return goog.Promise.resolve(user); }); // Simulate successful RpcHandler verifyPassword resolving with expected // token response. stubs.replace( fireauth.RpcHandler.prototype, 'verifyPassword', function(email, password) { // Return tokens for test user. return goog.Promise.resolve(expectedTokenResponse); }); asyncTestCase.waitForSignals(1); var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo1); // Initialize App and Auth. app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); // Listen to Auth changes. var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { // Unsubscribe of Auth state change listener. unsubscribe(); // Initially getUid() should return null; assertNull(auth1.getUid()); assertNull(authInternal1.getUid()); // Sign in with email and password. auth1.signInWithEmailAndPassword('user@example.com', 'password') .then(function(userCredential) { // getUid() should return the test user UID. assertEquals(accountInfo1['uid'], auth1.getUid()); assertEquals(accountInfo1['uid'], authInternal1.getUid()); asyncTestCase.signal(); }); }); } function testNotifyAuthListeners() { // Simulate available token. stubs.replace( fireauth.StsTokenManager.prototype, 'getToken', function(opt_forceRefresh) { return goog.Promise.resolve({ accessToken: currentAccessToken, refreshToken: 'refreshToken' }); }); // User reloaded. stubs.replace( fireauth.AuthUser.prototype, 'reload', function() { return goog.Promise.resolve(); }); var currentAccessToken = 'accessToken1'; var app1AuthTokenListener = goog.testing.recordFunction(); var app2AuthTokenListener = goog.testing.recordFunction(); var user = new fireauth.AuthUser( config1, expectedTokenResponse, accountInfo); var listener1 = goog.testing.recordFunction(); var listener2 = goog.testing.recordFunction(); var listener3 = goog.testing.recordFunction(); asyncTestCase.waitForSignals(2); // Set current user on auth1. currentUserStorageManager = new fireauth.storage.UserManager( config1['apiKey'] + ':' + appId1); currentUserStorageManager.setCurrentUser(user).then(function() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); authInternal1.addAuthTokenListener(app1AuthTokenListener); app2 = firebase.initializeApp(config2, appId2); auth2 = app2.auth(); authInternal2 = app2.container.getProvider('auth-internal').getImmediate(); authInternal2.addAuthTokenListener(app2AuthTokenListener); // Confirm all listeners reset. assertEquals(0, listener1.getCallCount()); assertEquals(0, listener2.getCallCount()); assertEquals(0, listener3.getCallCount()); assertEquals(0, app1AuthTokenListener.getCallCount()); assertEquals(0, app2AuthTokenListener.getCallCount()); auth1.addAuthTokenListener(listener1); auth1.addAuthTokenListener(listener2); auth2.addAuthTokenListener(listener3); // Wait for state to be ready on auth1. var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { unsubscribe(); // Listener 1 and 2 triggered. assertEquals(1, listener1.getCallCount()); assertEquals(listener1.getCallCount(), listener2.getCallCount()); // First trigger on init state. assertEquals( listener1.getCallCount(), app1AuthTokenListener.getCallCount()); assertEquals( 'accessToken1', app1AuthTokenListener.getLastCall().getArgument(0)); // Remove first listener and reset. auth1.removeAuthTokenListener(listener1); listener1.reset(); listener2.reset(); app1AuthTokenListener.reset(); // Force token change. currentAccessToken = 'accessToken2'; // Trigger getIdToken to force refresh and Auth token change. auth1['currentUser'].getIdToken().then(function(token) { assertEquals('accessToken2', token); assertEquals(0, listener1.getCallCount()); // Only listener2 triggered. assertEquals(1, listener2.getCallCount()); // Second trigger. assertEquals( 1, app1AuthTokenListener.getCallCount()); assertEquals( 'accessToken2', app1AuthTokenListener.getLastCall().getArgument(0)); // Remove remaining listeners and reset. app1AuthTokenListener.reset(); listener2.reset(); auth1.removeAuthTokenListener(listener2); authInternal1.removeAuthTokenListener(app1AuthTokenListener); // Force token change. currentAccessToken = 'accessToken3'; auth1['currentUser'].getIdToken().then(function(token) { assertEquals('accessToken3', token); // No listeners triggered anymore since they are all unsubscribed. assertEquals(0, app1AuthTokenListener.getCallCount()); assertEquals(0, listener1.getCallCount()); assertEquals(0, listener2.getCallCount()); asyncTestCase.signal(); }); }); }); // Wait for state to be ready on auth2. auth2.onIdTokenChanged(function(currentUser) { // auth2 listener triggered on init with null state once. assertEquals(1, listener3.getCallCount()); assertEquals( 1, app2AuthTokenListener.getCallCount()); assertNull( app2AuthTokenListener.getLastCall().getArgument(0)); assertNull(currentUser); asyncTestCase.signal(); }); }); } /** * Tests the notifications made to observers defined through the public API, * when calling the notifyAuthListeners. */ function testNotifyAuthStateObservers() { stubs.reset(); // Simulate available token. var counter = 0; stubs.replace( fireauth.StsTokenManager.prototype, 'getToken', function(opt_forceRefresh) { // Generate new token on each call. counter++; return goog.Promise.resolve({ accessToken: 'accessToken' + counter.toString(), refreshToken: 'refreshToken' }); }); // Simulate user logged in. stubs.replace( fireauth.storage.UserManager.prototype, 'getCurrentUser', function() { retur