OSDN Git Service

Version 0.6.150, fix X.Net.
[pettanr/clientJs.git] / 0.6.x / js / 06_net / 10_XOAuth2.js
1
2 var X_NET_OAUTH2_detection      = new Function( 'w', 'try{return w.location.search}catch(e){}' ),
3         X_NET_OAUTH2_authorizationWindow,
4         X_NET_OAUTH2_authorizationTimerID;
5
6 /**
7  * イベント
8  * <dl>
9  * <dt>X.Event.NEED_AUTH<dd>window を popup して認可を行う必要あり。ポインターイベント内で oauth2.requestAuth() を呼ぶ。
10  * <dt>X.Event.CANCELED<dd>認可 window が閉じられた。([x]等でウインドウが閉じられた、oauth2.cancelAuth() が呼ばれた)
11  * <dt>X.Event.SUCCESS<dd>認可 window でユーザーが認可し、続いてコードの認可が済んだ。
12  * <dt>X.Event.ERROR<dd>コードの認可のエラー、リフレッシュトークンのエラー、ネットワークエラー
13  * <dt>X.Event.PROGRESS<dd>コードを window から受け取った、リフレッシュトークンの開始、コードの認可を header -> params に切替
14  * </dl>
15  * 
16  * OAuth2 state
17  * <dl>
18  * <dt>0 : <dd>disconnected
19  * <dt>1 : <dd>now authentication ...
20  * <dt>+ : <dd>authorization_code
21  * <dt>2 : <dd>refresh_token
22  * <dt>3 : <dd>hasAccessToken
23  * </dl>
24  */
25 X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ](
26                 'X.OAuth2',
27                 X_Class.NONE,
28                 
29                 /** @lends OAuth2.prototype */
30                 {
31                         'Constructor' : function( obj ){
32                                 
33                                 X_Pair_create( this, obj = X_Object_clone( obj ) );
34                                 
35                                 if( _getAccessToken( this ) ){
36                                         obj.oauth2State = 3;
37                                 } else {
38                                         this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
39                                 }
40                                 
41                                 obj.onAuthError   = X_NET_OAUTH2_onXHR401Error;
42                                 obj.updateRequest = X_NET_OAUTH2_updateRequest;
43                                 
44                                 // TODO kill の cancel
45                         },
46
47                         'authState' : function(){
48                                 return X_Pair_get( this ).oauth2State || 0;
49                         },
50                         
51                         'requestAuth' : function(){
52                                 // pointer event 内で呼ぶこと
53                                 // 二つ以上の popup を作らない
54                                 if( X_NET_OAUTH2_authorizationWindow ) return;
55                                 
56                                 pair = X_Pair_get( this );
57                                 
58                                 if( pair.net || pair.oauth2State ) return;
59                                 
60                                 X_NET_OAUTH2_authorizationWindow = window.open(
61                                         pair[ 'authorizeEndpoint' ] + '?' + X_URL_objToParam(
62                                                 {
63                                                         'response_type' : 'code',
64                                                         'client_id'     : pair[ 'clientID' ],
65                                                         'redirect_uri'  : tpair[ 'redirectURI' ],
66                                                         'scope'         : ( pair[ 'scopes' ] || []).join(' ')
67                                                 }
68                                         ), 'oauthauthorize',
69                                         'width=' + pair[ 'authorizeWindowWidth' ]
70                                         + ',height=' + pair[ 'authorizeWindowHeight' ]
71                                         + ',left=' + (screen.width  - pair[ 'authorizeWindowWidth'  ] ) / 2
72                                         + ',top='  + (screen.height - pair[ 'authorizeWindowHeight' ] ) / 2
73                                         + ',menubar=no,toolbar=no');
74                                 
75                                 X_NET_OAUTH2_authorizationTimerID = X_Timer_add( 333, 0, this, X_Net_OAuth2_detectAuthPopup );
76                                 
77                                 pair.oauth2State = 1;
78                                 
79                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to auth.' } );
80                         },
81                         
82                         'cancelAuth' : function(){
83                                 pair = X_Pair_get( this );
84                                 
85                                 if( pair.net ){
86                                         pair.net[ 'kill' ]();
87                                         delete pair.net;
88                                 };
89                                 
90                                 X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.close();
91                                 X_NET_OAUTH2_authorizationWindow  = null;
92                                 
93                                 X_NET_OAUTH2_authorizationTimerID && X_Timer_remove( X_NET_OAUTH2_authorizationTimerID );
94                                 X_NET_OAUTH2_authorizationTimerID = 0;
95                                 
96                                 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
97                         },
98                         
99                         'refreshToken' : function(){
100                                 /*
101                                  *                              var expires_at = this._getAccessTokenExpiry();
102                                 if (expires_at && Date.now() + millis > expires_at)
103                                         this._refreshAccessToken({replay: false});
104                                  */
105                                 
106                                 pair = X_Pair_get( this );
107                                 
108                                 if( pair.net ) return;
109                                 
110                                 pair.oauth2State = 2;
111                                 
112                                 pair.net = X.Net( {
113                                         'xhr'      : pair[ 'tokenEndpoint' ],
114                                         'postdata' : X_URL_objToParam({
115                                                 'client_id'     : pair[ 'clientID' ],
116                                                 'client_secret' : pair[ 'clientSecret' ],
117                                                 'grant_type'    : 'refresh_token',
118                                                 'refresh_token' : _getRefreshToken( this )
119                                         }),
120                                         'dataType' : 'json',
121                                         'headers' : {
122                                                 'Accept'       : 'application/json',
123                                                 'Content-Type' : 'application/x-www-form-urlencoded'
124                                         }
125                                 } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], this, X_Net_OAuth2_responceHandler );
126                                 
127                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to refresh token.' } );
128                         }
129                 }
130         );
131
132 function X_Net_OAuth2_detectAuthPopup(){
133         var closed, search, pair;
134         
135         if( window.frames[ 'oauthauthorize' ] !== X_NET_OAUTH2_authorizationWindow || X_NET_OAUTH2_authorizationWindow.closed ){
136                 pair.oauth2State = 0;
137                 closed = true;
138                 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
139         } else
140         if( search = X_NET_OAUTH2_detection( X_NET_OAUTH2_authorizationWindow ) ){
141                 pair      = X_Pair_get( this );
142                 pair.code = X_URL_ParamToObj( search.slice( 1 ) )[ 'code' ];
143
144                 X_NET_OAUTH2_authorizationWindow.close();
145                 closed = true;
146
147                 X_Net_OAuth2_authorizationCode( this, pair );
148                 
149                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Get code success, then authorization code.' } );
150         };
151         
152         if( closed ){
153                 X_NET_OAUTH2_authorizationWindow  = null;
154                 X_NET_OAUTH2_authorizationTimerID = 0;
155                 
156                 return X_Callback_UN_LISTEN;    
157         };
158 };
159
160 function X_Net_OAuth2_authorizationCode( oauth2, pair ){        
161         pair.net = X.Net( {
162                 'xhr'      : pair[ 'tokenEndpoint' ],
163                 'postdata' : X_URL_objToParam({
164                         'client_id'     : pair[ 'clientID' ],
165                         'client_secret' : pair[ 'clientSecret' ],
166                         'grant_type'    : 'authorization_code',
167                         'code'          : pair.code,
168                         'redirect_uri'  : pair[ 'redirectURI' ]
169                 }),
170                 'dataType' : 'json',
171                 'headers' : {
172                         'Accept'       : 'application/json',
173                         'Content-Type' : 'application/x-www-form-urlencoded'
174                 }
175         } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], oauth2, X_Net_OAuth2_responceHandler );
176 };
177
178 function X_Net_OAuth2_responceHandler( e ){
179         var data = e.data,
180                 pair = X_Pair_get( this ),
181                 isRefresh = pair.oauth2State === 2;
182         
183         delete pair.net;
184         
185         switch( e.type ){
186                 case X_EVENT_SUCCESS :
187                         if( isRefresh && data.error ){
188                                 _removeRefreshToken( this );
189                                 pair.oauth2State = 0;
190                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' + data.error, data : data } );
191                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
192                                 return;
193                         } else
194                         if( data.error ){
195                                 pair.oauth2State = 0;
196                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Get new access token error.' + data.error, data : data } );
197                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
198                                 return;
199                         };
200                         
201                         _setAccessToken( this, data[ 'access_token' ] || '' );
202                         _setRefreshToken( this, data[ 'refresh_token' ] || '' );
203                         
204                         if( data[ 'expires_in' ] ){
205                                 _setAccessTokenExpiry( this, X_Timer_now() + data[ 'expires_in' ] * 1000 );
206                         } else
207                         if( _getAccessTokenExpiry( this ) ){
208                                 _removeAccessTokenExpiry( this );
209                         };
210                         
211                         pair.oauth2State = 3;
212                         this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, message : isRefresh ? 'Refresh access token success.' : 'Get new access token success.' } );
213                         break;
214                         
215                 case X_EVENT_ERROR :
216                         if( isRefresh ){
217                                 // other error, not auth
218                                 pair.oauth2State = 0;
219                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' } );
220                                 _removeRefreshToken( this );
221                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
222                         } else
223                         if( _getAuthMechanism( this ) === 'param' ){
224                                 pair.oauth2State = 0;
225                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'network-error' } );
226                         } else {
227                                 pair.oauth2State = 0;
228                                 _setAuthMechanism( 'param' );
229                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Refresh access token failed. retry header -> param. ' } );
230                                 // retry
231                                 X_Net_OAuth2_authorizationCode( this, pair );
232                         };
233                         break;
234         };
235 };
236
237 function X_NET_OAUTH2_onXHR401Error( oauth2 ){
238         var pair = this,
239                 xhr, bearerParams, headersExposed = false;
240         
241         if( _getAuthMechanism( oauth2 ) !== 'param' ){
242                 xhr            = X_NET_currentWrapper[ '_rawObject' ];
243                 bearerParams   = xhr.getResponseHeader( 'WWW-Authenticate' );
244                 headersExposed = !X_Net_XHR_X_DOMAIN || !!xhr.getAllResponseHeaders(); // this is a hack for Firefox and IE
245         };
246         
247         // http://d.hatena.ne.jp/ritou/20110402/1301679908
248         if ( bearerParams && bearerParams.indexOf( ' error="' ) === -1 ) {
249                 pair.oauth2State = 0;
250                 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
251         } else
252         if ((( bearerParams && bearerParams.indexOf( ' error="invalid_token"' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) {
253                 _removeAccessToken( oauth2 ); // It doesn't work any more.
254                 pair.oauth2State = 2;
255                 oauth2[ 'refreshToken' ]();
256         } else
257         if (!headersExposed && !_getRefreshToken( oauth2 )) {
258                 pair.oauth2State = 0;
259                 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
260         };
261 };
262
263 function X_NET_OAUTH2_updateRequest( oauth2, request ){
264         var token     = _getAccessToken( oauth2 ),
265                 mechanism = _getAuthMechanism( oauth2 ),
266                 url       = request[ 'url' ],
267                 headers;
268
269         if( token && mechanism === 'param'){
270                 request[ 'url' ] = url + ((url.indexOf('?') !== -1) ? '&' : '?') + 'bearer_token=' + encodeURIComponent( token );
271         };
272         
273         if( token && ( !mechanism || mechanism === 'header' ) ){
274                 request[ 'headers' ] || ( headers = request[ 'headers' ] = {} );
275                 headers[ 'Authorization' ] = 'Bearer ' + token;
276         };
277 };
278
279 function _getAccessToken( that ){ return updateLocalStorage( '', that, 'accessToken' ); }
280 function _getRefreshToken( that){ return updateLocalStorage( '', that, 'refreshToken' ); }
281 function _getAccessTokenExpiry( that ){ return updateLocalStorage( '', that, 'tokenExpiry' ); }
282 function _getAuthMechanism( that ){
283                 // IE's XDomainRequest doesn't support sending headers, so don't try.
284                 return X_Net_XHR_X_DOMAIN ? 'param' : updateLocalStorage( '', that, 'AuthMechanism' );
285         }
286 function _setAccessToken( that, value ){ updateLocalStorage( '+', that, 'accessToken' , value); }
287 function _setRefreshToken( that, value ){ updateLocalStorage( '+', that, 'refreshToken', value); }
288 function _setAccessTokenExpiry( that, value ){ updateLocalStorage( '+', that, 'tokenExpiry', value); }
289 function _setAuthMechanism( that, value ){ updateLocalStorage( '+', that, 'AuthMechanism', value); }
290
291 function _removeAccessToken( that ){ updateLocalStorage( '-', that, 'accessToken' ); }
292 function _removeRefreshToken( that ){ updateLocalStorage( '-', that, 'refreshToken' ); }
293 function _removeAccessTokenExpiry( that ){ updateLocalStorage( '-', that, 'tokenExpiry' ); }
294 function _removeAuthMechanism( that ){ updateLocalStorage( '-', that, 'AuthMechanism' ); }
295         
296 function updateLocalStorage( cmd, that, name, value ){
297         var action = cmd === '+' ? 'setItem' : '-' ? 'removeItem' : 'getItem',
298                 pair;
299         
300         if( window.localStorage ){
301                 return window.localStorage[ action ]( X_Pair_get( that )[ 'clientID' ] + name, value );
302         };
303         
304         pair = X_Pair_get( that );
305         switch( cmd ){
306                 case '+' :
307                         pair[ name ] = value;
308                         break;
309                 case '-' :
310                         if( pair[ name ] !== undefined ) delete pair[ name ];
311         };
312         return pair[ name ];
313 };
314