1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 <include src="saml_handler.js">
8 * @fileoverview An UI component to authenciate to Chrome. The component hosts
9 * IdP web pages in a webview. A client who is interested in monitoring
10 * authentication events should pass a listener object of type
11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
12 * call {@code load} to start the authentication flow.
15 cr.define('cr.login', function() {
18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead
19 // of hardcoding the prod URL here. As is, this does not work with staging
21 var IDP_ORIGIN = 'https://accounts.google.com/';
22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
25 var SIGN_IN_HEADER = 'google-accounts-signin';
26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
27 var LOCATION_HEADER = 'location';
28 var COOKIE_HEADER = 'cookie';
29 var SET_COOKIE_HEADER = 'set-cookie';
30 var OAUTH_CODE_COOKIE = 'oauth_code';
31 var GAPS_COOKIE = 'GAPS';
32 var SERVICE_ID = 'chromeoslogin';
33 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos';
36 * The source URL parameter for the constrained signin flow.
38 var CONSTRAINED_FLOW_SOURCE = 'chrome';
41 * Enum for the authorization mode, must match AuthMode defined in
42 * chrome/browser/ui/webui/inline_login_ui.cc.
52 * Enum for the authorization type.
61 * Supported Authenticator params.
62 * @type {!Array<string>}
65 var SUPPORTED_PARAMS = [
66 'gaiaId', // Obfuscated GAIA ID to skip the email prompt page
67 // during the re-auth flow.
68 'gaiaUrl', // Gaia url to use.
69 'gaiaPath', // Gaia path to use without a leading slash.
70 'hl', // Language code for the user interface.
71 'email', // Pre-fill the email field in Gaia UI.
72 'service', // Name of Gaia service.
73 'continueUrl', // Continue url to use.
74 'frameUrl', // Initial frame URL to use. If empty defaults to
76 'constrained', // Whether the extension is loaded in a constrained
78 'clientId', // Chrome client id.
79 'useEafe', // Whether to use EAFE.
80 'needPassword', // Whether the host is interested in getting a password.
81 // If this set to |false|, |confirmPasswordCallback| is
82 // not called before dispatching |authCopleted|.
84 'flow', // One of 'default', 'enterprise', or 'theftprotection'.
85 'enterpriseDomain', // Domain in which hosting device is (or should be)
87 'emailDomain', // Value used to prefill domain for email.
88 'clientVersion', // Version of the Chrome build.
89 'platformVersion', // Version of the OS build.
90 'releaseChannel', // Installation channel.
91 'endpointGen', // Current endpoint generation.
92 'gapsCookie', // GAPS cookie
96 * Initializes the authenticator component.
97 * @param {webview|string} webview The webview element or its ID to host IdP
101 function Authenticator(webview) {
102 this.webview_ = typeof webview == 'string' ? $(webview) : webview;
103 assert(this.webview_);
106 this.password_ = null;
108 this.sessionIndex_ = null;
109 this.chooseWhatToSync_ = false;
110 this.skipForNow_ = false;
111 this.authFlow = AuthFlow.DEFAULT;
112 this.authDomain = '';
113 this.idpOrigin_ = null;
114 this.continueUrl_ = null;
115 this.continueUrlWithoutParams_ = null;
116 this.initialFrameUrl_ = null;
117 this.reloadUrl_ = null;
118 this.trusted_ = true;
119 this.oauthCode_ = null;
120 this.gapsCookie_ = null;
121 this.gapsCookieSent_ = false;
122 this.newGapsCookie_ = null;
124 this.useEafe_ = false;
125 this.clientId_ = null;
127 this.samlHandler_ = new cr.login.SamlHandler(this.webview_);
128 this.confirmPasswordCallback = null;
129 this.noPasswordCallback = null;
130 this.insecureContentBlockedCallback = null;
131 this.samlApiUsedCallback = null;
132 this.missingGaiaInfoCallback = null;
133 this.needPassword = true;
134 this.samlHandler_.addEventListener(
135 'insecureContentBlocked',
136 this.onInsecureContentBlocked_.bind(this));
137 this.samlHandler_.addEventListener(
139 this.onAuthPageLoaded_.bind(this));
141 this.webview_.addEventListener('droplink', this.onDropLink_.bind(this));
142 this.webview_.addEventListener(
143 'newwindow', this.onNewWindow_.bind(this));
144 this.webview_.addEventListener(
145 'contentload', this.onContentLoad_.bind(this));
146 this.webview_.addEventListener(
147 'loadabort', this.onLoadAbort_.bind(this));
148 this.webview_.addEventListener(
149 'loadstop', this.onLoadStop_.bind(this));
150 this.webview_.addEventListener(
151 'loadcommit', this.onLoadCommit_.bind(this));
152 this.webview_.request.onCompleted.addListener(
153 this.onRequestCompleted_.bind(this),
154 {urls: ['<all_urls>'], types: ['main_frame']},
155 ['responseHeaders']);
156 this.webview_.request.onHeadersReceived.addListener(
157 this.onHeadersReceived_.bind(this),
158 {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']},
159 ['responseHeaders']);
160 window.addEventListener(
161 'message', this.onMessageFromWebview_.bind(this), false);
162 window.addEventListener(
163 'focus', this.onFocus_.bind(this), false);
164 window.addEventListener(
165 'popstate', this.onPopState_.bind(this), false);
168 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
171 * Reinitializes authentication parameters so that a failed login attempt
172 * would not result in an infinite loop.
174 Authenticator.prototype.clearCredentials_ = function() {
177 this.password_ = null;
178 this.oauthCode_ = null;
179 this.gapsCookie_ = null;
180 this.gapsCookieSent_ = false;
181 this.newGapsCookie_ = null;
182 this.chooseWhatToSync_ = false;
183 this.skipForNow_ = false;
184 this.sessionIndex_ = null;
185 this.trusted_ = true;
186 this.authFlow = AuthFlow.DEFAULT;
187 this.samlHandler_.reset();
191 * Loads the authenticator component with the given parameters.
192 * @param {AuthMode} authMode Authorization mode.
193 * @param {Object} data Parameters for the authorization flow.
195 Authenticator.prototype.load = function(authMode, data) {
196 this.authMode = authMode;
197 this.clearCredentials_();
198 // gaiaUrl parameter is used for testing. Once defined, it is never changed.
199 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
200 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
201 this.continueUrlWithoutParams_ =
202 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
204 this.isConstrainedWindow_ = data.constrained == '1';
205 this.isNewGaiaFlowChromeOS = data.isNewGaiaFlowChromeOS;
206 this.useEafe_ = data.useEafe || false;
207 this.clientId_ = data.clientId;
208 this.gapsCookie_ = data.gapsCookie;
209 this.gapsCookieSent_ = false;
210 this.newGapsCookie_ = null;
212 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
213 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
214 // Don't block insecure content for desktop flow because it lands on
215 // http. Otherwise, block insecure content as long as gaia is https.
216 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP &&
217 this.idpOrigin_.indexOf('https://') == 0;
218 this.needPassword = !('needPassword' in data) || data.needPassword;
220 if (this.isNewGaiaFlowChromeOS) {
221 this.webview_.contextMenus.onShow.addListener(function(e) {
225 if (!this.onBeforeSetHeadersSet_) {
226 this.onBeforeSetHeadersSet_ = true;
227 var filterPrefix = this.idpOrigin_ + EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
228 // This depends on gaiaUrl parameter, that is why it is here.
229 this.webview_.request.onBeforeSendHeaders.addListener(
230 this.onBeforeSendHeaders_.bind(this),
231 {urls: [filterPrefix + '?*', filterPrefix + '/*']},
232 ['requestHeaders', 'blocking']);
236 this.webview_.src = this.reloadUrl_;
240 * Reloads the authenticator component.
242 Authenticator.prototype.reload = function() {
243 this.clearCredentials_();
244 this.webview_.src = this.reloadUrl_;
247 Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
248 var path = data.gaiaPath;
249 if (!path && this.isNewGaiaFlowChromeOS)
250 path = EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
253 var url = this.idpOrigin_ + path;
255 if (this.isNewGaiaFlowChromeOS) {
257 url = appendParam(url, 'chrometype', data.chromeType);
259 url = appendParam(url, 'client_id', data.clientId);
260 if (data.enterpriseDomain)
261 url = appendParam(url, 'manageddomain', data.enterpriseDomain);
262 if (data.clientVersion)
263 url = appendParam(url, 'client_version', data.clientVersion);
264 if (data.platformVersion)
265 url = appendParam(url, 'platform_version', data.platformVersion);
266 if (data.releaseChannel)
267 url = appendParam(url, 'release_channel', data.releaseChannel);
268 if (data.endpointGen)
269 url = appendParam(url, 'endpoint_gen', data.endpointGen);
271 url = appendParam(url, 'continue', this.continueUrl_);
272 url = appendParam(url, 'service', data.service || SERVICE_ID);
275 url = appendParam(url, 'hl', data.hl);
277 url = appendParam(url, 'user_id', data.gaiaId);
279 url = appendParam(url, 'Email', data.email);
280 if (this.isConstrainedWindow_)
281 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
283 url = appendParam(url, 'flow', data.flow);
284 if (data.emailDomain)
285 url = appendParam(url, 'emaildomain', data.emailDomain);
290 * Invoked when a main frame request in the webview has completed.
293 Authenticator.prototype.onRequestCompleted_ = function(details) {
294 var currentUrl = details.url;
296 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
297 if (currentUrl.indexOf('ntp=1') >= 0)
298 this.skipForNow_ = true;
300 this.maybeCompleteAuth_();
304 if (currentUrl.indexOf('https') != 0)
305 this.trusted_ = false;
307 if (this.isConstrainedWindow_) {
308 var isEmbeddedPage = false;
309 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
310 var headers = details.responseHeaders;
311 for (var i = 0; headers && i < headers.length; ++i) {
312 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
313 isEmbeddedPage = true;
318 if (!isEmbeddedPage) {
319 this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl}));
324 this.updateHistoryState_(currentUrl);
328 * Manually updates the history. Invoked upon completion of a webview
330 * @param {string} url Request URL.
333 Authenticator.prototype.updateHistoryState_ = function(url) {
334 if (history.state && history.state.url != url)
335 history.pushState({url: url}, '');
337 history.replaceState({url: url}, '');
341 * Invoked when the sign-in page takes focus.
342 * @param {object} e The focus event being triggered.
345 Authenticator.prototype.onFocus_ = function(e) {
346 if (this.authMode == AuthMode.DESKTOP)
347 this.webview_.focus();
351 * Invoked when the history state is changed.
352 * @param {object} e The popstate event being triggered.
355 Authenticator.prototype.onPopState_ = function(e) {
357 if (state && state.url)
358 this.webview_.src = state.url;
362 * Invoked when headers are received in the main frame of the webview. It
363 * 1) reads the authenticated user info from a signin header,
364 * 2) signals the start of a saml flow upon receiving a saml header.
365 * @return {!Object} Modified request headers.
368 Authenticator.prototype.onHeadersReceived_ = function(details) {
369 var currentUrl = details.url;
370 if (currentUrl.lastIndexOf(this.idpOrigin_, 0) != 0)
373 var headers = details.responseHeaders;
374 for (var i = 0; headers && i < headers.length; ++i) {
375 var header = headers[i];
376 var headerName = header.name.toLowerCase();
377 if (headerName == SIGN_IN_HEADER) {
378 var headerValues = header.value.toLowerCase().split(',');
379 var signinDetails = {};
380 headerValues.forEach(function(e) {
381 var pair = e.split('=');
382 signinDetails[pair[0].trim()] = pair[1].trim();
384 // Removes "" around.
385 this.email_ = signinDetails['email'].slice(1, -1);
386 this.gaiaId_ = signinDetails['obfuscatedid'].slice(1, -1);
387 this.sessionIndex_ = signinDetails['sessionindex'];
388 } else if (headerName == LOCATION_HEADER) {
389 // If the "choose what to sync" checkbox was clicked, then the continue
390 // URL will contain a source=3 field.
391 var location = decodeURIComponent(header.value);
392 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/);
394 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) {
395 var headerValue = header.value;
396 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) {
398 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0];
400 if (headerValue.indexOf(GAPS_COOKIE + '=', 0) == 0) {
401 this.newGapsCookie_ =
402 headerValue.substring(GAPS_COOKIE.length + 1).split(';')[0];
409 * This method replaces cookie value in cookie header.
410 * @param@ {string} header_value Original string value of Cookie header.
411 * @param@ {string} cookie_name Name of cookie to be replaced.
412 * @param@ {string} cookie_value New cookie value.
413 * @return {string} New Cookie header value.
416 Authenticator.prototype.updateCookieValue_ = function(
417 header_value, cookie_name, cookie_value) {
418 var cookies = header_value.split(/\s*;\s*/);
420 for (var i = 0; i < cookies.length; ++i) {
421 if (cookies[i].indexOf(cookie_name + '=', 0) == 0) {
423 cookies[i] = cookie_name + '=' + cookie_value;
428 cookies.push(cookie_name + '=' + cookie_value);
430 return cookies.join('; ');
434 * Handler for webView.request.onBeforeSendHeaders .
435 * @return {!Object} Modified request headers.
438 Authenticator.prototype.onBeforeSendHeaders_ = function(details) {
439 // We should re-send cookie if first request was unsuccessful (i.e. no new
440 // GAPS cookie was received).
441 if (this.isNewGaiaFlowChromeOS && this.gapsCookie_ &&
442 (!this.gapsCookieSent_ || !this.newGapsCookie_)) {
443 var headers = details.requestHeaders;
445 var gapsCookie = this.gapsCookie_;
447 for (var i = 0, l = headers.length; i < l; ++i) {
448 if (headers[i].name == COOKIE_HEADER) {
449 headers[i].value = this.updateCookieValue_(headers[i].value,
450 GAPS_COOKIE, gapsCookie);
456 details.requestHeaders.push(
457 {name: COOKIE_HEADER, value: GAPS_COOKIE + '=' + gapsCookie});
459 this.gapsCookieSent_ = true;
462 requestHeaders: details.requestHeaders
467 * Returns true if given HTML5 message is received from the webview element.
468 * @param {object} e Payload of the received HTML5 message.
470 Authenticator.prototype.isGaiaMessage = function(e) {
471 if (!this.isWebviewEvent_(e))
474 // The event origin does not have a trailing slash.
475 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) {
479 // EAFE passes back auth code via message.
481 typeof e.data == 'object' &&
482 e.data.hasOwnProperty('authorizationCode')) {
483 assert(!this.oauthCode_);
484 this.oauthCode_ = e.data.authorizationCode;
486 new CustomEvent('authCompleted',
490 authCode: this.oauthCode_
496 // Gaia messages must be an object with 'method' property.
497 if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
504 * Invoked when an HTML5 message is received from the webview element.
505 * @param {object} e Payload of the received HTML5 message.
508 Authenticator.prototype.onMessageFromWebview_ = function(e) {
509 if (!this.isGaiaMessage(e))
513 if (msg.method == 'attemptLogin') {
514 this.email_ = msg.email;
515 this.chooseWhatToSync_ = msg.chooseWhatToSync;
516 // We need to dispatch only first event, before user enters password.
518 new CustomEvent('attemptLogin', {detail: msg.email}));
519 } else if (msg.method == 'dialogShown') {
520 this.dispatchEvent(new Event('dialogShown'));
521 } else if (msg.method == 'dialogHidden') {
522 this.dispatchEvent(new Event('dialogHidden'));
523 } else if (msg.method == 'backButton') {
524 this.dispatchEvent(new CustomEvent('backButton', {detail: msg.show}));
525 } else if (msg.method == 'showView') {
526 this.dispatchEvent(new Event('showView'));
527 } else if (msg.method == 'identifierEntered') {
528 this.dispatchEvent(new CustomEvent(
530 {detail: {accountIdentifier: msg.accountIdentifier}}));
532 console.warn('Unrecognized message from GAIA: ' + msg.method);
537 * Invoked by the hosting page to verify the Saml password.
539 Authenticator.prototype.verifyConfirmedPassword = function(password) {
540 if (!this.samlHandler_.verifyConfirmedPassword(password)) {
541 // Invoke confirm password callback asynchronously because the
542 // verification was based on messages and caller (GaiaSigninScreen)
543 // does not expect it to be called immediately.
544 // TODO(xiyuan): Change to synchronous call when iframe based code
546 var invokeConfirmPassword = (function() {
547 this.confirmPasswordCallback(this.email_,
548 this.samlHandler_.scrapedPasswordCount);
550 window.setTimeout(invokeConfirmPassword, 0);
554 this.password_ = password;
555 this.onAuthCompleted_();
559 * Check Saml flow and start password confirmation flow if needed. Otherwise,
560 * continue with auto completion.
563 Authenticator.prototype.maybeCompleteAuth_ = function() {
564 var missingGaiaInfo = !this.email_ || !this.gaiaId_ || !this.sessionIndex_;
565 if (missingGaiaInfo && !this.skipForNow_) {
566 if (this.missingGaiaInfoCallback)
567 this.missingGaiaInfoCallback();
569 this.webview_.src = this.initialFrameUrl_;
573 if (this.samlHandler_.samlApiUsed) {
574 if (this.samlApiUsedCallback) {
575 this.samlApiUsedCallback();
577 this.password_ = this.samlHandler_.apiPasswordBytes;
578 this.onAuthCompleted_();
582 if (this.samlHandler_.scrapedPasswordCount == 0) {
583 if (this.noPasswordCallback) {
584 this.noPasswordCallback(this.email_);
588 // Fall through to finish the auth flow even if this.needPassword
589 // is true. This is because the flag is used as an intention to get
590 // password when it is available but not a mandatory requirement.
591 console.warn('Authenticator: No password scraped for SAML.');
592 } else if (this.needPassword) {
593 if (this.confirmPasswordCallback) {
594 // Confirm scraped password. The flow follows in
595 // verifyConfirmedPassword.
596 this.confirmPasswordCallback(this.email_,
597 this.samlHandler_.scrapedPasswordCount);
602 this.onAuthCompleted_();
606 * Invoked to process authentication completion.
609 Authenticator.prototype.onAuthCompleted_ = function() {
610 assert(this.skipForNow_ ||
611 (this.email_ && this.gaiaId_ && this.sessionIndex_));
612 this.dispatchEvent(new CustomEvent(
614 // TODO(rsorokin): get rid of the stub values.
617 email: this.email_ || '',
618 gaiaId: this.gaiaId_ || '',
619 password: this.password_ || '',
620 authCode: this.oauthCode_,
621 usingSAML: this.authFlow == AuthFlow.SAML,
622 chooseWhatToSync: this.chooseWhatToSync_,
623 skipForNow: this.skipForNow_,
624 sessionIndex: this.sessionIndex_ || '',
625 trusted: this.trusted_,
626 gapsCookie: this.newGapsCookie_ || this.gapsCookie_ || '',
629 this.clearCredentials_();
633 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
636 Authenticator.prototype.onInsecureContentBlocked_ = function(e) {
637 if (this.insecureContentBlockedCallback) {
638 this.insecureContentBlockedCallback(e.detail.url);
640 console.error('Authenticator: Insecure content blocked.');
645 * Invoked when |samlHandler_| fires 'authPageLoaded' event.
648 Authenticator.prototype.onAuthPageLoaded_ = function(e) {
649 if (!e.detail.isSAMLPage)
652 this.authDomain = this.samlHandler_.authDomain;
653 this.authFlow = AuthFlow.SAML;
657 * Invoked when a link is dropped on the webview.
660 Authenticator.prototype.onDropLink_ = function(e) {
661 this.dispatchEvent(new CustomEvent('dropLink', {detail: e.url}));
665 * Invoked when the webview attempts to open a new window.
668 Authenticator.prototype.onNewWindow_ = function(e) {
669 this.dispatchEvent(new CustomEvent('newWindow', {detail: e}));
673 * Invoked when a new document is loaded.
676 Authenticator.prototype.onContentLoad_ = function(e) {
677 if (this.isConstrainedWindow_) {
678 // Signin content in constrained windows should not zoom. Isolate the
679 // webview from the zooming of other webviews using the 'per-view' zoom
680 // mode, and then set it to 100% zoom.
681 this.webview_.setZoomMode('per-view');
682 this.webview_.setZoom(1);
685 // Posts a message to IdP pages to initiate communication.
686 var currentUrl = this.webview_.src;
687 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
689 'method': 'handshake',
692 this.webview_.contentWindow.postMessage(msg, currentUrl);
694 this.dispatchEvent(new Event('ready'));
695 // Focus webview after dispatching event when webview is already visible.
696 this.webview_.focus();
701 * Invoked when the webview fails loading a page.
704 Authenticator.prototype.onLoadAbort_ = function(e) {
705 this.dispatchEvent(new CustomEvent('loadAbort',
706 {detail: {error: e.reason,
707 src: this.webview_.src}}));
711 * Invoked when the webview finishes loading a page.
714 Authenticator.prototype.onLoadStop_ = function(e) {
715 // Sends client id to EAFE on every loadstop after a small timeout. This is
716 // needed because EAFE sits behind SSO and initialize asynchrounouly
717 // and we don't know for sure when it is loaded and ready to listen
718 // for message. The postMessage is guarded by EAFE's origin.
720 // An arbitrary small timeout for delivering the initial message.
721 var EAFE_INITIAL_MESSAGE_DELAY_IN_MS = 500;
722 window.setTimeout((function() {
724 'clientId': this.clientId_
726 this.webview_.contentWindow.postMessage(msg, this.idpOrigin_);
727 }).bind(this), EAFE_INITIAL_MESSAGE_DELAY_IN_MS);
732 * Invoked when the webview navigates withing the current document.
735 Authenticator.prototype.onLoadCommit_ = function(e) {
736 if (this.oauthCode_) {
737 this.skipForNow_ = true;
738 this.maybeCompleteAuth_();
743 * Returns |true| if event |e| was sent from the hosted webview.
746 Authenticator.prototype.isWebviewEvent_ = function(e) {
747 // Note: <webview> prints error message to console if |contentWindow| is not
749 // TODO(dzhioev): remove the message. http://crbug.com/469522
750 var webviewWindow = this.webview_.contentWindow;
751 return !!webviewWindow && webviewWindow === e.source;
755 * The current auth flow of the hosted auth page.
758 cr.defineProperty(Authenticator, 'authFlow');
761 * The domain name of the current auth page.
764 cr.defineProperty(Authenticator, 'authDomain');
766 Authenticator.AuthFlow = AuthFlow;
767 Authenticator.AuthMode = AuthMode;
768 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
771 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
772 // iframe-based flow is deprecated.
773 GaiaAuthHost: Authenticator,
774 Authenticator: Authenticator