diff --git a/README.md b/README.md index 64eafaf..8a7ca9b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ The best part about this library is that it is not strictly coupled to one reque For now it is tested to work with [vue-resource](https://github.com/pagekit/vue-resource) and [axios](https://github.com/mzabriskie/axios) (using [vue-axios](https://github.com/imcvampire/vue-axios) wrapper). +This library supports authentication in popup windows and iframes. + **WARNING:** From version 1.3.0 default request library is `axios` using `vue-axios` wrapper plugin. This library was inspired by well known authentication library for Angular called [Satellizer](https://github.com/sahat/satellizer) developed by [Sahat Yalkabov](http://sahatyalkabov.com). They share almost identical configuration and API so you can easily switch from Angular to Vue.js project. diff --git a/dist/vue-authenticate.common.js b/dist/vue-authenticate.common.js index 02c2cf9..a6edad1 100644 --- a/dist/vue-authenticate.common.js +++ b/dist/vue-authenticate.common.js @@ -1,5 +1,5 @@ /*! - * vue-authenticate v1.3.5-beta.1 + * vue-authenticate v1.3.5-beta.1.4 * https://github.com/dgrubelic/vue-authenticate * Released under the MIT License. */ @@ -83,10 +83,10 @@ function objectExtend(a, b) { /** * Assemble url from two segments - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} baseUrl Base url * @param {String} url URI * @return {String} @@ -108,26 +108,29 @@ function joinUrl(baseUrl, url) { /** * Get full path based on current location - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {Location} location * @return {String} */ -function getFullUrlPath(location) { +function getFullUrlPath (location) { var isHttps = location.protocol === 'https:'; - return location.protocol + '//' + location.hostname + - ':' + (location.port || (isHttps ? '443' : '80')) + - (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); + var port = location.port; + if (!port || port === '0') { + port = isHttps ? '443' : '80'; + } + return location.protocol + '//' + location.hostname + ':' + port + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname) } /** * Parse query string variables - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} Query string * @return {String} */ @@ -149,7 +152,7 @@ function parseQueryString(str) { * Decode base64 string * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} str base64 encoded string * @return {Object} */ @@ -550,7 +553,7 @@ var defaultOptions = { scopeDelimiter: ',', display: 'popup', oauthType: '2.0', - popupOptions: { width: 580, height: 400 } + authContextOptions: { width: 580, height: 400 } }, google: { @@ -565,7 +568,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 452, height: 633 } + authContextOptions: { width: 452, height: 633 } }, github: { @@ -577,7 +580,7 @@ var defaultOptions = { scope: ['user:email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, instagram: { @@ -589,7 +592,7 @@ var defaultOptions = { scope: ['basic'], scopeDelimiter: '+', oauthType: '2.0', - popupOptions: { width: null, height: null } + authContextOptions: { width: null, height: null } }, twitter: { @@ -598,7 +601,7 @@ var defaultOptions = { authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: { width: 495, height: 645 } + authContextOptions: { width: 495, height: 645 } }, bitbucket: { @@ -610,7 +613,7 @@ var defaultOptions = { scope: ['email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, linkedin: { @@ -623,7 +626,7 @@ var defaultOptions = { scopeDelimiter: ' ', state: 'STATE', oauthType: '2.0', - popupOptions: { width: 527, height: 582 } + authContextOptions: { width: 527, height: 582 } }, live: { @@ -636,7 +639,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 500, height: 560 } + authContextOptions: { width: 500, height: 560 } }, oauth1: { @@ -645,7 +648,7 @@ var defaultOptions = { authorizationEndpoint: null, redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: null + authContextOptions: null }, oauth2: { @@ -662,7 +665,7 @@ var defaultOptions = { scopeDelimiter: null, state: null, oauthType: '2.0', - popupOptions: null, + authContextOptions: null, responseType: 'code', responseParams: { code: 'code', @@ -816,24 +819,34 @@ function StorageFactory(options) { } /** - * OAuth2 popup management class - * + * OAuth2 popup/iframe management class + * * @author Sahat Yalkabov - * @copyright Class mostly taken from https://github.com/sahat/satellizer + * @copyright Class mostly taken from https://github.com/sahat/satellizer/blob/master/src/popup.ts * and adjusted to fit vue-authenticate library */ -var OAuthPopup = function OAuthPopup(url, name, popupOptions) { - this.popup = null; +var OAuthContext = function OAuthContext (url, name, authContextOptions) { + if ( authContextOptions === void 0 ) authContextOptions = {}; + + this.authWindow = null; this.url = url; this.name = name; - this.popupOptions = popupOptions; + this.authContextOptions = authContextOptions; }; -OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { +var prototypeAccessors = { iframeTarget: {} }; + +OAuthContext.prototype.open = function open (redirectUri, skipPooling) { try { - this.popup = window.open(this.url, this.name, this._stringifyOptions()); - if (this.popup && this.popup.focus) { - this.popup.focus(); + if (this.authContextOptions.iframe) { + this.iframe = document.createElement('iframe'); + this.iframe.src = this.url; + this.iframeTarget.appendChild(this.iframe); + } else { + this.authWindow = window.open(this.url, this.name, this._stringifyOptions()); + if (this.authWindow && this.authWindow.focus) { + this.authWindow.focus(); + } } if (skipPooling) { @@ -841,33 +854,58 @@ OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { } else { return this.pooling(redirectUri) } - } catch(e) { - return Promise$1.reject(new Error('OAuth popup error occurred')) + } catch (e) { + return Promise$1.reject(new Error('Error occurred while opening authentication context')) + } +}; + +prototypeAccessors.iframeTarget.get = function () { + if (!this._iframeTarget) { + if (this.authContextOptions.iframeTarget) { + this._iframeTarget = this.authContextOptions.iframeTarget; + } else { + this._iframeTarget = document.createElement('div'); + this._iframeTarget.style.display = 'none'; + document.body.appendChild(this._iframeTarget); + } } + + return this._iframeTarget }; -OAuthPopup.prototype.pooling = function pooling (redirectUri) { +OAuthContext.prototype.pooling = function pooling (redirectUri) { var this$1 = this; + this.timedOut = false; + var authTimeout; + if (this.authContextOptions.timeout) { + authTimeout = setTimeout(function () { + this$1.timedOut = true; + }, this.authContextOptions.timeout); + } + return new Promise$1(function (resolve, reject) { var redirectUriParser = document.createElement('a'); redirectUriParser.href = redirectUri; var redirectUriPath = getFullUrlPath(redirectUriParser); - + var poolingInterval = setInterval(function () { - if (!this$1.popup || this$1.popup.closed || this$1.popup.closed === undefined) { - clearInterval(poolingInterval); - poolingInterval = null; - reject(new Error('Auth popup window closed')); + if (!this$1.iframe) { + if (!this$1.authWindow || this$1.authWindow.closed || this$1.authWindow.closed === undefined) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('Auth popup window closed')); + } } try { - var popupWindowPath = getFullUrlPath(this$1.popup.location); + var authWindow = this$1.authWindow || this$1.iframe.contentWindow; + var authWindowPath = getFullUrlPath(authWindow.location); - if (popupWindowPath === redirectUriPath) { - if (this$1.popup.location.search || this$1.popup.location.hash) { - var query = parseQueryString(this$1.popup.location.search.substring(1).replace(/\/$/, '')); - var hash = parseQueryString(this$1.popup.location.hash.substring(1).replace(/[\/$]/, '')); + if (authWindowPath === redirectUriPath) { + if (authWindow.location.search || authWindow.location.hash) { + var query = parseQueryString(authWindow.location.search.substring(1).replace(/\/$/, '')); + var hash = parseQueryString(authWindow.location.hash.substring(1).replace(/[\/$]/, '')); var params = objectExtend({}, query); params = objectExtend(params, hash); @@ -879,30 +917,50 @@ OAuthPopup.prototype.pooling = function pooling (redirectUri) { } else { reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')); } - + clearTimeout(authTimeout); clearInterval(poolingInterval); poolingInterval = null; - this$1.popup.close(); + if (this$1.iframe) { + if (this$1.authContextOptions.iframeTarget) { + this$1.iframeTarget.removeChild(this$1.iframe); + } else { + document.body.removeChild(this$1.iframeTarget); + } + } else { + this$1.authWindow.close(); + } + } else { + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); + } + } + } catch (e) { // DOMException: Blocked a frame with origin from accessing a cross-origin frame. + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); } - } catch(e) { - // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. } }, 250); }) }; -OAuthPopup.prototype._stringifyOptions = function _stringifyOptions () { +OAuthContext.prototype._stringifyOptions = function _stringifyOptions () { var this$1 = this; var options = []; - for (var optionKey in this$1.popupOptions) { - if (!isUndefined(this$1.popupOptions[optionKey])) { - options.push((optionKey + "=" + (this$1.popupOptions[optionKey]))); + for (var optionKey in this$1.authContextOptions) { + if (!isUndefined(this$1.authContextOptions[optionKey])) { + options.push((optionKey + "=" + (this$1.authContextOptions[optionKey]))); } } return options.join(',') }; +Object.defineProperties( OAuthContext.prototype, prototypeAccessors ); + var defaultProviderConfig = { name: null, url: null, @@ -914,7 +972,7 @@ var defaultProviderConfig = { requiredUrlParams: null, defaultUrlParams: null, oauthType: '1.0', - popupOptions: {} + authContextOptions: {} }; var OAuth = function OAuth($http, storage, providerConfig, options) { @@ -926,17 +984,17 @@ var OAuth = function OAuth($http, storage, providerConfig, options) { }; /** - * Initialize OAuth1 process + * Initialize OAuth1 process * @param{Object} userData User data * @return {Promise} */ OAuth.prototype.init = function init (userData) { var this$1 = this; - this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext('about:blank', this.providerConfig.name, this.providerConfig.authContextOptions); if (window && !window['cordova']) { - this.oauthPopup.open(this.providerConfig.redirectUri, true); + this.oauthContext.open(this.providerConfig.redirectUri, true); } return this.getRequestToken().then(function (response) { @@ -972,11 +1030,11 @@ OAuth.prototype.getRequestToken = function getRequestToken () { OAuth.prototype.openPopup = function openPopup (response) { var url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); - this.oauthPopup.popup.location = url; + this.oauthContext.popup.location = url; if (window && window['cordova']) { - return this.oauthPopup.open(this.providerConfig.redirectUri) + return this.oauthContext.open(this.providerConfig.redirectUri) } else { - return this.oauthPopup.pooling(this.providerConfig.redirectUri) + return this.oauthContext.pooling(this.providerConfig.redirectUri) } }; @@ -1033,7 +1091,7 @@ var defaultProviderConfig$1 = { redirectUri: 'redirectUri' }, oauthType: '2.0', - popupOptions: {} + authContextOptions: {} }; var OAuth2 = function OAuth2($http, storage, providerConfig, options) { @@ -1056,10 +1114,10 @@ OAuth2.prototype.init = function init (userData) { var url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?'); - this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext(url, this.providerConfig.name, this.providerConfig.authContextOptions); return new Promise(function (resolve, reject) { - this$1.oauthPopup.open(this$1.providerConfig.redirectUri).then(function (response) { + this$1.oauthContext.open(this$1.providerConfig.redirectUri).then(function (response) { if (this$1.providerConfig.responseType === 'token' || !this$1.providerConfig.url) { return resolve(response) } @@ -1079,7 +1137,7 @@ OAuth2.prototype.init = function init (userData) { * Exchange temporary oauth data for access token * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param{[type]} oauth [description] * @param{[type]} userData [description] * @return {[type]} [description] @@ -1127,7 +1185,7 @@ OAuth2.prototype.exchangeForToken = function exchangeForToken (oauth, userData) * Stringify oauth params * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @return {String} */ OAuth2.prototype._stringifyRequestParams = function _stringifyRequestParams () { diff --git a/dist/vue-authenticate.es2015.js b/dist/vue-authenticate.es2015.js index a0ca007..6c1fc8d 100644 --- a/dist/vue-authenticate.es2015.js +++ b/dist/vue-authenticate.es2015.js @@ -1,5 +1,5 @@ /*! - * vue-authenticate v1.3.5-beta.1 + * vue-authenticate v1.3.5-beta.1.4 * https://github.com/dgrubelic/vue-authenticate * Released under the MIT License. */ @@ -81,10 +81,10 @@ function objectExtend(a, b) { /** * Assemble url from two segments - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} baseUrl Base url * @param {String} url URI * @return {String} @@ -106,26 +106,29 @@ function joinUrl(baseUrl, url) { /** * Get full path based on current location - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {Location} location * @return {String} */ -function getFullUrlPath(location) { +function getFullUrlPath (location) { var isHttps = location.protocol === 'https:'; - return location.protocol + '//' + location.hostname + - ':' + (location.port || (isHttps ? '443' : '80')) + - (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); + var port = location.port; + if (!port || port === '0') { + port = isHttps ? '443' : '80'; + } + return location.protocol + '//' + location.hostname + ':' + port + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname) } /** * Parse query string variables - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} Query string * @return {String} */ @@ -147,7 +150,7 @@ function parseQueryString(str) { * Decode base64 string * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} str base64 encoded string * @return {Object} */ @@ -548,7 +551,7 @@ var defaultOptions = { scopeDelimiter: ',', display: 'popup', oauthType: '2.0', - popupOptions: { width: 580, height: 400 } + authContextOptions: { width: 580, height: 400 } }, google: { @@ -563,7 +566,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 452, height: 633 } + authContextOptions: { width: 452, height: 633 } }, github: { @@ -575,7 +578,7 @@ var defaultOptions = { scope: ['user:email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, instagram: { @@ -587,7 +590,7 @@ var defaultOptions = { scope: ['basic'], scopeDelimiter: '+', oauthType: '2.0', - popupOptions: { width: null, height: null } + authContextOptions: { width: null, height: null } }, twitter: { @@ -596,7 +599,7 @@ var defaultOptions = { authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: { width: 495, height: 645 } + authContextOptions: { width: 495, height: 645 } }, bitbucket: { @@ -608,7 +611,7 @@ var defaultOptions = { scope: ['email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, linkedin: { @@ -621,7 +624,7 @@ var defaultOptions = { scopeDelimiter: ' ', state: 'STATE', oauthType: '2.0', - popupOptions: { width: 527, height: 582 } + authContextOptions: { width: 527, height: 582 } }, live: { @@ -634,7 +637,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 500, height: 560 } + authContextOptions: { width: 500, height: 560 } }, oauth1: { @@ -643,7 +646,7 @@ var defaultOptions = { authorizationEndpoint: null, redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: null + authContextOptions: null }, oauth2: { @@ -660,7 +663,7 @@ var defaultOptions = { scopeDelimiter: null, state: null, oauthType: '2.0', - popupOptions: null, + authContextOptions: null, responseType: 'code', responseParams: { code: 'code', @@ -814,24 +817,34 @@ function StorageFactory(options) { } /** - * OAuth2 popup management class - * + * OAuth2 popup/iframe management class + * * @author Sahat Yalkabov - * @copyright Class mostly taken from https://github.com/sahat/satellizer + * @copyright Class mostly taken from https://github.com/sahat/satellizer/blob/master/src/popup.ts * and adjusted to fit vue-authenticate library */ -var OAuthPopup = function OAuthPopup(url, name, popupOptions) { - this.popup = null; +var OAuthContext = function OAuthContext (url, name, authContextOptions) { + if ( authContextOptions === void 0 ) authContextOptions = {}; + + this.authWindow = null; this.url = url; this.name = name; - this.popupOptions = popupOptions; + this.authContextOptions = authContextOptions; }; -OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { +var prototypeAccessors = { iframeTarget: {} }; + +OAuthContext.prototype.open = function open (redirectUri, skipPooling) { try { - this.popup = window.open(this.url, this.name, this._stringifyOptions()); - if (this.popup && this.popup.focus) { - this.popup.focus(); + if (this.authContextOptions.iframe) { + this.iframe = document.createElement('iframe'); + this.iframe.src = this.url; + this.iframeTarget.appendChild(this.iframe); + } else { + this.authWindow = window.open(this.url, this.name, this._stringifyOptions()); + if (this.authWindow && this.authWindow.focus) { + this.authWindow.focus(); + } } if (skipPooling) { @@ -839,33 +852,58 @@ OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { } else { return this.pooling(redirectUri) } - } catch(e) { - return Promise$1.reject(new Error('OAuth popup error occurred')) + } catch (e) { + return Promise$1.reject(new Error('Error occurred while opening authentication context')) + } +}; + +prototypeAccessors.iframeTarget.get = function () { + if (!this._iframeTarget) { + if (this.authContextOptions.iframeTarget) { + this._iframeTarget = this.authContextOptions.iframeTarget; + } else { + this._iframeTarget = document.createElement('div'); + this._iframeTarget.style.display = 'none'; + document.body.appendChild(this._iframeTarget); + } } + + return this._iframeTarget }; -OAuthPopup.prototype.pooling = function pooling (redirectUri) { +OAuthContext.prototype.pooling = function pooling (redirectUri) { var this$1 = this; + this.timedOut = false; + var authTimeout; + if (this.authContextOptions.timeout) { + authTimeout = setTimeout(function () { + this$1.timedOut = true; + }, this.authContextOptions.timeout); + } + return new Promise$1(function (resolve, reject) { var redirectUriParser = document.createElement('a'); redirectUriParser.href = redirectUri; var redirectUriPath = getFullUrlPath(redirectUriParser); - + var poolingInterval = setInterval(function () { - if (!this$1.popup || this$1.popup.closed || this$1.popup.closed === undefined) { - clearInterval(poolingInterval); - poolingInterval = null; - reject(new Error('Auth popup window closed')); + if (!this$1.iframe) { + if (!this$1.authWindow || this$1.authWindow.closed || this$1.authWindow.closed === undefined) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('Auth popup window closed')); + } } try { - var popupWindowPath = getFullUrlPath(this$1.popup.location); + var authWindow = this$1.authWindow || this$1.iframe.contentWindow; + var authWindowPath = getFullUrlPath(authWindow.location); - if (popupWindowPath === redirectUriPath) { - if (this$1.popup.location.search || this$1.popup.location.hash) { - var query = parseQueryString(this$1.popup.location.search.substring(1).replace(/\/$/, '')); - var hash = parseQueryString(this$1.popup.location.hash.substring(1).replace(/[\/$]/, '')); + if (authWindowPath === redirectUriPath) { + if (authWindow.location.search || authWindow.location.hash) { + var query = parseQueryString(authWindow.location.search.substring(1).replace(/\/$/, '')); + var hash = parseQueryString(authWindow.location.hash.substring(1).replace(/[\/$]/, '')); var params = objectExtend({}, query); params = objectExtend(params, hash); @@ -877,30 +915,50 @@ OAuthPopup.prototype.pooling = function pooling (redirectUri) { } else { reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')); } - + clearTimeout(authTimeout); clearInterval(poolingInterval); poolingInterval = null; - this$1.popup.close(); + if (this$1.iframe) { + if (this$1.authContextOptions.iframeTarget) { + this$1.iframeTarget.removeChild(this$1.iframe); + } else { + document.body.removeChild(this$1.iframeTarget); + } + } else { + this$1.authWindow.close(); + } + } else { + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); + } + } + } catch (e) { // DOMException: Blocked a frame with origin from accessing a cross-origin frame. + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); } - } catch(e) { - // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. } }, 250); }) }; -OAuthPopup.prototype._stringifyOptions = function _stringifyOptions () { +OAuthContext.prototype._stringifyOptions = function _stringifyOptions () { var this$1 = this; var options = []; - for (var optionKey in this$1.popupOptions) { - if (!isUndefined(this$1.popupOptions[optionKey])) { - options.push((optionKey + "=" + (this$1.popupOptions[optionKey]))); + for (var optionKey in this$1.authContextOptions) { + if (!isUndefined(this$1.authContextOptions[optionKey])) { + options.push((optionKey + "=" + (this$1.authContextOptions[optionKey]))); } } return options.join(',') }; +Object.defineProperties( OAuthContext.prototype, prototypeAccessors ); + var defaultProviderConfig = { name: null, url: null, @@ -912,7 +970,7 @@ var defaultProviderConfig = { requiredUrlParams: null, defaultUrlParams: null, oauthType: '1.0', - popupOptions: {} + authContextOptions: {} }; var OAuth = function OAuth($http, storage, providerConfig, options) { @@ -924,17 +982,17 @@ var OAuth = function OAuth($http, storage, providerConfig, options) { }; /** - * Initialize OAuth1 process + * Initialize OAuth1 process * @param{Object} userData User data * @return {Promise} */ OAuth.prototype.init = function init (userData) { var this$1 = this; - this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext('about:blank', this.providerConfig.name, this.providerConfig.authContextOptions); if (window && !window['cordova']) { - this.oauthPopup.open(this.providerConfig.redirectUri, true); + this.oauthContext.open(this.providerConfig.redirectUri, true); } return this.getRequestToken().then(function (response) { @@ -970,11 +1028,11 @@ OAuth.prototype.getRequestToken = function getRequestToken () { OAuth.prototype.openPopup = function openPopup (response) { var url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); - this.oauthPopup.popup.location = url; + this.oauthContext.popup.location = url; if (window && window['cordova']) { - return this.oauthPopup.open(this.providerConfig.redirectUri) + return this.oauthContext.open(this.providerConfig.redirectUri) } else { - return this.oauthPopup.pooling(this.providerConfig.redirectUri) + return this.oauthContext.pooling(this.providerConfig.redirectUri) } }; @@ -1031,7 +1089,7 @@ var defaultProviderConfig$1 = { redirectUri: 'redirectUri' }, oauthType: '2.0', - popupOptions: {} + authContextOptions: {} }; var OAuth2 = function OAuth2($http, storage, providerConfig, options) { @@ -1054,10 +1112,10 @@ OAuth2.prototype.init = function init (userData) { var url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?'); - this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext(url, this.providerConfig.name, this.providerConfig.authContextOptions); return new Promise(function (resolve, reject) { - this$1.oauthPopup.open(this$1.providerConfig.redirectUri).then(function (response) { + this$1.oauthContext.open(this$1.providerConfig.redirectUri).then(function (response) { if (this$1.providerConfig.responseType === 'token' || !this$1.providerConfig.url) { return resolve(response) } @@ -1077,7 +1135,7 @@ OAuth2.prototype.init = function init (userData) { * Exchange temporary oauth data for access token * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param{[type]} oauth [description] * @param{[type]} userData [description] * @return {[type]} [description] @@ -1125,7 +1183,7 @@ OAuth2.prototype.exchangeForToken = function exchangeForToken (oauth, userData) * Stringify oauth params * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @return {String} */ OAuth2.prototype._stringifyRequestParams = function _stringifyRequestParams () { diff --git a/dist/vue-authenticate.js b/dist/vue-authenticate.js index bddb965..fcb482f 100644 --- a/dist/vue-authenticate.js +++ b/dist/vue-authenticate.js @@ -1,5 +1,5 @@ /*! - * vue-authenticate v1.3.5-beta.1 + * vue-authenticate v1.3.5-beta.1.4 * https://github.com/dgrubelic/vue-authenticate * Released under the MIT License. */ @@ -87,10 +87,10 @@ function objectExtend(a, b) { /** * Assemble url from two segments - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} baseUrl Base url * @param {String} url URI * @return {String} @@ -112,26 +112,29 @@ function joinUrl(baseUrl, url) { /** * Get full path based on current location - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {Location} location * @return {String} */ -function getFullUrlPath(location) { +function getFullUrlPath (location) { var isHttps = location.protocol === 'https:'; - return location.protocol + '//' + location.hostname + - ':' + (location.port || (isHttps ? '443' : '80')) + - (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); + var port = location.port; + if (!port || port === '0') { + port = isHttps ? '443' : '80'; + } + return location.protocol + '//' + location.hostname + ':' + port + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname) } /** * Parse query string variables - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} Query string * @return {String} */ @@ -153,7 +156,7 @@ function parseQueryString(str) { * Decode base64 string * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} str base64 encoded string * @return {Object} */ @@ -554,7 +557,7 @@ var defaultOptions = { scopeDelimiter: ',', display: 'popup', oauthType: '2.0', - popupOptions: { width: 580, height: 400 } + authContextOptions: { width: 580, height: 400 } }, google: { @@ -569,7 +572,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 452, height: 633 } + authContextOptions: { width: 452, height: 633 } }, github: { @@ -581,7 +584,7 @@ var defaultOptions = { scope: ['user:email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, instagram: { @@ -593,7 +596,7 @@ var defaultOptions = { scope: ['basic'], scopeDelimiter: '+', oauthType: '2.0', - popupOptions: { width: null, height: null } + authContextOptions: { width: null, height: null } }, twitter: { @@ -602,7 +605,7 @@ var defaultOptions = { authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: { width: 495, height: 645 } + authContextOptions: { width: 495, height: 645 } }, bitbucket: { @@ -614,7 +617,7 @@ var defaultOptions = { scope: ['email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, linkedin: { @@ -627,7 +630,7 @@ var defaultOptions = { scopeDelimiter: ' ', state: 'STATE', oauthType: '2.0', - popupOptions: { width: 527, height: 582 } + authContextOptions: { width: 527, height: 582 } }, live: { @@ -640,7 +643,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 500, height: 560 } + authContextOptions: { width: 500, height: 560 } }, oauth1: { @@ -649,7 +652,7 @@ var defaultOptions = { authorizationEndpoint: null, redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: null + authContextOptions: null }, oauth2: { @@ -666,7 +669,7 @@ var defaultOptions = { scopeDelimiter: null, state: null, oauthType: '2.0', - popupOptions: null, + authContextOptions: null, responseType: 'code', responseParams: { code: 'code', @@ -820,24 +823,34 @@ function StorageFactory(options) { } /** - * OAuth2 popup management class - * + * OAuth2 popup/iframe management class + * * @author Sahat Yalkabov - * @copyright Class mostly taken from https://github.com/sahat/satellizer + * @copyright Class mostly taken from https://github.com/sahat/satellizer/blob/master/src/popup.ts * and adjusted to fit vue-authenticate library */ -var OAuthPopup = function OAuthPopup(url, name, popupOptions) { - this.popup = null; +var OAuthContext = function OAuthContext (url, name, authContextOptions) { + if ( authContextOptions === void 0 ) authContextOptions = {}; + + this.authWindow = null; this.url = url; this.name = name; - this.popupOptions = popupOptions; + this.authContextOptions = authContextOptions; }; -OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { +var prototypeAccessors = { iframeTarget: {} }; + +OAuthContext.prototype.open = function open (redirectUri, skipPooling) { try { - this.popup = window.open(this.url, this.name, this._stringifyOptions()); - if (this.popup && this.popup.focus) { - this.popup.focus(); + if (this.authContextOptions.iframe) { + this.iframe = document.createElement('iframe'); + this.iframe.src = this.url; + this.iframeTarget.appendChild(this.iframe); + } else { + this.authWindow = window.open(this.url, this.name, this._stringifyOptions()); + if (this.authWindow && this.authWindow.focus) { + this.authWindow.focus(); + } } if (skipPooling) { @@ -845,33 +858,58 @@ OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { } else { return this.pooling(redirectUri) } - } catch(e) { - return Promise$1.reject(new Error('OAuth popup error occurred')) + } catch (e) { + return Promise$1.reject(new Error('Error occurred while opening authentication context')) + } +}; + +prototypeAccessors.iframeTarget.get = function () { + if (!this._iframeTarget) { + if (this.authContextOptions.iframeTarget) { + this._iframeTarget = this.authContextOptions.iframeTarget; + } else { + this._iframeTarget = document.createElement('div'); + this._iframeTarget.style.display = 'none'; + document.body.appendChild(this._iframeTarget); + } } + + return this._iframeTarget }; -OAuthPopup.prototype.pooling = function pooling (redirectUri) { +OAuthContext.prototype.pooling = function pooling (redirectUri) { var this$1 = this; + this.timedOut = false; + var authTimeout; + if (this.authContextOptions.timeout) { + authTimeout = setTimeout(function () { + this$1.timedOut = true; + }, this.authContextOptions.timeout); + } + return new Promise$1(function (resolve, reject) { var redirectUriParser = document.createElement('a'); redirectUriParser.href = redirectUri; var redirectUriPath = getFullUrlPath(redirectUriParser); - + var poolingInterval = setInterval(function () { - if (!this$1.popup || this$1.popup.closed || this$1.popup.closed === undefined) { - clearInterval(poolingInterval); - poolingInterval = null; - reject(new Error('Auth popup window closed')); + if (!this$1.iframe) { + if (!this$1.authWindow || this$1.authWindow.closed || this$1.authWindow.closed === undefined) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('Auth popup window closed')); + } } try { - var popupWindowPath = getFullUrlPath(this$1.popup.location); + var authWindow = this$1.authWindow || this$1.iframe.contentWindow; + var authWindowPath = getFullUrlPath(authWindow.location); - if (popupWindowPath === redirectUriPath) { - if (this$1.popup.location.search || this$1.popup.location.hash) { - var query = parseQueryString(this$1.popup.location.search.substring(1).replace(/\/$/, '')); - var hash = parseQueryString(this$1.popup.location.hash.substring(1).replace(/[\/$]/, '')); + if (authWindowPath === redirectUriPath) { + if (authWindow.location.search || authWindow.location.hash) { + var query = parseQueryString(authWindow.location.search.substring(1).replace(/\/$/, '')); + var hash = parseQueryString(authWindow.location.hash.substring(1).replace(/[\/$]/, '')); var params = objectExtend({}, query); params = objectExtend(params, hash); @@ -883,30 +921,50 @@ OAuthPopup.prototype.pooling = function pooling (redirectUri) { } else { reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')); } - + clearTimeout(authTimeout); clearInterval(poolingInterval); poolingInterval = null; - this$1.popup.close(); + if (this$1.iframe) { + if (this$1.authContextOptions.iframeTarget) { + this$1.iframeTarget.removeChild(this$1.iframe); + } else { + document.body.removeChild(this$1.iframeTarget); + } + } else { + this$1.authWindow.close(); + } + } else { + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); + } + } + } catch (e) { // DOMException: Blocked a frame with origin from accessing a cross-origin frame. + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); } - } catch(e) { - // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. } }, 250); }) }; -OAuthPopup.prototype._stringifyOptions = function _stringifyOptions () { +OAuthContext.prototype._stringifyOptions = function _stringifyOptions () { var this$1 = this; var options = []; - for (var optionKey in this$1.popupOptions) { - if (!isUndefined(this$1.popupOptions[optionKey])) { - options.push((optionKey + "=" + (this$1.popupOptions[optionKey]))); + for (var optionKey in this$1.authContextOptions) { + if (!isUndefined(this$1.authContextOptions[optionKey])) { + options.push((optionKey + "=" + (this$1.authContextOptions[optionKey]))); } } return options.join(',') }; +Object.defineProperties( OAuthContext.prototype, prototypeAccessors ); + var defaultProviderConfig = { name: null, url: null, @@ -918,7 +976,7 @@ var defaultProviderConfig = { requiredUrlParams: null, defaultUrlParams: null, oauthType: '1.0', - popupOptions: {} + authContextOptions: {} }; var OAuth = function OAuth($http, storage, providerConfig, options) { @@ -930,17 +988,17 @@ var OAuth = function OAuth($http, storage, providerConfig, options) { }; /** - * Initialize OAuth1 process + * Initialize OAuth1 process * @param{Object} userData User data * @return {Promise} */ OAuth.prototype.init = function init (userData) { var this$1 = this; - this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext('about:blank', this.providerConfig.name, this.providerConfig.authContextOptions); if (window && !window['cordova']) { - this.oauthPopup.open(this.providerConfig.redirectUri, true); + this.oauthContext.open(this.providerConfig.redirectUri, true); } return this.getRequestToken().then(function (response) { @@ -976,11 +1034,11 @@ OAuth.prototype.getRequestToken = function getRequestToken () { OAuth.prototype.openPopup = function openPopup (response) { var url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); - this.oauthPopup.popup.location = url; + this.oauthContext.popup.location = url; if (window && window['cordova']) { - return this.oauthPopup.open(this.providerConfig.redirectUri) + return this.oauthContext.open(this.providerConfig.redirectUri) } else { - return this.oauthPopup.pooling(this.providerConfig.redirectUri) + return this.oauthContext.pooling(this.providerConfig.redirectUri) } }; @@ -1037,7 +1095,7 @@ var defaultProviderConfig$1 = { redirectUri: 'redirectUri' }, oauthType: '2.0', - popupOptions: {} + authContextOptions: {} }; var OAuth2 = function OAuth2($http, storage, providerConfig, options) { @@ -1060,10 +1118,10 @@ OAuth2.prototype.init = function init (userData) { var url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?'); - this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext(url, this.providerConfig.name, this.providerConfig.authContextOptions); return new Promise(function (resolve, reject) { - this$1.oauthPopup.open(this$1.providerConfig.redirectUri).then(function (response) { + this$1.oauthContext.open(this$1.providerConfig.redirectUri).then(function (response) { if (this$1.providerConfig.responseType === 'token' || !this$1.providerConfig.url) { return resolve(response) } @@ -1083,7 +1141,7 @@ OAuth2.prototype.init = function init (userData) { * Exchange temporary oauth data for access token * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param{[type]} oauth [description] * @param{[type]} userData [description] * @return {[type]} [description] @@ -1131,7 +1189,7 @@ OAuth2.prototype.exchangeForToken = function exchangeForToken (oauth, userData) * Stringify oauth params * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @return {String} */ OAuth2.prototype._stringifyRequestParams = function _stringifyRequestParams () { diff --git a/dist/vue-authenticate.min.js b/dist/vue-authenticate.min.js index ef6d989..7aad764 100644 --- a/dist/vue-authenticate.min.js +++ b/dist/vue-authenticate.min.js @@ -1,7 +1,7 @@ /*! - * vue-authenticate v1.3.5-beta.1 + * vue-authenticate v1.3.5-beta.1.4 * https://github.com/dgrubelic/vue-authenticate * Released under the MIT License. */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.VueAuthenticate=e()}(this,function(){"use strict";function t(t){return t.replace(/([\:\-\_]+(.))/g,function(t,e,o,n){return n?o.toUpperCase():o})}function e(t){return void 0===t}function o(t){return null!==t&&"object"==typeof t}function n(t){return"string"==typeof t}function r(t){return"function"==typeof t}function i(t,e){return null==t||null==e?t:(Object.keys(e).forEach(function(o){"[object Object]"==Object.prototype.toString.call(e[o])?"[object Object]"!=Object.prototype.toString.call(t[o])?t[o]=e[o]:t[o]=i(t[o],e[o]):t[o]=e[o]}),t)}function a(t,e){if(/^(?:[a-z]+:)?\/\//i.test(e))return e;var o=[t,e].join("/");return function(t){return t.replace(/[\/]+/g,"/").replace(/\/\?/g,"?").replace(/\/\#/g,"#").replace(/\:\//g,"://")}(o)}function s(t){var e="https:"===t.protocol;return t.protocol+"//"+t.hostname+":"+(t.port||(e?"443":"80"))+(/^\//.test(t.pathname)?t.pathname:"/"+t.pathname)}function u(t){var e,o,n={};return(t||"").split("&").forEach(function(t){t&&(o=t.split("="),e=decodeURIComponent(o[0]),n[e]=!o[1]||decodeURIComponent(o[1]))}),n}function p(t){var e;if("undefined"!=typeof module&&module.exports)try{e=require("buffer").Buffer}catch(t){}var o=String.fromCharCode,n=new RegExp(["[À-ß][€-¿]","[à-ï][€-¿]{2}","[ð-÷][€-¿]{3}"].join("|"),"g"),r=function(t){switch(t.length){case 4:var e=(7&t.charCodeAt(0))<<18|(63&t.charCodeAt(1))<<12|(63&t.charCodeAt(2))<<6|63&t.charCodeAt(3),n=e-65536;return o(55296+(n>>>10))+o(56320+(1023&n));case 3:return o((15&t.charCodeAt(0))<<12|(63&t.charCodeAt(1))<<6|63&t.charCodeAt(2));default:return o((31&t.charCodeAt(0))<<6|63&t.charCodeAt(1))}},i=function(t){return t.replace(n,r)};return(e?function(t){return(t.constructor===e.constructor?t:new e(t,"base64")).toString()}:function(t){return i(atob(t))})(String(t).replace(/[-_]/g,function(t){return"-"===t?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))}function c(t){if(0===t.length)return{};var e={},o=new RegExp("\\s*;\\s*");return t.split(o).forEach(function(t){var o=t.split("="),n=o[0],r=o[1],i=decodeURIComponent(n),a=decodeURIComponent(r);e[i]=a}),e}function h(t){var e=t.path,o=t.domain,n=t.expires,r=t.secure;return[void 0===e||null===e?"":";path="+e,void 0===o||null===o?"":";domain="+o,void 0===n||null===n?"":";expires="+n.toUTCString(),void 0===r||null===r||!1===r?"":";secure"].join("")}function l(t,e,o){return[encodeURIComponent(t),"=",encodeURIComponent(e),h(o)].join("")}function d(){}function f(t,e){return function(){t.apply(e,arguments)}}function g(t){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof t)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],_(t,this)}function m(t,e){for(;3===t._state;)t=t._value;if(0===t._state)return void t._deferreds.push(e);t._handled=!0,g._immediateFn(function(){var o=1===t._state?e.onFulfilled:e.onRejected;if(null===o)return void(1===t._state?v:y)(e.promise,t._value);var n;try{n=o(t._value)}catch(t){return void y(e.promise,t)}v(e.promise,n)})}function v(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var o=e.then;if(e instanceof g)return t._state=3,t._value=e,void w(t);if("function"==typeof o)return void _(f(o,e),t)}t._state=1,t._value=e,w(t)}catch(e){y(t,e)}}function y(t,e){t._state=2,t._value=e,w(t)}function w(t){2===t._state&&0===t._deferreds.length&&g._immediateFn(function(){t._handled||g._unhandledRejectionFn(t._value)});for(var e=0,o=t._deferreds.length;e>>10))+o(56320+(1023&n));case 3:return o((15&t.charCodeAt(0))<<12|(63&t.charCodeAt(1))<<6|63&t.charCodeAt(2));default:return o((31&t.charCodeAt(0))<<6|63&t.charCodeAt(1))}},i=function(t){return t.replace(n,r)};return(e?function(t){return(t.constructor===e.constructor?t:new e(t,"base64")).toString()}:function(t){return i(atob(t))})(String(t).replace(/[-_]/g,function(t){return"-"===t?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))}function h(t){if(0===t.length)return{};var e={},o=new RegExp("\\s*;\\s*");return t.split(o).forEach(function(t){var o=t.split("="),n=o[0],r=o[1],i=decodeURIComponent(n),a=decodeURIComponent(r);e[i]=a}),e}function p(t){var e=t.path,o=t.domain,n=t.expires,r=t.secure;return[void 0===e||null===e?"":";path="+e,void 0===o||null===o?"":";domain="+o,void 0===n||null===n?"":";expires="+n.toUTCString(),void 0===r||null===r||!1===r?"":";secure"].join("")}function l(t,e,o){return[encodeURIComponent(t),"=",encodeURIComponent(e),p(o)].join("")}function d(){}function f(t,e){return function(){t.apply(e,arguments)}}function m(t){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof t)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],_(t,this)}function g(t,e){for(;3===t._state;)t=t._value;if(0===t._state)return void t._deferreds.push(e);t._handled=!0,m._immediateFn(function(){var o=1===t._state?e.onFulfilled:e.onRejected;if(null===o)return void(1===t._state?v:y)(e.promise,t._value);var n;try{n=o(t._value)}catch(t){return void y(e.promise,t)}v(e.promise,n)})}function v(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var o=e.then;if(e instanceof m)return t._state=3,t._value=e,void w(t);if("function"==typeof o)return void _(f(o,e),t)}t._state=1,t._value=e,w(t)}catch(e){y(t,e)}}function y(t,e){t._state=2,t._value=e,w(t)}function w(t){2===t._state&&0===t._deferreds.length&&m._immediateFn(function(){t._handled||m._unhandledRejectionFn(t._value)});for(var e=0,o=t._deferreds.length;e * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} baseUrl Base url * @param {String} url URI * @return {String} @@ -112,26 +112,29 @@ function joinUrl(baseUrl, url) { /** * Get full path based on current location - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {Location} location * @return {String} */ -function getFullUrlPath(location) { +function getFullUrlPath (location) { var isHttps = location.protocol === 'https:'; - return location.protocol + '//' + location.hostname + - ':' + (location.port || (isHttps ? '443' : '80')) + - (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); + var port = location.port; + if (!port || port === '0') { + port = isHttps ? '443' : '80'; + } + return location.protocol + '//' + location.hostname + ':' + port + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname) } /** * Parse query string variables - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} Query string * @return {String} */ @@ -153,7 +156,7 @@ function parseQueryString(str) { * Decode base64 string * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} str base64 encoded string * @return {Object} */ @@ -554,7 +557,7 @@ var defaultOptions = { scopeDelimiter: ',', display: 'popup', oauthType: '2.0', - popupOptions: { width: 580, height: 400 } + authContextOptions: { width: 580, height: 400 } }, google: { @@ -569,7 +572,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 452, height: 633 } + authContextOptions: { width: 452, height: 633 } }, github: { @@ -581,7 +584,7 @@ var defaultOptions = { scope: ['user:email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, instagram: { @@ -593,7 +596,7 @@ var defaultOptions = { scope: ['basic'], scopeDelimiter: '+', oauthType: '2.0', - popupOptions: { width: null, height: null } + authContextOptions: { width: null, height: null } }, twitter: { @@ -602,7 +605,7 @@ var defaultOptions = { authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: { width: 495, height: 645 } + authContextOptions: { width: 495, height: 645 } }, bitbucket: { @@ -614,7 +617,7 @@ var defaultOptions = { scope: ['email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, linkedin: { @@ -627,7 +630,7 @@ var defaultOptions = { scopeDelimiter: ' ', state: 'STATE', oauthType: '2.0', - popupOptions: { width: 527, height: 582 } + authContextOptions: { width: 527, height: 582 } }, live: { @@ -640,7 +643,7 @@ var defaultOptions = { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 500, height: 560 } + authContextOptions: { width: 500, height: 560 } }, oauth1: { @@ -649,7 +652,7 @@ var defaultOptions = { authorizationEndpoint: null, redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: null + authContextOptions: null }, oauth2: { @@ -666,7 +669,7 @@ var defaultOptions = { scopeDelimiter: null, state: null, oauthType: '2.0', - popupOptions: null, + authContextOptions: null, responseType: 'code', responseParams: { code: 'code', @@ -820,24 +823,34 @@ function StorageFactory(options) { } /** - * OAuth2 popup management class - * + * OAuth2 popup/iframe management class + * * @author Sahat Yalkabov - * @copyright Class mostly taken from https://github.com/sahat/satellizer + * @copyright Class mostly taken from https://github.com/sahat/satellizer/blob/master/src/popup.ts * and adjusted to fit vue-authenticate library */ -var OAuthPopup = function OAuthPopup(url, name, popupOptions) { - this.popup = null; +var OAuthContext = function OAuthContext (url, name, authContextOptions) { + if ( authContextOptions === void 0 ) authContextOptions = {}; + + this.authWindow = null; this.url = url; this.name = name; - this.popupOptions = popupOptions; + this.authContextOptions = authContextOptions; }; -OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { +var prototypeAccessors = { iframeTarget: {} }; + +OAuthContext.prototype.open = function open (redirectUri, skipPooling) { try { - this.popup = window.open(this.url, this.name, this._stringifyOptions()); - if (this.popup && this.popup.focus) { - this.popup.focus(); + if (this.authContextOptions.iframe) { + this.iframe = document.createElement('iframe'); + this.iframe.src = this.url; + this.iframeTarget.appendChild(this.iframe); + } else { + this.authWindow = window.open(this.url, this.name, this._stringifyOptions()); + if (this.authWindow && this.authWindow.focus) { + this.authWindow.focus(); + } } if (skipPooling) { @@ -845,33 +858,58 @@ OAuthPopup.prototype.open = function open (redirectUri, skipPooling) { } else { return this.pooling(redirectUri) } - } catch(e) { - return Promise$1.reject(new Error('OAuth popup error occurred')) + } catch (e) { + return Promise$1.reject(new Error('Error occurred while opening authentication context')) + } +}; + +prototypeAccessors.iframeTarget.get = function () { + if (!this._iframeTarget) { + if (this.authContextOptions.iframeTarget) { + this._iframeTarget = this.authContextOptions.iframeTarget; + } else { + this._iframeTarget = document.createElement('div'); + this._iframeTarget.style.display = 'none'; + document.body.appendChild(this._iframeTarget); + } } + + return this._iframeTarget }; -OAuthPopup.prototype.pooling = function pooling (redirectUri) { +OAuthContext.prototype.pooling = function pooling (redirectUri) { var this$1 = this; + this.timedOut = false; + var authTimeout; + if (this.authContextOptions.timeout) { + authTimeout = setTimeout(function () { + this$1.timedOut = true; + }, this.authContextOptions.timeout); + } + return new Promise$1(function (resolve, reject) { var redirectUriParser = document.createElement('a'); redirectUriParser.href = redirectUri; var redirectUriPath = getFullUrlPath(redirectUriParser); - + var poolingInterval = setInterval(function () { - if (!this$1.popup || this$1.popup.closed || this$1.popup.closed === undefined) { - clearInterval(poolingInterval); - poolingInterval = null; - reject(new Error('Auth popup window closed')); + if (!this$1.iframe) { + if (!this$1.authWindow || this$1.authWindow.closed || this$1.authWindow.closed === undefined) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('Auth popup window closed')); + } } try { - var popupWindowPath = getFullUrlPath(this$1.popup.location); + var authWindow = this$1.authWindow || this$1.iframe.contentWindow; + var authWindowPath = getFullUrlPath(authWindow.location); - if (popupWindowPath === redirectUriPath) { - if (this$1.popup.location.search || this$1.popup.location.hash) { - var query = parseQueryString(this$1.popup.location.search.substring(1).replace(/\/$/, '')); - var hash = parseQueryString(this$1.popup.location.hash.substring(1).replace(/[\/$]/, '')); + if (authWindowPath === redirectUriPath) { + if (authWindow.location.search || authWindow.location.hash) { + var query = parseQueryString(authWindow.location.search.substring(1).replace(/\/$/, '')); + var hash = parseQueryString(authWindow.location.hash.substring(1).replace(/[\/$]/, '')); var params = objectExtend({}, query); params = objectExtend(params, hash); @@ -883,30 +921,50 @@ OAuthPopup.prototype.pooling = function pooling (redirectUri) { } else { reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')); } - + clearTimeout(authTimeout); clearInterval(poolingInterval); poolingInterval = null; - this$1.popup.close(); + if (this$1.iframe) { + if (this$1.authContextOptions.iframeTarget) { + this$1.iframeTarget.removeChild(this$1.iframe); + } else { + document.body.removeChild(this$1.iframeTarget); + } + } else { + this$1.authWindow.close(); + } + } else { + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); + } + } + } catch (e) { // DOMException: Blocked a frame with origin from accessing a cross-origin frame. + if (this$1.timedOut) { + clearInterval(poolingInterval); + poolingInterval = null; + reject(new Error('auth_timeout')); } - } catch(e) { - // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. } }, 250); }) }; -OAuthPopup.prototype._stringifyOptions = function _stringifyOptions () { +OAuthContext.prototype._stringifyOptions = function _stringifyOptions () { var this$1 = this; var options = []; - for (var optionKey in this$1.popupOptions) { - if (!isUndefined(this$1.popupOptions[optionKey])) { - options.push((optionKey + "=" + (this$1.popupOptions[optionKey]))); + for (var optionKey in this$1.authContextOptions) { + if (!isUndefined(this$1.authContextOptions[optionKey])) { + options.push((optionKey + "=" + (this$1.authContextOptions[optionKey]))); } } return options.join(',') }; +Object.defineProperties( OAuthContext.prototype, prototypeAccessors ); + var defaultProviderConfig = { name: null, url: null, @@ -918,7 +976,7 @@ var defaultProviderConfig = { requiredUrlParams: null, defaultUrlParams: null, oauthType: '1.0', - popupOptions: {} + authContextOptions: {} }; var OAuth = function OAuth($http, storage, providerConfig, options) { @@ -930,17 +988,17 @@ var OAuth = function OAuth($http, storage, providerConfig, options) { }; /** - * Initialize OAuth1 process + * Initialize OAuth1 process * @param{Object} userData User data * @return {Promise} */ OAuth.prototype.init = function init (userData) { var this$1 = this; - this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext('about:blank', this.providerConfig.name, this.providerConfig.authContextOptions); if (window && !window['cordova']) { - this.oauthPopup.open(this.providerConfig.redirectUri, true); + this.oauthContext.open(this.providerConfig.redirectUri, true); } return this.getRequestToken().then(function (response) { @@ -976,11 +1034,11 @@ OAuth.prototype.getRequestToken = function getRequestToken () { OAuth.prototype.openPopup = function openPopup (response) { var url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); - this.oauthPopup.popup.location = url; + this.oauthContext.popup.location = url; if (window && window['cordova']) { - return this.oauthPopup.open(this.providerConfig.redirectUri) + return this.oauthContext.open(this.providerConfig.redirectUri) } else { - return this.oauthPopup.pooling(this.providerConfig.redirectUri) + return this.oauthContext.pooling(this.providerConfig.redirectUri) } }; @@ -1037,7 +1095,7 @@ var defaultProviderConfig$1 = { redirectUri: 'redirectUri' }, oauthType: '2.0', - popupOptions: {} + authContextOptions: {} }; var OAuth2 = function OAuth2($http, storage, providerConfig, options) { @@ -1060,10 +1118,10 @@ OAuth2.prototype.init = function init (userData) { var url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?'); - this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions); + this.oauthContext = new OAuthContext(url, this.providerConfig.name, this.providerConfig.authContextOptions); return new Promise(function (resolve, reject) { - this$1.oauthPopup.open(this$1.providerConfig.redirectUri).then(function (response) { + this$1.oauthContext.open(this$1.providerConfig.redirectUri).then(function (response) { if (this$1.providerConfig.responseType === 'token' || !this$1.providerConfig.url) { return resolve(response) } @@ -1083,7 +1141,7 @@ OAuth2.prototype.init = function init (userData) { * Exchange temporary oauth data for access token * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param{[type]} oauth [description] * @param{[type]} userData [description] * @return {[type]} [description] @@ -1131,7 +1189,7 @@ OAuth2.prototype.exchangeForToken = function exchangeForToken (oauth, userData) * Stringify oauth params * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @return {String} */ OAuth2.prototype._stringifyRequestParams = function _stringifyRequestParams () { diff --git a/package.json b/package.json index bb767fe..753f79e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "vue-authenticate", - "version": "1.3.5-beta.1", + "name": "@bsarvari/vue-authenticate", + "version": "1.3.5-beta.1.4", "description": "Authentication library for Vue.js", "main": "dist/vue-authenticate.js", "module": "dist/vue-authenticate.es2015.js", @@ -19,9 +19,13 @@ "email": "davor.grubelic@gmail.com", "url": "https://dgrubelic.me" }, + "contributors": [{ + "name": "Balazs Sarvari", + "email": "balazs.sarvari@gmail.com" + }], "repository": { "type": "git", - "url": "https://github.com/dgrubelic/vue-authenticate.git" + "url": "https://github.com/bsarvari/vue-authenticate.git" }, "license": "MIT", "devDependencies": { @@ -61,11 +65,13 @@ }, "tags": [ "auth", + "oauth", "authentication", "login", "registration", "jwt", "token", + "iframe", "vuejs", "vue.js", "github", diff --git a/src/oauth/oauth-context.js b/src/oauth/oauth-context.js new file mode 100644 index 0000000..fd8cb84 --- /dev/null +++ b/src/oauth/oauth-context.js @@ -0,0 +1,137 @@ +import Promise from '../promise.js' +import { objectExtend, parseQueryString, getFullUrlPath, isUndefined } from '../utils.js' + +/** + * OAuth2 popup/iframe management class + * + * @author Sahat Yalkabov + * @copyright Class mostly taken from https://github.com/sahat/satellizer/blob/master/src/popup.ts + * and adjusted to fit vue-authenticate library + */ +export default class OAuthContext { + constructor (url, name, authContextOptions = {}) { + this.authWindow = null + this.url = url + this.name = name + this.authContextOptions = authContextOptions + } + + open (redirectUri, skipPooling) { + try { + if (this.authContextOptions.iframe) { + this.iframe = document.createElement('iframe') + this.iframe.src = this.url + this.iframeTarget.appendChild(this.iframe) + } else { + this.authWindow = window.open(this.url, this.name, this._stringifyOptions()) + if (this.authWindow && this.authWindow.focus) { + this.authWindow.focus() + } + } + + if (skipPooling) { + return Promise.resolve() + } else { + return this.pooling(redirectUri) + } + } catch (e) { + return Promise.reject(new Error('Error occurred while opening authentication context')) + } + } + + get iframeTarget () { + if (!this._iframeTarget) { + if (this.authContextOptions.iframeTarget) { + this._iframeTarget = this.authContextOptions.iframeTarget + } else { + this._iframeTarget = document.createElement('div') + this._iframeTarget.style.display = 'none' + document.body.appendChild(this._iframeTarget) + } + } + + return this._iframeTarget + } + + pooling (redirectUri) { + this.timedOut = false + let authTimeout + if (this.authContextOptions.timeout) { + authTimeout = setTimeout(() => { + this.timedOut = true + }, this.authContextOptions.timeout) + } + + return new Promise((resolve, reject) => { + const redirectUriParser = document.createElement('a') + redirectUriParser.href = redirectUri + const redirectUriPath = getFullUrlPath(redirectUriParser) + + let poolingInterval = setInterval(() => { + if (!this.iframe) { + if (!this.authWindow || this.authWindow.closed || this.authWindow.closed === undefined) { + clearInterval(poolingInterval) + poolingInterval = null + reject(new Error('Auth popup window closed')) + } + } + + try { + const authWindow = this.authWindow || this.iframe.contentWindow + const authWindowPath = getFullUrlPath(authWindow.location) + + if (authWindowPath === redirectUriPath) { + if (authWindow.location.search || authWindow.location.hash) { + const query = parseQueryString(authWindow.location.search.substring(1).replace(/\/$/, '')) + const hash = parseQueryString(authWindow.location.hash.substring(1).replace(/[\/$]/, '')) + let params = objectExtend({}, query) + params = objectExtend(params, hash) + + if (params.error) { + reject(new Error(params.error)) + } else { + resolve(params) + } + } else { + reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')) + } + clearTimeout(authTimeout) + clearInterval(poolingInterval) + poolingInterval = null + if (this.iframe) { + if (this.authContextOptions.iframeTarget) { + this.iframeTarget.removeChild(this.iframe) + } else { + document.body.removeChild(this.iframeTarget) + } + } else { + this.authWindow.close() + } + } else { + if (this.timedOut) { + clearInterval(poolingInterval) + poolingInterval = null + reject(new Error('auth_timeout')) + } + } + } catch (e) { // DOMException: Blocked a frame with origin from accessing a cross-origin frame. + if (this.timedOut) { + clearInterval(poolingInterval) + poolingInterval = null + reject(new Error('auth_timeout')) + } + } + }, 250) + }) + } + + _stringifyOptions () { + let options = [] + for (var optionKey in this.authContextOptions) { + if (!isUndefined(this.authContextOptions[optionKey])) { + options.push(`${optionKey}=${this.authContextOptions[optionKey]}`) + } + } + return options.join(',') + } +} diff --git a/src/oauth/oauth1.js b/src/oauth/oauth1.js index 3014b86..05448bc 100644 --- a/src/oauth/oauth1.js +++ b/src/oauth/oauth1.js @@ -1,4 +1,4 @@ -import OAuthPopup from './popup.js' +import OAuthContext from './oauth-context.js' import { objectExtend, isString, isObject, isFunction, joinUrl } from '../utils.js' const defaultProviderConfig = { @@ -12,7 +12,7 @@ const defaultProviderConfig = { requiredUrlParams: null, defaultUrlParams: null, oauthType: '1.0', - popupOptions: {} + authContextOptions: {} } export default class OAuth { @@ -25,15 +25,15 @@ export default class OAuth { } /** - * Initialize OAuth1 process + * Initialize OAuth1 process * @param {Object} userData User data * @return {Promise} */ init(userData) { - this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions) + this.oauthContext = new OAuthContext('about:blank', this.providerConfig.name, this.providerConfig.authContextOptions) if (window && !window['cordova']) { - this.oauthPopup.open(this.providerConfig.redirectUri, true) + this.oauthContext.open(this.providerConfig.redirectUri, true) } return this.getRequestToken().then((response) => { @@ -69,11 +69,11 @@ export default class OAuth { openPopup(response) { const url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); - this.oauthPopup.popup.location = url + this.oauthContext.popup.location = url if (window && window['cordova']) { - return this.oauthPopup.open(this.providerConfig.redirectUri) + return this.oauthContext.open(this.providerConfig.redirectUri) } else { - return this.oauthPopup.pooling(this.providerConfig.redirectUri) + return this.oauthContext.pooling(this.providerConfig.redirectUri) } } @@ -106,4 +106,4 @@ export default class OAuth { } return parsedParams.join('&'); } -} \ No newline at end of file +} diff --git a/src/oauth/oauth2.js b/src/oauth/oauth2.js index f527ad1..3b6c300 100644 --- a/src/oauth/oauth2.js +++ b/src/oauth/oauth2.js @@ -1,4 +1,4 @@ -import OAuthPopup from './popup.js' +import OAuthContext from './oauth-context.js' import { camelCase, isFunction, isString, objectExtend, joinUrl } from '../utils.js' /** @@ -24,7 +24,7 @@ const defaultProviderConfig = { redirectUri: 'redirectUri' }, oauthType: '2.0', - popupOptions: {} + authContextOptions: {} } export default class OAuth2 { @@ -46,10 +46,10 @@ export default class OAuth2 { let url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?') - this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions) + this.oauthContext = new OAuthContext(url, this.providerConfig.name, this.providerConfig.authContextOptions) return new Promise((resolve, reject) => { - this.oauthPopup.open(this.providerConfig.redirectUri).then((response) => { + this.oauthContext.open(this.providerConfig.redirectUri).then((response) => { if (this.providerConfig.responseType === 'token' || !this.providerConfig.url) { return resolve(response) } @@ -69,7 +69,7 @@ export default class OAuth2 { * Exchange temporary oauth data for access token * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {[type]} oauth [description] * @param {[type]} userData [description] * @return {[type]} [description] @@ -115,7 +115,7 @@ export default class OAuth2 { * Stringify oauth params * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @return {String} */ _stringifyRequestParams() { @@ -151,4 +151,4 @@ export default class OAuth2 { return param.join('=') }).join('&') } -} \ No newline at end of file +} diff --git a/src/oauth/popup.js b/src/oauth/popup.js deleted file mode 100644 index e75fcf4..0000000 --- a/src/oauth/popup.js +++ /dev/null @@ -1,88 +0,0 @@ -import Promise from '../promise.js' -import { objectExtend, parseQueryString, getFullUrlPath, isUndefined } from '../utils.js' - -/** - * OAuth2 popup management class - * - * @author Sahat Yalkabov - * @copyright Class mostly taken from https://github.com/sahat/satellizer - * and adjusted to fit vue-authenticate library - */ -export default class OAuthPopup { - constructor(url, name, popupOptions) { - this.popup = null - this.url = url - this.name = name - this.popupOptions = popupOptions - } - - open(redirectUri, skipPooling) { - try { - this.popup = window.open(this.url, this.name, this._stringifyOptions()) - if (this.popup && this.popup.focus) { - this.popup.focus() - } - - if (skipPooling) { - return Promise.resolve() - } else { - return this.pooling(redirectUri) - } - } catch(e) { - return Promise.reject(new Error('OAuth popup error occurred')) - } - } - - pooling(redirectUri) { - return new Promise((resolve, reject) => { - const redirectUriParser = document.createElement('a') - redirectUriParser.href = redirectUri - const redirectUriPath = getFullUrlPath(redirectUriParser) - - let poolingInterval = setInterval(() => { - if (!this.popup || this.popup.closed || this.popup.closed === undefined) { - clearInterval(poolingInterval) - poolingInterval = null - reject(new Error('Auth popup window closed')) - } - - try { - const popupWindowPath = getFullUrlPath(this.popup.location) - - if (popupWindowPath === redirectUriPath) { - if (this.popup.location.search || this.popup.location.hash) { - const query = parseQueryString(this.popup.location.search.substring(1).replace(/\/$/, '')); - const hash = parseQueryString(this.popup.location.hash.substring(1).replace(/[\/$]/, '')); - let params = objectExtend({}, query); - params = objectExtend(params, hash) - - if (params.error) { - reject(new Error(params.error)); - } else { - resolve(params); - } - } else { - reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')) - } - - clearInterval(poolingInterval) - poolingInterval = null - this.popup.close() - } - } catch(e) { - // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. - } - }, 250) - }) - } - - _stringifyOptions() { - let options = [] - for (var optionKey in this.popupOptions) { - if (!isUndefined(this.popupOptions[optionKey])) { - options.push(`${optionKey}=${this.popupOptions[optionKey]}`) - } - } - return options.join(',') - } -} \ No newline at end of file diff --git a/src/options.js b/src/options.js index a5d34bb..5f54eaa 100644 --- a/src/options.js +++ b/src/options.js @@ -70,7 +70,7 @@ export default { scopeDelimiter: ',', display: 'popup', oauthType: '2.0', - popupOptions: { width: 580, height: 400 } + authContextOptions: { width: 580, height: 400 } }, google: { @@ -85,7 +85,7 @@ export default { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 452, height: 633 } + authContextOptions: { width: 452, height: 633 } }, github: { @@ -97,7 +97,7 @@ export default { scope: ['user:email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, instagram: { @@ -109,7 +109,7 @@ export default { scope: ['basic'], scopeDelimiter: '+', oauthType: '2.0', - popupOptions: { width: null, height: null } + authContextOptions: { width: null, height: null } }, twitter: { @@ -118,7 +118,7 @@ export default { authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: { width: 495, height: 645 } + authContextOptions: { width: 495, height: 645 } }, bitbucket: { @@ -130,7 +130,7 @@ export default { scope: ['email'], scopeDelimiter: ' ', oauthType: '2.0', - popupOptions: { width: 1020, height: 618 } + authContextOptions: { width: 1020, height: 618 } }, linkedin: { @@ -143,7 +143,7 @@ export default { scopeDelimiter: ' ', state: 'STATE', oauthType: '2.0', - popupOptions: { width: 527, height: 582 } + authContextOptions: { width: 527, height: 582 } }, live: { @@ -156,7 +156,7 @@ export default { scopeDelimiter: ' ', display: 'popup', oauthType: '2.0', - popupOptions: { width: 500, height: 560 } + authContextOptions: { width: 500, height: 560 } }, oauth1: { @@ -165,7 +165,7 @@ export default { authorizationEndpoint: null, redirectUri: getRedirectUri(), oauthType: '1.0', - popupOptions: null + authContextOptions: null }, oauth2: { @@ -182,7 +182,7 @@ export default { scopeDelimiter: null, state: null, oauthType: '2.0', - popupOptions: null, + authContextOptions: null, responseType: 'code', responseParams: { code: 'code', diff --git a/src/utils.js b/src/utils.js index 883999d..e2c64a5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -77,10 +77,10 @@ export function objectExtend(a, b) { /** * Assemble url from two segments - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} baseUrl Base url * @param {String} url URI * @return {String} @@ -102,26 +102,29 @@ export function joinUrl(baseUrl, url) { /** * Get full path based on current location - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {Location} location * @return {String} */ -export function getFullUrlPath(location) { - const isHttps = location.protocol === 'https:'; - return location.protocol + '//' + location.hostname + - ':' + (location.port || (isHttps ? '443' : '80')) + - (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); +export function getFullUrlPath (location) { + const isHttps = location.protocol === 'https:' + let port = location.port + if (!port || port === '0') { + port = isHttps ? '443' : '80' + } + return location.protocol + '//' + location.hostname + ':' + port + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname) } /** * Parse query string variables - * + * * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} Query string * @return {String} */ @@ -143,7 +146,7 @@ export function parseQueryString(str) { * Decode base64 string * @author Sahat Yalkabov * @copyright Method taken from https://github.com/sahat/satellizer - * + * * @param {String} str base64 encoded string * @return {Object} */ diff --git a/src/utils.test.js b/src/utils.test.js new file mode 100644 index 0000000..41a5d96 --- /dev/null +++ b/src/utils.test.js @@ -0,0 +1,34 @@ +import { getFullUrlPath } from './utils' + +const urlParser = url => { + const a = document.createElement('a') + a.href = url + return a +} + +describe('vue-authenticate/utils', () => { + describe('#getFullUrlPath', () => { + describe('with no port in URL', () => { + it('should add port 80 for http URL', () => { + expect(getFullUrlPath(urlParser('http://example.com/auth'))).toBe('http://example.com:80/auth') + }) + it('should add port 443 for https URL', () => { + expect(getFullUrlPath(urlParser('https://example.com/auth'))).toBe('https://example.com:443/auth') + }) + }) + describe('with port in URL', () => { + it('should respect port other than "0" for http URL', () => { + expect(getFullUrlPath(urlParser('http://example.com:2999/auth'))).toBe('http://example.com:2999/auth') + }) + it('should respect port other than "0" for https URL', () => { + expect(getFullUrlPath(urlParser('https://example.com:1/auth'))).toBe('https://example.com:1/auth') + }) + it('should convert port "0" to "80" for http URL', () => { + expect(getFullUrlPath(urlParser('http://example.com:0/auth'))).toBe('http://example.com:80/auth') + }) + it('should convert port "0" to "443" for https URL', () => { + expect(getFullUrlPath(urlParser('https://example.com:0/auth'))).toBe('https://example.com:443/auth') + }) + }) + }) +})