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.
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
19 * @implements {remoting.OAuth2Api}
21 remoting.OAuth2ApiImpl = function() {
25 * @return {string} OAuth2 token URL.
27 remoting.OAuth2ApiImpl.prototype.getOAuth2TokenEndpoint_ = function() {
28 return remoting.settings.OAUTH2_BASE_URL + '/token';
32 * @return {string} OAuth2 userinfo API URL.
34 remoting.OAuth2ApiImpl.prototype.getOAuth2ApiUserInfoEndpoint_ = function() {
35 return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo';
40 * Interprets HTTP error responses in authentication XMLHttpRequests.
43 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
44 * @return {!remoting.Error} An error code to be raised.
46 remoting.OAuth2ApiImpl.prototype.interpretXhrStatus_ =
48 // Return AUTHENTICATION_FAILED by default, so that the user can try to
49 // recover from an unexpected failure by signing in again.
50 /** @type {!remoting.Error} */
51 var error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
52 if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) {
53 error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
54 } else if (xhrStatus == 502 || xhrStatus == 503) {
55 error = new remoting.Error(remoting.Error.Tag.SERVICE_UNAVAILABLE);
56 } else if (xhrStatus == 0) {
57 error = new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE);
59 console.warn('Unexpected authentication response code: ' + xhrStatus);
65 * Asynchronously retrieves a new access token from the server.
67 * @param {function(string, number): void} onDone Callback to invoke when
68 * the access token and expiration time are successfully fetched.
69 * @param {function(!remoting.Error):void} onError Callback invoked if an
71 * @param {string} clientId OAuth2 client ID.
72 * @param {string} clientSecret OAuth2 client secret.
73 * @param {string} refreshToken OAuth2 refresh token to be redeemed.
74 * @return {void} Nothing.
76 remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function(
77 onDone, onError, clientId, clientSecret, refreshToken) {
78 /** @param {!remoting.Xhr.Response} response */
79 var onResponse = function(response) {
80 if (response.status == 200) {
82 // Don't use base.jsonParseSafe here unless you also include base.js,
83 // otherwise this won't work from the OAuth trampoline.
84 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
85 var tokens = JSON.parse(response.getText());
86 onDone(tokens['access_token'], tokens['expires_in']);
87 } catch (/** @type {Error} */ err) {
88 console.error('Invalid "token" response from server:', err);
89 onError(remoting.Error.unexpected());
92 console.error('Failed to refresh token. Status: ' + response.status +
93 ' response: ' + response.getText());
94 onError(remoting.Error.fromHttpStatus(response.status));
100 url: this.getOAuth2TokenEndpoint_(),
102 'client_id': clientId,
103 'client_secret': clientSecret,
104 'refresh_token': refreshToken,
105 'grant_type': 'refresh_token'
107 }).start().then(onResponse);
111 * Asynchronously exchanges an authorization code for access and refresh tokens.
113 * @param {function(string, string, number): void} onDone Callback to
114 * invoke when the refresh token, access token and access token expiration
115 * time are successfully fetched.
116 * @param {function(!remoting.Error):void} onError Callback invoked if an
118 * @param {string} clientId OAuth2 client ID.
119 * @param {string} clientSecret OAuth2 client secret.
120 * @param {string} code OAuth2 authorization code.
121 * @param {string} redirectUri Redirect URI used to obtain this code.
122 * @return {void} Nothing.
124 remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
125 onDone, onError, clientId, clientSecret, code, redirectUri) {
126 /** @param {!remoting.Xhr.Response} response */
127 var onResponse = function(response) {
128 if (response.status == 200) {
130 // Don't use base.jsonParseSafe here unless you also include base.js,
131 // otherwise this won't work from the OAuth trampoline.
132 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
133 var tokens = JSON.parse(response.getText());
134 onDone(tokens['refresh_token'],
135 tokens['access_token'], tokens['expires_in']);
136 } catch (/** @type {Error} */ err) {
137 console.error('Invalid "token" response from server:', err);
138 onError(remoting.Error.unexpected());
141 console.error('Failed to exchange code for token. Status: ' +
142 response.status + ' response: ' + response.getText());
143 onError(remoting.Error.fromHttpStatus(response.status));
149 url: this.getOAuth2TokenEndpoint_(),
151 'client_id': clientId,
152 'client_secret': clientSecret,
153 'redirect_uri': redirectUri,
155 'grant_type': 'authorization_code'
157 }).start().then(onResponse);
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 {!remoting.Xhr.Response} response */
172 var onResponse = function(response) {
173 if (response.status == 200) {
175 var result = JSON.parse(response.getText());
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: ' + response.status +
183 ' response: ' + response.getText());
184 onError(remoting.Error.fromHttpStatus(response.status));
189 url: this.getOAuth2ApiUserInfoEndpoint_(),
191 }).start().then(onResponse);
195 * Get the user's email address and full name.
197 * @param {function(string, string):void} onDone Callback invoked when the email
198 * address and full name are available.
199 * @param {function(!remoting.Error):void} onError Callback invoked if an
201 * @param {string} token Access token.
202 * @return {void} Nothing.
204 remoting.OAuth2ApiImpl.prototype.getUserInfo =
205 function(onDone, onError, token) {
206 /** @param {!remoting.Xhr.Response} response */
207 var onResponse = function(response) {
208 if (response.status == 200) {
210 var result = JSON.parse(response.getText());
211 onDone(result['email'], result['name']);
212 } catch (/** @type {Error} */ err) {
213 console.error('Invalid "userinfo" response from server:', err);
214 onError(remoting.Error.unexpected());
217 console.error('Failed to get user info. Status: ' + response.status +
218 ' response: ' + response.getText());
219 onError(remoting.Error.fromHttpStatus(response.status));
224 url: this.getOAuth2ApiUserInfoEndpoint_(),
226 }).start().then(onResponse);
229 /** @returns {!remoting.Error} */
230 function fromHttpStatus(/** number */ status) {
231 var error = remoting.Error.fromHttpStatus(status);
232 if (error === remoting.Error.unexpected()) {
233 // Return AUTHENTICATION_FAILED by default, so that the user can try to
234 // recover from an unexpected failure by signing in again.
235 return new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
240 /** @type {remoting.OAuth2Api} */
241 remoting.oauth2Api = new remoting.OAuth2ApiImpl();