diff --git a/.changeset/clean-toes-pump.md b/.changeset/clean-toes-pump.md new file mode 100644 index 00000000000..39a4f026ebc --- /dev/null +++ b/.changeset/clean-toes-pump.md @@ -0,0 +1,6 @@ +--- +'@firebase/auth': minor +'firebase': minor +--- + +Add ability to configure the SDK to communicate with the Firebase Auth emulator. diff --git a/packages/auth/buildtools/run_demo.sh b/packages/auth/buildtools/run_demo.sh index 8afd21f6964..43c151af518 100755 --- a/packages/auth/buildtools/run_demo.sh +++ b/packages/auth/buildtools/run_demo.sh @@ -34,4 +34,4 @@ cp ../firebase/firebase-auth.js demo/public/dist/firebase-auth.js cp ../firebase/firebase-database.js demo/public/dist/firebase-database.js # Serve demo app. cd demo -`yarn bin`/firebase serve --only hosting,functions +`yarn bin`/firebase emulators:start diff --git a/packages/auth/demo/.gitignore b/packages/auth/demo/.gitignore new file mode 100644 index 00000000000..dbb58ffbfa3 --- /dev/null +++ b/packages/auth/demo/.gitignore @@ -0,0 +1,66 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/packages/auth/demo/firebase.json b/packages/auth/demo/firebase.json index e3385ebe78d..c44bbb73ccf 100644 --- a/packages/auth/demo/firebase.json +++ b/packages/auth/demo/firebase.json @@ -10,5 +10,22 @@ "function": "checkIfAuthenticated" } ] + }, + "emulators": { + "auth": { + "port": 9099 + }, + "functions": { + "port": 5001 + }, + "database": { + "port": 9000 + }, + "hosting": { + "port": 5000 + }, + "ui": { + "enabled": true + } } } diff --git a/packages/auth/demo/public/sample-config.js b/packages/auth/demo/public/sample-config.js index 2a8bd9c2bfb..3b21d36701a 100644 --- a/packages/auth/demo/public/sample-config.js +++ b/packages/auth/demo/public/sample-config.js @@ -21,3 +21,6 @@ var config = { storageBucket: "your-app.appspot.com", messagingSenderId: "MESSAGING_SENDER_ID" }; + +// Uncomment the following line to use with emulator +// var emulatorUrl = 'http://localhost:9099'; \ No newline at end of file diff --git a/packages/auth/demo/public/script.js b/packages/auth/demo/public/script.js index 69255429a32..be8d424d63d 100644 --- a/packages/auth/demo/public/script.js +++ b/packages/auth/demo/public/script.js @@ -1623,12 +1623,18 @@ function initApp(){ log('Initializing app...'); app = firebase.initializeApp(config); auth = app.auth(); + if (window.emulatorUrl) { + auth.useEmulator(emulatorUrl); + } tempApp = firebase.initializeApp({ 'apiKey': config['apiKey'], 'authDomain': config['authDomain'] }, auth['app']['name'] + '-temp'); tempAuth = tempApp.auth(); + if (window.emulatorUrl) { + tempAuth.useEmulator(emulatorUrl); + } // Listen to reCAPTCHA config togglers. initRecaptchaToggle(function(size) { diff --git a/packages/auth/demo/public/service-worker.js b/packages/auth/demo/public/service-worker.js index 5a63d384f26..34848b0ccd8 100644 --- a/packages/auth/demo/public/service-worker.js +++ b/packages/auth/demo/public/service-worker.js @@ -91,7 +91,7 @@ self.addEventListener('install', function(event) { }); // As this is a test app, let's only return cached data when offline. -self.addEventListener('fetch', function(event) { +self.addEventListener('fetch', function (event) { var fetchEvent = event; var requestProcessor = function(idToken) { var req = event.request; @@ -154,7 +154,10 @@ self.addEventListener('fetch', function(event) { event.respondWith(getIdToken().then(requestProcessor, requestProcessor)); }); -self.addEventListener('activate', function(event) { +self.addEventListener('activate', function (event) { + if (window.emulatorUrl) { + firebase.auth().useEmulator(emulatorUrl); + } // Update this list with all caches that need to remain cached. var cacheWhitelist = ['cache-v1']; event.waitUntil(caches.keys().then(function(cacheNames) { diff --git a/packages/auth/package.json b/packages/auth/package.json index b304ae41fc2..ad481f0a4cc 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -25,6 +25,7 @@ "@firebase/auth-types": "0.10.1" }, "devDependencies": { + "firebase-tools": "8.11.2", "google-closure-compiler": "20200112.0.0", "google-closure-library": "20200224.0.0", "gulp": "4.0.2", diff --git a/packages/auth/src/auth.js b/packages/auth/src/auth.js index f65c9ccdfd3..8c21ea2f955 100644 --- a/packages/auth/src/auth.js +++ b/packages/auth/src/auth.js @@ -184,6 +184,12 @@ fireauth.Auth = function(app) { * is currently only used to log FirebaseUI. */ this.frameworks_ = []; + + /** + * @private {?fireauth.constants.EmulatorSettings} The current + * emulator settings. + */ + this.emulatorConfig_ = null; }; goog.inherits(fireauth.Auth, goog.events.EventTarget); @@ -202,6 +208,20 @@ fireauth.Auth.LanguageCodeChangeEvent = function(languageCode) { goog.inherits(fireauth.Auth.LanguageCodeChangeEvent, goog.events.Event); +/** + * Emulator config change custom event. + * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The new + * emulator settings. + * @constructor + * @extends {goog.events.Event} + */ +fireauth.Auth.EmulatorConfigChangeEvent = function(emulatorConfig) { + goog.events.Event.call(this, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED); + this.emulatorConfig = emulatorConfig; +}; +goog.inherits(fireauth.Auth.EmulatorConfigChangeEvent, goog.events.Event); + + /** * Framework change custom event. * @param {!Array} frameworks The new frameworks array. @@ -272,6 +292,65 @@ fireauth.Auth.prototype.useDeviceLanguage = function() { }; +/** + * Sets the emulator configuration (go/firebase-emulator-connection-api). + * @param {string} url The url for the Auth emulator. + */ +fireauth.Auth.prototype.useEmulator = function(url) { + // Emulator config can only be set once. + if (!this.emulatorConfig_) { + // Emit a warning so dev knows we are now in test mode. + this.emitEmulatorWarning_(); + // Persist the config. + this.emulatorConfig_ = { url }; + // Disable app verification. + this.settings_().setAppVerificationDisabledForTesting(true); + // Update RPC handler endpoints. + this.rpcHandler_.updateEmulatorConfig(this.emulatorConfig_); + // Notify external event listeners. + this.notifyEmulatorConfigListeners_(); + } +} + + +/** + * Emits a console warning and a visual banner if emulator integration is + * enabled. + */ +fireauth.Auth.prototype.emitEmulatorWarning_ = function() { + fireauth.util.consoleWarn('WARNING: You are using the Auth Emulator,' + + ' which is intended for local testing only. Do not use with' + + ' production credentials.'); + if (goog.global.document) { + fireauth.util.onDomReady().then(() => { + const ele = goog.global.document.createElement('div'); + ele.innerText = 'Running in emulator mode. Do not use with production' + + ' credentials.'; + ele.style.position = 'fixed'; + ele.style.width = '100%'; + ele.style.backgroundColor = '#ffffff'; + ele.style.border = '.1em solid #000000'; + ele.style.color = '#ff0000'; + ele.style.bottom = '0px'; + ele.style.left = '0px'; + ele.style.margin = '0px'; + ele.style.zIndex = 10000; + ele.style.textAlign = 'center'; + ele.classList.add('firebase-emulator-warning'); + goog.global.document.body.appendChild(ele); + }); + } +} + + +/** + * @return {?fireauth.constants.EmulatorSettings} + */ +fireauth.Auth.prototype.getEmulatorConfig = function() { + return this.emulatorConfig_; +} + + /** * @param {string} frameworkId The framework identifier. */ @@ -396,7 +475,15 @@ fireauth.Auth.prototype.notifyLanguageCodeListeners_ = function() { }; - +/** + * Notifies all external listeners of the emulator config change. + * @private + */ +fireauth.Auth.prototype.notifyEmulatorConfigListeners_ = function() { + // Notify external listeners on the emulator config change. + this.dispatchEvent( + new fireauth.Auth.EmulatorConfigChangeEvent(this.emulatorConfig_)); +} /** @@ -449,7 +536,10 @@ fireauth.Auth.prototype.initAuthEventManager_ = function() { // By this time currentUser should be ready if available and will be able // to resolve linkWithRedirect if detected. self.authEventManager_ = fireauth.AuthEventManager.getManager( - authDomain, apiKey, self.app_().name); + authDomain, + apiKey, + self.app_().name, + self.emulatorConfig_); // Subscribe Auth instance. self.authEventManager_.subscribe(self); // Subscribe current user by enabling popup and redirect on that user. @@ -471,7 +561,10 @@ fireauth.Auth.prototype.initAuthEventManager_ = function() { /** @type {!fireauth.AuthUser} */ (self.redirectUser_)); // Set the user Firebase frameworks for the redirect user. self.setUserFramework_( - /** @type {!fireauth.AuthUser} */ (self.redirectUser_)); + /** @type {!fireauth.AuthUser} */(self.redirectUser_)); + // Set the user Emulator configuration for the redirect user. + self.setUserEmulatorConfig_( + /** @type {!fireauth.AuthUser} */(self.redirectUser_)); // Reference to redirect user no longer needed. self.redirectUser_ = null; } @@ -650,7 +743,8 @@ fireauth.Auth.prototype.signInWithPopup = function(provider) { firebase.SDK_VERSION || null, null, null, - this.getTenantId()); + this.getTenantId(), + this.emulatorConfig_); } // The popup must have a name, otherwise when successive popups are triggered // they will all render in the same instance and none will succeed since the @@ -856,6 +950,9 @@ fireauth.Auth.prototype.signInWithIdTokenResponse = options['apiKey'] = self.app_().options['apiKey']; options['authDomain'] = self.app_().options['authDomain']; options['appName'] = self.app_().name; + if (self.emulatorConfig_) { + options['emulatorConfig'] = self.emulatorConfig_; + } // Wait for state to be ready. // This is used internally and is also used for redirect sign in so there is // no need for waiting for redirect result to resolve since redirect result @@ -911,6 +1008,9 @@ fireauth.Auth.prototype.setCurrentUser_ = function(user) { // Set the current frameworks used on the user and set current Auth instance // as the framework change dispatcher. this.setUserFramework_(user); + // If a user is available, set the emulator config on it and set current + // Auth instance as emulator config change dispatcher. + this.setUserEmulatorConfig_(user); } }; @@ -1001,7 +1101,7 @@ fireauth.Auth.prototype.initAuthState_ = function() { var p = this.initRedirectUser_().then(function() { // Override user's authDomain with app's authDomain if there is a mismatch. return /** @type {!fireauth.storage.UserManager} */ ( - self.userStorageManager_).getCurrentUser(authDomain); + self.userStorageManager_).getCurrentUser(authDomain, self.emulatorConfig_); }).then(function(user) { // Logged in user. if (user) { @@ -1179,6 +1279,22 @@ fireauth.Auth.prototype.setUserLanguage_ = function(user) { }; +/** + * Updates the emulator config on the provided user and configures the user + * to listen to the Auth instance for any emulator config changes. + * @param {!fireauth.AuthUser} user The user to whose emulator config needs + * to be set. + * @private + */ +fireauth.Auth.prototype.setUserEmulatorConfig_ = function(user) { + // Sets the current emulator config on the user. + user.setEmulatorConfig(this.emulatorConfig_); + // Sets current Auth instance as emulator config change dispatcher on the + // user. + user.setEmulatorConfigChangeDispatcher(this); +} + + /** * Handles user state changes. * @param {!fireauth.AuthUser} user The user which triggered the state changes. @@ -1679,6 +1795,15 @@ fireauth.Auth.prototype.app_ = function() { }; +/** + * @return {!fireauth.AuthSettings} The settings for this Auth object. + * @private + */ +fireauth.Auth.prototype.settings_ = function () { + return this['settings']; +}; + + /** * @return {!fireauth.RpcHandler} The RPC handler. */ diff --git a/packages/auth/src/autheventmanager.js b/packages/auth/src/autheventmanager.js index 4eb21c5c066..a6bb62df6af 100644 --- a/packages/auth/src/autheventmanager.js +++ b/packages/auth/src/autheventmanager.js @@ -47,9 +47,11 @@ goog.require('goog.array'); * @param {string} apiKey The API key for sending backend Auth requests. * @param {string} appName The App ID for the Auth instance that triggered this * request. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @constructor */ -fireauth.AuthEventManager = function(authDomain, apiKey, appName) { +fireauth.AuthEventManager = function(authDomain, apiKey, appName, emulatorConfig) { /** * @private {!Object} The map of processed auth event IDs. */ @@ -62,6 +64,8 @@ fireauth.AuthEventManager = function(authDomain, apiKey, appName) { this.apiKey_ = apiKey; /** @private {string} The App name. */ this.appName_ = appName; + /** @private @const {?fireauth.constants.EmulatorSettings|undefined} The emulator config. */ + this.emulatorConfig_ = emulatorConfig; /** * @private {!Array} List of subscribed handlers. */ @@ -111,9 +115,12 @@ fireauth.AuthEventManager = function(authDomain, apiKey, appName) { */ this.oauthSignInHandler_ = fireauth.AuthEventManager.instantiateOAuthSignInHandler( - this.authDomain_, this.apiKey_, this.appName_, - firebase.SDK_VERSION || null, - fireauth.constants.clientEndpoint); + this.authDomain_, + this.apiKey_, + this.appName_, + firebase.SDK_VERSION || null, + fireauth.constants.clientEndpoint, + this.emulatorConfig_); }; @@ -149,11 +156,13 @@ fireauth.AuthEventManager.prototype.getPopupAuthEventProcessor = function() { * request. * @param {?string} version The SDK client version. * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @return {!fireauth.OAuthSignInHandler} The OAuth sign in handler depending * on the current environment. */ fireauth.AuthEventManager.instantiateOAuthSignInHandler = - function(authDomain, apiKey, appName, version, opt_endpointId) { + function(authDomain, apiKey, appName, version, opt_endpointId, emulatorConfig) { // This assumes that android/iOS file environment must be a Cordova // environment which is not true. This is the best way currently available // to instantiate this synchronously without waiting for checkIfCordova to @@ -161,10 +170,21 @@ fireauth.AuthEventManager.instantiateOAuthSignInHandler = // be caught via actionable public popup and redirect methods. return fireauth.util.isAndroidOrIosCordovaScheme() ? new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, undefined, undefined, - opt_endpointId) : + authDomain, + apiKey, + appName, + version, + undefined, + undefined, + opt_endpointId, + emulatorConfig) : new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, opt_endpointId); + authDomain, + apiKey, + appName, + version, + opt_endpointId, + emulatorConfig); }; @@ -179,8 +199,12 @@ fireauth.AuthEventManager.prototype.reset = function() { this.oauthSignInHandler_ = fireauth.AuthEventManager.instantiateOAuthSignInHandler( - this.authDomain_, this.apiKey_, this.appName_, - firebase.SDK_VERSION || null); + this.authDomain_, + this.apiKey_, + this.appName_, + firebase.SDK_VERSION || null, + null, + this.emulatorConfig_); this.processedEvents_ = {}; }; @@ -648,12 +672,18 @@ fireauth.AuthEventManager.KEY_SEPARATOR_ = ':'; /** * @param {string} apiKey The API key for sending backend Auth requests. * @param {string} appName The Auth instance that initiated the Auth event. - * @return {string} The key identifying the Auth event manager instance. - * @private - */ -fireauth.AuthEventManager.getKey_ = function(apiKey, appName) { - return apiKey + fireauth.AuthEventManager.KEY_SEPARATOR_ + appName; -}; + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. + * @return {string} The key identifying the Auth event manager instance. + * @private + */ +fireauth.AuthEventManager.getKey_ = function(apiKey, appName, emulatorConfig) { + var key = apiKey + fireauth.AuthEventManager.KEY_SEPARATOR_ + appName; + if (emulatorConfig) { + key = key + fireauth.AuthEventManager.KEY_SEPARATOR_ + emulatorConfig.url; + } + return key; +} /** @@ -662,20 +692,30 @@ fireauth.AuthEventManager.getKey_ = function(apiKey, appName) { * @param {string} apiKey The API key for sending backend Auth requests. * @param {string} appName The Auth instance that initiated the Auth event * manager. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @return {!fireauth.AuthEventManager} the requested manager instance. */ -fireauth.AuthEventManager.getManager = function(authDomain, apiKey, appName) { +fireauth.AuthEventManager.getManager = function (authDomain, apiKey, appName, emulatorConfig) { // Construct storage key. - var key = fireauth.AuthEventManager.getKey_(apiKey, appName); + var key = fireauth.AuthEventManager.getKey_( + apiKey, + appName, + emulatorConfig + ); if (!fireauth.AuthEventManager.manager_[key]) { fireauth.AuthEventManager.manager_[key] = - new fireauth.AuthEventManager(authDomain, apiKey, appName); + new fireauth.AuthEventManager( + authDomain, + apiKey, + appName, + emulatorConfig + ); } return fireauth.AuthEventManager.manager_[key]; }; - /** * The interface that represents a specific type of Auth event processor. * @interface diff --git a/packages/auth/src/authuser.js b/packages/auth/src/authuser.js index 7ff53b79f74..96ff1fd3379 100644 --- a/packages/auth/src/authuser.js +++ b/packages/auth/src/authuser.js @@ -182,7 +182,10 @@ fireauth.AuthUser = this.apiKey_, // Get the client Auth endpoint used. fireauth.constants.getEndpointConfig(fireauth.constants.clientEndpoint), - clientFullVersion); + clientFullVersion); + if (appOptions['emulatorConfig']) { + this.rpcHandler_.updateEmulatorConfig(appOptions['emulatorConfig']); + } // TODO: Consider having AuthUser take a fireauth.StsTokenManager // instance instead of a token response but make sure lastAccessToken_ also // initialized at the right time. In this case initializeFromIdTokenResponse @@ -249,6 +252,20 @@ fireauth.AuthUser = */ this.languageCodeChangeEventDispatcher_ = null; + /** + * @private {function(!goog.events.Event)} The on emulator config changed + * event handler. + */ + this.onEmulatorConfigChanged_ = function (event) { + // Update the emulator config. + self.setEmulatorConfig(event.emulatorConfig); + }; + /** + * @private {?goog.events.EventTarget} The emulator code change event + * dispatcher. + */ + this.emulatorConfigChangeEventDispatcher_ = null; + /** @private {!Array} The current Firebase frameworks. */ this.frameworks_ = []; /** @@ -288,6 +305,17 @@ fireauth.AuthUser.prototype.setLanguageCode = function(languageCode) { }; +/** + * Updates the emulator config. + * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The current + * emulator config to use in user requests. + */ +fireauth.AuthUser.prototype.setEmulatorConfig = function(emulatorConfig) { + // Update the emulator config. + this.rpcHandler_.updateEmulatorConfig(emulatorConfig); +}; + + /** @return {?string} The current user's language code. */ fireauth.AuthUser.prototype.getLanguageCode = function() { return this.languageCode_; @@ -322,6 +350,32 @@ fireauth.AuthUser.prototype.setLanguageCodeChangeDispatcher = }; +/** + * Listens to emulator config changes triggered by the provided dispatcher. + * @param {?goog.events.EventTarget} dispatcher The emulator config changed + * event dispatcher. + */ +fireauth.AuthUser.prototype.setEmulatorConfigChangeDispatcher = function(dispatcher) { + // Remove any previous listener. + if (this.emulatorConfigChangeEventDispatcher_) { + goog.events.unlisten( + this.emulatorConfigChangeEventDispatcher_, + fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, + this.onEmulatorConfigChanged_); + } + // Update current dispatcher. + this.emulatorConfigChangeEventDispatcher_ = dispatcher; + // Using an event listener makes it easy for non-currentUsers to detect + // emulator changes on the parent Auth instance. A developer could still + // call APIs that require emulation on signed out user references. + if (dispatcher) { + goog.events.listen( + dispatcher, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, + this.onEmulatorConfigChanged_); + } +} + + /** * Updates the Firebase frameworks on the current user. * @param {!Array} framework The list of Firebase frameworks. @@ -1043,7 +1097,7 @@ fireauth.AuthUser.prototype.notifyUserInvalidatedListeners_ = function() { * @return {!goog.Promise} * @private */ -fireauth.AuthUser.prototype.setUserAccountInfoFromToken_ = function(idToken) { +fireauth.AuthUser.prototype.setUserAccountInfoFromToken_ = function (idToken) { return this.rpcHandler_.getAccountInfoByIdToken(idToken) .then(goog.bind(this.parseAccountInfo_, this)); }; @@ -2176,6 +2230,8 @@ fireauth.AuthUser.prototype.destroy = function() { } // Stop listening to language code changes. this.setLanguageCodeChangeDispatcher(null); + // Stop listening to emulator config changes. + this.setEmulatorConfigChangeDispatcher(null); // Stop listening to framework changes. this.setFrameworkChangeDispatcher(null); // Empty pending promises array. @@ -2366,7 +2422,8 @@ fireauth.AuthUser.fromPlainObject = function(user) { var options = { 'apiKey': user['apiKey'], 'authDomain': user['authDomain'], - 'appName': user['appName'] + 'appName': user['appName'], + 'emulatorConfig': user['emulatorConfig'] }; // Convert to server response format. Constructor does not take // stsTokenManager toPlainObject as that format is different than the return diff --git a/packages/auth/src/cordovahandler.js b/packages/auth/src/cordovahandler.js index 86d1b85d219..6646d2da821 100644 --- a/packages/auth/src/cordovahandler.js +++ b/packages/auth/src/cordovahandler.js @@ -38,6 +38,7 @@ goog.require('fireauth.DynamicLink'); goog.require('fireauth.OAuthSignInHandler'); goog.require('fireauth.UniversalLinkSubscriber'); goog.require('fireauth.authenum.Error'); +goog.require('fireauth.constants'); goog.require('fireauth.iframeclient.IfcHandler'); goog.require('fireauth.storage.AuthEventManager'); goog.require('fireauth.storage.OAuthHandlerManager'); @@ -55,16 +56,17 @@ goog.require('goog.crypt.Sha256'); * @param {string} authDomain The application authDomain. * @param {string} apiKey The API key. * @param {string} appName The App name. - * @param {?string=} opt_clientVersion The optional client version string. - * @param {number=} opt_initialTimeout Initial Auth event timeout. - * @param {number=} opt_redirectTimeout Redirect result timeout. - * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). + * @param {?string=} clientVersion The optional client version string. + * @param {number=} initialTimeout Initial Auth event timeout. + * @param {number=} redirectTimeout Redirect result timeout. + * @param {?string=} endpointId The endpoint ID (staging, test Gaia, etc). + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration * @constructor * @implements {fireauth.OAuthSignInHandler} */ fireauth.CordovaHandler = function(authDomain, apiKey, appName, - opt_clientVersion, opt_initialTimeout, opt_redirectTimeout, - opt_endpointId) { + clientVersion, initialTimeout, redirectTimeout, endpointId, emulatorConfig) { /** @private {string} The application authDomain. */ this.authDomain_ = authDomain; /** @private {string} The application API key. */ @@ -72,9 +74,14 @@ fireauth.CordovaHandler = function(authDomain, apiKey, appName, /** @private {string} The application name. */ this.appName_ = appName; /** @private {?string} The client version */ - this.clientVersion_ = opt_clientVersion || null; + this.clientVersion_ = clientVersion || null; /** @private {?string} The Auth endpoint ID. */ - this.endpointId_ = opt_endpointId || null; + this.endpointId_ = endpointId || null; + /** + * @private @const {?fireauth.constants.EmulatorSettings|undefined} + * The emulator configuration + */ + this.emulatorConfig_ = emulatorConfig; /** @private {string} The storage key. */ this.storageKey_ = fireauth.util.createStorageKey(apiKey, appName); /** @@ -101,10 +108,10 @@ fireauth.CordovaHandler = function(authDomain, apiKey, appName, */ this.authEventListeners_ = []; /** @private {number} The initial Auth event timeout. */ - this.initialTimeout_ = opt_initialTimeout || + this.initialTimeout_ = initialTimeout || fireauth.CordovaHandler.INITIAL_TIMEOUT_MS_; /** @private {number} The return to app after redirect timeout. */ - this.redirectTimeout_ = opt_redirectTimeout || + this.redirectTimeout_ = redirectTimeout || fireauth.CordovaHandler.REDIRECT_TIMEOUT_MS_; /** * @private {?goog.Promise} The last pending redirect promise. This is null if @@ -544,7 +551,8 @@ fireauth.CordovaHandler.prototype.processRedirectInternal_ = function( this.clientVersion_, additionalParams, this.endpointId_, - opt_tenantId); + opt_tenantId, + this.emulatorConfig_); // Make sure handler initialized and ready. // This should also ensure all plugins are installed. return this.initializeAndWait().then(function() { diff --git a/packages/auth/src/defines.js b/packages/auth/src/defines.js index 4b545b4af0f..d7bc309fc56 100644 --- a/packages/auth/src/defines.js +++ b/packages/auth/src/defines.js @@ -39,6 +39,8 @@ fireauth.constants.OperationType = { * @enum {string} */ fireauth.constants.AuthEventType = { + /** Dispatched when emulator config is changed. */ + EMULATOR_CONFIG_CHANGED: 'emulatorConfigChanged', /** Dispatched when Firebase framework is changed. */ FRAMEWORK_CHANGED: 'frameworkChanged', /** Dispatched when language code is changed. */ @@ -166,3 +168,14 @@ fireauth.constants.SAML_PREFIX = 'saml.'; /** @const {string} The required OIDC provider ID prefix. */ fireauth.constants.OIDC_PREFIX = 'oidc.'; + +/** + * The settings of an Auth emulator. The fields are: + *
    + *
  • url: defines the URL where the emulator is running.
  • + *
