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();