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