15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @fileoverview
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * OAuth2 class that handles retrieval/storage of an OAuth2 token.
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Uses a content script to trampoline the OAuth redirect page back into the
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * extension context.  This works around the lack of native support for
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * chrome-extensions in OAuth2.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// TODO(jamiewalch): Delete this code once Chromoting is a v2 app and uses the
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// identity API (http://crbug.com/ 134213).
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'use strict';
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @suppress {duplicate} */
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)var remoting = remoting || {};
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @type {remoting.OAuth2} */
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.oauth2 = null;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @constructor */
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2 = function() {
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Constants representing keys used for storing persistent state.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_REVOKABLE_ =
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'oauth2-refresh-token-revokable';
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.KEY_XSRF_TOKEN_ = 'oauth2-xsrf-token';
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private */
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_EMAIL_ = 'remoting-email';
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Constants for parameters used in retrieving the OAuth2 credentials.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.SCOPE_ =
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/chromoting ' +
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/googletalk ' +
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/userinfo#email';
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Configurable URLs/strings.
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 redirect URI.
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getRedirectUri_ = function() {
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_REDIRECT_URL;
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} API client ID.
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getClientId_ = function() {
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_CLIENT_ID;
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} API client secret.
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getClientSecret_ = function() {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_CLIENT_SECRET;
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 authentication URL.
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getOAuth2AuthEndpoint_ = function() {
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_BASE_URL + '/auth';
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @return {boolean} True if the app is already authenticated. */
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.isAuthenticated = function() {
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.getRefreshToken_()) {
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Removes all storage, and effectively unauthenticates the user.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clear = function() {
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_EMAIL_);
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearAccessToken_();
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearRefreshToken_();
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Sets the refresh token.
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This method also marks the token as revokable, so that this object will
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * revoke the token when it no longer needs it.
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} token The new refresh token.
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
106a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @private
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
108a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)remoting.OAuth2.prototype.setRefreshToken_ = function(token) {
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_REFRESH_TOKEN_REVOKABLE_, true);
1117d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_EMAIL_);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearAccessToken_();
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Gets the refresh token.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This method also marks the refresh token as not revokable, so that this
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * object will not revoke the token when it no longer needs it. After this
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * object has exported the token, it cannot know whether it is still in use
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * when this object no longer needs it.
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The refresh token, if authenticated, or NULL.
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.exportRefreshToken = function() {
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_REVOKABLE_);
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.getRefreshToken_();
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The refresh token, if authenticated, or NULL.
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getRefreshToken_ = function() {
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var value = window.localStorage.getItem(this.KEY_REFRESH_TOKEN_);
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof value == 'string') {
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return unescape(value);
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return null;
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Clears the refresh token.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clearRefreshToken_ = function() {
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (window.localStorage.getItem(this.KEY_REFRESH_TOKEN_REVOKABLE_)) {
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.revokeToken_(this.getRefreshToken_());
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_REVOKABLE_);
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} token The new access token.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} expiration Expiration time in milliseconds since epoch.
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
160a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @private
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
162a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)remoting.OAuth2.prototype.setAccessToken_ = function(token, expiration) {
163a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // Offset expiration by 120 seconds so that we can guarantee that the token
164a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // we return will be valid for at least 2 minutes.
165a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // If the access token is to be useful, this object must make some
166a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // guarantee as to how long the token will be valid for.
167a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // The choice of 2 minutes is arbitrary, but that length of time
168a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // is part of the contract satisfied by callWithToken().
169a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // Offset by a further 30 seconds to account for RTT issues.
170a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var access_token = {
171a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    'token': token,
172a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    'expiration': (expiration - (120 + 30)) * 1000 + Date.now()
173a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  };
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_ACCESS_TOKEN_,
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              JSON.stringify(access_token));
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns the current access token, setting it to a invalid value if none
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * existed before.
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{token: string, expiration: number}} The current access token, or
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * an invalid token if not authenticated.
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getAccessTokenInternal_ = function() {
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!window.localStorage.getItem(this.KEY_ACCESS_TOKEN_)) {
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Always be able to return structured data.
189a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    this.setAccessToken_('', 0);
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var accessToken = window.localStorage.getItem(this.KEY_ACCESS_TOKEN_);
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof accessToken == 'string') {
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var result = jsonParseSafe(accessToken);
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (result && 'token' in result && 'expiration' in result) {
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return /** @type {{token: string, expiration: number}} */ result;
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  console.log('Invalid access token stored.');
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return {'token': '', 'expiration': 0};
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns true if the access token is expired, or otherwise invalid.
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Will throw if !isAuthenticated().
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if a new access token is needed.
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.needsNewAccessToken_ = function() {
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.isAuthenticated()) {
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw 'Not Authenticated.';
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var access_token = this.getAccessTokenInternal_();
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!access_token['token']) {
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (Date.now() > access_token['expiration']) {
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clearAccessToken_ = function() {
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update state based on token response from the OAuth2 /token endpoint.
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
235a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {function(string):void} onOk Called with the new access token.
236a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {string} accessToken Access token.
237a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {number} expiresIn Expiration time for the access token.
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
239a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @private
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
241a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)remoting.OAuth2.prototype.onAccessToken_ =
242a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    function(onOk, accessToken, expiresIn) {
243a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  this.setAccessToken_(accessToken, expiresIn);
244a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  onOk(accessToken);
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
248a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * Update state based on token response from the OAuth2 /token endpoint.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
250a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {function():void} onOk Called after the new tokens are stored.
251a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {string} refreshToken Refresh token.
252a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {string} accessToken Access token.
253a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {number} expiresIn Expiration time for the access token.
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
257a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)remoting.OAuth2.prototype.onTokens_ =
258a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    function(onOk, refreshToken, accessToken, expiresIn) {
259a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  this.setAccessToken_(accessToken, expiresIn);
260a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  this.setRefreshToken_(refreshToken);
261a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  onOk();
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Redirect page to get a new OAuth2 Refresh Token.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.doAuthRedirect = function() {
270762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  /** @type {remoting.OAuth2} */
271762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  var that = this;
272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  var xsrf_token = remoting.generateXsrfToken();
2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  window.localStorage.setItem(this.KEY_XSRF_TOKEN_, xsrf_token);
2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var GET_CODE_URL = this.getOAuth2AuthEndpoint_() + '?' +
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    remoting.xhr.urlencodeParamHash({
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'client_id': this.getClientId_(),
2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'redirect_uri': this.getRedirectUri_(),
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'scope': this.SCOPE_,
2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'state': xsrf_token,
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'response_type': 'code',
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'access_type': 'offline',
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'approval_prompt': 'force'
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        });
284762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)
285762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  /**
286762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)   * Processes the results of the oauth flow.
287762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)   *
288762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)   * @param {Object.<string, string>} message Dictionary containing the parsed
289762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)   *   OAuth redirect URL parameters.
290762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)   */
291762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  function oauth2MessageListener(message) {
292762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)    if ('code' in message && 'state' in message) {
293762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      var onDone = function() {
294762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)        window.location.reload();
295762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      };
296762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      that.exchangeCodeForToken(
297762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)          message['code'], message['state'], onDone);
298762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)    } else {
299762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      if ('error' in message) {
300762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)        console.error(
301762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)            'Could not obtain authorization code: ' + message['error']);
302762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      } else {
303762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)        // We intentionally don't log the response - since we don't understand
304762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)        // it, we can't tell if it has sensitive data.
305762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)        console.error('Invalid oauth2 response.');
306762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)      }
307762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)    }
308762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)    chrome.extension.onMessage.removeListener(oauth2MessageListener);
309762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  }
310762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  chrome.extension.onMessage.addListener(oauth2MessageListener);
311762b2f2129a215bc851e73887244c3a82a892731Torne (Richard Coles)  window.open(GET_CODE_URL, '_blank', 'location=yes,toolbar=no,menubar=no');
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Asynchronously exchanges an authorization code for a refresh token.
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
317a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {string} code The OAuth2 authorization code.
3182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {string} state The state parameter received from the OAuth redirect.
319a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @param {function():void} onDone Callback to invoke on completion.
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.exchangeCodeForToken = function(code, state, onDone) {
3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var xsrf_token = window.localStorage.getItem(this.KEY_XSRF_TOKEN_);
3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  window.localStorage.removeItem(this.KEY_XSRF_TOKEN_);
3252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (xsrf_token == undefined || state != xsrf_token) {
3262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Invalid XSRF token, or unexpected OAuth2 redirect. Abort.
327a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    onDone();
3282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
329a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /** @param {remoting.Error} error */
330a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var onError = function(error) {
331a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    console.error('Unable to exchange code for token: ', error);
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
334a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  remoting.OAuth2Api.exchangeCodeForTokens(
335a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      this.onTokens_.bind(this, onDone), onError,
336a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      this.getClientId_(), this.getClientSecret_(), code,
337a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      this.getRedirectUri_());
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Revokes a refresh or an access token.
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string?} token An access or refresh token.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.revokeToken_ = function(token) {
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!token || (token.length == 0)) {
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
352a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  remoting.OAuth2Api.revokeToken(function() {}, function() {}, token);
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Call a function with an access token, refreshing it first if necessary.
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The access token will remain valid for at least 2 minutes.
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(string):void} onOk Function to invoke with access token if
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     an access token was successfully retrieved.
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(remoting.Error):void} onError Function to invoke with an
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     error code on failure.
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
366a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var refreshToken = this.getRefreshToken_();
367a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  if (refreshToken) {
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.needsNewAccessToken_()) {
369a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      remoting.OAuth2Api.refreshAccessToken(
370a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          this.onAccessToken_.bind(this, onOk), onError,
371a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          this.getClientId_(), this.getClientSecret_(),
372a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          refreshToken);
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      onOk(this.getAccessTokenInternal_()['token']);
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    onError(remoting.Error.NOT_AUTHENTICATED);
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Get the user's email address.
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(string):void} onOk Callback invoked when the email
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     address is available.
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(remoting.Error):void} onError Callback invoked if an
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     error occurs.
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getEmail = function(onOk, onError) {
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var cached = window.localStorage.getItem(this.KEY_EMAIL_);
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof cached == 'string') {
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    onOk(cached);
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @type {remoting.OAuth2} */
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var that = this;
398a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /** @param {string} email */
399a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var onResponse = function(email) {
400a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    window.localStorage.setItem(that.KEY_EMAIL_, email);
401a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    onOk(email);
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
404a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  this.callWithToken(
405a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      remoting.OAuth2Api.getEmail.bind(null, onResponse, onError), onError);
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * If the user's email address is cached, return it, otherwise return null.
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The email address, if it has been cached by a previous call
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     to getEmail, otherwise null.
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getCachedEmail = function() {
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var value = window.localStorage.getItem(this.KEY_EMAIL_);
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof value == 'string') {
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return value;
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return null;
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
421