+ * @typedef {{ + * url: string, + * }} + */ +fireauth.constants.EmulatorSettings; \ No newline at end of file diff --git a/packages/auth/src/exports_auth.js b/packages/auth/src/exports_auth.js index 17f396cc291..9b52bbe96a7 100644 --- a/packages/auth/src/exports_auth.js +++ b/packages/auth/src/exports_auth.js @@ -197,6 +197,12 @@ fireauth.exportlib.exportPrototypeMethods( name: 'useDeviceLanguage', args: [] }, + useEmulator: { + name: 'useEmulator', + args: [ + fireauth.args.string('url') + ] + }, verifyPasswordResetCode: { name: 'verifyPasswordResetCode', args: [fireauth.args.string('code')] diff --git a/packages/auth/src/iframeclient/ifchandler.js b/packages/auth/src/iframeclient/ifchandler.js index 8a5768f816e..9bf7a29a4e5 100644 --- a/packages/auth/src/iframeclient/ifchandler.js +++ b/packages/auth/src/iframeclient/ifchandler.js @@ -64,21 +64,37 @@ fireauth.iframeclient.PORT_NUMBER = null; * @param {string} authDomain The application authDomain. * @param {string} apiKey The API key. * @param {string} appName The App name. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @constructor */ -fireauth.iframeclient.IframeUrlBuilder = function(authDomain, apiKey, appName) { +fireauth.iframeclient.IframeUrlBuilder = function(authDomain, apiKey, appName, emulatorConfig) { /** @private {string} The application authDomain. */ this.authDomain_ = authDomain; /** @private {string} The API key. */ this.apiKey_ = apiKey; /** @private {string} The App name. */ this.appName_ = appName; - /** @private {?string|undefined} The client version. */ - this.v_ = null; /** - * @private {!goog.Uri} The URI object used to build the iframe URL. + * @private @const {?fireauth.constants.EmulatorSettings|undefined} + * The emulator configuration. */ - this.uri_ = goog.Uri.create( + this.emulatorConfig_ = emulatorConfig; + /** @private {?string|undefined} The client version. */ + this.v_ = null; + let uri; + if (this.emulatorConfig_) { + const emulatorUri = goog.Uri.parse(this.emulatorConfig_.url); + uri = goog.Uri.create( + emulatorUri.getScheme(), + null, + emulatorUri.getDomain(), + emulatorUri.getPort(), + '/emulator/auth/iframe', + null, + null); + } else { + uri = goog.Uri.create( fireauth.iframeclient.SCHEME, null, this.authDomain_, @@ -86,6 +102,11 @@ fireauth.iframeclient.IframeUrlBuilder = function(authDomain, apiKey, appName) { '/__/auth/iframe', null, null); + } + /** + * @private @const {!goog.Uri} The URI object used to build the iframe URL. + */ + this.uri_ = uri; this.uri_.setParameterValue('apiKey', this.apiKey_); this.uri_.setParameterValue('appName', this.appName_); /** @private {?string|undefined} The endpoint ID. */ @@ -169,10 +190,12 @@ fireauth.iframeclient.IframeUrlBuilder.prototype.toString = function() { * @param {string} authType The Auth operation type. * @param {!fireauth.AuthProvider} provider The Auth provider that the OAuth * handler request is built to sign in to. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @constructor */ fireauth.iframeclient.OAuthUrlBuilder = - function(authDomain, apiKey, appName, authType, provider) { + function(authDomain, apiKey, appName, authType, provider, emulatorConfig) { /** @private {string} The application authDomain. */ this.authDomain_ = authDomain; /** @private {string} The API key. */ @@ -181,6 +204,11 @@ fireauth.iframeclient.OAuthUrlBuilder = this.appName_ = appName; /** @private {string} The Auth operation type. */ this.authType_ = authType; + /** + * @private @const {?fireauth.constants.EmulatorSettings|undefined} + * The emulator configuration. + */ + this.emulatorConfig_ = emulatorConfig; /** * @private {?string|undefined} The redirect URL used in redirect operations. */ @@ -284,8 +312,20 @@ fireauth.iframeclient.OAuthUrlBuilder.prototype.setAdditionalParameters = * @return {string} The constructed OAuth URL string. * @override */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.toString = function() { - var uri = goog.Uri.create( +fireauth.iframeclient.OAuthUrlBuilder.prototype.toString = function () { + var uri; + if (this.emulatorConfig_) { + const emulatorUri = goog.Uri.parse(this.emulatorConfig_.url); + uri = goog.Uri.create( + emulatorUri.getScheme(), + null, + emulatorUri.getDomain(), + emulatorUri.getPort(), + '/emulator/auth/handler', + null, + null); + } else { + uri = goog.Uri.create( fireauth.iframeclient.SCHEME, null, this.authDomain_, @@ -293,6 +333,7 @@ fireauth.iframeclient.OAuthUrlBuilder.prototype.toString = function() { '/__/auth/handler', null, null); + } uri.setParameterValue('apiKey', this.apiKey_); uri.setParameterValue('appName', this.appName_); uri.setParameterValue('authType', this.authType_); @@ -426,17 +467,24 @@ fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_ = * request. * @param {?string=} opt_clientVersion The optional client version string. * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @constructor * @implements {fireauth.OAuthSignInHandler} */ fireauth.iframeclient.IfcHandler = function(authDomain, apiKey, appName, - opt_clientVersion, opt_endpointId) { + opt_clientVersion, opt_endpointId, emulatorConfig) { /** @private {string} The Auth domain. */ this.authDomain_ = authDomain; /** @private {string} The API key. */ this.apiKey_ = apiKey; /** @private {string} The App name. */ this.appName_ = appName; + /** + * @private @const {?fireauth.constants.EmulatorSettings|undefined} + * The emulator configuration. + */ + this.emulatorConfig_ = emulatorConfig; /** @private {?string} The client version. */ this.clientVersion_ = opt_clientVersion || null; /** @private {?string} The Auth endpoint ID. */ @@ -693,7 +741,8 @@ fireauth.iframeclient.IfcHandler.prototype.processPopup = function( self.clientVersion_, undefined, self.endpointId_, - opt_tenantId); + opt_tenantId, + self.emulatorConfig_); // Redirect popup to OAuth helper widget URL. fireauth.util.goTo(oauthHelperWidgetUrl, /** @type {!Window} */ (popupWin)); }).thenCatch(function(e) { @@ -725,6 +774,9 @@ fireauth.iframeclient.IfcHandler.prototype.getRpcHandler_ = function() { // Get the client Auth endpoint used. fireauth.constants.getEndpointConfig(this.endpointId_), this.fullClientVersion_); + if (this.emulatorConfig_) { + this.rpcHandler_.updateEmulatorConfig(this.emulatorConfig_); + } } return this.rpcHandler_; }; @@ -763,7 +815,8 @@ fireauth.iframeclient.IfcHandler.prototype.processRedirect = self.clientVersion_, undefined, self.endpointId_, - opt_tenantId); + opt_tenantId, + self.emulatorConfig_); // Redirect to OAuth helper widget URL. fireauth.util.goTo(oauthHelperWidgetUrl); }).thenCatch(function(e) { @@ -780,10 +833,14 @@ fireauth.iframeclient.IfcHandler.prototype.processRedirect = fireauth.iframeclient.IfcHandler.prototype.getIframeUrl = function() { if (!this.iframeUrl_) { this.iframeUrl_ = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - this.authDomain_, this.apiKey_, this.appName_, this.clientVersion_, - this.endpointId_, - fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_( - this.appName_)); + this.authDomain_, + this.apiKey_, + this.appName_, + this.clientVersion_, + this.endpointId_, + fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_( + this.appName_), + this.emulatorConfig_); } return this.iframeUrl_; }; @@ -827,13 +884,15 @@ fireauth.iframeclient.IfcHandler.prototype.unloadsOnRedirect = function() { * @param {?string=} opt_clientVersion The optional client version string. * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). * @param {?Array=} opt_frameworks The optional list of framework IDs. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @return {string} The data iframe src URL. */ fireauth.iframeclient.IfcHandler.getAuthIframeUrl = function(authDomain, apiKey, - appName, opt_clientVersion, opt_endpointId, opt_frameworks) { + appName, opt_clientVersion, opt_endpointId, opt_frameworks, emulatorConfig) { // OAuth helper iframe URL. var builder = new fireauth.iframeclient.IframeUrlBuilder( - authDomain, apiKey, appName); + authDomain, apiKey, appName, emulatorConfig); return builder .setVersion(opt_clientVersion) .setEndpointId(opt_endpointId) @@ -858,6 +917,8 @@ fireauth.iframeclient.IfcHandler.getAuthIframeUrl = function(authDomain, apiKey, * additional parameters. * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). * @param {?string=} opt_tenantId The optional tenant ID. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator + * configuration. * @return {string} The OAuth helper widget URL. */ fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl = function( @@ -871,10 +932,11 @@ fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl = function( opt_clientVersion, opt_additionalParams, opt_endpointId, - opt_tenantId) { + opt_tenantId, + emulatorConfig) { // OAuth helper widget URL. var builder = new fireauth.iframeclient.OAuthUrlBuilder( - authDomain, apiKey, appName, authType, provider); + authDomain, apiKey, appName, authType, provider, emulatorConfig); return builder .setRedirectUrl(opt_redirectUrl) .setEventId(opt_eventId) diff --git a/packages/auth/src/rpchandler.js b/packages/auth/src/rpchandler.js index 118f51fce08..df1e287f9ce 100644 --- a/packages/auth/src/rpchandler.js +++ b/packages/auth/src/rpchandler.js @@ -26,6 +26,7 @@ goog.provide('fireauth.XmlHttpFactory'); goog.require('fireauth.AuthError'); goog.require('fireauth.AuthErrorWithCredential'); goog.require('fireauth.authenum.Error'); +goog.require('fireauth.constants'); goog.require('fireauth.idp'); goog.require('fireauth.idp.ProviderId'); goog.require('fireauth.object'); @@ -110,10 +111,10 @@ fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) { this.secureTokenHeaders_ = goog.object.clone( config['secureTokenHeaders'] || fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_); - /** @private @const {string} The Firebase Auth endpoint. */ + /** @private {string} The Firebase Auth endpoint. */ this.firebaseEndpoint_ = config['firebaseEndpoint'] || fireauth.RpcHandler.FIREBASE_ENDPOINT_; - /** @private @const {string} The identity platform endpoint. */ + /** @private {string} The identity platform endpoint. */ this.identityPlatformEndpoint_ = config['identityPlatformEndpoint'] || fireauth.RpcHandler.IDENTITY_PLATFORM_ENDPOINT_; /** @@ -435,6 +436,46 @@ fireauth.RpcHandler.prototype.updateCustomLocaleHeader = }; +/** + * Updates the emulator configuration. + * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The new + * emulator config. + */ +fireauth.RpcHandler.prototype.updateEmulatorConfig = function(emulatorConfig) { + if (!emulatorConfig) { + return; + } + // If an emulator config is provided, update the endpoints. + this.secureTokenEndpoint_ = + fireauth.RpcHandler.generateEmululatorEndpointUrl_( + fireauth.RpcHandler.SECURE_TOKEN_ENDPOINT_, emulatorConfig); + this.firebaseEndpoint_ = + fireauth.RpcHandler.generateEmululatorEndpointUrl_( + fireauth.RpcHandler.FIREBASE_ENDPOINT_, emulatorConfig); + this.identityPlatformEndpoint_ = + fireauth.RpcHandler.generateEmululatorEndpointUrl_( + fireauth.RpcHandler.IDENTITY_PLATFORM_ENDPOINT_, emulatorConfig); +} + + /** + * Creates an endpoint URL intended for use by the emulator. + * @param {string} endpoint the production endpoint URL. + * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The emulator + * config. + * @return {string} The emulator endpoint URL. + * @private + */ +fireauth.RpcHandler.generateEmululatorEndpointUrl_ = function(endpoint, emulatorConfig) { + const uri = goog.Uri.parse(endpoint); + const emulatorUri = goog.Uri.parse(emulatorConfig.url); + uri.setPath(uri.getDomain() + uri.getPath()); + uri.setScheme(emulatorUri.getScheme()); + uri.setDomain(emulatorUri.getDomain()); + uri.setPort(emulatorUri.getPort()); + return uri.toString(); +} + + /** * Updates the X-Client-Version in the header. * @param {?string} clientVersion The new client version. diff --git a/packages/auth/src/storageusermanager.js b/packages/auth/src/storageusermanager.js index 816664f1f6d..c3d3b28d1eb 100644 --- a/packages/auth/src/storageusermanager.js +++ b/packages/auth/src/storageusermanager.js @@ -67,6 +67,7 @@ goog.provide('fireauth.storage.UserManager'); goog.require('fireauth.AuthUser'); goog.require('fireauth.authStorage'); +goog.require('fireauth.constants'); goog.require('goog.Promise'); @@ -399,13 +400,14 @@ fireauth.storage.UserManager.prototype.removeCurrentUser = function() { /** - * @param {?string=} opt_authDomain The optional Auth domain to override if + * @param {?string=} authDomain The optional Auth domain to override if * provided. + * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The current + * emulator config to use in user requests. * @return {!goog.Promise} A promise that resolves with * the stored current user for the provided app ID. */ -fireauth.storage.UserManager.prototype.getCurrentUser = - function(opt_authDomain) { +fireauth.storage.UserManager.prototype.getCurrentUser = function(authDomain, emulatorConfig) { var self = this; // Wait for any pending persistence change to be resolved. return this.waitForReady_(function() { @@ -420,8 +422,11 @@ fireauth.storage.UserManager.prototype.getCurrentUser = // authDomain for the purpose of linking with a popup. The loaded user // (stored without the authDomain) must have this field updated with // the current authDomain. - if (response && opt_authDomain) { - response['authDomain'] = opt_authDomain; + if (response && authDomain) { + response['authDomain'] = authDomain; + } + if (response && emulatorConfig) { + response['emulatorConfig'] = emulatorConfig; } return fireauth.AuthUser.fromPlainObject(response || {}); }); diff --git a/packages/auth/test/auth_test.js b/packages/auth/test/auth_test.js index 9aedb64ddef..71a924733a0 100644 --- a/packages/auth/test/auth_test.js +++ b/packages/auth/test/auth_test.js @@ -449,6 +449,14 @@ function 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); + } + }); + } } @@ -898,6 +906,89 @@ function testUseDeviceLanguage() { } +function testUseEmulator() { + // Listen to emulator config calls on RpcHandler. + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction()); + stubs.replace( + fireauth.util, + 'consoleWarn', + 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.consoleWarn.getCallCount()); + assertEquals( + 0, + fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting. + getCallCount()); + + // Update the emulator config. + auth1.useEmulator('http://emulator.test.domain:1234'); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234', + }, + auth1.getEmulatorConfig()); + // Should notify the RPC handler. + assertEquals( + 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234', + }, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0) + ); + // Should emit a console warning. + assertEquals(1, fireauth.util.consoleWarn.getCallCount()); + // 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( + { + url: 'http://emulator.test.domain:1234', + }, + auth1.getEmulatorConfig()); + assertEquals( + 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); + assertEquals(1, fireauth.util.consoleWarn.getCallCount()); + + // Updating to different config should still not trigger event. + auth1.useEmulator('http://emulator.other.domain:9876'); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234', + }, + auth1.getEmulatorConfig()); + assertEquals( + 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); +} + + function testGetSetTenantId() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); @@ -2158,6 +2249,113 @@ function testAuth_authEventManager() { } +function testAuth_authEventManager_withEmulator() { + // Test Auth event manager. + fireauth.AuthEventManager.ENABLED = true; + stubs.reset(); + initializeMockStorage(); + var expectedManager = { + 'subscribe': goog.testing.recordFunction(), + 'unsubscribe': goog.testing.recordFunction(), + 'clearRedirectResult': goog.testing.recordFunction() + }; + // Return stub manager. + stubs.replace( + fireauth.AuthEventManager, + 'getManager', + function (authDomain, apiKey, appName, emulatorConfig) { + assertEquals('subdomain.firebaseapp.com', authDomain); + assertEquals('API_KEY', apiKey); + assertEquals(appId1, appName); + assertObjectEquals(emulatorConfig, { + url: 'http://emulator.test.domain:1234' + }); + return expectedManager; + }); + asyncTestCase.waitForSignals(1); + app1 = firebase.initializeApp(config3, appId1); + auth1 = app1.auth(); + auth1.useEmulator('http://emulator.test.domain:1234'); + // Test manager initialized and Auth subscribed. + auth1.onIdTokenChanged(function (user) { + var manager = fireauth.AuthEventManager.getManager( + config3['authDomain'], config3['apiKey'], app1.name, { + url: 'http://emulator.test.domain:1234', + }); + assertEquals(expectedManager, manager); + assertEquals(0, expectedManager.unsubscribe.getCallCount()); + assertEquals(1, expectedManager.subscribe.getCallCount()); + assertEquals( + auth1, expectedManager.subscribe.getLastCall().getArgument(0)); + assertEquals(0, expectedManager.clearRedirectResult.getCallCount()); + // Delete should trigger unsubscribe and redirect result clearing. + auth1.delete(); + // After destroy, Auth should be unsubscribed. + assertEquals(1, expectedManager.subscribe.getCallCount()); + assertEquals(1, expectedManager.unsubscribe.getCallCount()); + // Redirect result should also be cleared. + assertEquals(1, expectedManager.clearRedirectResult.getCallCount()); + assertEquals( + auth1, expectedManager.unsubscribe.getLastCall().getArgument(0)); + asyncTestCase.signal(); + }); +} + + +/** Asserts that AuthEventManager can pass through emulator settings. */ +function testAuth_authEventManager_withEmulator() { + // Test Auth event manager. + fireauth.AuthEventManager.ENABLED = true; + stubs.reset(); + initializeMockStorage(); + var expectedManager = { + 'subscribe': goog.testing.recordFunction(), + 'unsubscribe': goog.testing.recordFunction(), + 'clearRedirectResult': goog.testing.recordFunction() + }; + // Return stub manager. + stubs.replace( + fireauth.AuthEventManager, + 'getManager', + function (authDomain, apiKey, appName, emulatorConfig) { + assertEquals('subdomain.firebaseapp.com', authDomain); + assertEquals('API_KEY', apiKey); + assertEquals(appId1, appName); + assertObjectEquals(emulatorConfig, { + url: 'http://emulator.host:1234' + }); + return expectedManager; + }); + asyncTestCase.waitForSignals(1); + app1 = firebase.initializeApp(config3, appId1); + auth1 = app1.auth(); + auth1.useEmulator('http://emulator.host:1234'); + // Test manager initialized and Auth subscribed. + auth1.onIdTokenChanged(function (user) { + var manager = fireauth.AuthEventManager.getManager( + config3['authDomain'], config3['apiKey'], app1.name, { + url: 'http://emulator.host:1234' + }); + assertEquals(expectedManager, manager); + assertEquals(0, expectedManager.unsubscribe.getCallCount()); + assertEquals(1, expectedManager.subscribe.getCallCount()); + assertEquals( + auth1, expectedManager.subscribe.getLastCall().getArgument(0)); + assertEquals(0, expectedManager.clearRedirectResult.getCallCount()); + // Delete should trigger unsubscribe and redirect result clearing. + auth1.delete(); + // After destroy, Auth should be unsubscribed. + assertEquals(1, expectedManager.subscribe.getCallCount()); + assertEquals(1, expectedManager.unsubscribe.getCallCount()); + // Redirect result should also be cleared. + assertEquals(1, expectedManager.clearRedirectResult.getCallCount()); + assertEquals( + auth1, expectedManager.unsubscribe.getLastCall().getArgument(0)); + asyncTestCase.signal(); + }); +} + + function testAuth_signout() { // Test successful sign out. fireauth.AuthEventManager.ENABLED = true; @@ -2346,6 +2544,71 @@ function testAuth_initState_signedInStatus() { } +function testAuth_initState_signedInStatus_withEmulator() { + // Test init state with previously signed in user. + fireauth.AuthEventManager.ENABLED = true; + stubs.reset(); + // Simulate current origin is whitelisted. + simulateWhitelistedOrigin(); + stubs.replace( + goog, + 'now', + function () { + return now; + }); + initializeMockStorage(); + // Stub OAuth sign in handler. + fakeOAuthSignInHandler(); + // New loaded user should be reloaded before being set as current user. + stubs.replace( + fireauth.AuthUser.prototype, + 'reload', + function () { + // Access token unchanged, should trigger notifyAuthListeners_. + return goog.Promise.resolve(); + }); + // Listen to calls on RPC Handler. + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction( + fireauth.RpcHandler.prototype.updateEmulatorConfig)); + asyncTestCase.waitForSignals(1); + // Logged in user to be detected in initState. + var user1 = new fireauth.AuthUser( + config3, expectedTokenResponse, accountInfo); + // Save signed in user to storage. + currentUserStorageManager = new fireauth.storage.UserManager( + config3['apiKey'] + ':' + appId1); + currentUserStorageManager.setCurrentUser(user1).then(function () { + app1 = firebase.initializeApp(config3, appId1); + auth1 = app1.auth(); + // Set emulator. + auth1.useEmulator('http://emulator.test.domain:1234'); + // Before init state current user is null. + assertNull(auth1['currentUser']); + // User state change triggered with user. + auth1.onAuthStateChanged(function (user) { + // Signed in user should be detected. + assertUserEquals(user1, auth1['currentUser']); + // Emulator config should propagate to currentUser. + assertEquals( + 3, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); + assertEquals(auth1['currentUser'].getRpcHandler(), + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getThis()); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234' + }, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0)); + asyncTestCase.signal(); + }); + }); +} + + /** * Test that external changes on a saved user will apply after loading from * storage and reload on user is called. Confirm the updated user is saved. @@ -4163,6 +4426,57 @@ function testAuth_signInWithIdTokenResponse_newUserDifferentFromCurrent() { } +/** + * Asserts that a new signed in user gets emulator configuration set correctly. + */ +function testAuth_signInWithIdTokenResponse_withEmulator() { + // Test signInWithIdTokenResponse returning a new user. + fireauth.AuthEventManager.ENABLED = true; + stubs.reset(); + // Simulate current origin is whitelisted. + simulateWhitelistedOrigin(); + stubs.replace( + goog, + 'now', + function () { + return now; + }); + initializeMockStorage(); + // Stub OAuth sign in handler. + fakeOAuthSignInHandler(); + // Initialize from ID token response should be called and resolved with the + // new signed in user. + stubs.replace( + fireauth.AuthUser, + 'initializeFromIdTokenResponse', + function (options, idTokenResponse) { + // Confirm emulatorConfig set on user's app options. + assertObjectEquals(expectedOptions, options); + return goog.Promise.resolve(user1); + }); + asyncTestCase.waitForSignals(1); + var expectedOptions = Object.assign({}, config3); + expectedOptions['emulatorConfig'] = { + url: 'http://emulator.test.domain:1234', + }; + // The newly signed in user. + var user1 = new fireauth.AuthUser( + config3, expectedTokenResponse, accountInfo); + + app1 = firebase.initializeApp(config3, appId1); + auth1 = app1.auth(); + // Set emulator. + auth1.useEmulator('http://emulator.test.domain:1234'); + // User not logged in yet. Run sign in with ID token response. + // The user should be initialized with the emulator config. + auth1.signInWithIdTokenResponse(expectedTokenResponse).then(function () { + // Current user should be set to user1. + assertEquals(user1, auth1['currentUser']); + asyncTestCase.signal(); + }); +} + + function testAuth_getIdToken_signedInUser() { // Tests getIdToken with a signed in user. fireauth.AuthEventManager.ENABLED = true; diff --git a/packages/auth/test/autheventmanager_test.js b/packages/auth/test/autheventmanager_test.js index e36d39dd56b..3613aa3a2cd 100644 --- a/packages/auth/test/autheventmanager_test.js +++ b/packages/auth/test/autheventmanager_test.js @@ -272,6 +272,19 @@ function testGetManager() { assertEquals( fireauth.AuthEventManager.getManager(authDomain2, apiKey2, appName2), manager2); + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + var managerWithEmulator = fireauth.AuthEventManager.getManager( + authDomain1, apiKey1, appName1, emulatorConfig); + assertEquals( + fireauth.AuthEventManager.manager_[ + fireauth.AuthEventManager.getKey_(apiKey1, appName1)], + manager1); + assertEquals( + fireauth.AuthEventManager.manager_[ + fireauth.AuthEventManager.getKey_(apiKey1, appName1, emulatorConfig)], + managerWithEmulator); } @@ -286,7 +299,7 @@ function testInstantiateOAuthSignInHandler_ifcHandler() { // Confirm expected endpoint used. ifcHandlerConstructor( authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id).$returns(ifcHandler); + fireauth.constants.Endpoint.STAGING.id, ignoreArgument).$returns(ifcHandler); mockControl.$replayAll(); fireauth.AuthEventManager.instantiateOAuthSignInHandler( @@ -295,6 +308,31 @@ function testInstantiateOAuthSignInHandler_ifcHandler() { } +/** Asserts that emulator config is propagated to ifcHandler. */ +function testInstantiateOAuthSignInHandler_ifcHandler_withEmulator() { + // Simulate browser environment. + setOAuthSignInHandlerEnvironment(false); + // IfcHandler should be instantiated. + var ifcHandler = mockControl.createStrictMock( + fireauth.iframeclient.IfcHandler); + var ifcHandlerConstructor = mockControl.createConstructorMock( + fireauth.iframeclient, 'IfcHandler'); + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + // Confirm expected endpoint used. + ifcHandlerConstructor( + authDomain1, apiKey1, appName1, firebase.SDK_VERSION, + fireauth.constants.Endpoint.STAGING.id, + emulatorConfig).$returns(ifcHandler); + mockControl.$replayAll(); + + fireauth.AuthEventManager.instantiateOAuthSignInHandler( + authDomain1, apiKey1, appName1, firebase.SDK_VERSION, + fireauth.constants.Endpoint.STAGING.id, emulatorConfig); +} + + function testInstantiateOAuthSignInHandler_cordovaHandler() { // Simulate Cordova environment setOAuthSignInHandlerEnvironment(true); @@ -306,7 +344,7 @@ function testInstantiateOAuthSignInHandler_cordovaHandler() { // Confirm expected endpoint used. cordovaHandlerConstructor( authDomain1, apiKey1, appName1, firebase.SDK_VERSION, undefined, - undefined, fireauth.constants.Endpoint.STAGING.id) + undefined, fireauth.constants.Endpoint.STAGING.id, ignoreArgument) .$returns(cordovaHandler); mockControl.$replayAll(); @@ -316,6 +354,30 @@ function testInstantiateOAuthSignInHandler_cordovaHandler() { } +/** Asserts that emulator config is propagated to cordovaHandler. */ +function testInstantiateOAuthSignInHandler_cordovaHandler_withEmulator() { + // Simulate Cordova environment + setOAuthSignInHandlerEnvironment(true); + // CordovaHandler should be instantiated. + var cordovaHandler = mockControl.createStrictMock( + fireauth.CordovaHandler); + var cordovaHandlerConstructor = mockControl.createConstructorMock( + fireauth, 'CordovaHandler'); + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + // Confirm expected endpoint used. + cordovaHandlerConstructor( + authDomain1, apiKey1, appName1, firebase.SDK_VERSION, undefined, + undefined, fireauth.constants.Endpoint.STAGING.id, emulatorConfig) + .$returns(cordovaHandler); + mockControl.$replayAll(); + + fireauth.AuthEventManager.instantiateOAuthSignInHandler( + authDomain1, apiKey1, appName1, firebase.SDK_VERSION, + fireauth.constants.Endpoint.STAGING.id, emulatorConfig); +} + function testAuthEventManager_initialize_manually_withSubscriber() { var expectedAuthEvent = new fireauth.AuthEvent( @@ -396,6 +458,48 @@ function testAuthEventManager_initialize_manually_withNoSubscriber() { } +function testAuthEventManager_initialize_manually_withEmulator() { + var expectedEmulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + var expectedAuthEvent = new fireauth.AuthEvent( + 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); + var isReady = false; + // This test is not environment specific. + stubs.replace( + fireauth.AuthEventManager, + 'instantiateOAuthSignInHandler', + function (authDomain, apiKey, appName, version, endpoint, emulatorConfig) { + assertEquals('subdomain1.firebaseapp.com', authDomain); + assertEquals('API_KEY1', apiKey); + assertEquals('APP1', appName); + assertEquals(firebase.SDK_VERSION, version); + assertUndefined(endpoint); + assertObjectEquals(emulatorConfig, expectedEmulatorConfig); + isReady = true; + return { + 'addAuthEventListener': function (handler) { + handler(expectedAuthEvent); + }, + 'initializeAndWait': function () { return goog.Promise.resolve(); }, + 'shouldBeInitializedEarly': function () { + return false; + }, + 'hasVolatileStorage': function () { + return false; + } + }; + }); + asyncTestCase.waitForSignals(1); + var manager = fireauth.AuthEventManager.getManager( + authDomain1, apiKey1, appName1, expectedEmulatorConfig); + manager.initialize().then(function () { + assertTrue(isReady); + asyncTestCase.signal(); + }); +} + + function testAuthEventManager_initialize_automatically_pendingRedirect() { // Test automatic initialization when pending redirect available on // subscription. diff --git a/packages/auth/test/authuser_test.js b/packages/auth/test/authuser_test.js index 10636c097b7..33c4dbc0064 100644 --- a/packages/auth/test/authuser_test.js +++ b/packages/auth/test/authuser_test.js @@ -718,6 +718,41 @@ function testUser() { } +/** + * Asserts that a user initiated with an emulator config will propagate + * the config to the RPC handler. + */ +function tesUser_initWithEmulator() { + // Listen to emulator config calls on RpcHandler. + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction()); + + // Initialize a user. + user = new fireauth.AuthUser( + { + appName: config1.appName, + apiKey: config1.apiKey, + emulatorConfig: { + url: 'http://emulator.test.domain:1234' + } + } + ); + + // Should notify the RPC handler. + assertEquals( + 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234', + }, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0) + ); +} + + function testUser_multiFactor() { user = new fireauth.AuthUser( config1, tokenResponse, accountInfoWithEnrolledFactors); @@ -768,7 +803,7 @@ function testUser_copyUser() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -2156,7 +2191,7 @@ function testUser_getIdToken_expiredToken_reauthWithPopupAfterInvalidation() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -2469,7 +2504,7 @@ function testUser_getIdToken_expiredToken_reauthWithPopupBeforeInvalidation() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -4404,7 +4439,7 @@ function testDestroy() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); mockControl.$replayAll(); // Confirm a user is subscribed to Auth event manager. stubs.replace( @@ -5793,7 +5828,7 @@ function testUser_linkWithRedirect_success_unloadsOnRedirect() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -5881,7 +5916,7 @@ function testUser_reauthenticateWithRedirect_success_unloadsOnRedirect() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -5963,7 +5998,7 @@ function testUser_linkWithRedirect_success_unloadsOnRedirect_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6061,7 +6096,7 @@ function testUser_reauthWithRedirect_success_unloadsOnRedirect_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6146,7 +6181,7 @@ function testUser_linkWithRedirect_success_doesNotUnloadOnRedirect() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6230,7 +6265,7 @@ function testUser_reauthenticateWithRedirect_success_doesNotUnloadOnRedirect() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6307,7 +6342,7 @@ function testUser_linkWithRedirect_success_doesNotUnloadOnRedirect_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6400,7 +6435,7 @@ function testUser_reauthWithRedirect_success_doesNotUnload_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6479,7 +6514,7 @@ function testUser_linkWithRedirect_success_noStorageManager() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6561,7 +6596,7 @@ function testUser_reauthenticateWithRedirect_success_noStorageManager() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6704,7 +6739,7 @@ function testUser_linkWithRedirect_invalidProvider() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( @@ -6828,7 +6863,7 @@ function testUser_linkWithPopup_success_slowIframeEmbed() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -6983,7 +7018,7 @@ function testUser_reauthenticateWithPopup_success_slowIframeEmbed() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -7136,7 +7171,7 @@ function testUser_linkWithPopup_error_popupClosed() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -7269,7 +7304,7 @@ function testUser_reauthenticateWithPopup_error_popupClosed() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -7390,7 +7425,7 @@ function testUser_linkWithPopup_error_iframeWebStorageNotSupported() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -7516,7 +7551,7 @@ function testUser_reauthWithPopup_error_iframeWebStorageNotSupported() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -7635,7 +7670,7 @@ function testUser_linkWithPopup_success_withoutPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -7784,7 +7819,7 @@ function testUser_linkWithPopup_success_withPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -7939,7 +7974,7 @@ function testUser_linkWithPopup_success_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -8098,7 +8133,7 @@ function testUser_reauthenticateWithPopup_success_withoutPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -8251,7 +8286,7 @@ function testUser_reauthenticateWithPopup_success_withPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -8407,7 +8442,7 @@ function testUser_linkWithPopup_emailCredentialError() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -8549,7 +8584,7 @@ function testUser_reauthenticateWithPopup_userMismatchError() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -8776,7 +8811,7 @@ function testUser_linkWithPopup_success_cannotRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -8957,7 +8992,7 @@ function testUser_linkWithPopup_success_cannotRunInBackground_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -9151,7 +9186,7 @@ function testUser_reauthenticateWithPopup_success_cannotRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -9322,7 +9357,7 @@ function testUser_reauthenticateWithPopup_success_cannotRunInBkg_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -9506,7 +9541,7 @@ function testUser_linkWithPopup_success_iframeCanRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -9699,7 +9734,7 @@ function testUser_reauthenticateWithPopup_success_iframeCanRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -9880,7 +9915,7 @@ function testUser_linkWithPopup_webStorageUnsupported_cannotRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -10037,7 +10072,7 @@ function testUser_reauthWithPopup_webStorageUnsupported_cantRunInBackground() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -10188,7 +10223,7 @@ function testUser_linkWithPopup_multipleUsers_success() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); @@ -10406,7 +10441,7 @@ function testUser_reauthenticateWithPopup_multipleUsers_success() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -10615,7 +10650,7 @@ function testUser_linkWithPopup_timeout() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -10802,7 +10837,7 @@ function testUser_reauthenticateWithPopup_timeout() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -10979,7 +11014,7 @@ function testUser_linkWithPopup_error() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processPopup( @@ -11112,7 +11147,7 @@ function testUser_reauthenticateWithPopup_error() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -11261,7 +11296,7 @@ function testUser_returnFromLinkWithRedirect_success_withoutPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11336,7 +11371,7 @@ function testUser_returnFromLinkWithRedirect_success_withPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11414,7 +11449,7 @@ function testUser_returnFromLinkWithRedirect_success_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11493,7 +11528,7 @@ function testUser_returnFromReauthenticateWithRedirect_success_noPostBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11568,7 +11603,7 @@ function testUser_returnFromReauthenticateWithRedirect_success_postBody() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11646,7 +11681,7 @@ function testUser_returnFromReauthenticateWithRedirect_success_tenantId() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11725,7 +11760,7 @@ function testUser_returnFromLinkWithRedirect_success_multipleUsers() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Both users should be subscribed. @@ -11818,7 +11853,7 @@ function testUser_returnFromReauthenticateWithRedirect_success_multipleUsers() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Both users should be subscribed. @@ -11907,7 +11942,7 @@ function testUser_returnFromLinkWithRedirect_invalidUser() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -11969,7 +12004,7 @@ function testUser_returnFromReauthenticateWithRedirect_invalidUser() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -12032,7 +12067,7 @@ function testUser_returnFromLinkWithRedirect_error() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -12096,7 +12131,7 @@ function testUser_returnFromReauthenticateWithRedirect_error() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -13172,6 +13207,56 @@ function testUser_customLocaleChanges() { } +function testUser_emulatorConfigChanges() { + // Listen to all custom emulator config change calls on RpcHandler. + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction()); + // Dummy event dispatchers. + var dispatcher1 = createEventDispatcher(); + var dispatcher2 = createEventDispatcher(); + user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); + var emulatorConfig = { + url: 'http://emulator.test.domain:1234', + }; + + var otherEmulatorConfig = { + url: 'http://other.emulator.host:9876' + }; + + // Set emulator config. + user.setEmulatorConfig(emulatorConfig); + // Rpc handler emulator config should be updated. + assertEquals( + 1, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount() + ); + assertObjectEquals( + emulatorConfig, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0) + ); + + // Set dispatcher1 as emulator config change dispatcher. + user.setEmulatorConfigChangeDispatcher(dispatcher1); + dispatcher1.dispatchEvent( + new fireauth.Auth.EmulatorConfigChangeEvent(emulatorConfig)); + dispatcher2.dispatchEvent( + new fireauth.Auth.EmulatorConfigChangeEvent(otherEmulatorConfig)); + // Only first dispatcher should be detected. + assertEquals( + 2, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount() + ); + assertObjectEquals( + emulatorConfig, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0) + ); +} + + function testUser_frameworkLoggingChanges() { // Helper function to get the client version for the test. var getVersion = function(frameworks) { @@ -13891,7 +13976,7 @@ function testReauthenticateWithPopup_multiFactor_success() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -14120,7 +14205,7 @@ function testReauthenticateWithPopup_multiFactor_mismatchError() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.processPopup( ignoreArgument, ignoreArgument, @@ -14272,7 +14357,7 @@ function testReturnFromReauthenticateWithRedirect_multiFactor_success() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from @@ -14429,7 +14514,7 @@ function testReturnFromReauthenticateWithRedirect_multiFactor_mismatchError() { fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument).$returns(oAuthSignInHandlerInstance); + ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) .$does(function(handler) { // Dispatch expected Auth event immediately to simulate return from diff --git a/packages/auth/test/cordovahandler_test.js b/packages/auth/test/cordovahandler_test.js index c21d35293c7..e002d434c4c 100644 --- a/packages/auth/test/cordovahandler_test.js +++ b/packages/auth/test/cordovahandler_test.js @@ -1248,6 +1248,103 @@ function testCordovaHandler_processRedirect_success_parallelCalls_tenantId() { } +function testCordovaHandler_processRedirect_success_withEmulator() { + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + return installAndRunTest( + 'testCordovaHandler_processRedirect_success_withEmulator', + function () { + var provider = new fireauth.GoogleAuthProvider(); + // Construct OAuth handler URL. + var expectedUrl = + fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( + authDomain, + apiKey, + appName, + 'linkViaRedirect', + provider, + null, + '1234', + version, + { + apn: 'com.example.app', + appDisplayName: 'Test App', + sessionId: sha256('11111111111111111111') + }, + // Confirm expected endpoint ID passed. + fireauth.constants.Endpoint.STAGING.id, + null, + emulatorConfig); + // Stub this so the session ID generated can be predictable. + stubs.replace( + Math, + 'random', + function () { + return 0; + }); + // Simulate Android environment. + stubs.replace( + fireauth.util, + 'getUserAgentString', + function () { + return androidUA; + }); + var incomingUrl = + 'http://emulator.test.domain:1234/__/auth/callback#oauthResponse'; + // Completed event. + var completeEvent = new fireauth.AuthEvent( + 'linkViaRedirect', + '1234', + incomingUrl, + '11111111111111111111'); + // Initial unknown event. + var noEvent = new fireauth.AuthEvent( + fireauth.AuthEvent.Type.UNKNOWN, + null, + null, + null, + new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); + var savedCb = null; + // Save the universal link callback on subscription. + universalLinks.subscribe = function (eventType, cb) { + savedCb = cb; + }; + cordova.plugins.browsertab.openUrl = function (url) { + // Confirm expected URL. + assertEquals(expectedUrl, url); + // On openUrl, simulate completion by triggering the universal link + // callback with the OAuth response. + savedCb({ url: incomingUrl }); + }; + var handler = goog.testing.recordFunction(); + cordovaHandler = new fireauth.CordovaHandler( + authDomain, apiKey, appName, version, 10, undefined, + fireauth.constants.Endpoint.STAGING.id, emulatorConfig); + cordovaHandler.addAuthEventListener(handler); + return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') + .then(function () { + // Confirm browsertab close called. + assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); + // Handler triggered twice. + assertEquals(2, handler.getCallCount()); + // First with no event. + assertAuthEventEquals( + noEvent, + handler.getCalls()[0].getArgument(0)); + // Then with resolved event. + assertAuthEventEquals( + completeEvent, + handler.getLastCall().getArgument(0)); + // Confirm event deleted from storage. + return getAndDeletePartialEventManager.getAuthEvent(); + }).then(function (event) { + assertNull(event); + }); + }); +} + + function testCordovaHandler_processRedirect_success_ios() { return installAndRunTest('processRedirect_success_ios', function() { var provider = new fireauth.GoogleAuthProvider(); diff --git a/packages/auth/test/iframeclient/ifchandler_test.js b/packages/auth/test/iframeclient/ifchandler_test.js index 468a48d3149..55ff6c49dfa 100644 --- a/packages/auth/test/iframeclient/ifchandler_test.js +++ b/packages/auth/test/iframeclient/ifchandler_test.js @@ -205,6 +205,21 @@ function testIframeUrlBuilder() { } +/** + * Asserts IframeUrlBuilder.prototype.toString handles emulator URLs. + */ +function testIframeUrlBuilder_withEmulator() { + var builder = new fireauth.iframeclient.IframeUrlBuilder( + 'example.firebaseapp.com', 'API_KEY', 'MY_APP', { + url: 'http://emulator.test.domain:1234' + }); + assertEquals( + 'http://emulator.test.domain:1234/emulator/auth/iframe?' + + 'apiKey=API_KEY&appName=MY_APP', + builder.setVersion(null).toString()); +} + + function testOAuthUrlBuilder() { var redirectUrl = 'http://www.example.com/redirect?a=b#c'; var redirectUrl2 = 'http://www.example.com/redirect2?d=e#f'; @@ -480,6 +495,24 @@ function testOAuthUrlBuilder_notOAuthProviderInstance() { } +/** + * Tests OAuth URL Builder with an emulator config + */ +function testOAuthUrlBuilder_withEmulatorConfig() { + var provider = new fireauth.GoogleAuthProvider(); + var emulatorConfig = { + url: "http://emulator.host:1234" + }; + var builder = new fireauth.iframeclient.OAuthUrlBuilder( + 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', + provider, emulatorConfig); + var url = 'http://emulator.host:1234/emulator/auth/handler?' + + 'apiKey=API_KEY&appName=APP_NAME&authType=signInWithPopup&' + + 'providerId=google.com&scopes=profile'; + assertEquals(url, builder.toString()); +} + + /** * Tests initialization of Auth iframe and its event listeners. */ @@ -487,7 +520,7 @@ function testIfcHandler() { asyncTestCase.waitForSignals(6); // The expected iframe URL. var expectedUrl = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName, version); + authDomain, apiKey, appName, version); var authEvent = new fireauth.AuthEvent( 'unknown', '1234', 'http://www.example.com/#oauthResponse', 'SESSION_ID'); var resp = { @@ -587,6 +620,25 @@ function testIfcHandler() { } +/** + * Asserts getIframeUrl handles emulator URLs. + */ +function testIfcHandler_withEmulator() { + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + // The expected iframe URL. + var expectedUrl = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( + authDomain, apiKey, appName, version, undefined, undefined, + emulatorConfig); + // Initialize the ifcHandler. + ifcHandler = new fireauth.iframeclient.IfcHandler( + authDomain, apiKey, appName, version, undefined, emulatorConfig); + // Confirm expected iframe URL. + assertEquals(ifcHandler.getIframeUrl(), expectedUrl); +} + + function testIfcHandler_shouldNotBeInitializedEarly() { ifcHandler = new fireauth.iframeclient.IfcHandler( authDomain, apiKey, appName, version); @@ -690,7 +742,7 @@ function testIfcHandler_initializeAndWait_success() { fireauth.iframeclient, 'IframeWrapper'); // Iframe initialized with expected endpoint ID. getAuthIframeUrl(authDomain, apiKey, appName, ignoreArgument, - fireauth.constants.Endpoint.STAGING.id, expectedFrameworks) + fireauth.constants.Endpoint.STAGING.id, expectedFrameworks, ignoreArgument) .$returns('https://url'); // Confirm iframe URL returned by getAuthIframeUrl used to initialize the // IframeWrapper. @@ -917,7 +969,7 @@ function testIfcHandler_processPopup_notAlreadyRedirected_success() { } -function testIfcHandler_processPopup_notAlreadyRedirected_tenentId_success() { +function testIfcHandler_processPopup_notAlreadyRedirected_tenantId_success() { asyncTestCase.waitForSignals(1); var popupWin = { close: goog.testing.recordFunction() @@ -975,6 +1027,77 @@ function testIfcHandler_processPopup_notAlreadyRedirected_tenentId_success() { } +/** Asserts processRedirect can handle emulator URLs. */ +function testIfcHandler_processPopup_success_withEmulator() { + var emulatorConfig = { + url: 'http:/emulator.test.domain:1234' + }; + asyncTestCase.waitForSignals(1); + var popupWin = { + close: goog.testing.recordFunction() + }; + var onInit = goog.testing.recordFunction(); + var onError = goog.testing.recordFunction(); + stubs.replace( + fireauth.util, + 'goTo', + goog.testing.recordFunction()); + // 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]); + }); + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction()); + var provider = new fireauth.GoogleAuthProvider(); + var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( + authDomain, + apiKey, + appName, + 'linkViaPopup', + provider, + null, + '1234', + version, + undefined, + // Check expected endpoint ID appended. + fireauth.constants.Endpoint.STAGING.id, + undefined, + emulatorConfig); + ifcHandler = new fireauth.iframeclient.IfcHandler( + authDomain, apiKey, appName, version, + fireauth.constants.Endpoint.STAGING.id, emulatorConfig); + // Should succeed. + ifcHandler.processPopup( + popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) + .then(function () { + // On init should be called as the iframe is initialized. + assertEquals(1, onInit.getCallCount()); + // No error. + assertEquals(0, onError.getCallCount()); + // Popup redirected. + assertEquals( + expectedUrl, + fireauth.util.goTo.getLastCall().getArgument(0)); + // Emulator config set on RpcHandler. + assertObjectEquals( + emulatorConfig, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0)); + assertEquals( + popupWin, + fireauth.util.goTo.getLastCall().getArgument(1)); + asyncTestCase.signal(); + }); +} + + function testIfcHandler_processPopup_redirected_iframeCanRunInBG_success() { asyncTestCase.waitForSignals(1); // Simulate that the app can run in the background but is running in an @@ -1467,6 +1590,64 @@ function testIfcHandler_processRedirect_networkError_then_success() { } +/** Asserts that processRedirects works with emulator URLs. */ +function testIfcHandler_processRedirect_success_withEmulator() { + var emulatorConfig = { + url: 'http:/emulator.test.domain:1234' + }; + var provider = new fireauth.GoogleAuthProvider(); + var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( + authDomain, + apiKey, + appName, + 'linkViaRedirect', + provider, + fireauth.util.getCurrentUrl(), + '1234', + version, + undefined, + // Check expected endpoint ID appended. + fireauth.constants.Endpoint.STAGING.id, + null, + emulatorConfig); + asyncTestCase.waitForSignals(1); + // 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]); + }); + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction()); + stubs.replace( + fireauth.util, + 'goTo', + goog.testing.recordFunction()); + ifcHandler = new fireauth.iframeclient.IfcHandler( + authDomain, apiKey, appName, version, + fireauth.constants.Endpoint.STAGING.id, emulatorConfig); + // Should succeed and redirect. + ifcHandler.processRedirect('linkViaRedirect', provider, '1234') + .then(function () { + /** @suppress {missingRequire} */ + assertEquals( + expectedUrl, + fireauth.util.goTo.getLastCall().getArgument(0)); + // Emulator config set on RpcHandler. + assertObjectEquals( + emulatorConfig, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0)); + asyncTestCase.signal(); + }); +} + + /** * Tests getAuthIframeUrl. */ @@ -1484,6 +1665,33 @@ function testGetAuthIframeUrl() { } +/** + * Asserts getAuthIframeUrl handles emulator URLs. + */ +function testGetAuthIframeUrl_withEmulator() { + var authDomain = 'subdomain.firebaseapp.com'; + var apiKey = 'apiKey1'; + var appName = 'appName1'; + var version = '3.0.0-rc.1'; + var endpointId = 's'; + var emulatorConfig = { + url: "http://emulator.host:1234" + }; + assertEquals( + 'http://emulator.host:1234/emulator/auth/iframe?apiKey=apiKey1&appNa' + + 'me=appName1&v=' + encodeURIComponent(version) + '&eid=' + endpointId, + fireauth.iframeclient.IfcHandler.getAuthIframeUrl( + authDomain, + apiKey, + appName, + version, + endpointId, + null, + emulatorConfig) + ); +} + + /** * Tests getAuthIframeUrl with frameworks. */ @@ -1796,3 +2004,39 @@ function testGetOAuthHelperWidgetUrl_frameworksAndLanguageCode() { authDomain, apiKey, appName, authType, provider)); } + +/** + * Asserts getOAuthHelperWidgetUrl handles emulator URLs. + */ +function testGetOAuthHelperWidgetUrl_withEmulator() { + var authDomain = 'subdomain.firebaseapp.com'; + var apiKey = 'apiKey1'; + var appName = 'appName1'; + var authType = 'signInWithPopup'; + var providerId = 'facebook.com'; + var provider = new fireauth.FacebookAuthProvider(); + var emulatorConfig = { + url: 'http://emulator.test.domain:1234' + }; + var expectedWidgetUrl = 'http://emulator.test.domain:1234/' + + 'emulator/auth/handler' + + '?apiKey=' + encodeURIComponent(apiKey) + + '&appName=' + encodeURIComponent(appName) + + '&authType=' + encodeURIComponent(authType) + + '&providerId=' + encodeURIComponent(providerId); + assertEquals( + expectedWidgetUrl, + fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( + authDomain, + apiKey, + appName, + authType, + provider, + null, + null, + null, + null, + null, + null, + emulatorConfig)); +} \ No newline at end of file diff --git a/packages/auth/test/rpchandler_test.js b/packages/auth/test/rpchandler_test.js index 908dbc10198..916ded8a37f 100644 --- a/packages/auth/test/rpchandler_test.js +++ b/packages/auth/test/rpchandler_test.js @@ -1180,6 +1180,30 @@ function testRequestStsToken_specificErrorResponse() { } +function testRequestStsToken_emulator() { + asyncTestCase.waitForSignals(1); + // Confirm correct parameters passed and run on complete. + assertSendXhrAndRunCallback( + 'http://emulator.test.domain:1234/securetoken.googleapis.com/' + + 'v1/token?key=apiKey', + 'POST', + 'grant_type=authorization_code&code=idToken', + fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, + delay, + expectedStsTokenResponse); + // Set an emulator config. + rpcHandler.updateEmulatorConfig( + { url: 'http://emulator.test.domain:1234' }); + // Send STS token request, emulator config will be used. + rpcHandler + .requestStsToken({ 'grant_type': 'authorization_code', 'code': 'idToken' }) + .then(function (response) { + assertObjectEquals(expectedStsTokenResponse, response); + asyncTestCase.signal(); + }); +} + + function testRequestFirebaseEndpoint_success() { var expectedResponse = { 'status': 'success' @@ -1211,6 +1235,31 @@ function testRequestFirebaseEndpoint_success() { } +function testRequestFirebaseEndpoint_emulator() { + var expectedResponse = { 'status': 'success' }; + asyncTestCase.waitForSignals(1); + assertSendXhrAndRunCallback( + 'http://emulator.test.domain:1234/www.googleapis.com/identitytoolkit' + + '/v3/relyingparty/method1?key=apiKey', + 'POST', + goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), + fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, + delay, + expectedResponse); + // Set an emulator config. + rpcHandler.updateEmulatorConfig( + { url: 'http://emulator.test.domain:1234' }); + + rpcHandler + .requestFirebaseEndpoint( + 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) + .then(function (response) { + assertObjectEquals(expectedResponse, response); + asyncTestCase.signal(); + }); +} + + function testRequestIdentityPlatformEndpoint_success() { var expectedResponse = { 'status': 'success' @@ -1241,6 +1290,30 @@ function testRequestIdentityPlatformEndpoint_success() { } +function testRequestIdentityPlatformEndpoint_emulator() { + var expectedResponse = { 'status': 'success' }; + asyncTestCase.waitForSignals(1); + assertSendXhrAndRunCallback( + 'http://emulator.test.domain:1234/identitytoolkit.googleapis.com' + + '/v2/method1?key=apiKey', + 'POST', + goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), + fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, + delay, + expectedResponse); + // Set an emulator config. + rpcHandler.updateEmulatorConfig( + { url: 'http://emulator.test.domain:1234' }); + rpcHandler + .requestIdentityPlatformEndpoint( + 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) + .then(function (response) { + assertObjectEquals(expectedResponse, response); + asyncTestCase.signal(); + }); +} + + function testRequestFirebaseEndpoint_updateClientVersion() { var expectedResponse = { 'status': 'success' diff --git a/packages/auth/test/storageusermanager_test.js b/packages/auth/test/storageusermanager_test.js index 7acc451a509..991755b8d83 100644 --- a/packages/auth/test/storageusermanager_test.js +++ b/packages/auth/test/storageusermanager_test.js @@ -190,7 +190,13 @@ function testGetSetRemoveCurrentUser() { expectedUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); // Expected user with authDomain. expectedUserWithAuthDomain = - new fireauth.AuthUser(configWithAuthDomain, tokenResponse, accountInfo); + new fireauth.AuthUser(configWithAuthDomain, tokenResponse, accountInfo); + // Listen to calls on RPC Handler. + stubs.replace( + fireauth.RpcHandler.prototype, + 'updateEmulatorConfig', + goog.testing.recordFunction( + fireauth.RpcHandler.prototype.updateEmulatorConfig)); var storageKey = 'firebase:authUser:appId1'; return goog.Promise.resolve() .then(function() { @@ -210,7 +216,23 @@ function testGetSetRemoveCurrentUser() { }) .then(function(user) { fireauth.common.testHelper.assertUserEquals( - expectedUserWithAuthDomain, user); + expectedUserWithAuthDomain, user); + // Get user with authDomain & emulator config. + return userManager.getCurrentUser('project.firebaseapp.com', + { + url: 'http://emulator.test.domain:1234' + }); + }) + .then(function () { + // Verify RpcHandler was notified of config change. + assertEquals(1, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); + assertObjectEquals( + { + url: 'http://emulator.test.domain:1234' + }, + fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() + .getArgument(0)); return userManager.removeCurrentUser(); }) .then(function() { diff --git a/packages/firebase/index.d.ts b/packages/firebase/index.d.ts index f2ee707d543..97fa10dcc04 100644 --- a/packages/firebase/index.d.ts +++ b/packages/firebase/index.d.ts @@ -3119,6 +3119,14 @@ declare namespace firebase.auth { * Sets the current language to the default device/browser preference. */ useDeviceLanguage(): void; + /** + * Modify this Auth instance to communicate with the Firebase Auth emulator. This must be + * called synchronously immediately following the first call to `firebase.auth()`. Do not use + * with production credentials as emulator traffic is not encrypted. + * + * @param url The URL at which the emulator is running (eg, 'http://localhost:9099') + */ + useEmulator(url: string): void; /** * Checks a password reset code sent to the user by email or other out-of-band * mechanism.