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