WIP window/iframe reuse

This commit is contained in:
AJ ONeal 2017-02-14 12:30:35 -07:00
parent 5a73cb1413
commit 4dd07c9f80
1 changed files with 178 additions and 60 deletions

View File

@ -22,6 +22,9 @@
}
, uri: {
normalize: function (uri) {
if ('string' !== typeof uri) {
console.error((new Error('stack')).stack);
}
// tested with
// example.com
// example.com/
@ -35,6 +38,9 @@
}
, url: {
normalize: function (url) {
if ('string' !== typeof url) {
console.error((new Error('stack')).stack);
}
// tested with
// example.com
// example.com/
@ -45,6 +51,15 @@
.replace(/\/?$/, '')
;
}
, resolve: function (base, next) {
if (/^https:\/\//i.test(next)) {
return next;
}
return this.normalize(base) + '/' + this._normalizePath(next);
}
, _normalizePath: function (path) {
return path.replace(/^\//, '').replace(/\/$/, '');
}
}
, query: {
stringify: function (params) {
@ -115,7 +130,7 @@
var params = {
action: 'directives'
, state: OAUTH3.utils.randomState()
, state: opts.state || OAUTH3.utils.randomState()
, redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/')
, response_type: 'rpc'
, _method: 'GET'
@ -130,24 +145,95 @@
, query: params
};
return result;
}
, implicitGrant: function (directive, opts) {
//console.log('[implicitGrant]');
//
// Example Implicit Grant Request
// (for generating a browser-only session, not a session on your server)
//
// GET https://example.com/api/org.oauth3.provider/authorization_dialog
// ?response_type=token
// &scope=`encodeURIComponent('profile.login profile.email')`
// &state=`cryptoutil.random().toString('hex')`
// &client_id=xxxxxxxxxxx
// &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
//
// NOTE: `redirect_uri` itself may also contain URI-encoded components
//
opts = opts || {};
var type = 'authorization_dialog';
var responseType = 'token';
var scope = opts.scope || directive.authn_scope;
var args = directive[type];
var uri = args.url;
var state = opts.state || OAUTH3.utils.randomState();
console.log('implicit grant opts.state', opts.state);
var params = {
debug: opts.debug || undefined
, client_uri: opts.client_uri || opts.clientUri || undefined
, client_id: opts.client_id || opts.client_uri || undefined
};
var result;
params.state = state;
params.response_type = responseType;
if (scope) {
params.scope = OAUTH3.utils.scope.stringify(scope);
}
if (!opts.redirect_uri) {
// TODO consider making this optional
console.warn("auto-generating redirect_uri from hard-coded callback.html"
+ " (should be configurable... but then redirect_uri could just be manually-generated)");
opts.redirect_uri = OAUTH3.utils.url.resolve(
OAUTH3.utils.url.normalize(params.client_uri)
, '.well-known/oauth3/callback.html'
);
}
params.redirect_uri = opts.redirect_uri;
uri += '?' + OAUTH3.utils.query.stringify(params);
result = {
url: uri
, state: state
, method: args.method
, query: params
};
return result;
}
}
, hooks: {
directives: {
get: function (providerUri) {
_get: function (providerUri) {
providerUri = OAUTH3.utils.uri.normalize(providerUri);
console.warn('[Warn] You should implement: OAUTH3.hooks.directives.get = function (providerUri) { return directives; }');
if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; }
return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._cache[providerUri] || this.get(providerUri))
.then(function (directives) {
// or do .then(this._set) to keep DRY?
OAUTH3.hooks.directives._cache[providerUri] = directives;
});
}
, _getCached: function (providerUri) {
return OAUTH3.hooks.directives._cache[providerUri];
}
, get: function (providerUri) {
console.warn('[Warn] You should implement: OAUTH3.hooks.directives.get = function (providerUri) { return directives; }');
return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}');
}
, set: function (providerUri, directives) {
, _set: function (providerUri, directives) {
providerUri = OAUTH3.utils.uri.normalize(providerUri);
console.warn('[Warn] You should implement: OAUTH3.hooks.directives.set = function (providerUri, directives) { return directives; }');
console.warn(directives);
if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; }
window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
OAUTH3.hooks.directives._cache[providerUri] = directives;
return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.set(providerUri, directives));
}
, set: function (providerUri, directives) {
console.warn('[Warn] You should implement: OAUTH3.hooks.directives.set = function (providerUri, directives) { return directives; }');
window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives));
return directives;
}
}
@ -161,17 +247,13 @@
if (directives && directives.issuer) {
return directives;
}
return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) {
return OAUTH3._discover(providerUri, opts).then(function (directives) {
directives.issuer = directives.issuer || OAUTH3.utils.url.normalize(providerUri);
// OAUTH3.PromiseA.resolve() is taken care of because this is wrapped
return OAUTH3.hooks.directives.set(providerUri, directives);
});
});
}
// this is the browser version
, _discoverHelper: function (providerUri, opts) {
return OAUTH3._browser.discover(providerUri, opts);
}
, request: function (preq) {
return OAUTH3._browser.request(preq);
}
@ -179,10 +261,15 @@
var promise;
if (opts.broker) {
// Discovery can happen in-flow because we know that this is
// a valid oauth3 provider
console.info("broker implicit grant");
promise = OAUTH3._discoverThenImplicitGrant(providerUri, opts);
}
else {
promise = OAUTH3._implicitGrant(providerUri, opts);
// Discovery must take place before calling implicitGrant
console.info("direct implicit grant");
promise = OAUTH3._implicitGrant(OAUTH3.hooks.directives._getCached(providerUri), opts);
}
return promise.then(function (tokens) {
@ -199,9 +286,14 @@
, _discoverThenImplicitGrant: function(providerUri, opts) {
opts.windowType = opts.windowType || 'popup';
return OAUTH3._discover(providerUri, opts).then(function (directives) {
console.info('discover complete');
console.log(directives);
console.log('DISCOVER COMPLETE opts._state', opts._state);
return OAUTH3._implicitGrant(directives, opts).then(function (tokens) {
console.info('implicit grant complete', tokens);
OAUTH3._browser.closeFrame(tokens.state || opts._state);
opts._state = undefined;
//opts._state = undefined;
return tokens;
});
});
}
@ -229,6 +321,7 @@
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
, windowType: opts.broker && opts.windowType || 'background'
, broker: opts.broker
, state: opts._state || undefined
, debug: opts.debug
}
);
@ -242,7 +335,7 @@
// TODO allow postMessage from providerUri in addition to callback
// TODO allow node to open a desktop browser window
return OAUTH3._browser.frameRequest(
discReq.url
OAUTH3.utils.url.resolve(providerUri, discReq.url)
, discReq.state
, { windowType: opts.windowType
, reuseWindow: opts.broker && '-broker'
@ -255,41 +348,48 @@
return OAUTH3.hooks.directives.set(providerUri, directives);
});
}
, _implicitGrant: function(providerUri, opts) {
, _implicitGrant: function(directives, opts) {
// TODO this may need to be synchronous for browser security policy
return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.get(providerUri)).then(function (directives) {
// Do some stuff
var authReq = OAUTH3.urls.implicitGrant(
directives
, { redirect_uri: opts.redirect_uri
, client_id: opts.client_id || opts.client_uri
, client_uri: opts.client_uri || opts.client_id
, state: opts._state
// Do some stuff
var authReq = OAUTH3.urls.implicitGrant(
directives
, { redirect_uri: opts.redirect_uri
, client_id: opts.client_id || opts.client_uri
, client_uri: opts.client_uri || opts.client_id
, state: opts._state || undefined
, debug: opts.debug
}
);
if (opts.debug) {
window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!");
}
if (opts._state) {
console.log('equal states authReq?', authReq.state === opts._state);
console.log(opts._state);
console.log(authReq.state);
}
return new OAUTH3.PromiseA(function (resolve, reject) {
console.log("framing request for implicit grant");
return OAUTH3._browser.frameRequest(
OAUTH3.utils.url.resolve(directives.issuer, authReq.url)
, authReq.state // state should recycle params
, { windowType: opts.windowType
, reuseWindow: opts.broker && '-broker'
, debug: opts.debug
}
);
).then(function (tokens) {
console.log("completed implicit grant");
if (tokens.error) {
// TODO directives.audience
return reject(OAUTH3.utils._formatError(directives.issuer /*providerUri*/, tokens));
}
if (opts.debug) {
window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!");
}
OAUTH3._browser.closeFrame(authReq.state, { debug: opts.debug || tokens.debug });
return new OAUTH3.PromiseA(function (resolve, reject) {
return OAUTH3._browser.frameRequest(
authReq.url
, authReq.state // state should recycle params
, { windowType: opts.windowType
, reuseWindow: opts.broker && '-broker'
, debug: opts.debug
}
).then(function (tokens) {
if (tokens.error) {
return reject(OAUTH3.utils._formatError(tokens.error));
}
OAUTH3._browser.closeFrame(authReq.state, { debug: opts.debug || tokens.debug });
return tokens;
});
return tokens;
});
});
}
@ -358,28 +458,40 @@
console.warn("It looks like your client_id doesn't match your current window... this probably won't end well");
console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname);
}
var discObj = OAUTH3.urls.discover(
var discReq = OAUTH3.urls.discover(
providerUri
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)), debug: opts.debug }
, { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location))
, state: opts._state || undefined
, debug: opts.debug }
);
if (opts._state) {
console.log('equal states discObj?', discReq.state === opts._state);
}
// TODO ability to reuse iframe instead of closing
opts._windowType = opts.windowType;
opts.windowType = opts.windowType || 'background';
return OAUTH3._browser.frameRequest(discObj.url, discObj.state, opts).then(function (params) {
return OAUTH3._browser.frameRequest(
OAUTH3.utils.url.resolve(providerUri, discReq.url)
, discReq.state
, opts
).then(function (params) {
opts.windowType = opts._windowType;
OAUTH3._browser.closeFrame(discObj.state, { debug: opts.debug || params.debug });
OAUTH3._browser.closeFrame(discReq.state, { debug: opts.debug || params.debug });
if (params.error) {
return OAUTH3.utils._formatError(providerUri, params.error);
// TODO directives.issuer || directives.audience
return OAUTH3.PromiseA.reject(OAUTH3.utils._formatError(providerUri, params));
}
var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils._urlSafeBase64ToBase64(params.result || params.directives)));
return directives;
}, function (err) {
OAUTH3._browser.closeFrame(discObj.state, { debug: opts.debug || err.debug });
OAUTH3._browser.closeFrame(discReq.state, { debug: opts.debug || err.debug });
return OAUTH3.PromiseA.reject(err);
});
}
, frameRequest: function (url, state, opts) {
console.log('frameRequest state', state);
opts = opts || {};
var previousFrame = OAUTH3._browser._frames[state];
@ -412,6 +524,8 @@
console.log('[oauth3.implicit.js] callbackName', '--oauth3-callback-' + state);
window['--oauth3-callback-' + state] = function (params) {
console.log("YO HO YO HO, A Pirate's life for me!", state);
console.error(new Error("Pirate's Life").stack);
resolve(params);
cleanup();
};
@ -434,20 +548,26 @@
if ('background' === windowType) {
if (previousFrame) {
console.log('previous frame in background');
previousFrame.location = url;
//promise = previousFrame.promise;
}
else {
console.log('NO previous frame in background');
OAUTH3._browser._frames[state] = OAUTH3._browser.iframe(url, state, opts);
}
} else if ('popup' === windowType) {
if (previousFrame) {
console.log('previous frame in pop');
console.log(previousFrame);
console.log(url);
previousFrame.location = url;
if (opts.debug) {
previousFrame.focus();
}
}
else {
console.log('NO previous frame in popup');
OAUTH3._browser._frames[state] = OAUTH3._browser.frame(url, state, opts);
}
} else if ('inline' === windowType) {
@ -463,27 +583,25 @@
}
}).then(function (params) {
var err;
if (params.error || params.error_description) {
err = new Error(params.error_description || "Unknown response error");
err.code = params.error || "E_UKNOWN_ERROR";
err.params = params;
//_formatError
return OAUTH3.PromiseA.reject(err);
console.log('frameRequest formatting params (weird that this place exists, but not weird to be here)');
if (params.error) {
// TODO directives.issuer || directives.audience
return OAUTH3.PromiseA.reject(OAUTH3.utils._formatError('https://oauth3.org', params));
}
return params;
});
}
, closeFrame: function (state, opts) {
function close() {
console.log("Attempting to close... ", OAUTH3._browser._frames[state]);
try {
OAUTH3._browser._frames[state].close();
} catch(e) {
console.error(e);
try {
OAUTH3._browser._frames[state].remove();
} catch(e) {
console.error(e);
}
}