UNPKG

@oat-sa/tao-core-sdk

Version:
219 lines (211 loc) 8.39 kB
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; });