1// Copyright 2013 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. 4 5/** 6 * @fileoverview 7 * OAuth2 API flow implementations. 8 */ 9 10'use strict'; 11 12/** @suppress {duplicate} */ 13var remoting = remoting || {}; 14 15/** @constructor */ 16remoting.OAuth2Api = function() { 17}; 18 19/** @private 20 * @return {string} OAuth2 token URL. 21 */ 22remoting.OAuth2Api.getOAuth2TokenEndpoint_ = function() { 23 return remoting.settings.OAUTH2_BASE_URL + '/token'; 24}; 25 26/** @private 27 * @return {string} OAuth2 userinfo API URL. 28 */ 29remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_ = function() { 30 return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo'; 31}; 32 33 34/** 35 * Interprets HTTP error responses in authentication XMLHttpRequests. 36 * 37 * @private 38 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest. 39 * @return {remoting.Error} An error code to be raised. 40 */ 41remoting.OAuth2Api.interpretXhrStatus_ = 42 function(xhrStatus) { 43 // Return AUTHENTICATION_FAILED by default, so that the user can try to 44 // recover from an unexpected failure by signing in again. 45 /** @type {remoting.Error} */ 46 var error = remoting.Error.AUTHENTICATION_FAILED; 47 if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) { 48 error = remoting.Error.AUTHENTICATION_FAILED; 49 } else if (xhrStatus == 502 || xhrStatus == 503) { 50 error = remoting.Error.SERVICE_UNAVAILABLE; 51 } else if (xhrStatus == 0) { 52 error = remoting.Error.NETWORK_FAILURE; 53 } else { 54 console.warn('Unexpected authentication response code: ' + xhrStatus); 55 } 56 return error; 57}; 58 59/** 60 * Asynchronously retrieves a new access token from the server. 61 * 62 * @param {function(string, number): void} onDone Callback to invoke when 63 * the access token and expiration time are successfully fetched. 64 * @param {function(remoting.Error):void} onError Callback invoked if an 65 * error occurs. 66 * @param {string} clientId OAuth2 client ID. 67 * @param {string} clientSecret OAuth2 client secret. 68 * @param {string} refreshToken OAuth2 refresh token to be redeemed. 69 * @return {void} Nothing. 70 */ 71remoting.OAuth2Api.refreshAccessToken = function( 72 onDone, onError, clientId, clientSecret, refreshToken) { 73 /** @param {XMLHttpRequest} xhr */ 74 var onResponse = function(xhr) { 75 if (xhr.status == 200) { 76 try { 77 // Don't use jsonParseSafe here unless you move the definition out of 78 // remoting.js, otherwise this won't work from the OAuth trampoline. 79 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. 80 var tokens = JSON.parse(xhr.responseText); 81 onDone(tokens['access_token'], tokens['expires_in']); 82 } catch (err) { 83 console.error('Invalid "token" response from server:', 84 /** @type {*} */ (err)); 85 onError(remoting.Error.UNEXPECTED); 86 } 87 } else { 88 console.error('Failed to refresh token. Status: ' + xhr.status + 89 ' response: ' + xhr.responseText); 90 onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); 91 } 92 }; 93 94 var parameters = { 95 'client_id': clientId, 96 'client_secret': clientSecret, 97 'refresh_token': refreshToken, 98 'grant_type': 'refresh_token' 99 }; 100 101 remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(), 102 onResponse, parameters); 103}; 104 105/** 106 * Asynchronously exchanges an authorization code for access and refresh tokens. 107 * 108 * @param {function(string, string, number): void} onDone Callback to 109 * invoke when the refresh token, access token and access token expiration 110 * time are successfully fetched. 111 * @param {function(remoting.Error):void} onError Callback invoked if an 112 * error occurs. 113 * @param {string} clientId OAuth2 client ID. 114 * @param {string} clientSecret OAuth2 client secret. 115 * @param {string} code OAuth2 authorization code. 116 * @param {string} redirectUri Redirect URI used to obtain this code. 117 * @return {void} Nothing. 118 */ 119remoting.OAuth2Api.exchangeCodeForTokens = function( 120 onDone, onError, clientId, clientSecret, code, redirectUri) { 121 /** @param {XMLHttpRequest} xhr */ 122 var onResponse = function(xhr) { 123 if (xhr.status == 200) { 124 try { 125 // Don't use jsonParseSafe here unless you move the definition out of 126 // remoting.js, otherwise this won't work from the OAuth trampoline. 127 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. 128 var tokens = JSON.parse(xhr.responseText); 129 onDone(tokens['refresh_token'], 130 tokens['access_token'], tokens['expires_in']); 131 } catch (err) { 132 console.error('Invalid "token" response from server:', 133 /** @type {*} */ (err)); 134 onError(remoting.Error.UNEXPECTED); 135 } 136 } else { 137 console.error('Failed to exchange code for token. Status: ' + xhr.status + 138 ' response: ' + xhr.responseText); 139 onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); 140 } 141 }; 142 143 var parameters = { 144 'client_id': clientId, 145 'client_secret': clientSecret, 146 'redirect_uri': redirectUri, 147 'code': code, 148 'grant_type': 'authorization_code' 149 }; 150 remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(), 151 onResponse, parameters); 152}; 153 154/** 155 * Get the user's email address. 156 * 157 * @param {function(string):void} onDone Callback invoked when the email 158 * address is available. 159 * @param {function(remoting.Error):void} onError Callback invoked if an 160 * error occurs. 161 * @param {string} token Access token. 162 * @return {void} Nothing. 163 */ 164remoting.OAuth2Api.getEmail = function(onDone, onError, token) { 165 /** @param {XMLHttpRequest} xhr */ 166 var onResponse = function(xhr) { 167 if (xhr.status == 200) { 168 try { 169 var result = JSON.parse(xhr.responseText); 170 onDone(result['email']); 171 } catch (err) { 172 console.error('Invalid "userinfo" response from server:', 173 /** @type {*} */ (err)); 174 onError(remoting.Error.UNEXPECTED); 175 } 176 } else { 177 console.error('Failed to get email. Status: ' + xhr.status + 178 ' response: ' + xhr.responseText); 179 onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); 180 } 181 }; 182 var headers = { 'Authorization': 'OAuth ' + token }; 183 remoting.xhr.get(remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_(), 184 onResponse, '', headers); 185}; 186