forked from coolaj86/walnut.js
		
	
		
			
	
	
		
			327 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			327 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | (function () { | ||
|  |   'use strict'; | ||
|  | 
 | ||
|  |   console.log('[DAPLIE oauth3.js]'); | ||
|  |   console.log(window.location); | ||
|  | 
 | ||
|  |   var iter = 0; | ||
|  | 
 | ||
|  |   function main() { | ||
|  | 
 | ||
|  |     var rpc = {}; | ||
|  |     //var myself = location.protocol + '//' + location.host + location.pathname;
 | ||
|  |     var incoming; | ||
|  |     var forwarding = {}; | ||
|  |     var anchor; | ||
|  |     var err; | ||
|  |     var browserState; | ||
|  |     var browserCallback; | ||
|  |     var action; | ||
|  | 
 | ||
|  |     function parseParams() { | ||
|  |       var params = {}; | ||
|  | 
 | ||
|  |       function parseParamsString(str) { | ||
|  |         str.substr(1).split('&').filter(function (el) { return el; }).forEach(function (pair) { | ||
|  |           pair = pair.split('='); | ||
|  |           var key = decodeURIComponent(pair[0]); | ||
|  |           var val = decodeURIComponent(pair[1]); | ||
|  | 
 | ||
|  |           if (params[key]) { | ||
|  |             console.warn("overwriting key '" + key + "' '" + params[key] + "'"); | ||
|  |           } | ||
|  |           params[key] = val; | ||
|  |         }); | ||
|  |       } | ||
|  | 
 | ||
|  |       anchor = document.createElement('a'); | ||
|  |       anchor.href = window.location.href; | ||
|  | 
 | ||
|  |       parseParamsString(anchor.search); | ||
|  |       parseParamsString(anchor.hash); | ||
|  | 
 | ||
|  |       return params; | ||
|  |     } | ||
|  | 
 | ||
|  |     function querystringify(params) { | ||
|  |       var arr = []; | ||
|  | 
 | ||
|  |       Object.keys(params).forEach(function (k) { | ||
|  |         arr.push(encodeURIComponent(k) + '=' + encodeURIComponent(params[k])); | ||
|  |       }); | ||
|  | 
 | ||
|  |       return arr.join('&'); | ||
|  |     } | ||
|  | 
 | ||
|  |     function phoneAway(/*redirectURi, params*/) { | ||
|  |       // TODO test for ? / #
 | ||
|  |       window.location.href = incoming.redirect_uri + '#' + querystringify(forwarding); | ||
|  |     } | ||
|  | 
 | ||
|  |     function lintAndSetRedirectable(browserState, params) { | ||
|  |       if (!params.redirect_uri) { | ||
|  |         window.alert('redirect_uri not defined'); | ||
|  |         err = new Error('redirect_uri not defined'); | ||
|  |         console.error(err.message); | ||
|  |         console.warn(err.stack); | ||
|  |         params.redirect_uri = document.referer; | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!browserState) { | ||
|  |         forwarding.error = "E_NO_BROWSER_STATE"; | ||
|  |         forwarding.error_description = "you must specify a state parameter"; | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       localStorage.setItem('oauth3.states.' + browserState, JSON.stringify(params)); | ||
|  |       return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     function redirectCallback() { | ||
|  |       var redirect_uri = incoming.redirect_uri; | ||
|  |       forwarding.callback = browserState; | ||
|  |       forwarding.action = 'close'; | ||
|  | 
 | ||
|  |       var url = redirect_uri + '#' + querystringify(forwarding); | ||
|  | 
 | ||
|  |       console.log('[debug] redirect_uri + params:', url); | ||
|  |       window.location.href = url; | ||
|  |       setTimeout(function () { | ||
|  |         if (iter >= 3) { | ||
|  |           console.log("dancing way too much... stopping now"); | ||
|  |           return; | ||
|  |         } | ||
|  |         iter += 1; | ||
|  |         console.log("I'm dancing by myse-e-elf"); | ||
|  |         // in case I'm redirecting to myself
 | ||
|  |         main(); | ||
|  |       }, 0); | ||
|  |     } | ||
|  | 
 | ||
|  |     rpc = {}; | ||
|  | 
 | ||
|  |     // Act as a provider and log the user out
 | ||
|  |     rpc.logout = function (browserState, incoming) { | ||
|  |       var url; | ||
|  |       if (!lintAndSetRedirectable(browserState, incoming)) { | ||
|  |         // TODO fail
 | ||
|  |       } | ||
|  | 
 | ||
|  |       localStorage.setItem('oauth3.states.' + browserState, JSON.stringify(incoming)); | ||
|  |       url = '/#/logout/' + browserState; | ||
|  | 
 | ||
|  |       // TODO specify specific account or all?
 | ||
|  |       window.location.href = url; | ||
|  |       setTimeout(function () { | ||
|  |         // in case I'm redirecting to myself
 | ||
|  |         main(); | ||
|  |       }, 0); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Act as a provider and inform the consumer the logout is complete
 | ||
|  |     rpc.logout_callback = function (browserState/*, incoming*/) { | ||
|  |       // TODO pass redirect_uri and state through here so we can avoid localStorage
 | ||
|  |       var forwarding = {}; | ||
|  |       var originalRequest; | ||
|  | 
 | ||
|  |       if (!browserState) { | ||
|  |         forwarding.error = "E_NO_BROWSER_STATE"; | ||
|  |         forwarding.error_description = "you must specify a state parameter"; | ||
|  |         if (incoming.redirect_uri) { | ||
|  |           phoneAway(incoming.redirect_uri, forwarding); | ||
|  |         } | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       originalRequest = JSON.parse(localStorage.getItem('oauth3.states.' + browserState)); | ||
|  |       forwarding.action = 'close'; | ||
|  |       forwarding.state = browserState; | ||
|  |       //phoneAway(originalRequest.redirect_uri, forwarding);
 | ||
|  |       window.location.href = originalRequest.redirect_uri + '#' + querystringify(forwarding); | ||
|  |     }; | ||
|  | 
 | ||
|  |     rpc.directives = function (browserState, incoming) { | ||
|  |       if (!lintAndSetRedirectable(browserState, incoming)) { | ||
|  |         phoneAway(); | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       var updatedAt = new Date(localStorage.getItem('oauth3.directives.updated_at')).valueOf(); | ||
|  |       var fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000); | ||
|  |       var directives = localStorage.getItem('oauth3.directives'); | ||
|  |       var redirected = false; | ||
|  | 
 | ||
|  |       function redirectIf() { | ||
|  |         if (redirected) { | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         redirected = true; | ||
|  |         redirectCallback(); | ||
|  |       } | ||
|  | 
 | ||
|  |       if (directives) { | ||
|  |         forwarding.directives = directives; | ||
|  |         redirectIf(); | ||
|  |         if (fresh) { | ||
|  |           return; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       var req = new XMLHttpRequest(); | ||
|  |       req.open('GET', 'oauth3.json', true); | ||
|  |       req.addEventListener('readystatechange', function () { | ||
|  |         if (4 !== req.readyState) { | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (200 !== req.status) { | ||
|  |           forwarding.error = "E_STATUS_" + req.status; | ||
|  |           forwarding.error_description = "expected 200 OK json or text response for oauth3.json but got '" + req.status + "'"; | ||
|  |           redirectIf(); | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         try { | ||
|  |           directives = btoa(JSON.stringify(JSON.parse(req.responseText))); | ||
|  |           forwarding.directives = directives; | ||
|  |           forwarding.callback = browserState; | ||
|  |           localStorage.setItem('oauth3.directives', directives); | ||
|  |           localStorage.setItem('oauth3.directives.updated_at', new Date().toISOString()); | ||
|  |         } catch(e) { | ||
|  |           forwarding.error = "E_PARSE_JSON"; | ||
|  |           forwarding.error_description = e.message; | ||
|  |           console.error(forwarding.error); | ||
|  |           console.error(forwarding.error_description); | ||
|  |           console.error(req.responseText); | ||
|  |         } | ||
|  | 
 | ||
|  |         redirectIf(); | ||
|  |       }); | ||
|  |       req.send(); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // the provider is contacting me
 | ||
|  |     rpc.close = function (browserState, incoming) { | ||
|  |       incoming.callback = browserState; | ||
|  |       catchAll(); | ||
|  |     }; | ||
|  |     // the provider is contacting me
 | ||
|  |     rpc.redirect = function (/*browserState, incoming*/) { | ||
|  |       catchAll(); | ||
|  |     }; | ||
|  | 
 | ||
|  |     function catchAll() { | ||
|  |       function phoneHome() { | ||
|  |         if (browserCallback === 'completeLogin') { | ||
|  |           // Deprecated
 | ||
|  |           (window.opener||window.parent).completeLogin(null, null, incoming); | ||
|  |         } else { | ||
|  |           console.log('I would be closed by my parent now'); | ||
|  |           (window.opener||window.parent)['__oauth3_' + browserCallback](incoming); | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!(incoming.browser_state || incoming.state)) { | ||
|  |         window.alert("callback URLs should include 'browser_state' (authorization code)" | ||
|  |           + " or 'state' (implicit grant))"); | ||
|  |       } | ||
|  | 
 | ||
|  |       setTimeout(function () { | ||
|  |         // opener is for popup window, new tab
 | ||
|  |         // parent is for iframe
 | ||
|  |         phoneHome(); | ||
|  |       }, 10); | ||
|  | 
 | ||
|  |       // iOS Webview (namely Chrome) workaround
 | ||
|  |       setTimeout(function () { | ||
|  |         console.log('I would close now'); | ||
|  |         window.open('', '_self', ''); | ||
|  |         window.close(); | ||
|  |       }, 50); | ||
|  | 
 | ||
|  |       setTimeout(function () { | ||
|  |         var i; | ||
|  |         var len = localStorage.length; | ||
|  |         var key; | ||
|  |         var json; | ||
|  |         var fresh; | ||
|  | 
 | ||
|  |         for (i = 0; i < len; i += 1) { | ||
|  |           key = localStorage.key(i); | ||
|  |           // TODO check updatedAt
 | ||
|  |           if (/^oauth3\./.test(key)) { | ||
|  |             try { | ||
|  |               json = localStorage.getItem(key); | ||
|  |               if (json) { | ||
|  |                 json = JSON.parse(json); | ||
|  |               } | ||
|  |             } catch (e) { | ||
|  |               // ignore
 | ||
|  |               json = null; | ||
|  |             } | ||
|  | 
 | ||
|  |             fresh = json && (Date.now() - json.updatedAt < (5 * 60 * 1000)); | ||
|  | 
 | ||
|  |             if (!fresh) { | ||
|  |               localStorage.removeItem(key); | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |         forwarding.updatedAt = Date.now(); | ||
|  |         localStorage.setItem('oauth3.' + (forwarding.browser_state || forwarding.state), JSON.stringify(forwarding)); | ||
|  |       }, 0); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     function parseAction(params) { | ||
|  |       if (params.action) { | ||
|  |         return params.action; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (params.close) { | ||
|  |         return 'close'; | ||
|  |       } | ||
|  |       if (params.logout_callback) { | ||
|  |         return 'logout_callback'; | ||
|  |       } | ||
|  |       if (params.logout) { | ||
|  |         return 'logout'; | ||
|  |       } | ||
|  |       if (params.callback) { | ||
|  |         return 'close'; | ||
|  |       } | ||
|  |       if (params.directives) { | ||
|  |         return 'directives'; | ||
|  |       } | ||
|  | 
 | ||
|  |       return 'redirect'; | ||
|  |     } | ||
|  | 
 | ||
|  |     incoming = parseParams(); | ||
|  |     browserState = incoming.browser_state || incoming.state; | ||
|  |     action = parseAction(incoming); | ||
|  |     forwarding.url = window.location.href; | ||
|  |     forwarding.browser_state = browserState; | ||
|  |     forwarding.state = browserState; | ||
|  | 
 | ||
|  |     if (!incoming.provider_uri) { | ||
|  |       browserCallback = incoming.callback || browserState; | ||
|  |     } else { | ||
|  |       // deprecated
 | ||
|  |       browserCallback = 'completeLogin'; | ||
|  |     } | ||
|  | 
 | ||
|  |     console.log('[debug]', action, incoming); | ||
|  | 
 | ||
|  |     if (rpc[action]) { | ||
|  |       rpc[action](browserState, incoming); | ||
|  |     } else { | ||
|  |       window.alert('unsupported action'); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   main(); | ||
|  | }()); |