'use strict';
const hmac = require('./hmac').hmac;
const deprectated = require('./deprecated');
/**
* @interface util.TokenStorageFactory
*/
/**
* Creates a new tokenStorage which persist tokens for the given origin
* @param {string} origin The origin where the token contains to
* @return {Promise<TokenStorage>} The initialized token storage
* @name create
* @memberOf util.TokenStorageFactory.prototype
* @method
*/
/**
* @alias util.TokenStorage
*/
class TokenStorage {
/**
* Parse a token string in its components
* @param {string} token The token string to parse, time values are returned as timestamps
* @return {{data: string, createdAt: int, expireAt: int, sig: string}}
*/
static parse(token) {
return {
val: token,
createdAt: parseInt(token.substring(0, 8), 16) * 1000,
expireAt: parseInt(token.substring(8, 16), 16) * 1000,
sig: token.substring(token.length - 40),
data: token.substring(0, token.length - 40),
};
}
/**
* Get the stored token
* @return {string} The token or undefined, if no token is available
*/
get token() {
return this.tokenData ? this.tokenData.val : null;
}
static create(origin) {
return Promise.resolve(new TokenStorage(origin));
}
/**
* @param {string} origin The origin where the token belongs to
* @param {string} token The initial token
* @param {boolean=} temporary If the token should be saved temporary or permanently
*/
constructor(origin, token, temporary) {
/**
* The actual stored token
*/
this.tokenData = token ? TokenStorage.parse(token) : null;
this.origin = origin;
/**
* Indicates if the token should keep temporary only or should be persisted for later sessions
* @type boolean
*/
this.temporary = temporary;
}
/**
* Use the underlying storage implementation to save the token
* @param {string} origin The origin where the token belongs to
* @param {string} token The initial token
* @param {boolean} temporary If the token should be saved temporary or permanently
* @return {void}
* @protected
* @abstract
*/
saveToken(origin, token, temporary) {
// eslint-disable-next-line no-underscore-dangle
if (this._saveToken !== TokenStorage.prototype._saveToken) {
// eslint-disable-next-line no-console
console.log('Using deprecated TokenStorage._saveToken implementation.');
// eslint-disable-next-line no-underscore-dangle
this._saveToken(origin, token, temporary);
}
}
/**
* Use the underlying storage implementation to save the token
* @param {string} origin The origin where the token belongs to
* @param {string} token The initial token
* @param {boolean} temporary If the token should be saved temporary or permanently
* @return {void}
* @deprecated Use TokenStorage#saveToken instead
* @protected
* @abstract
*/
_saveToken(origin, token, temporary) {} // eslint-disable-line no-unused-vars
/**
* Update the token for the givin origin, the operation may be asynchronous
* @param {String} token The token to store or <code>null</code> to remove the token
* @return {void}
*/
update(token) {
const t = token ? TokenStorage.parse(token) : null;
if (this.tokenData && t && this.tokenData.expireAt > t.expireAt) {
// an older token was fetched from the cache, so ignore it
return;
}
this.tokenData = t;
this.saveToken(this.origin, token, this.temporary);
}
/**
* Derives a resource token from the stored origin token and signs the resource with the generated resource token
*
* @param {string} resource The resource which will be accessible with the returned token
* @return {string} A resource token which can only be used to access the specified resource
*/
signPath(resource) {
if (this.tokenData) {
const path = resource.split('/').map(encodeURIComponent).join('/');
return path + '?BAT=' + (this.tokenData.data + hmac(path + this.tokenData.data, this.tokenData.sig));
}
return resource;
}
}
deprectated(TokenStorage.prototype, '_token', 'tokenData');
deprectated(TokenStorage.prototype, '_origin', 'origin');
const tokens = {};
/**
* @ignore
*/
class GlobalStorage extends TokenStorage {
/**
* Creates a global token storage instance for the given origin
* A global token storage use a global variable to store the actual origin tokens
* @param origin
* @return {Promise.<GlobalStorage>}
*/
static create(origin) {
return Promise.resolve(new GlobalStorage(origin, tokens[origin]));
}
/**
* @inheritDoc
*/
saveToken(origin, token, temporary) {
if (!temporary) {
if (token) {
tokens[origin] = token;
} else {
delete tokens[origin];
}
}
}
}
/**
* @alias util.TokenStorage.GLOBAL
* @type {util.TokenStorageFactory}
*/
TokenStorage.GLOBAL = GlobalStorage;
/**
* @ignore
*/
class WebStorage extends TokenStorage {
static isAvailable() {
try {
// firefox throws an exception if cookies are disabled
if (typeof localStorage === 'undefined') {
return false;
}
localStorage.setItem('bq_webstorage_test', 'bq');
localStorage.removeItem('bq_webstorage_test');
return true;
} catch (e) {
return false;
}
}
/**
* Creates a global web storage instance for the given origin
* A web token storage use the localStorage or sessionStorage to store the origin tokens
* @param origin
* @return {Promise.<WebStorage>}
*/
static create(origin) {
let temporary = false;
let token = localStorage.getItem('BAT:' + origin);
if (!token && typeof sessionStorage !== 'undefined') {
token = sessionStorage.getItem('BAT:' + origin);
temporary = !!token;
}
return Promise.resolve(new WebStorage(origin, token, temporary));
}
/**
* @inheritDoc
*/
saveToken(origin, token, temporary) {
const webStorage = temporary ? sessionStorage : localStorage;
if (token) {
webStorage.setItem('BAT:' + origin, token);
} else {
webStorage.removeItem('BAT:' + origin);
}
}
}
if (WebStorage.isAvailable()) {
/**
* @alias util.TokenStorage.WEB_STORAGE
* @type {util.TokenStorageFactory}
*/
TokenStorage.WEB_STORAGE = WebStorage;
}
module.exports = TokenStorage;