From 7ae4d83cfe6d488add972596908f2d3cfbb12ffd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 8 Feb 2017 00:48:07 -0500 Subject: [PATCH] refactor browser-only code --- oauth3.browser.js | 234 ++++++++++++++++++++++++++++++ oauth3.cache.js | 42 ++++++ oauth3.core.js | 65 ++++++++- oauth3.js | 356 ++-------------------------------------------- oauth3.lint.js | 44 +++++- 5 files changed, 397 insertions(+), 344 deletions(-) create mode 100644 oauth3.browser.js create mode 100644 oauth3.cache.js diff --git a/oauth3.browser.js b/oauth3.browser.js new file mode 100644 index 0000000..b0ceb70 --- /dev/null +++ b/oauth3.browser.js @@ -0,0 +1,234 @@ +;(function (exports) { + 'use strict'; + + var OAUTH3 = exports.OAUTH3; + var OAUTH3_CORE = exports.OAUTH3_CORE; + + function getDefaultAppUrl() { + console.warn('[deprecated] using window.location.{protocol, host, pathname} when opts.appUrl should be used'); + return window.location.protocol + + '//' + window.location.host + + (window.location.pathname).replace(/\/?$/, '') + ; + } + + var browser = exports.OAUTH3_BROWSER = { + discover: function (providerUri, opts) { + opts = opts || {}; + opts.debug = true; + console.log('discover providerUri', providerUri); + providerUri = OAUTH3_CORE.normalizeUrl(providerUri); + var discObj = OAUTH3_CORE.discover(providerUri, { appUrl: (opts.appUrl || getDefaultAppUrl()), debug: opts.debug }); + + return browser.insertIframe(discObj.url, discObj.state, opts).then(function (params) { + if (params.error) { + return OAUTH3_CORE.formatError(providerUri, params.error); + } + var directives = JSON.parse(atob(OAUTH3_CORE.utils.urlSafeBase64ToBase64(params.result || params.directives))); + directives.issuer = directives.issuer || OAUTH3_CORE.normalizeUrl(providerUri); + return directives; + }, function (err) { + return OAUTH3.PromiseA.reject(err); + }); + } + + , frameRequest: function (url, state, opts) { + var promise; + + if ('background' === opts.type) { + promise = browser.insertIframe(url, state, opts); + } else if ('popup' === opts.type) { + promise = browser.openWindow(url, state, opts); + } else { + throw new Error("login framing method not specified or not type yet implemented"); + } + + return promise.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; + return OAUTH3.PromiseA.reject(err); + } + + return params; + }); + } + + , insertIframe: function (url, state, opts) { + opts = opts || {}; + var promise = new OAUTH3.PromiseA(function (resolve, reject) { + var tok; + var $iframe; + + function cleanup() { + delete window['--oauth3-callback-' + state]; + $iframe.remove(); + clearTimeout(tok); + tok = null; + } + + window['--oauth3-callback-' + state] = function (params) { + console.info('[iframe] complete', params); + resolve(params); + cleanup(); + }; + + tok = setTimeout(function () { + var err = new Error("the iframe request did not complete within 15 seconds"); + err.code = "E_TIMEOUT"; + reject(err); + cleanup(); + }, opts.timeout || 15000); + + // TODO hidden / non-hidden (via directive even) + var framesrc = '' - + '" width="1px" height="1px" frameborder="0">' - ); - - $('body').append($iframe); - }); - - // TODO periodically garbage collect expired handlers from window object - return promise; - }; - - oauth3.openWindow = function (url, state, opts) { - var promise = new oauth3.PromiseA(function (resolve, reject) { - var winref; - var tok; - - function cleanup() { - delete window['--oauth3-callback-' + state]; - clearTimeout(tok); - tok = null; - // this is last in case the window self-closes synchronously - // (should never happen, but that's a negotiable implementation detail) - //winref.close(); - } - - window['--oauth3-callback-' + state] = function (params) { - //console.info('[popup] (or window) complete', params); - resolve(params); - cleanup(); - }; - - tok = setTimeout(function () { - var err = new Error("the windowed request did not complete within 3 minutes"); - err.code = "E_TIMEOUT"; - reject(err); - cleanup(); - }, opts.timeout || 3 * 60 * 1000); - - // TODO allow size changes (via directive even) - winref = window.open(url, 'oauth3-login-' + state, 'height=720,width=620'); - if (!winref) { - reject("TODO: open the iframe first and discover oauth3 directives before popup"); - cleanup(); - } - }); - - // TODO periodically garbage collect expired handlers from window object - return promise; - }; - - oauth3.logout = function (providerUri, opts) { - opts = opts || {}; - - // Oauth3.init({ logout: function () {} }); - //return Oauth3.logout(); - - var state = parseInt(Math.random().toString().replace('0.', ''), 10).toString('36'); - var url = providerUri.replace(/\/$/, '') + (opts.providerOauth3Html || '/oauth3.html'); - var redirectUri = opts.redirectUri - || (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html') - ; - var params = { - // logout=true for all logins/accounts - // logout=app-scoped-login-id for a single login - action: 'logout' - // TODO specify specific accounts / logins to delete from session - , accounts: true - , logins: true - , redirect_uri: redirectUri - , state: state - }; - - //console.log('DEBUG oauth3.logout NIX insertIframe'); - //console.log(url, params.redirect_uri); - //console.log(state); - //console.log(params); // redirect_uri - //console.log(opts); - - if (url === params.redirect_uri) { - return oauth3.PromiseA.resolve(); - } - - url += '#' + core.querystringify(params); - - return oauth3.insertIframe(url, state, opts); - }; - - oauth3.createState = function () { - // TODO mo' betta' random function - // maybe gather some entropy from mouse / keyboard events? - // (probably not, just use webCrypto or be sucky) - return parseInt(Math.random().toString().replace('0.', ''), 10).toString('36'); - }; - - oauth3.normalizeProviderUri = function (providerUri) { - // tested with - // example.com - // example.com/ - // http://example.com - // https://example.com/ - providerUri = providerUri - .replace(/^(https?:\/\/)?/, 'https://') - .replace(/\/?$/, '') - ; - - return providerUri; - }; - - oauth3._discoverHelper = function (providerUri, opts) { - return oauth3._discoverHelperNew(providerUri, opts).then(function () { - }, function () { - console.warn('[directives] fallback to old /oauth3.html'); - return oauth3._discoverHelperOld(providerUri, opts); - }); - }; - oauth3._discoverHelperNew = function (providerUri, opts) { - opts = opts || {}; - var state = oauth3.createState(); - var discObj = oauth3.core.discover(providerUri, { state: state, appUrl: (opts.appUrl || getDefaultAppUrl()) }); - - return oauth3.insertIframe(discObj.url, state, opts).then(function (directives) { - return directives; - }, function (err) { - return oauth3.PromiseA.reject(err); - }); - }; - oauth3._discoverHelperOld = function (providerUri, opts) { - opts = opts || {}; - var state = oauth3.createState(); - var params; - var url; - - params = { - action: 'directives' - , state: state - // TODO this should be configurable (i.e. I want a dev vs production oauth3.html) - , redirect_uri: window.location.protocol + '//' + window.location.host - + (window.location.pathname + '/oauth3.html').replace(/\/\//, '/') - }; - - url = providerUri + '/oauth3.html#' + core.querystringify(params); - - return oauth3.insertIframe(url, state, opts).then(function (directives) { - return directives; - }, function (err) { - return oauth3.PromiseA.reject(err); - }); - }; - - oauth3.discover = function (providerUri, opts) { - opts = opts || {}; - - console.log('DEBUG oauth3.discover', providerUri); - console.log(opts); - if (opts.directives) { - return oauth3.PromiseA.resolve(opts.directives); - } - - var promise; - var promise2; - var directives; - var updatedAt; - var fresh; - - providerUri = oauth3.normalizeProviderUri(providerUri); - try { - directives = JSON.parse(localStorage.getItem('oauth3.' + providerUri + '.directives')); - console.log('DEBUG oauth3.discover cache', directives); - updatedAt = localStorage.getItem('oauth3.' + providerUri + '.directives.updated_at'); - console.log('DEBUG oauth3.discover updatedAt', updatedAt); - updatedAt = new Date(updatedAt).valueOf(); - console.log('DEBUG oauth3.discover updatedAt', updatedAt); - } catch(e) { - // ignore - } - - fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000); - - if (directives) { - promise = oauth3.PromiseA.resolve(directives); - - if (fresh) { - //console.log('[local] [fresh directives]', directives); - return promise; - } - } - - promise2 = oauth3._discoverHelper(providerUri, opts).then(function (params) { - console.log('DEBUG oauth3._discoverHelper', params); - var err; - - if (!params.directives) { - err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); - err.code = params.error || "E_UNKNOWN_ERROR"; - return oauth3.PromiseA.reject(err); - } - - try { - directives = JSON.parse(atob(params.directives)); - console.log('DEBUG oauth3._discoverHelper directives', directives); - } catch(e) { - err = new Error(params.error_description || "could not parse directives for provider '" + providerUri + "'"); - err.code = params.error || "E_PARSE_DIRECTIVE"; - return oauth3.PromiseA.reject(err); - } - - if ( - (directives.authorization_dialog && directives.authorization_dialog.url) - || (directives.access_token && directives.access_token.url) - ) { - // TODO lint directives - // TODO self-reference in directive for providerUri? - directives.provider_uri = providerUri; - localStorage.setItem('oauth3.' + providerUri + '.directives', JSON.stringify(directives)); - localStorage.setItem('oauth3.' + providerUri + '.directives.updated_at', new Date().toISOString()); - - return oauth3.PromiseA.resolve(directives); - } else { - // ignore - console.error("the directives provided by '" + providerUri + "' were invalid."); - params.error = params.error || "E_INVALID_DIRECTIVE"; - params.error_description = params.error_description - || "directives did not include authorization_dialog.url"; - err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); - err.code = params.error; - return oauth3.PromiseA.reject(err); - } - }); - - return promise || promise2; - }; - oauth3.core = core; oauth3.querystringify = core.querystringify; oauth3.scopestringify = core.stringifyscope; diff --git a/oauth3.lint.js b/oauth3.lint.js index d41722e..36c89a1 100644 --- a/oauth3.lint.js +++ b/oauth3.lint.js @@ -1,5 +1,6 @@ + // TODO move to a test / lint suite? - oauth3._testPromise = function (PromiseA) { + oauth3._lintPromise = function (PromiseA) { var promise; var x = 1; @@ -48,3 +49,44 @@ x = 2; return promise; }; + + oauth3._lintDirectives = function (providerUri, directives) { + var params = { directives: directives }; + console.log('DEBUG oauth3._discoverHelper', directives); + var err; + if (!params.directives) { + err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); + err.code = params.error || "E_UNKNOWN_ERROR"; + return OAUTH3.PromiseA.reject(err); + } + + try { + directives = JSON.parse(atob(params.directives)); + console.log('DEBUG oauth3._discoverHelper directives', directives); + } catch(e) { + err = new Error(params.error_description || "could not parse directives for provider '" + providerUri + "'"); + err.code = params.error || "E_PARSE_DIRECTIVE"; + return OAUTH3.PromiseA.reject(err); + } + if ( + (directives.authorization_dialog && directives.authorization_dialog.url) + || (directives.access_token && directives.access_token.url) + ) { + // TODO lint directives + // TODO self-reference in directive for providerUri? + directives.provider_uri = providerUri; + localStorage.setItem('oauth3.' + providerUri + '.directives', JSON.stringify(directives)); + localStorage.setItem('oauth3.' + providerUri + '.directives.updated_at', new Date().toISOString()); + + return OAUTH3.PromiseA.resolve(directives); + } else { + // ignore + console.error("the directives provided by '" + providerUri + "' were invalid."); + params.error = params.error || "E_INVALID_DIRECTIVE"; + params.error_description = params.error_description + || "directives did not include authorization_dialog.url"; + err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); + err.code = params.error; + return OAUTH3.PromiseA.reject(err); + } + };