OSDN Git Service

f11bb1371f59d30b164eb3fab6b66626910ce45d
[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
45                         'authState' : function(){
46                                 return X_Pair_get( this ).oauth2State || 0;
47                         },
48                         
49                         'requestAuth' : function(){
50                                 // pointer event 内で呼ぶこと
51                                 // 二つ以上の popup を作らない
52                                 if( X_NET_OAUTH2_authorizationWindow ) return;
53                                 
54                                 pair = X_Pair_get( this );
55                                 
56                                 if( pair.net || pair.oauth2State ) return;
57                                 
58                                 X_NET_OAUTH2_authorizationWindow = window.open(
59                                         pair[ 'authorizeEndpoint' ] + '?' + X_URL_objToParam(
60                                                 {
61                                                         response_type : 'code',
62                                                         client_id     : pair[ 'clientID' ],
63                                                         redirect_uri  : tpair[ 'redirectURI' ],
64                                                         scope         : ( pair[ 'scopes' ] || []).join(' ')
65                                                 }
66                                         ), 'oauthauthorize',
67                                         'width=' + pair[ 'authorizeWindowWidth' ]
68                                         + ',height=' + pair[ 'authorizeWindowHeight' ]
69                                         + ',left=' + (screen.width  - pair[ 'authorizeWindowWidth'  ] ) / 2
70                                         + ',top='  + (screen.height - pair[ 'authorizeWindowHeight' ] ) / 2
71                                         + ',menubar=no,toolbar=no');
72                                 
73                                 X_NET_OAUTH2_authorizationTimerID = X_Timer_add( 333, 0, this, X_Net_OAuth2_detectAuthPopup );
74                                 
75                                 pair.oauth2State = 1;
76                                 
77                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to auth.' } );
78                         },
79                         
80                         'cancelAuth' : function(){
81                                 pair = X_Pair_get( this );
82                                 
83                                 if( pair.net ){
84                                         pair.net[ 'kill' ]();
85                                         delete pair.net;
86                                 };
87                                 
88                                 X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.close();
89                                 X_NET_OAUTH2_authorizationWindow  = null;
90                                 
91                                 X_NET_OAUTH2_authorizationTimerID && X_Timer_remove( X_NET_OAUTH2_authorizationTimerID );
92                                 X_NET_OAUTH2_authorizationTimerID = 0;
93                                 
94                                 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
95                         },
96                         
97                         'refreshToken' : function(){
98                                 /*
99                                  *                              var expires_at = this._getAccessTokenExpiry();
100                                 if (expires_at && Date.now() + millis > expires_at)
101                                         this._refreshAccessToken({replay: false});
102                                  */
103                                 
104                                 pair = X_Pair_get( this );
105                                 
106                                 if( pair.net ) return;
107                                 
108                                 pair.oauth2State = 2;
109                                 
110                                 pair.net = X.Net( {
111                                         'xhr'      : pair[ 'tokenEndpoint' ],
112                                         'postdata' : X_URL_objToParam({
113                                                 'client_id'     : pair[ 'clientID' ],
114                                                 'client_secret' : pair[ 'clientSecret' ],
115                                                 'grant_type'    : 'refresh_token',
116                                                 'refresh_token' : _getRefreshToken( this )
117                                         }),
118                                         'dataType' : 'json',
119                                         'headers' : {
120                                                 'Accept'       : 'application/json',
121                                                 'Content-Type' : 'application/x-www-form-urlencoded'
122                                         }
123                                 } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], this, X_Net_OAuth2_responceHandler );
124                                 
125                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to refresh token.' } );
126                         }
127                 }
128         );
129
130 function X_Net_OAuth2_detectAuthPopup(){
131         var closed, search, pair;
132         
133         if( window.frames[ 'oauthauthorize' ] !== X_NET_OAUTH2_authorizationWindow || X_NET_OAUTH2_authorizationWindow.closed ){
134                 pair.oauth2State = 0;
135                 closed = true;
136                 this[ 'asyncDispatch' ]( X_EVENT_CANCELED );
137         } else
138         if( search = X_NET_OAUTH2_detection( X_NET_OAUTH2_authorizationWindow ) ){
139                 pair      = X_Pair_get( this );
140                 pair.code = X_URL_ParamToObj( search.slice( 1 ) )[ 'code' ];
141
142                 X_NET_OAUTH2_authorizationWindow.close();
143                 closed = true;
144
145                 X_Net_OAuth2_authorizationCode( this, pair );
146                 
147                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Get code success, then authorization code.' } );
148         };
149         
150         if( closed ){
151                 X_NET_OAUTH2_authorizationWindow  = null;
152                 X_NET_OAUTH2_authorizationTimerID = 0;
153                 
154                 return X_Callback_UN_LISTEN;    
155         };
156 };
157
158 function X_Net_OAuth2_authorizationCode( oauth2, pair ){        
159         pair.net = X.Net( {
160                 'xhr'      : pair[ 'tokenEndpoint' ],
161                 'postdata' : X_URL_objToParam({
162                         'client_id'     : pair[ 'clientID' ],
163                         'client_secret' : pair[ 'clientSecret' ],
164                         'grant_type'    : 'authorization_code',
165                         'code'          : pair.code,
166                         'redirect_uri'  : pair[ 'redirectURI' ]
167                 }),
168                 'dataType' : 'json',
169                 'headers' : {
170                         'Accept'       : 'application/json',
171                         'Content-Type' : 'application/x-www-form-urlencoded'
172                 }
173         } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], oauth2, X_Net_OAuth2_responceHandler );
174 };
175
176 function X_Net_OAuth2_responceHandler( e ){
177         var data = e.data,
178                 pair = X_Pair_get( this ),
179                 isRefresh = pair.oauth2State === 2;
180         
181         delete pair.net;
182         
183         switch( e.type ){
184                 case X_EVENT_SUCCESS :
185                         if( isRefresh && data.error ){
186                                 _removeRefreshToken( this );
187                                 pair.oauth2State = 0;
188                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' + data.error, data : data } );
189                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
190                                 return;
191                         } else
192                         if( data.error ){
193                                 pair.oauth2State = 0;
194                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Get new access token error.' + data.error, data : data } );
195                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
196                                 return;
197                         };
198                         
199                         _setAccessToken( this, data[ 'access_token' ] || '' );
200                         _setRefreshToken( this, data[ 'refresh_token' ] || '' );
201                         
202                         if( data[ 'expires_in' ] ){
203                                 _setAccessTokenExpiry( this, X_Timer_now() + data[ 'expires_in' ] * 1000 );
204                         } else
205                         if( _getAccessTokenExpiry( this ) ){
206                                 _removeAccessTokenExpiry( this );
207                         };
208                         
209                         pair.oauth2State = 3;
210                         this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, message : isRefresh ? 'Refresh access token success.' : 'Get new access token success.' } );
211                         break;
212                         
213                 case X_EVENT_ERROR :
214                         if( isRefresh ){
215                                 // other error, not auth
216                                 pair.oauth2State = 0;
217                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' } );
218                                 _removeRefreshToken( this );
219                                 this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
220                         } else
221                         if( _getAuthMechanism( this ) === 'param' ){
222                                 pair.oauth2State = 0;
223                                 this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'network-error' } );
224                         } else {
225                                 pair.oauth2State = 0;
226                                 _setAuthMechanism( 'param' );
227                                 this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Refresh access token failed. retry header -> param. ' } );
228                                 // retry
229                                 X_Net_OAuth2_authorizationCode( this, pair );
230                         };
231                         break;
232         };
233 };
234
235 function X_NET_OAUTH2_onXHR401Error( oauth2 ){
236         var pair = this,
237                 xhr, bearerParams, headersExposed = false;
238         
239         if( _getAuthMechanism( oauth2 ) !== 'param' ){
240                 xhr            = X_NET_currentWrapper[ '_rawObject' ];
241                 bearerParams   = xhr.getResponseHeader( 'WWW-Authenticate' );
242                 headersExposed = !X_Net_XHR_X_DOMAIN || !!xhr.getAllResponseHeaders(); // this is a hack for Firefox and IE
243         };
244         
245         // http://d.hatena.ne.jp/ritou/20110402/1301679908
246         if ( bearerParams && bearerParams.indexOf( ' error="' ) === -1 ) {
247                 pair.oauth2State = 0;
248                 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
249         } else
250         if ((( bearerParams && bearerParams.indexOf( ' error="invalid_token"' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) {
251                 _removeAccessToken( oauth2 ); // It doesn't work any more.
252                 pair.oauth2State = 2;
253                 oauth2.refreshToken();
254         } else
255         if (!headersExposed && !_getRefreshToken( oauth2 )) {
256                 pair.oauth2State = 0;
257                 oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH );
258         };
259 };
260
261 function X_NET_OAUTH2_updateRequest( oauth2, request ){
262         var token     = _getAccessToken( oauth2 ),
263                 mechanism = _getAuthMechanism( oauth2 ),
264                 url       = request[ 'url' ],
265                 headers;
266
267         if( token && mechanism === 'param'){
268                 request[ 'url' ] = url + ((url.indexOf('?') !== -1) ? '&' : '?') + 'bearer_token=' + encodeURIComponent( token );
269         };
270         
271         if( token && ( !mechanism || mechanism === 'header' ) ){
272                 request[ 'headers' ] || ( headers = request[ 'headers' ] = {} );
273                 headers[ 'Authorization' ] = 'Bearer ' + token;
274         };
275 };
276
277 function _getAccessToken( that ){ return updateLocalStorage( '', that, 'accessToken' ); }
278 function _getRefreshToken( that){ return updateLocalStorage( '', that, 'refreshToken' ); }
279 function _getAccessTokenExpiry( that ){ return updateLocalStorage( '', that, 'tokenExpiry' ); }
280 function _getAuthMechanism( that ){
281                 // IE's XDomainRequest doesn't support sending headers, so don't try.
282                 return X_Net_XHR_X_DOMAIN ? 'param' : updateLocalStorage( '', that, 'AuthMechanism' );
283         }
284 function _setAccessToken( that, value ){ updateLocalStorage( '+', that, 'accessToken' , value); }
285 function _setRefreshToken( that, value ){ updateLocalStorage( '+', that, 'refreshToken', value); }
286 function _setAccessTokenExpiry( that, value ){ updateLocalStorage( '+', that, 'tokenExpiry', value); }
287 function _setAuthMechanism( that, value ){ updateLocalStorage( '+', that, 'AuthMechanism', value); }
288
289 function _removeAccessToken( that ){ updateLocalStorage( '-', that, 'accessToken' ); }
290 function _removeRefreshToken( that ){ updateLocalStorage( '-', that, 'refreshToken' ); }
291 function _removeAccessTokenExpiry( that ){ updateLocalStorage( '-', that, 'tokenExpiry' ); }
292 function _removeAuthMechanism( that ){ updateLocalStorage( '-', that, 'AuthMechanism' ); }
293         
294 function updateLocalStorage( cmd, that, name, value ){
295         var action = cmd === '+' ? 'setItem' : '-' ? 'removeItem' : 'getItem',
296                 pair;
297         
298         if( window.localStorage ){
299                 return window.localStorage[ action ]( X_Pair_get( that )[ 'clientID' ] + name, value );
300         };
301         
302         pair = X_Pair_get( that );
303         switch( cmd ){
304                 case '+' :
305                         pair[ name ] = value;
306                         break;
307                 case '-' :
308                         if( pair[ name ] !== undefined ) delete pair[ name ];
309         };
310         return pair[ name ];
311 };
312