@oat-sa/tao-core-sdk
Version:
Core libraries of TAO
219 lines (211 loc) • 8.39 kB
JavaScript
define(['core/jwt/jwtTokenStore', 'core/promiseQueue', 'core/error/TokenError'], function (jwtTokenStoreFactory, promiseQueue, TokenError) { 'use strict';
jwtTokenStoreFactory = jwtTokenStoreFactory && Object.prototype.hasOwnProperty.call(jwtTokenStoreFactory, 'default') ? jwtTokenStoreFactory['default'] : jwtTokenStoreFactory;
promiseQueue = promiseQueue && Object.prototype.hasOwnProperty.call(promiseQueue, 'default') ? promiseQueue['default'] : promiseQueue;
TokenError = TokenError && Object.prototype.hasOwnProperty.call(TokenError, 'default') ? TokenError['default'] : TokenError;
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2019-2022 (original work) Open Assessment Technologies SA ;
*/
/**
* JWT token handler factory
* @param {Object} options Options of JWT token handler
* @param {String} options.serviceName Name of the service what JWT token belongs to
* @param {String} options.refreshTokenUrl Url where handler could refresh JWT token
* @param {Number} [options.accessTokenTTL] Set accessToken TTL in ms for token store
* @param {Boolean} [options.usePerTokenTTL] if true, accessToken TTL should be extractable from JWT payload, and accessTokenTTL will be used as fallback
* @param {Boolean} [options.useCredentials] refreshToken stored in cookie instead of store
* @param {Object} [options.refreshTokenParameters] Parameters that should be send in refreshToken call
* @param {Boolean} [options.oauth2RequestFormat] use oauth2 request format
* @returns {Object} JWT Token handler instance
*/
const jwtTokenHandlerFactory = function jwtTokenHandlerFactory() {
let {
serviceName = 'tao',
refreshTokenUrl,
accessTokenTTL,
usePerTokenTTL = false,
refreshTokenParameters,
useCredentials = false,
oauth2RequestFormat = false
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const tokenStorage = jwtTokenStoreFactory({
namespace: serviceName,
accessTokenTTL,
usePerTokenTTL
});
/**
* Action queue to avoid concurrent token updates
* @type {Promise<any>}
*/
const actionQueue = promiseQueue();
/**
* This is an "unsafe" refresh token, because it allows to call multiple time paralelly
* It will refresh the token from provided API and saves it for later use
* @returns {Promise<String>} Promise of new token
*/
const unQueuedRefreshToken = () => {
let parameters;
let credentials;
let flow;
if (refreshTokenParameters) {
parameters = Object.assign({}, refreshTokenParameters);
}
if (useCredentials) {
credentials = 'include';
flow = Promise.resolve();
} else {
flow = tokenStorage.getRefreshToken().then(refreshToken => {
if (!refreshToken) {
throw new Error('Refresh token is not available');
}
if (oauth2RequestFormat) {
parameters = Object.assign({}, parameters, {
refresh_token: refreshToken
});
} else {
parameters = Object.assign({}, parameters, {
refreshToken
});
}
});
}
return flow.then(() => {
const headers = {};
let body;
if (oauth2RequestFormat) {
body = new FormData();
Object.keys(parameters).forEach(key => {
body.append(key, parameters[key]);
});
} else {
if (parameters) {
body = JSON.stringify(parameters);
}
headers['Content-Type'] = 'application/json';
}
return fetch(refreshTokenUrl, {
method: 'POST',
credentials,
headers,
body
});
}).then(response => {
if (response.status === 200) {
return response.json();
}
if (response.status === 401) {
const error = new TokenError('Refresh-token expired', response);
return Promise.reject(error);
}
let error = new Error('Unsuccessful token refresh');
error.response = response;
return Promise.reject(error);
}).then(response => {
let accessToken, refreshToken, expiresIn;
if (oauth2RequestFormat) {
accessToken = response.access_token;
refreshToken = response.refresh_token;
expiresIn = response.expires_in;
} else {
accessToken = response.accessToken;
refreshToken = response.refreshToken;
}
if (expiresIn) {
tokenStorage.setAccessTokenTTL(expiresIn * 1000);
}
if (accessToken && refreshToken) {
return tokenStorage.setTokens(accessToken, refreshToken).then(() => accessToken);
}
return tokenStorage.setAccessToken(accessToken).then(() => accessToken);
});
};
return {
/**
* service name of token handler
*/
serviceName,
/**
* Get access token
* @returns {Promise<String|null>} Promise of access token
*/
getToken() {
return actionQueue.serie(() => tokenStorage.getAccessToken().then(accessToken => {
if (accessToken) {
return accessToken;
}
if (useCredentials) {
return unQueuedRefreshToken();
}
return tokenStorage.getRefreshToken().then(refreshToken => {
if (refreshToken) {
return unQueuedRefreshToken();
} else {
throw new Error('Token not available and cannot be refreshed');
}
});
}));
},
/**
* Saves refresh token for later
* @param {String} refreshToken
* @returns {Promise<Boolean>} Promise of token is stored
*/
storeRefreshToken(refreshToken) {
if (useCredentials) {
return Promise.resolve(false);
}
return actionQueue.serie(() => tokenStorage.setRefreshToken(refreshToken));
},
/**
* Returns the refresh token from the token storage
* @returns {Promise<String|null>} Promise that returns the token
*/
getRefreshToken() {
return tokenStorage.getRefreshToken();
},
/**
* Saves initial access token
* @param {String} accessToken
* @returns {Promise<Boolean>} Promise of token is stored
*/
storeAccessToken(accessToken) {
return actionQueue.serie(() => tokenStorage.setAccessToken(accessToken));
},
/**
* Clear all tokens from store
* @returns {Promise<Boolean>} Promise of store is cleared
*/
clearStore() {
return actionQueue.serie(() => tokenStorage.clear());
},
/**
* Refresh access token
* @returns {Promise<String>} Promise of new access token
*/
refreshToken() {
return actionQueue.serie(() => unQueuedRefreshToken());
},
/**
* Set accessToken TTL
* @param {Number} newAccessTokenTTL - accessToken TTL in ms
*/
setAccessTokenTTL(newAccessTokenTTL) {
tokenStorage.setAccessTokenTTL(newAccessTokenTTL);
}
};
};
return jwtTokenHandlerFactory;
});