UNPKG

osnmarket-abstract-core

Version:
368 lines (356 loc) 11.8 kB
'use strict'; var axios = require('axios'); var FormData = require('form-data'); var querystring = require('querystring'); var NodeCache = require('node-cache'); var configYml = require('config-yml'); const appConfigs = { localBase: process.env.NEXT_PUBLIC_LOCAL_URL, defaultApiTarget: process.env.NEXT_PUBLIC_DEFAULT_TARGET, dispatcherRoute: process.env.NEXT_PUBLIC_DISPATCHER_ROUTE, imageServer: process.env.NEXT_PUBLIC_IMG_SERVER, isApplicationBuilding: process.env.NODE_APPLICATION_BUILDING, tokenLifespan: process.env.TOKEN_LIFETIME, pageTimeOut: process.env.NEXT_PUBLIC_LOGOUT_TTL, redisUrl: process.env.REDIS_URL }; var appConfigs$1 = appConfigs; const enviromentKeys = ({ needle, data }) => { return Object.keys(data).filter(function (k) { return k.indexOf(needle) == 0; }).reduce(function (newData, k) { newData[k] = data[k]; return newData; }, {}); }; const targetMapper = ({ authProvider, configs, source, sourceName }) => { return { identifier: { key: authProvider.identifier_key, value: configs[`${sourceName.toUpperCase()}_IDENTIFIER`] }, password: { key: authProvider.password_key, value: configs[`${sourceName.toUpperCase()}_PASSWORD`] }, token_type: source.token_type || authProvider.token_type, token_key: source.token_key || authProvider.token_key, backend_url: configs[`${sourceName.toUpperCase()}_BACKEND_URL`], authentication_path: source.authentication_path || authProvider.authentication_path, ...(configs[`${sourceName.toUpperCase()}_SCOPE`] && { authentication_scope: configs[`${sourceName.toUpperCase()}_SCOPE`] }), ...(configs[`${sourceName.toUpperCase()}_GRANT_TYPE`] && { authentication_grant_type: configs[`${sourceName.toUpperCase()}_GRANT_TYPE`] }) }; }; const getErrStack = err => { const { stack, code, message, response } = err; const errStack = { ...(code && { code: code }), ...(response?.request && { instance: response.request.path }), ...(response?.data && { data: response.data }), ...(message && { message: message }) }; if (!process.env.NODE_ENV.startsWith('prod') && stack) { errStack.stack = stack; } return errStack; }; const nodeCache = new NodeCache(); var nodeCache$1 = nodeCache; /** * A function to manage caching using either NodeCache or Redis. * * @param {Object} options - The options for the caching system. * @param {any} options.store - The data to be stored in the cache. * @param {any} options.retrieve - The key to retrieve data from the cache. * @param {string} options.name - The name of the cache. * @param {string} [options.strategy='nodeCache'] - The caching strategy to use. * @param {number} [options.ttl=appConfigs.tokenLifespan] - The time-to-live for the cache in seconds. * @returns {Promise<any>} - The result of the caching operation. * @throws {Error} - If an invalid caching strategy is provided. */ async function CachingSystem({ store, retrieve, name, strategy = 'nodeCache', ttl = appConfigs$1.tokenLifespan }) { if (strategy != 'nodeCache') { throw new Error(`Invalid caching strategy: ${strategy}`); } try { if (strategy == 'nodeCache') { if (store) { return nodeCache$1.set(name, store, ttl); } if (retrieve) { try { if (nodeCache$1.has(name)) { return nodeCache$1.get(name); } return false; } catch (err) { throw new Error(err); } } } } catch (err) { throw new Error(err); } } /** * Authenticates a service account and retrieves an access token. * If the token is already cached, it retrieves it from the cache. * Otherwise, it authenticates with the specified backend and caches the token. * * @param {Object} params - The parameters for the function. * @param {Object} params.source - The source configuration object. * @param {string} params.sourceName - The name of the source. * @param {Object} params.authProvider - The authentication provider configuration object. * @param {string} params.preferedCache - The preferred caching strategy. * * @returns {Promise<string>} - A promise that resolves with the access token. * * @throws {Error} - If an error occurs during the authentication process. */ const serviceAccountLogin = async ({ source, sourceName, authProvider, preferedCache }) => { try { const savedToken = await CachingSystem({ retrieve: true, name: sourceName + '_access_token', strategy: preferedCache }); if (savedToken) { process.env.NODE_ENV == 'development' && console.log(Date.now(), `Got ${sourceName} token from cache ${savedToken}`); return savedToken; } process.env.NODE_ENV == 'development' && console.log(Date.now(), `Authenticating ${sourceName}`); let envConfigs = enviromentKeys({ needle: sourceName.toUpperCase(), data: process.env }); const application = targetMapper({ authProvider, configs: envConfigs, source, sourceName }); let body; let axiosKey = 'post'; if (authProvider.authentication_via_form || source.authentication_via_form) { axiosKey = 'postForm'; let formData = new FormData(); if (application.authentication_grant_type) { formData.append('grant_type', application.authentication_grant_type); } if (application.authentication_scope) { formData.append('scope', application.authentication_scope); } formData.append(application.identifier.key, application.identifier.value); formData.append(application.password.key, application.password.value); body = formData; } else { const payload = { [application.identifier.key]: application.identifier.value, [application.password.key]: application.password.value, ...(application.authentication_grant_type && { grant_type: application.authentication_grant_type }), ...(application.authentication_scope && { grant_type: application.authentication_scope }) }; if (authProvider.authentication_via_query_string || source.authentication_via_query_string) { body = querystring.stringify(payload); } else { body = payload; } } const { data } = await axios[axiosKey](`${application.backend_url}${application.authentication_path}`, body); const applicationToken = application.token_type == 'none' ? data[application.token_key] : `${application.token_type} ${data[application.token_key]}`; await CachingSystem({ store: applicationToken, name: sourceName + '_access_token', strategy: preferedCache }); return applicationToken; } catch (err) { throw getErrStack(err); } }; const appYMLConfigs = configYml.load(process.env.NODE_ENV); /** * Retrieves and prepares API configurations based on the targeted API. * * This function fetches the necessary configurations from YAML files, * environment variables, and performs authentication based on the API provider. * * @param {string} targetedApi - The name of the targeted API for which configurations are required. * @returns {Promise<Object>} - A promise that resolves to an object containing the API configurations. * @throws {Error} - If any error occurs during the process. */ const getApiConfigs = async targetedApi => { try { const apiConfigs = appYMLConfigs.app[targetedApi]; const providerConfigs = appYMLConfigs.providers[apiConfigs.provider]; const apiEnviromentVariables = enviromentKeys({ needle: targetedApi.toUpperCase(), data: process.env }); let target = { backend_url: apiEnviromentVariables[`${targetedApi.toUpperCase()}_BACKEND_URL`] }; const apiPassword = apiEnviromentVariables[`${targetedApi.toUpperCase()}_PASSWORD`]; switch (apiConfigs.provider) { case 'basic': target = { ...target, auth: { username: apiEnviromentVariables[`${targetedApi.toUpperCase()}_IDENTIFIER`], password: apiPassword } }; break; case 'apiKey': target = { ...target, authorization: { [providerConfigs.authorization_key]: `${providerConfigs.token_type} ${apiPassword}`, authorization_key: providerConfigs.authorization_key } }; break; default: const token = await serviceAccountLogin({ source: apiConfigs, sourceName: targetedApi, authProvider: providerConfigs, preferedCache: appConfigs$1.caching_sytem }); target = { ...target, authorization: { [providerConfigs.authorization_key]: token }, authorization_key: providerConfigs.authorization_key }; break; } return target; } catch (error) { const apiEnviromentVariables = enviromentKeys({ needle: targetedApi.toUpperCase(), data: process.env }); if (!process.env.NODE_ENV.startsWith('prod')) { if (error?.stack?.includes('ECONNREFUSED')) { console.warn(`Check that your ${targetedApi} server is running`); } if ([405, 404, 500].includes(error?.status_code)) { console.log(apiEnviromentVariables); } else { console.warn(`\nDid you try creating a "config/${process.env.NODE_ENV}.yml" file?`); console.log(error); } } throw error; } }; /** * A wrapper function for axios only to be used in server that handles fetching data from a specified URI. * It adds a Bearer token to the request headers if a valid token is provided. * If no token is available, the request is made without adding an Authorization header. * * @param {object} params - An object containing the parameters for the fetch request. * @param {string} params.uri - The URI to fetch data from. * @param {object} [params.options={ method: 'get' }] - Options for the fetch request. * @param {string} [params.target=appConfigs.defaultApiTarget] - The target API configuration. * @param {string} [params.user_token] - The access token to be added to the request headers. * @param {object} [params.next={}] - Additional parameters to be passed to the next function. * @param {object} [params.cache={}] - Parameters related to caching. * * @returns {Promise<any>} - A Promise that resolves with the fetched data or rejects with an error. */ const serverFetch = async ({ uri, options = { method: 'get' }, target = appConfigs$1.defaultApiTarget, user_token, next = {}, cache = {} }) => { try { const targetedApi = await getApiConfigs(target); const reqMethod = options.method.toLowerCase(); const destinationUrl = `${targetedApi.backend_url}${uri}`; const addOnOptions = { headers: { ...options.headers, ...(user_token ? { [targetedApi.authorization_key]: user_token } : targetedApi.authorization) }, ...(targetedApi.auth && { auth: targetedApi.auth }), next, cache }; if (appConfigs$1.isApplicationBuilding || reqMethod.includes('get')) { const { data } = await axios.get(destinationUrl, { ...(options.body && { data: options.body }), ...addOnOptions }); return data; } else { const requester = axios[options.axs_method || reqMethod]; const { data } = await requester(destinationUrl, options.body, { ...addOnOptions }); return data; } } catch (err) { return getErrStack(err); } }; exports.serverFetch = serverFetch;