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.
7 * OAuth2 API flow implementations.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
17 * @implements {remoting.OAuth2Api}
19 remoting.OAuth2ApiImpl = function() {
23 * @return {string} OAuth2 token URL.
25 remoting.OAuth2ApiImpl.prototype.getOAuth2TokenEndpoint_ = function() {
26 return remoting.settings.OAUTH2_BASE_URL + '/token';
30 * @return {string} OAuth2 userinfo API URL.
32 remoting.OAuth2ApiImpl.prototype.getOAuth2ApiUserInfoEndpoint_ = function() {
33 return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo';
38 * Interprets HTTP error responses in authentication XMLHttpRequests.
41 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
42 * @return {remoting.Error} An error code to be raised.
44 remoting.OAuth2ApiImpl.prototype.interpretXhrStatus_ =
46 // Return AUTHENTICATION_FAILED by default, so that the user can try to
47 // recover from an unexpected failure by signing in again.
48 /** @type {remoting.Error} */
49 var error = remoting.Error.AUTHENTICATION_FAILED;
50 if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) {
51 error = remoting.Error.AUTHENTICATION_FAILED;
52 } else if (xhrStatus == 502 || xhrStatus == 503) {
53 error = remoting.Error.SERVICE_UNAVAILABLE;
54 } else if (xhrStatus == 0) {
55 error = remoting.Error.NETWORK_FAILURE;
57 console.warn('Unexpected authentication response code: ' + xhrStatus);
63 * Asynchronously retrieves a new access token from the server.
65 * @param {function(string, number): void} onDone Callback to invoke when
66 * the access token and expiration time are successfully fetched.
67 * @param {function(remoting.Error):void} onError Callback invoked if an
69 * @param {string} clientId OAuth2 client ID.
70 * @param {string} clientSecret OAuth2 client secret.
71 * @param {string} refreshToken OAuth2 refresh token to be redeemed.
72 * @return {void} Nothing.
74 remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function(
75 onDone, onError, clientId, clientSecret, refreshToken) {
76 /** @param {XMLHttpRequest} xhr */
77 var onResponse = function(xhr) {
78 if (xhr.status == 200) {
80 // Don't use base.jsonParseSafe here unless you also include base.js,
81 // otherwise this won't work from the OAuth trampoline.
82 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
83 var tokens = JSON.parse(xhr.responseText);
84 onDone(tokens['access_token'], tokens['expires_in']);
85 } catch (/** @type {Error} */ err) {
86 console.error('Invalid "token" response from server:', err);
87 onError(remoting.Error.UNEXPECTED);
90 console.error('Failed to refresh token. Status: ' + xhr.status +
91 ' response: ' + xhr.responseText);
92 onError(remoting.Error.fromHttpStatus(xhr.status));
98 url: this.getOAuth2TokenEndpoint_(),
101 'client_id': clientId,
102 'client_secret': clientSecret,
103 'refresh_token': refreshToken,
104 'grant_type': 'refresh_token'
110 * Asynchronously exchanges an authorization code for access and refresh tokens.
112 * @param {function(string, string, number): void} onDone Callback to
113 * invoke when the refresh token, access token and access token expiration
114 * time are successfully fetched.
115 * @param {function(remoting.Error):void} onError Callback invoked if an
117 * @param {string} clientId OAuth2 client ID.
118 * @param {string} clientSecret OAuth2 client secret.
119 * @param {string} code OAuth2 authorization code.
120 * @param {string} redirectUri Redirect URI used to obtain this code.
121 * @return {void} Nothing.
123 remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
124 onDone, onError, clientId, clientSecret, code, redirectUri) {
125 /** @param {XMLHttpRequest} xhr */
126 var onResponse = function(xhr) {
127 if (xhr.status == 200) {
129 // Don't use base.jsonParseSafe here unless you also include base.js,
130 // otherwise this won't work from the OAuth trampoline.
131 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
132 var tokens = JSON.parse(xhr.responseText);
133 onDone(tokens['refresh_token'],
134 tokens['access_token'], tokens['expires_in']);
135 } catch (/** @type {Error} */ err) {
136 console.error('Invalid "token" response from server:', err);
137 onError(remoting.Error.UNEXPECTED);
140 console.error('Failed to exchange code for token. Status: ' + xhr.status +
141 ' response: ' + xhr.responseText);
142 onError(remoting.Error.fromHttpStatus(xhr.status));
148 url: this.getOAuth2TokenEndpoint_(),
151 'client_id': clientId,
152 'client_secret': clientSecret,
153 'redirect_uri': redirectUri,
155 'grant_type': 'authorization_code'
161 * Get the user's email address.
163 * @param {function(string):void} onDone Callback invoked when the email
164 * address is available.
165 * @param {function(remoting.Error):void} onError Callback invoked if an
167 * @param {string} token Access token.
168 * @return {void} Nothing.
170 remoting.OAuth2ApiImpl.prototype.getEmail = function(onDone, onError, token) {
171 /** @param {XMLHttpRequest} xhr */
172 var onResponse = function(xhr) {
173 if (xhr.status == 200) {
175 var result = JSON.parse(xhr.responseText);
176 onDone(result['email']);
177 } catch (/** @type {Error} */ err) {
178 console.error('Invalid "userinfo" response from server:', err);
179 onError(remoting.Error.UNEXPECTED);
182 console.error('Failed to get email. Status: ' + xhr.status +
183 ' response: ' + xhr.responseText);
184 onError(remoting.Error.fromHttpStatus(xhr.status));
189 url: this.getOAuth2ApiUserInfoEndpoint_(),
196 * Get the user's email address and full name.
198 * @param {function(string, string):void} onDone Callback invoked when the email
199 * address and full name are available.
200 * @param {function(remoting.Error):void} onError Callback invoked if an
202 * @param {string} token Access token.
203 * @return {void} Nothing.
205 remoting.OAuth2ApiImpl.prototype.getUserInfo =
206 function(onDone, onError, token) {
207 /** @param {XMLHttpRequest} xhr */
208 var onResponse = function(xhr) {
209 if (xhr.status == 200) {
211 var result = JSON.parse(xhr.responseText);
212 onDone(result['email'], result['name']);
213 } catch (/** @type {Error} */ err) {
214 console.error('Invalid "userinfo" response from server:', err);
215 onError(remoting.Error.UNEXPECTED);
218 console.error('Failed to get user info. Status: ' + xhr.status +
219 ' response: ' + xhr.responseText);
220 onError(remoting.Error.fromHttpStatus(xhr.status));
225 url: this.getOAuth2ApiUserInfoEndpoint_(),
231 /** @type {remoting.OAuth2Api} */
232 remoting.oauth2Api = new remoting.OAuth2ApiImpl();