Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: remoting/webapp/me2mom/oauth2.js

Issue 8336004: Improve web-app type safety. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * OAuth2 class that handles retrieval/storage of an OAuth2 token. 7 * OAuth2 class that handles retrieval/storage of an OAuth2 token.
8 * 8 *
9 * Uses a content script to trampoline the OAuth redirect page back into the 9 * Uses a content script to trampoline the OAuth redirect page back into the
10 * extension context. This works around the lack of native support for 10 * extension context. This works around the lack of native support for
11 * chrome-extensions in OAuth2. 11 * chrome-extensions in OAuth2.
12 */ 12 */
13 13
14 'use strict'; 14 'use strict';
15 15
16 /** @suppress {duplicate} */ 16 /** @suppress {duplicate} */
17 var remoting = remoting || {}; 17 var remoting = remoting || {};
18 18
19 (function() { 19 /** @type {remoting.OAuth2} */
20 remoting.oauth2 = null;
21
22
20 /** @constructor */ 23 /** @constructor */
21 remoting.OAuth2 = function() { 24 remoting.OAuth2 = function() {
22 } 25 };
23 26
24 // Constants representing keys used for storing persistent state. 27 // Constants representing keys used for storing persistent state.
28 /** @private */
25 remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token'; 29 remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
30 /** @private */
26 remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token'; 31 remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
27 32
28 // Constants for parameters used in retrieving the OAuth2 credentials. 33 // Constants for parameters used in retrieving the OAuth2 credentials.
29 remoting.OAuth2.prototype.CLIENT_ID_ = 34 /** @private */ remoting.OAuth2.prototype.CLIENT_ID_ =
30 '440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' + 35 '440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' +
31 'apps.googleusercontent.com'; 36 'apps.googleusercontent.com';
37 /** @private */
32 remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_'; 38 remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_';
33 remoting.OAuth2.prototype.SCOPE_ = 39 /** @private */ remoting.OAuth2.prototype.SCOPE_ =
34 'https://www.googleapis.com/auth/chromoting ' + 40 'https://www.googleapis.com/auth/chromoting ' +
35 'https://www.googleapis.com/auth/googletalk ' + 41 'https://www.googleapis.com/auth/googletalk ' +
36 'https://www.googleapis.com/auth/userinfo#email'; 42 'https://www.googleapis.com/auth/userinfo#email';
37 remoting.OAuth2.prototype.REDIRECT_URI_ = 43 /** @private */ remoting.OAuth2.prototype.REDIRECT_URI_ =
38 'https://talkgadget.google.com/talkgadget/blank'; 44 'https://talkgadget.google.com/talkgadget/blank';
39 remoting.OAuth2.prototype.OAUTH2_TOKEN_ENDPOINT_ = 45 /** @private */ remoting.OAuth2.prototype.OAUTH2_TOKEN_ENDPOINT_ =
40 'https://accounts.google.com/o/oauth2/token'; 46 'https://accounts.google.com/o/oauth2/token';
41 47
42 /** @return {boolean} True if the app is already authenticated. */ 48 /** @return {boolean} True if the app is already authenticated. */
43 remoting.OAuth2.prototype.isAuthenticated = function() { 49 remoting.OAuth2.prototype.isAuthenticated = function() {
44 if (this.getRefreshToken()) { 50 if (this.getRefreshToken()) {
45 return true; 51 return true;
46 } 52 }
47 return false; 53 return false;
48 } 54 };
49 55
50 /** 56 /**
51 * Removes all storage, and effectively unauthenticates the user. 57 * Removes all storage, and effectively unauthenticates the user.
52 * 58 *
53 * @return {void} Nothing. 59 * @return {void} Nothing.
54 */ 60 */
55 remoting.OAuth2.prototype.clear = function() { 61 remoting.OAuth2.prototype.clear = function() {
56 window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_); 62 window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
57 this.clearAccessToken(); 63 this.clearAccessToken();
58 } 64 };
59 65
60 /** 66 /**
61 * @param {string} token The new refresh token. 67 * @param {string} token The new refresh token.
62 * @return {void} Nothing. 68 * @return {void} Nothing.
63 */ 69 */
64 remoting.OAuth2.prototype.setRefreshToken = function(token) { 70 remoting.OAuth2.prototype.setRefreshToken = function(token) {
65 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token)); 71 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
66 this.clearAccessToken(); 72 this.clearAccessToken();
67 } 73 };
68 74
69 /** @return {?string} The refresh token, if authenticated, or NULL. */ 75 /** @return {?string} The refresh token, if authenticated, or NULL. */
70 remoting.OAuth2.prototype.getRefreshToken = function() { 76 remoting.OAuth2.prototype.getRefreshToken = function() {
71 var value = window.localStorage.getItem(this.KEY_REFRESH_TOKEN_); 77 var value = window.localStorage.getItem(this.KEY_REFRESH_TOKEN_);
72 if (typeof value == 'string') { 78 if (typeof value == 'string') {
73 return unescape(value); 79 return unescape(value);
74 } 80 }
75 return null; 81 return null;
76 } 82 };
77 83
78 /** 84 /**
79 * @param {string} token The new access token. 85 * @param {string} token The new access token.
80 * @param {number} expiration Expiration time in milliseconds since epoch. 86 * @param {number} expiration Expiration time in milliseconds since epoch.
81 * @return {void} Nothing. 87 * @return {void} Nothing.
82 */ 88 */
83 remoting.OAuth2.prototype.setAccessToken = function(token, expiration) { 89 remoting.OAuth2.prototype.setAccessToken = function(token, expiration) {
84 var access_token = {'token': token, 'expiration': expiration}; 90 var access_token = {'token': token, 'expiration': expiration};
85 window.localStorage.setItem(this.KEY_ACCESS_TOKEN_, 91 window.localStorage.setItem(this.KEY_ACCESS_TOKEN_,
86 JSON.stringify(access_token)); 92 JSON.stringify(access_token));
87 } 93 };
88 94
89 /** 95 /**
90 * Returns the current access token, setting it to a invalid value if none 96 * Returns the current access token, setting it to a invalid value if none
91 * existed before. 97 * existed before.
92 * 98 *
99 * @private
93 * @return {{token: string, expiration: number}} The current access token, or 100 * @return {{token: string, expiration: number}} The current access token, or
94 * an invalid token if not authenticated. 101 * an invalid token if not authenticated.
95 */ 102 */
96 remoting.OAuth2.prototype.getAccessTokenInternal_ = function() { 103 remoting.OAuth2.prototype.getAccessTokenInternal_ = function() {
97 if (!window.localStorage.getItem(this.KEY_ACCESS_TOKEN_)) { 104 if (!window.localStorage.getItem(this.KEY_ACCESS_TOKEN_)) {
98 // Always be able to return structured data. 105 // Always be able to return structured data.
99 this.setAccessToken('', 0); 106 this.setAccessToken('', 0);
100 } 107 }
101 var accessToken = window.localStorage.getItem(this.KEY_ACCESS_TOKEN_); 108 var accessToken = window.localStorage.getItem(this.KEY_ACCESS_TOKEN_);
102 if (typeof accessToken == 'string') { 109 if (typeof accessToken == 'string') {
103 var result = JSON.parse(accessToken); 110 var result = JSON.parse(accessToken);
104 if ('token' in result && 'expiration' in result) { 111 if ('token' in result && 'expiration' in result) {
105 return /** @type {{token: string, expiration: number}} */ result; 112 return /** @type {{token: string, expiration: number}} */ result;
106 } 113 }
107 } 114 }
108 console.log('Invalid access token stored.'); 115 console.log('Invalid access token stored.');
109 return {'token': '', 'expiration': 0}; 116 return {'token': '', 'expiration': 0};
110 } 117 };
111 118
112 /** 119 /**
113 * Returns true if the access token is expired, or otherwise invalid. 120 * Returns true if the access token is expired, or otherwise invalid.
114 * 121 *
115 * Will throw if !isAuthenticated(). 122 * Will throw if !isAuthenticated().
116 * 123 *
117 * @return {boolean} True if a new access token is needed. 124 * @return {boolean} True if a new access token is needed.
118 */ 125 */
119 remoting.OAuth2.prototype.needsNewAccessToken = function() { 126 remoting.OAuth2.prototype.needsNewAccessToken = function() {
120 if (!this.isAuthenticated()) { 127 if (!this.isAuthenticated()) {
121 throw 'Not Authenticated.'; 128 throw 'Not Authenticated.';
122 } 129 }
123 var access_token = this.getAccessTokenInternal_(); 130 var access_token = this.getAccessTokenInternal_();
124 if (!access_token['token']) { 131 if (!access_token['token']) {
125 return true; 132 return true;
126 } 133 }
127 if (Date.now() > access_token['expiration']) { 134 if (Date.now() > access_token['expiration']) {
128 return true; 135 return true;
129 } 136 }
130 return false; 137 return false;
131 } 138 };
132 139
133 /** 140 /**
134 * Returns the current access token. 141 * Returns the current access token.
135 * 142 *
136 * Will throw if !isAuthenticated() or needsNewAccessToken(). 143 * Will throw if !isAuthenticated() or needsNewAccessToken().
137 * 144 *
138 * @return {{token: string, expiration: number}} 145 * @return {string} The access token.
139 */ 146 */
140 remoting.OAuth2.prototype.getAccessToken = function() { 147 remoting.OAuth2.prototype.getAccessToken = function() {
141 if (this.needsNewAccessToken()) { 148 if (this.needsNewAccessToken()) {
142 throw 'Access Token expired.'; 149 throw 'Access Token expired.';
143 } 150 }
144 return this.getAccessTokenInternal_()['token']; 151 return this.getAccessTokenInternal_()['token'];
145 } 152 };
146 153
147 /** @return {void} Nothing. */ 154 /** @return {void} Nothing. */
148 remoting.OAuth2.prototype.clearAccessToken = function() { 155 remoting.OAuth2.prototype.clearAccessToken = function() {
149 window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_); 156 window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
150 } 157 };
151 158
152 /** 159 /**
153 * Update state based on token response from the OAuth2 /token endpoint. 160 * Update state based on token response from the OAuth2 /token endpoint.
154 * 161 *
162 * @private
155 * @param {XMLHttpRequest} xhr The XHR object for this request. 163 * @param {XMLHttpRequest} xhr The XHR object for this request.
156 * @return {void} Nothing. 164 * @return {void} Nothing.
157 */ 165 */
158 remoting.OAuth2.prototype.processTokenResponse_ = function(xhr) { 166 remoting.OAuth2.prototype.processTokenResponse_ = function(xhr) {
159 if (xhr.status == 200) { 167 if (xhr.status == 200) {
160 var tokens = JSON.parse(xhr.responseText); 168 var tokens = JSON.parse(xhr.responseText);
161 if ('refresh_token' in tokens) { 169 if ('refresh_token' in tokens) {
162 this.setRefreshToken(tokens['refresh_token']); 170 this.setRefreshToken(tokens['refresh_token']);
163 } 171 }
164 172
165 // Offset by 120 seconds so that we can guarantee that the token 173 // Offset by 120 seconds so that we can guarantee that the token
166 // we return will be valid for at least 2 minutes. 174 // we return will be valid for at least 2 minutes.
167 // If the access token is to be useful, this object must make some 175 // If the access token is to be useful, this object must make some
168 // guarantee as to how long the token will be valid for. 176 // guarantee as to how long the token will be valid for.
169 // The choice of 2 minutes is arbitrary, but that length of time 177 // The choice of 2 minutes is arbitrary, but that length of time
170 // is part of the contract satisfied by callWithToken(). 178 // is part of the contract satisfied by callWithToken().
171 // Offset by a further 30 seconds to account for RTT issues. 179 // Offset by a further 30 seconds to account for RTT issues.
172 this.setAccessToken(tokens['access_token'], 180 this.setAccessToken(tokens['access_token'],
173 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now()); 181 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now());
174 } else { 182 } else {
175 console.log('Failed to get tokens. Status: ' + xhr.status + 183 console.log('Failed to get tokens. Status: ' + xhr.status +
176 ' response: ' + xhr.responseText); 184 ' response: ' + xhr.responseText);
177 } 185 }
178 } 186 };
179 187
180 /** 188 /**
181 * Asynchronously retrieves a new access token from the server. 189 * Asynchronously retrieves a new access token from the server.
182 * 190 *
183 * Will throw if !isAuthenticated(). 191 * Will throw if !isAuthenticated().
184 * 192 *
185 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on 193 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on
186 * completion. 194 * completion.
187 * @return {void} Nothing. 195 * @return {void} Nothing.
188 */ 196 */
189 remoting.OAuth2.prototype.refreshAccessToken = function(onDone) { 197 remoting.OAuth2.prototype.refreshAccessToken = function(onDone) {
190 if (!this.isAuthenticated()) { 198 if (!this.isAuthenticated()) {
191 throw 'Not Authenticated.'; 199 throw 'Not Authenticated.';
192 } 200 }
193 201
194 var parameters = { 202 var parameters = {
195 'client_id': this.CLIENT_ID_, 203 'client_id': this.CLIENT_ID_,
196 'client_secret': this.CLIENT_SECRET_, 204 'client_secret': this.CLIENT_SECRET_,
197 'refresh_token': this.getRefreshToken(), 205 'refresh_token': this.getRefreshToken(),
198 'grant_type': 'refresh_token' 206 'grant_type': 'refresh_token'
199 }; 207 };
200 208
209 /** @type {remoting.OAuth2} */
201 var that = this; 210 var that = this;
211 /** @param {XMLHttpRequest} xhr The XHR reply. */
212 var processTokenResponse = function(xhr) {
213 that.processTokenResponse_(xhr);
214 onDone(xhr);
215 };
202 remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_, 216 remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
203 function(xhr) { 217 processTokenResponse,
204 that.processTokenResponse_(xhr);
205 onDone(xhr);
206 },
207 parameters); 218 parameters);
208 } 219 };
209 220
210 /** 221 /**
211 * Redirect page to get a new OAuth2 Refresh Token. 222 * Redirect page to get a new OAuth2 Refresh Token.
212 * 223 *
213 * @return {void} Nothing. 224 * @return {void} Nothing.
214 */ 225 */
215 remoting.OAuth2.prototype.doAuthRedirect = function() { 226 remoting.OAuth2.prototype.doAuthRedirect = function() {
216 var GET_CODE_URL = 'https://accounts.google.com/o/oauth2/auth?' + 227 var GET_CODE_URL = 'https://accounts.google.com/o/oauth2/auth?' +
217 remoting.xhr.urlencodeParamHash({ 228 remoting.xhr.urlencodeParamHash({
218 'client_id': this.CLIENT_ID_, 229 'client_id': this.CLIENT_ID_,
219 'redirect_uri': this.REDIRECT_URI_, 230 'redirect_uri': this.REDIRECT_URI_,
220 'scope': this.SCOPE_, 231 'scope': this.SCOPE_,
221 'response_type': 'code' 232 'response_type': 'code'
222 }); 233 });
223 window.location.replace(GET_CODE_URL); 234 window.location.replace(GET_CODE_URL);
224 } 235 };
225 236
226 /** 237 /**
227 * Asynchronously exchanges an authorization code for a refresh token. 238 * Asynchronously exchanges an authorization code for a refresh token.
228 * 239 *
229 * @param {string} code The new refresh token. 240 * @param {string} code The new refresh token.
230 * @param {function(XMLHttpRequest):void} onDone Callback to invoke on 241 * @param {function(XMLHttpRequest):void} onDone Callback to invoke on
231 * completion. 242 * completion.
232 * @return {void} Nothing. 243 * @return {void} Nothing.
233 */ 244 */
234 remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) { 245 remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) {
235 var parameters = { 246 var parameters = {
236 'client_id': this.CLIENT_ID_, 247 'client_id': this.CLIENT_ID_,
237 'client_secret': this.CLIENT_SECRET_, 248 'client_secret': this.CLIENT_SECRET_,
238 'redirect_uri': this.REDIRECT_URI_, 249 'redirect_uri': this.REDIRECT_URI_,
239 'code': code, 250 'code': code,
240 'grant_type': 'authorization_code' 251 'grant_type': 'authorization_code'
241 }; 252 };
242 253
254 /** @type {remoting.OAuth2} */
243 var that = this; 255 var that = this;
256 /** @param {XMLHttpRequest} xhr The XHR reply. */
257 var processTokenResponse = function(xhr) {
258 that.processTokenResponse_(xhr);
259 onDone(xhr);
260 };
244 remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_, 261 remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
245 function(xhr) { 262 processTokenResponse,
246 that.processTokenResponse_(xhr);
247 onDone(xhr);
248 },
249 parameters); 263 parameters);
250 } 264 };
251 265
252 /** 266 /**
253 * Call myfunc with an access token as the only parameter. 267 * Call myfunc with an access token as the only parameter.
254 * 268 *
255 * This will refresh the access token if necessary. If the access token 269 * This will refresh the access token if necessary. If the access token
256 * cannot be refreshed, an error is thrown. 270 * cannot be refreshed, an error is thrown.
257 * 271 *
258 * The access token will remain valid for at least 2 minutes. 272 * The access token will remain valid for at least 2 minutes.
259 * 273 *
260 * @param {function({token: string, expiration: number}):void} myfunc 274 * @param {function(string):void} myfunc
261 * Function to invoke with access token. 275 * Function to invoke with access token.
262 * @return {void} Nothing. 276 * @return {void} Nothing.
263 */ 277 */
264 remoting.OAuth2.prototype.callWithToken = function(myfunc) { 278 remoting.OAuth2.prototype.callWithToken = function(myfunc) {
279 /** @type {remoting.OAuth2} */
265 var that = this; 280 var that = this;
266 if (remoting.oauth2.needsNewAccessToken()) { 281 if (remoting.oauth2.needsNewAccessToken()) {
267 remoting.oauth2.refreshAccessToken(function() { 282 remoting.oauth2.refreshAccessToken(function() {
268 if (remoting.oauth2.needsNewAccessToken()) { 283 if (remoting.oauth2.needsNewAccessToken()) {
269 // If we still need it, we're going to infinite loop. 284 // If we still need it, we're going to infinite loop.
270 throw 'Unable to get access token.'; 285 throw 'Unable to get access token.';
271 } 286 }
272 myfunc(that.getAccessToken()); 287 myfunc(that.getAccessToken());
273 }); 288 });
274 return; 289 return;
275 } 290 }
276 291
277 myfunc(this.getAccessToken()); 292 myfunc(this.getAccessToken());
278 } 293 };
279 }());
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698