osnmarket-abstract-core
Version:
Abstract Core Functionalities
307 lines (295 loc) • 9.75 kB
JavaScript
;
var axios = require('axios');
var FormData = require('form-data');
var querystring = require('querystring');
var NodeCache = require('node-cache');
var configYml = require('config-yml');
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();
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
};
/**
* 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.tokenLifespan
}) {
if (strategy != 'nodeCache') {
throw new Error(`Invalid caching strategy: ${strategy}`);
}
try {
if (strategy == 'nodeCache') {
if (store) {
return nodeCache.set(name, store, ttl);
}
if (retrieve) {
try {
if (nodeCache.has(name)) {
return nodeCache.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.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;
}
};
const extractSavedToken = async target => {
const {
authorization
} = await getApiConfigs(target);
return authorization;
};
exports.extractSavedToken = extractSavedToken;