'use strict';
/**
* @author Marco Lehmann <marco.lehmann@kiwigrid.com>
* @copyright Kiwigrid GmbH 2014-2015
* @module keta.services.AccessToken
* @description AccessToken Factory
*/
angular.module('keta.services.AccessToken',
[
'keta.services.AppContext'
])
/**
* @class ketaAccessTokenConstants
* @propertyOf keta.services.AccessToken
* @description Access Token Constants
*/
.constant('ketaAccessTokenConstants', {
// session types
SESSION_TYPE: {
NORMAL: 'normal',
IMPERSONATED: 'impersonated'
}
})
/**
* @class ketaAccessToken
* @propertyOf keta.services.AccessToken
* @description Access Token Factory
*/
.factory('ketaAccessToken', function AccessTokenFactory(
$http, $q,
ketaAppContext, ketaAccessTokenConstants
) {
/**
* @private
* @description Internal representation of access token which was injected by web server into context.js.
*/
var accessToken = ketaAppContext.get('oauth.accessToken');
/**
* @private
* @description Decoded access token.
*/
var decodedAccessToken = null;
/**
* @private
* @description Refresh promise.
*/
var refreshPromise = null;
/**
* @private
* @description Flag if refresh call is currently in progress.
*/
var refreshInProgress = false;
/*eslint-disable no-magic-numbers */
var Base64 = {
keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
decode: function(input) {
var output = '';
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
while (i < input.length) {
enc1 = this.keyStr.indexOf(input.charAt(i++));
enc2 = this.keyStr.indexOf(input.charAt(i++));
enc3 = this.keyStr.indexOf(input.charAt(i++));
enc4 = this.keyStr.indexOf(input.charAt(i++));
chr1 = enc1 << 2 | enc2 >> 4;
chr2 = (enc2 & 15) << 4 | enc3 >> 2;
chr3 = (enc3 & 3) << 6 | enc4;
output += String.fromCharCode(chr1);
if (enc3 !== 64) {
output += String.fromCharCode(chr2);
}
if (enc4 !== 64) {
output += String.fromCharCode(chr3);
}
}
return Base64.utf8Decode(output);
},
encode: function(input) {
var output = '';
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64.utf8Encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = (chr1 & 3) << 4 | chr2 >> 4;
enc3 = (chr2 & 15) << 2 | chr3 >> 6;
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this.keyStr.charAt(enc1) +
this.keyStr.charAt(enc2) +
this.keyStr.charAt(enc3) +
this.keyStr.charAt(enc4);
}
return output;
},
utf8Decode: function(utfText) {
var string = '';
var i = 0;
var c = 0, c2 = 0, c3 = 0;
while (i < utfText.length) {
c = utfText.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
} else if (c > 191 && c < 224) {
c2 = utfText.charCodeAt(i + 1);
string += String.fromCharCode((c & 31) << 6 | c2 & 63);
i += 2;
} else {
c2 = utfText.charCodeAt(i + 1);
c3 = utfText.charCodeAt(i + 2);
string += String.fromCharCode((c & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
i += 3;
}
}
return string;
},
utf8Encode: function(string) {
string = string.replace(/\r\n/g, '\n');
var utfText = '';
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utfText += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
utfText += String.fromCharCode(c >> 6 | 192);
utfText += String.fromCharCode(c & 63 | 128);
} else {
utfText += String.fromCharCode(c >> 12 | 224);
utfText += String.fromCharCode(c >> 6 & 63 | 128);
utfText += String.fromCharCode(c & 63 | 128);
}
}
return utfText;
}
};
/*eslint-enable no-magic-numbers */
/**
* @private
* @param {string} property property to extract from token
* @returns {*} property value
*/
var getProperty = function(property) {
return decodedAccessToken !== null &&
angular.isDefined(decodedAccessToken[property]) ?
decodedAccessToken[property] : null;
};
var api = {
/**
* @name get
* @function
* @description Get access token.
* @param {boolean} decoded Return in decoded or raw format.
* @returns {string} access token
* @example
* angular.module('exampleApp', ['keta.services.AccessToken'])
* .controller('ExampleController', function(ketaAccessToken) {
* var accessToken = ketaAccessToken.get();
* });
*/
get: function(decoded) {
if (accessToken !== null && decodedAccessToken === null) {
decodedAccessToken = api.decode(accessToken);
}
return angular.isDefined(decoded) && decoded === true ? decodedAccessToken : accessToken;
},
/**
* @name set
* @function
* @description Set access token.
* @param {string} token new access token
* @returns {void} returns nothing
* @example
* angular.module('exampleApp', ['keta.services.AccessToken'])
* .controller('ExampleController', function(ketaAccessToken) {
* ketaAccessToken.set('new-token');
* });
*/
set: function(token) {
if (angular.isDefined(token) && angular.isString(token)) {
accessToken = token;
decodedAccessToken = api.decode(token);
}
},
/**
* @name decode
* @function
* @description Decode access token.
* @param {string} token access token to decode
* @returns {Object} access token properties
* @example
* angular.module('exampleApp', ['keta.services.AccessToken'])
* .controller('ExampleController', function(ketaAccessToken) {
* var accessTokenProps = ketaAccessToken.decode(AccessToken.get());
* });
*/
decode: function(token) {
var props = {};
try {
var decoded = Base64.decode(token);
// strip away everything after }.
if (decoded.indexOf('}.') !== -1) {
decoded = decoded.substr(0, decoded.indexOf('}.') + 1);
}
props = JSON.parse(decoded);
} catch (e) {
return null;
}
return props;
},
/**
* @name encode
* @function
* @description Encode access token properties.
* @param {Object} props access token properties to encode
* @returns {string} access token
* @example
* angular.module('exampleApp', ['keta.services.AccessToken'])
* .controller('ExampleController', function(ketaAccessToken) {
* var accessTokenProps = ketaAccessToken.decode(ketaAccessToken.get());
* accessTokenProps.loaded = true;
* var accessToken = ketaAccessToken.encode(accessTokenProps);
* });
*/
encode: function(props) {
return Base64.encode(JSON.stringify(props));
},
/**
* @name refresh
* @function
* @description Refresh access token by requesting backend.
* @returns {promise} Promise which is resolved when query is returned
* @example
* angular.module('exampleApp', [])
* .controller('ExampleController', function(ketaAccessToken) {
* ketaAccessToken.refresh().then(
* function(response) {
* if (angular.isDefined(response.data.accessToken)) {
* ketaAccessToken.set(response.data.accessToken);
* }
* },
* function(message) {
* console.error(message);
* }
* );
* });
*/
refresh: function() {
if (refreshPromise === null || !refreshInProgress) {
refreshPromise = $q.defer();
refreshInProgress = true;
var refreshUrl = ketaAppContext.get('oauth.refreshTokenPath') || '/refreshAccessToken';
$http({method: 'GET', url: refreshUrl}).then(
function(response) {
refreshPromise.resolve(response);
refreshInProgress = false;
},
function() {
refreshPromise.reject('Could not refresh access token');
refreshInProgress = false;
}
);
}
return refreshPromise.promise;
},
/**
* @name hasPermission
* @function
* @description Checks if current user has a certain permission.
* @param {string} permission permission to check
* @returns {boolean} result
*/
hasPermission: function(permission) {
var has = false;
var decoded = api.get(true);
if (decoded !== null &&
angular.isArray(decoded.scope)) {
has = decoded.scope.indexOf(permission) !== -1;
}
return has;
},
/**
* @name isType
* @function
* @description Checks if session is of a certain type.
* @param {string} type session type (use ketaAccessTokenConstants.SESSION_TYPE)
* @returns {boolean} result
*/
isType: function(type) {
var decoded = api.get(true);
return decoded !== null &&
angular.isDefined(decoded.session) &&
angular.isDefined(decoded.session.type) &&
decoded.session.type === type;
},
/**
* @name getBackUrl
* @function
* @description Returns back URL for an impersonated session.
* @returns {string} back URL
*/
getBackUrl: function() {
var backUrl = null;
if (api.isType(ketaAccessTokenConstants.SESSION_TYPE.IMPERSONATED)) {
var decoded = api.get(true);
if (decoded !== null &&
angular.isDefined(decoded.session) &&
angular.isDefined(decoded.session.backUrl)) {
backUrl = decoded.session.backUrl;
}
}
return backUrl;
},
/**
* @name getUserId
* @function
* @description Get user id from token.
* @returns {string} user id
*/
getUserId: function() {
return getProperty('user_id');
},
/**
* @name getChannel
* @function
* @description Get channel from token.
* @returns {string} channel
*/
getChannel: function() {
return getProperty('channel');
}
};
return api;
});