vue-authenticate
Version:
Authentication library for Vue.js
1,641 lines (1,459 loc) • 41.9 kB
JavaScript
/**
* vue-authenticate v1.5.0
* https://github.com/dgrubelic/vue-authenticate
* Released under the MIT License.
*
*/
if (typeof Object.assign != 'function') {
Object.assign = function (target, varArgs) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) {
// Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}
function camelCase(name) {
return name.replace(/([\:\-\_]+(.))/g, function (
_,
separator,
letter,
offset
) {
return offset ? letter.toUpperCase() : letter;
});
}
function isUndefined(value) {
return typeof value === 'undefined';
}
function isObject(value) {
return value !== null && typeof value === 'object';
}
function isString(value) {
return typeof value === 'string';
}
function isFunction(value) {
return typeof value === 'function';
}
function objectExtend(a, b) {
// Don't touch 'null' or 'undefined' objects.
if (a == null || b == null) {
return a;
}
Object.keys(b).forEach(function (key) {
if (Object.prototype.toString.call(b[key]) == '[object Object]') {
if (Object.prototype.toString.call(a[key]) != '[object Object]') {
a[key] = b[key];
} else {
a[key] = objectExtend(a[key], b[key]);
}
} else {
a[key] = b[key];
}
});
return a;
}
/**
* Assemble url from two segments
*
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @param {String} baseUrl Base url
* @param {String} url URI
* @return {String}
*/
function joinUrl(baseUrl, url) {
if (/^(?:[a-z]+:)?\/\//i.test(url)) {
return url;
}
let joined = [baseUrl, url].join('/');
let normalize = function (str) {
return str
.replace(/[\/]+/g, '/')
.replace(/\/\?/g, '?')
.replace(/\/\#/g, '#')
.replace(/\:\//g, '://');
};
return normalize(joined);
}
/**
* Get full path based on current location
*
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @param {Location} location
* @return {String}
*/
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)
);
}
/**
* Parse query string variables
*
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @param {String} Query string
* @return {String}
*/
function parseQueryString(str) {
let obj = {};
let key;
let value;
(str || '').split('&').forEach(keyValue => {
if (keyValue) {
value = keyValue.split('=');
key = decodeURIComponent(value[0]);
obj[key] = !!value[1] ? decodeURIComponent(value[1]) : true;
}
});
return obj;
}
/**
* Decode base64 string
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @param {String} str base64 encoded string
* @return {Object}
*/
function decodeBase64(str) {
let buffer;
if (typeof module !== 'undefined' && module.exports) {
try {
buffer = require('buffer').Buffer;
} catch (err) {
// noop
}
}
let fromCharCode = String.fromCharCode;
let re_btou = new RegExp(
[
'[\xC0-\xDF][\x80-\xBF]',
'[\xE0-\xEF][\x80-\xBF]{2}',
'[\xF0-\xF7][\x80-\xBF]{3}',
].join('|'),
'g'
);
let cb_btou = function (cccc) {
switch (cccc.length) {
case 4:
let cp =
((0x07 & cccc.charCodeAt(0)) << 18) |
((0x3f & cccc.charCodeAt(1)) << 12) |
((0x3f & cccc.charCodeAt(2)) << 6) |
(0x3f & cccc.charCodeAt(3));
let offset = cp - 0x10000;
return (
fromCharCode((offset >>> 10) + 0xd800) +
fromCharCode((offset & 0x3ff) + 0xdc00)
);
case 3:
return fromCharCode(
((0x0f & cccc.charCodeAt(0)) << 12) |
((0x3f & cccc.charCodeAt(1)) << 6) |
(0x3f & cccc.charCodeAt(2))
);
default:
return fromCharCode(
((0x1f & cccc.charCodeAt(0)) << 6) | (0x3f & cccc.charCodeAt(1))
);
}
};
let btou = function (b) {
return b.replace(re_btou, cb_btou);
};
let _decode = buffer
? function (a) {
return (a.constructor === buffer.constructor
? a
: new buffer(a, 'base64')
).toString();
}
: function (a) {
return btou(atob(a));
};
return _decode(
String(str)
.replace(/[-_]/g, function (m0) {
return m0 === '-' ? '+' : '/';
})
.replace(/[^A-Za-z0-9\+\/]/g, '')
);
}
function parseCookies(str = '') {
if (str.length === 0) return {};
const parsed = {};
const pattern = new RegExp('\\s*;\\s*');
str.split(pattern).forEach(i => {
const [encodedKey, encodedValue] = i.split('=');
const key = decodeURIComponent(encodedKey);
const value = decodeURIComponent(encodedValue);
parsed[key] = value;
});
return parsed;
}
function formatOptions(options) {
const { path, domain, expires, secure } = options;
return [
typeof path === 'undefined' || path === null ? '' : ';path=' + path,
typeof domain === 'undefined' || domain === null ? '' : ';domain=' + domain,
typeof expires === 'undefined' || expires === null
? ''
: ';expires=' + expires.toUTCString(),
typeof secure === 'undefined' || secure === null || secure === false
? ''
: ';secure',
].join('');
}
function formatCookie(key, value, options) {
return [
encodeURIComponent(key),
'=',
encodeURIComponent(value),
formatOptions(options),
].join('');
}
function getObjectProperty(objectRef, propertyName) {
let value = undefined;
let valueRef = objectRef;
const propNames = propertyName.split('.');
for (var i = 0; i < propNames.length; i++) {
const key = propNames[i];
value = valueRef[key];
if (isObject(value)) {
valueRef = valueRef[key];
} else {
break;
}
}
return value;
}
// Store setTimeout reference so promise-polyfill will be unaffected by
// other code modifying setTimeout (like sinon.useFakeTimers())
var setTimeoutFunc = setTimeout;
function noop() {}
// Polyfill for Function.prototype.bind
function bind(fn, thisArg) {
return function () {
fn.apply(thisArg, arguments);
};
}
function Promise$1(fn) {
if (typeof this !== 'object')
throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise$1._immediateFn(function () {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self)
throw new TypeError('A promise cannot be resolved with itself.');
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
var then = newValue.then;
if (newValue instanceof Promise$1) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
if (self._state === 2 && self._deferreds.length === 0) {
Promise$1._immediateFn(function () {
if (!self._handled) {
Promise$1._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, self) {
var done = false;
try {
fn(
function (value) {
if (done) return;
done = true;
resolve(self, value);
},
function (reason) {
if (done) return;
done = true;
reject(self, reason);
}
);
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise$1.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise$1.prototype.then = function (onFulfilled, onRejected) {
var prom = new this.constructor(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise$1.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise$1(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(
val,
function (val) {
res(i, val);
},
reject
);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
Promise$1.resolve = function (value) {
if (value && typeof value === 'object' && value.constructor === Promise$1) {
return value;
}
return new Promise$1(function (resolve) {
resolve(value);
});
};
Promise$1.reject = function (value) {
return new Promise$1(function (resolve, reject) {
reject(value);
});
};
Promise$1.race = function (values) {
return new Promise$1(function (resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
// Use polyfill for setImmediate for performance gains
Promise$1._immediateFn =
(typeof setImmediate === 'function' &&
function (fn) {
setImmediate(fn);
}) ||
function (fn) {
setTimeoutFunc(fn, 0);
};
Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) {
if (typeof console !== 'undefined' && console) {
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
}
};
/**
* Set the immediate function to execute callbacks
* @param fn {function} Function to execute
* @deprecated
*/
Promise$1._setImmediateFn = function _setImmediateFn(fn) {
Promise$1._immediateFn = fn;
};
/**
* Change the function to execute on unhandled rejection
* @param {function} fn Function to execute on unhandled rejection
* @deprecated
*/
Promise$1._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
Promise$1._unhandledRejectionFn = fn;
};
const fakeDocument = {
createElement() { },
};
const fakeWindow = {
atob() { },
open() { },
location: {},
localStorage: {
setItem() { },
getItem() { },
removeItem() { },
},
sessionStorage: {
setItem() { },
getItem() { },
removeItem() { },
},
};
const $document = (typeof document !== undefined)
? document
: fakeDocument;
const $window = (typeof window !== undefined)
? window
: fakeWindow;
function getCookieDomainUrl() {
try {
return $window.location.hostname;
} catch (e) {}
return '';
}
function getRedirectUri(uri) {
try {
return !isUndefined(uri)
? `${$window.location.origin}${uri}`
: $window.location.origin;
} catch (e) {}
return uri || null;
}
/**
* Default configuration
*/
var defaultOptions = {
baseUrl: null,
tokenPath: 'access_token',
tokenName: 'token',
tokenPrefix: 'vueauth',
tokenHeader: 'Authorization',
tokenType: 'Bearer',
loginUrl: '/auth/login',
registerUrl: '/auth/register',
logoutUrl: null,
storageType: 'localStorage',
storageNamespace: 'vue-authenticate',
cookieStorage: {
domain: getCookieDomainUrl(),
path: '/',
secure: false,
},
requestDataKey: 'data',
responseDataKey: 'data',
/**
* Default request interceptor for Axios library
* @context {VueAuthenticate}
*/
bindRequestInterceptor: function ($auth) {
const tokenHeader = $auth.options.tokenHeader;
$auth.$http.interceptors.request.use(config => {
if ($auth.isAuthenticated()) {
config.headers[tokenHeader] = [
$auth.options.tokenType,
$auth.getToken(),
].join(' ');
} else {
delete config.headers[tokenHeader];
}
return config;
});
},
providers: {
facebook: {
name: 'facebook',
url: '/auth/facebook',
authorizationEndpoint: 'https://www.facebook.com/v10.0/dialog/oauth',
redirectUri: getRedirectUri('/'),
requiredUrlParams: ['display', 'scope'],
scope: ['email'],
scopeDelimiter: ',',
display: 'popup',
oauthType: '2.0',
popupOptions: { width: 580, height: 400 },
},
google: {
name: 'google',
url: '/auth/google',
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
redirectUri: getRedirectUri(),
requiredUrlParams: ['scope'],
optionalUrlParams: ['display'],
scope: ['profile', 'email'],
scopePrefix: 'openid',
scopeDelimiter: ' ',
display: 'popup',
oauthType: '2.0',
popupOptions: { width: 452, height: 633 },
},
github: {
name: 'github',
url: '/auth/github',
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
redirectUri: getRedirectUri(),
optionalUrlParams: ['scope'],
scope: ['user:email'],
scopeDelimiter: ' ',
oauthType: '2.0',
popupOptions: { width: 1020, height: 618 },
},
instagram: {
name: 'instagram',
url: '/auth/instagram',
authorizationEndpoint: 'https://api.instagram.com/oauth/authorize',
redirectUri: getRedirectUri(),
requiredUrlParams: ['scope'],
scope: ['basic'],
scopeDelimiter: '+',
oauthType: '2.0',
popupOptions: { width: null, height: null },
},
twitter: {
name: 'twitter',
url: '/auth/twitter',
authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate',
redirectUri: getRedirectUri(),
oauthType: '1.0',
popupOptions: { width: 495, height: 645 },
},
bitbucket: {
name: 'bitbucket',
url: '/auth/bitbucket',
authorizationEndpoint: 'https://bitbucket.org/site/oauth2/authorize',
redirectUri: getRedirectUri('/'),
optionalUrlParams: ['scope'],
scope: ['email'],
scopeDelimiter: ' ',
oauthType: '2.0',
popupOptions: { width: 1020, height: 618 },
},
linkedin: {
name: 'linkedin',
url: '/auth/linkedin',
authorizationEndpoint: 'https://www.linkedin.com/oauth/v2/authorization',
redirectUri: getRedirectUri(),
requiredUrlParams: ['state'],
scope: ['r_emailaddress'],
scopeDelimiter: ' ',
state: 'STATE',
oauthType: '2.0',
popupOptions: { width: 527, height: 582 },
},
live: {
name: 'live',
url: '/auth/live',
authorizationEndpoint: 'https://login.live.com/oauth20_authorize.srf',
redirectUri: getRedirectUri(),
requiredUrlParams: ['display', 'scope'],
scope: ['wl.emails'],
scopeDelimiter: ' ',
display: 'popup',
oauthType: '2.0',
popupOptions: { width: 500, height: 560 },
},
oauth1: {
name: null,
url: '/auth/oauth1',
authorizationEndpoint: null,
redirectUri: getRedirectUri(),
oauthType: '1.0',
popupOptions: null,
},
oauth2: {
name: null,
url: '/auth/oauth2',
clientId: null,
redirectUri: getRedirectUri(),
authorizationEndpoint: null,
defaultUrlParams: ['response_type', 'client_id', 'redirect_uri'],
requiredUrlParams: null,
optionalUrlParams: null,
scope: null,
scopePrefix: null,
scopeDelimiter: null,
state: null,
oauthType: '2.0',
popupOptions: null,
responseType: 'code',
responseParams: {
code: 'code',
clientId: 'clientId',
redirectUri: 'redirectUri',
},
},
},
};
class CookieStorage {
constructor(defaultOptions) {
this._defaultOptions = objectExtend(
{
domain: getCookieDomainUrl(),
expires: null,
path: '/',
secure: false,
},
defaultOptions
);
}
setItem(key, value) {
const options = objectExtend({}, this._defaultOptions);
const cookie = formatCookie(key, value, options);
this._setCookie(cookie);
}
getItem(key) {
const cookies = parseCookies(this._getCookie());
return cookies.hasOwnProperty(key) ? cookies[key] : null;
}
removeItem(key) {
const value = '';
const defaultOptions = objectExtend({}, this._defaultOptions);
const options = objectExtend(defaultOptions, {
expires: new Date(0),
});
const cookie = formatCookie(key, value, options);
this._setCookie(cookie);
}
_getCookie() {
try {
return $document.cookie === 'undefined' ? '' : $document.cookie;
} catch (e) {}
return '';
}
_setCookie(cookie) {
try {
$document.cookie = cookie;
} catch (e) {}
}
}
class LocalStorage {
constructor(namespace) {
this.namespace = namespace || null;
}
setItem(key, value) {
$window.localStorage.setItem(this._getStorageKey(key), value);
}
getItem(key) {
return $window.localStorage.getItem(this._getStorageKey(key));
}
removeItem(key) {
$window.localStorage.removeItem(this._getStorageKey(key));
}
_getStorageKey(key) {
if (this.namespace) {
return [this.namespace, key].join('.');
}
return key;
}
}
class MemoryStorage {
constructor(namespace) {
this.namespace = namespace || null;
this._storage = {};
}
setItem(key, value) {
this._storage[this._getStorageKey(key)] = value;
}
getItem(key) {
return this._storage[this._getStorageKey(key)];
}
removeItem(key) {
delete this._storage[this._getStorageKey(key)];
}
_getStorageKey(key) {
if (this.namespace) {
return [this.namespace, key].join('.');
}
return key;
}
}
class SessionStorage {
constructor(namespace) {
this.namespace = namespace || null;
}
setItem(key, value) {
$window.sessionStorage.setItem(this._getStorageKey(key), value);
}
getItem(key) {
return $window.sessionStorage.getItem(this._getStorageKey(key));
}
removeItem(key) {
$window.sessionStorage.removeItem(this._getStorageKey(key));
}
_getStorageKey(key) {
if (this.namespace) {
return [this.namespace, key].join('.');
}
return key;
}
}
function StorageFactory(options) {
switch (options.storageType) {
case 'localStorage':
try {
$window.localStorage.setItem('testKey', 'test');
$window.localStorage.removeItem('testKey');
return new LocalStorage(options.storageNamespace);
} catch (e) {}
case 'sessionStorage':
try {
$window.sessionStorage.setItem('testKey', 'test');
$window.sessionStorage.removeItem('testKey');
return new SessionStorage(options.storageNamespace);
} catch (e) {}
case 'cookieStorage':
return new CookieStorage(options.cookieStorage);
case 'memoryStorage':
default:
return new MemoryStorage(options.storageNamespace);
}
}
/**
* OAuth2 popup management class
*
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Class mostly taken from https://github.com/sahat/satellizer
* and adjusted to fit vue-authenticate library
*/
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$1.resolve();
} else {
return this.pooling(redirectUri);
}
} catch (e) {
return Promise$1.reject(new Error('OAuth popup error occurred'));
}
}
pooling(redirectUri) {
return new Promise$1((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(',');
}
}
const defaultProviderConfig$1 = {
name: null,
url: null,
authorizationEndpoint: null,
scope: null,
scopePrefix: null,
scopeDelimiter: null,
redirectUri: null,
requiredUrlParams: null,
defaultUrlParams: null,
oauthType: '1.0',
popupOptions: {},
};
class OAuth {
constructor($http, storage, providerConfig, options) {
this.$http = $http;
this.storage = storage;
this.providerConfig = objectExtend({}, defaultProviderConfig$1);
this.providerConfig = objectExtend(this.providerConfig, providerConfig);
this.options = options;
}
/**
* Initialize OAuth1 process
* @param {Object} userData User data
* @return {Promise}
*/
init(userData) {
this.oauthPopup = new OAuthPopup(
'about:blank',
this.providerConfig.name,
this.providerConfig.popupOptions
);
if (!$window['cordova']) {
this.oauthPopup.open(this.providerConfig.redirectUri, true);
}
return this.getRequestToken().then(response => {
return this.openPopup(response).then(popupResponse => {
return this.exchangeForToken(popupResponse, userData);
});
});
}
/**
* Get OAuth1 request token
* @return {Promise}
*/
getRequestToken() {
let requestOptions = {};
requestOptions.method = 'POST';
requestOptions[this.options.requestDataKey] = objectExtend(
{},
this.providerConfig
);
requestOptions.withCredentials = this.options.withCredentials;
if (this.options.baseUrl) {
requestOptions.url = joinUrl(
this.options.baseUrl,
this.providerConfig.url
);
} else {
requestOptions.url = this.providerConfig.url;
}
return this.$http(requestOptions);
}
/**
* Open OAuth1 popup
* @param {Object} response Response object containing request token
* @return {Promise}
*/
openPopup(response) {
const url = [
this.providerConfig.authorizationEndpoint,
this.buildQueryString(response[this.options.responseDataKey]),
].join('?');
this.oauthPopup.popup.location = url;
if ($window['cordova']) {
return this.oauthPopup.open(this.providerConfig.redirectUri);
} else {
return this.oauthPopup.pooling(this.providerConfig.redirectUri);
}
}
/**
* Exchange token and token verifier for access token
* @param {Object} oauth OAuth data containing token and token verifier
* @param {Object} userData User data
* @return {Promise}
*/
exchangeForToken(oauth, userData) {
let payload = objectExtend({}, userData);
payload = objectExtend(payload, oauth);
let requestOptions = {};
requestOptions.method = 'POST';
requestOptions[this.options.requestDataKey] = payload;
requestOptions.withCredentials = this.options.withCredentials;
if (this.options.baseUrl) {
requestOptions.url = joinUrl(
this.options.baseUrl,
this.providerConfig.url
);
} else {
requestOptions.url = this.providerConfig.url;
}
return this.$http(requestOptions);
}
buildQueryString(params) {
const parsedParams = [];
for (var key in params) {
let value = params[key];
parsedParams.push(
encodeURIComponent(key) + '=' + encodeURIComponent(value)
);
}
return parsedParams.join('&');
}
}
/**
* Default provider configuration
* @type {Object}
*/
const defaultProviderConfig = {
name: null,
url: null,
clientId: null,
authorizationEndpoint: null,
redirectUri: null,
scope: null,
scopePrefix: null,
scopeDelimiter: null,
state: null,
requiredUrlParams: null,
defaultUrlParams: ['response_type', 'client_id', 'redirect_uri'],
responseType: 'code',
responseParams: {
code: 'code',
clientId: 'clientId',
redirectUri: 'redirectUri',
},
oauthType: '2.0',
popupOptions: {},
};
class OAuth2 {
constructor($http, storage, providerConfig, options) {
this.$http = $http;
this.storage = storage;
this.providerConfig = objectExtend({}, defaultProviderConfig);
this.providerConfig = objectExtend(this.providerConfig, providerConfig);
this.options = options;
}
init(userData) {
let stateName = this.providerConfig.name + '_state';
if (isFunction(this.providerConfig.state)) {
this.storage.setItem(stateName, this.providerConfig.state());
} else if (isString(this.providerConfig.state)) {
this.storage.setItem(stateName, this.providerConfig.state);
}
let url = [
this.providerConfig.authorizationEndpoint,
this._stringifyRequestParams(),
].join('?');
this.oauthPopup = new OAuthPopup(
url,
this.providerConfig.name,
this.providerConfig.popupOptions
);
return new Promise((resolve, reject) => {
this.oauthPopup
.open(this.providerConfig.redirectUri)
.then(response => {
if (
this.providerConfig.responseType === 'token' ||
!this.providerConfig.url
) {
return resolve(response);
}
if (
response.state &&
response.state !== this.storage.getItem(stateName)
) {
return reject(
new Error(
'State parameter value does not match original OAuth request state value'
)
);
}
resolve(this.exchangeForToken(response, userData));
})
.catch(err => {
reject(err);
});
});
}
/**
* Exchange temporary oauth data for access token
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @param {[type]} oauth [description]
* @param {[type]} userData [description]
* @return {[type]} [description]
*/
exchangeForToken(oauth, userData) {
let payload = objectExtend({}, userData);
for (let key in this.providerConfig.responseParams) {
this.providerConfig.responseParams[key];
switch (key) {
case 'code':
payload[key] = oauth.code;
break;
case 'clientId':
payload[key] = this.providerConfig.clientId;
break;
case 'redirectUri':
payload[key] = this.providerConfig.redirectUri;
break;
default:
payload[key] = oauth[key];
}
}
if (oauth.state) {
payload.state = oauth.state;
}
let exchangeTokenUrl;
if (this.options.baseUrl) {
exchangeTokenUrl = joinUrl(this.options.baseUrl, this.providerConfig.url);
} else {
exchangeTokenUrl = this.providerConfig.url;
}
return this.$http.post(exchangeTokenUrl, payload, {
withCredentials: this.options.withCredentials,
});
}
/**
* Stringify oauth params
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
*
* @return {String}
*/
_stringifyRequestParams() {
let keyValuePairs = [];
let paramCategories = [
'defaultUrlParams',
'requiredUrlParams',
'optionalUrlParams',
];
paramCategories.forEach(categoryName => {
if (!this.providerConfig[categoryName]) return;
if (!Array.isArray(this.providerConfig[categoryName])) return;
this.providerConfig[categoryName].forEach(paramName => {
let camelCaseParamName = camelCase(paramName);
let paramValue = isFunction(this.providerConfig[paramName])
? this.providerConfig[paramName]()
: this.providerConfig[camelCaseParamName];
if (paramName === 'redirect_uri' && !paramValue) return;
if (paramName === 'state') {
let stateName = this.providerConfig.name + '_state';
paramValue = encodeURIComponent(this.storage.getItem(stateName));
}
if (paramName === 'scope' && Array.isArray(paramValue)) {
paramValue = paramValue.join(this.providerConfig.scopeDelimiter);
if (this.providerConfig.scopePrefix) {
paramValue = [this.providerConfig.scopePrefix, paramValue].join(
this.providerConfig.scopeDelimiter
);
}
}
keyValuePairs.push([paramName, paramValue]);
});
});
return keyValuePairs
.map(param => {
return param.join('=');
})
.join('&');
}
}
class VueAuthenticate {
constructor($http, overrideOptions) {
let options = objectExtend({}, defaultOptions);
options = objectExtend(options, overrideOptions);
let storage = StorageFactory(options);
Object.defineProperties(this, {
$http: {
get() {
return $http;
},
},
options: {
get() {
return options;
},
},
storage: {
get() {
return storage;
},
},
tokenName: {
get() {
if (this.options.tokenPrefix) {
return [this.options.tokenPrefix, this.options.tokenName].join('_');
} else {
return this.options.tokenName;
}
},
},
});
// Setup request interceptors
if (
this.options.bindRequestInterceptor &&
isFunction(this.options.bindRequestInterceptor)
) {
this.options.bindRequestInterceptor.call(this, this);
} else {
throw new Error('Request interceptor must be functions');
}
}
/**
* Check if user is authenticated
* @author Sahat Yalkabov <https://github.com/sahat>
* @copyright Method taken from https://github.com/sahat/satellizer
* @return {Boolean}
*/
isAuthenticated() {
let token = this.storage.getItem(this.tokenName);
if (token) {
// Token is present
if (token.split('.').length === 3) {
// Token with a valid JWT format XXX.YYY.ZZZ
try {
// Could be a valid JWT or an access token with the same format
const base64Url = token.split('.')[1];
const base64 = base64Url.replace('-', '+').replace('_', '/');
const exp = JSON.parse($window.atob(base64)).exp;
if (typeof exp === 'number') {
// JWT with an optonal expiration claims
return Math.round(new Date().getTime() / 1000) < exp;
}
} catch (e) {
return true; // Pass: Non-JWT token that looks like JWT
}
}
return true; // Pass: All other tokens
}
return false;
}
/**
* Get token if user is authenticated
* @return {String} Authentication token
*/
getToken() {
return this.storage.getItem(this.tokenName);
}
/**
* Set new authentication token
* @param {String|Object} token
*/
setToken(response, tokenPath) {
if (response[this.options.responseDataKey]) {
response = response[this.options.responseDataKey];
}
const responseTokenPath = tokenPath || this.options.tokenPath;
const token = getObjectProperty(response, responseTokenPath);
if (token) {
this.storage.setItem(this.tokenName, token);
}
}
getPayload() {
const token = this.storage.getItem(this.tokenName);
if (token && token.split('.').length === 3) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace('-', '+').replace('_', '/');
return JSON.parse(decodeBase64(base64));
} catch (e) {}
}
}
/**
* Login user using email and password
* @param {Object} user User data
* @param {Object} requestOptions Request options
* @return {Promise} Request promise
*/
login(user, requestOptions) {
requestOptions = requestOptions || {};
requestOptions.url = requestOptions.url
? requestOptions.url
: joinUrl(this.options.baseUrl, this.options.loginUrl);
requestOptions[this.options.requestDataKey] =
user || requestOptions[this.options.requestDataKey];
requestOptions.method = requestOptions.method || 'POST';
requestOptions.withCredentials =
requestOptions.withCredentials || this.options.withCredentials;
return this.$http(requestOptions).then(response => {
this.setToken(response);
return response;
});
}
/**
* Register new user
* @param {Object} user User data
* @param {Object} requestOptions Request options
* @return {Promise} Request promise
*/
register(user, requestOptions) {
requestOptions = requestOptions || {};
requestOptions.url = requestOptions.url
? requestOptions.url
: joinUrl(this.options.baseUrl, this.options.registerUrl);
requestOptions[this.options.requestDataKey] =
user || requestOptions[this.options.requestDataKey];
requestOptions.method = requestOptions.method || 'POST';
requestOptions.withCredentials =
requestOptions.withCredentials || this.options.withCredentials;
return this.$http(requestOptions).then(response => {
this.setToken(response);
return response;
});
}
/**
* Logout current user
* @param {Object} requestOptions Logout request options object
* @return {Promise} Request promise
*/
logout(requestOptions) {
if (!this.isAuthenticated()) {
return Promise$1.reject(
new Error('There is no currently authenticated user')
);
}
requestOptions = requestOptions || {};
if (requestOptions.url || this.options.logoutUrl) {
requestOptions.url = requestOptions.url
? requestOptions.url
: joinUrl(this.options.baseUrl, this.options.logoutUrl);
requestOptions.method = requestOptions.method || 'POST';
requestOptions[this.options.requestDataKey] =
requestOptions[this.options.requestDataKey] || undefined;
requestOptions.withCredentials =
requestOptions.withCredentials || this.options.withCredentials;
return this.$http(requestOptions).then(response => {
this.storage.removeItem(this.tokenName);
return response;
});
} else {
this.storage.removeItem(this.tokenName);
return Promise$1.resolve();
}
}
/**
* Authenticate user using authentication provider
*
* @param {String} provider Provider name
* @param {Object} userData User data
* @return {Promise} Request promise
*/
authenticate(provider, userData) {
return new Promise$1((resolve, reject) => {
var providerConfig = this.options.providers[provider];
if (!providerConfig) {
return reject(new Error('Unknown provider'));
}
let providerInstance;
switch (providerConfig.oauthType) {
case '1.0':
providerInstance = new OAuth(
this.$http,
this.storage,
providerConfig,
this.options
);
break;
case '2.0':
providerInstance = new OAuth2(
this.$http,
this.storage,
providerConfig,
this.options
);
break;
default:
return reject(new Error('Invalid OAuth type'));
}
return providerInstance
.init(userData)
.then(response => {
this.setToken(response, providerConfig.tokenPath);
if (this.isAuthenticated()) {
return resolve(response);
} else {
return reject(new Error('Authentication failed'));
}
})
.catch(err => reject(err));
});
}
/**
* Link user using authentication provider without login
*
* @param {String} provider Provider name
* @param {Object} userData User data
* @return {Promise} Request promise
*/
link(provider, userData) {
return new Promise$1((resolve, reject) => {
var providerConfig = this.options.providers[provider];
if (!providerConfig) {
return reject(new Error('Unknown provider'));
}
let providerInstance;
switch (providerConfig.oauthType) {
case '1.0':
providerInstance = new OAuth(
this.$http,
this.storage,
providerConfig,
this.options
);
break;
case '2.0':
providerInstance = new OAuth2(
this.$http,
this.storage,
providerConfig,
this.options
);
break;
default:
return reject(new Error('Invalid OAuth type'));
}
return providerInstance
.init(userData)
.then(response => {
if (response[this.options.responseDataKey]) {
response = response[this.options.responseDataKey];
}
resolve(response);
})
.catch(reject);
});
}
}
/**
* VueAuthenticate plugin
* @param {Object} Vue
* @param {Object} options
*/
function plugin(Vue, options) {
if (plugin.installed) {
return;
}
plugin.installed = true;
let vueAuthInstance = null;
Object.defineProperties(Vue.prototype, {
$auth: {
get() {
if (!vueAuthInstance) {
// Request handler library not found, throw error
if (!this.$http) {
throw new Error('Request handler instance not found');
}
vueAuthInstance = new VueAuthenticate(this.$http, options);
}
return vueAuthInstance;
},
},
});
}
/**
* External factory helper for ES5 and CommonJS
* @param {Object} $http Instance of request handling library
* @param {Object} options Configuration object
* @return {VueAuthenticate} VueAuthenticate instance
*/
plugin.factory = function ($http, options) {
return new VueAuthenticate($http, options);
};
export default plugin;