move .well-known/oauth3 to assets/org.oauth3/.well-known/oauth3
This commit is contained in:
parent
35e2a29e4c
commit
a449358dd6
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Redirecting...</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #ffcccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
Redirecting...
|
||||
|
||||
<!-- TODO permanently cache with appcache (or service worker?) -->
|
||||
<!-- TODO slim this all down to a single file -->
|
||||
<script src="/assets/org.oauth3/oauth3.core.js"></script>
|
||||
<script src="callback.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,66 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
var loc = window.location;
|
||||
var loginWinObj = window.OAUTH3_CORE.queryparse(loc.hash || loc.search);
|
||||
var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/callback.html]";
|
||||
|
||||
if (loginWinObj.debug) {
|
||||
console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled.");
|
||||
}
|
||||
// '--oauth3-callback-' prefix exist for security so that an attacker can't social engineer execution an arbitrary function
|
||||
// TODO finalize name of '--oauth3-callback-', this will be a defacto standard
|
||||
// TODO maybe call it 'self-xss-' or 'hack-my-account-' to discourage people from doing dumb things?
|
||||
var callbackName = '--oauth3-callback-' + loginWinObj.state;
|
||||
|
||||
console.log(prefix, loc.href);
|
||||
console.log('Parsed URL Obj: ', loginWinObj);
|
||||
console.log('callbackName: ', callbackName);
|
||||
|
||||
window.oauth3complete = function () {
|
||||
// The hacks that used to be necessary for this on iOS should no longer be necessary in iOS 9+
|
||||
// see https://bugs.chromium.org/p/chromium/issues/detail?id=136610 and https://crbug.com/423444
|
||||
// TODO Should we still create an abstraction for older versions?
|
||||
if (window.parent) {
|
||||
// iframe
|
||||
try {
|
||||
window.parent[callbackName](loginWinObj);
|
||||
return;
|
||||
} catch(e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.opener) {
|
||||
try {
|
||||
window.opener[callbackName](loginWinObj);
|
||||
return;
|
||||
} catch(e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
console.error("neither window.parent nor window.opener existed to complete callback");
|
||||
|
||||
/*
|
||||
// the caller should close (or signal to close) the window
|
||||
try {
|
||||
window.close();
|
||||
} catch (err) {
|
||||
console.log('Error: ', err);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
if (!loginWinObj.debug) {
|
||||
window.oauth3complete();
|
||||
}
|
||||
else {
|
||||
document.body.innerHTML = window.location.hostname + window.location.pathname
|
||||
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
|
||||
+ ' to let you look at logs or whatever it is that you intended to do.'
|
||||
+ '<br/><br/>Continue with callback: <a href="javascript:window.oauth3complete()">javascript:window.oauth3complete()</' + 'a>';
|
||||
return;
|
||||
}
|
||||
|
||||
}());
|
|
@ -0,0 +1,330 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
console.log('[DAPLIE oauth3 directives.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 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;
|
||||
});
|
||||
}
|
||||
|
||||
parseParamsString(window.location.search);
|
||||
// handle cases where hash is treated like it's own href
|
||||
// TODO /#/?search=blah
|
||||
parseParamsString(window.location.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', '.well-known/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
|
||||
console.log('[deprecated] callback completeLogin');
|
||||
(window.opener||window.parent).completeLogin(null, null, incoming);
|
||||
} else {
|
||||
console.log('[DEBUG] I would be closed by my parent now');
|
||||
console.log('__oauth3_' + browserCallback);
|
||||
console.log(window.opener && window.opener['__oauth3_' + browserCallback]);
|
||||
console.log(window.parent && window.parent['__oauth3_' + browserCallback]);
|
||||
console.log(incoming);
|
||||
(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');
|
||||
// XXX OAUTH3 DEBUG FRAME XXX // make this easy to find
|
||||
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();
|
||||
}());
|
|
@ -0,0 +1,9 @@
|
|||
{ "authorization_dialog": { "url": "#/authorization_dialog" }
|
||||
, "access_token": { "method": "POST", "url": "api/org.oauth3.provider/access_token" }
|
||||
, "otp": { "method": "POST" , "url": "api/org.oauth3.provider/otp" }
|
||||
, "credential_otp": { "method": "POST" , "url": "api/org.oauth3.provider/otp" }
|
||||
, "credential_meta": { "url": "api/org.oauth3.provider/logins/meta/:type/:id" }
|
||||
, "credential_create": { "method": "POST" , "url": "api/org.oauth3.provider/logins" }
|
||||
, "grants": { "method": "GET", "url": "api/org.oauth3.provider/grants/:azp/:sub" }
|
||||
, "authorization_decision": { "method": "POST", "url": "api/org.oauth3.provider/authorization_decision" }
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #ffcccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
OAuth3 RPC
|
||||
|
||||
<script src="/assets/com.jquery/jquery-3.1.1.js"></script>
|
||||
<script src="/assets/org.oauth3/oauth3.core.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// TODO what about search within hash?
|
||||
var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]";
|
||||
var params = OAUTH3_CORE.queryparse(window.location.hash || window.location.search);
|
||||
if (params.debug) {
|
||||
console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled.");
|
||||
}
|
||||
|
||||
console.log(prefix, 'hash||search:');
|
||||
console.log(window.location.hash || window.location.search);
|
||||
|
||||
console.log(prefix, 'params:');
|
||||
console.log(params);
|
||||
|
||||
$.ajax({ url: 'directives.json' }).then(function (resp) {
|
||||
var b64 = btoa(JSON.stringify(resp, null, 0))
|
||||
var urlsafe64 = OAUTH3_CORE.utils.base64ToUrlSafeBase64(b64);
|
||||
var redirect;
|
||||
|
||||
console.log(prefix, 'directives');
|
||||
console.log(resp);
|
||||
|
||||
console.log(prefix, 'base64');
|
||||
console.log(urlsafe64);
|
||||
|
||||
// TODO try postMessage back to redirect_uri domain right here
|
||||
// window.postMessage();
|
||||
|
||||
// TODO make sure it's https NOT http
|
||||
// NOTE: this can be only up to 2,083 characters
|
||||
console.log(prefix, 'params.redirect_uri:', params.redirect_uri);
|
||||
redirect = params.redirect_uri + '?' + OAUTH3_CORE.querystringify({
|
||||
state: params.state
|
||||
, directives: urlsafe64
|
||||
, debug: params.debug || undefined
|
||||
})
|
||||
|
||||
console.log(prefix, 'redirect');
|
||||
console.log(redirect);
|
||||
if (!params.debug) {
|
||||
window.location = redirect;
|
||||
} else {
|
||||
// yes, we're violating the security lint with purpose
|
||||
document.body.innerHTML += window.location.host + window.location.pathname
|
||||
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
|
||||
+ ' to let you look at logs or whatever it is that you intended to do.'
|
||||
+ '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue