dragonchain/dragonchain-sdk-javascript

View on GitHub
src/services/config-service/ConfigCient.ts

Summary

Maintainability
C
7 hrs
Test Coverage
/**
 * Copyright 2020 Dragonchain, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { readFile } from 'fs';
import { promisify } from 'util';
import { platform, homedir } from 'os';
import * as path from 'path';
import * as ini from 'ini';
import fetch from 'node-fetch';
import { DragonchainCredentials } from '../credential-service/DragonchainCredentials';
import { FailureByDesign } from '../../errors/FailureByDesign';
import { logger } from '../../index';
/**
 * @hidden
 */
let readFileAsync: any = async () => '';
if (readFile) readFileAsync = promisify(readFile);

/**
 * @hidden
 * Get the path for the configuration file depending on the OS
 * @returns {string} dragonchain configuration file path
 * @example e.g.: "~/.dragonchain/credentials" or "%LOCALAPPDATA%\dragonchain\credentials" on Windows
 */
const getConfigFilePath = (injected: any = { platform, homedir }): string => {
  if (injected.platform() === 'win32') {
    return path.join(process.env.LOCALAPPDATA || '', 'dragonchain', 'credentials');
  }
  return path.join(injected.homedir(), '.dragonchain', 'credentials');
};

/**
 * @hidden
 * Get the endpoint for a dragonchain from environment variables
 * @returns {string} Dragonchain enpdoint if found, empty string if not
 */
const getIdFromEnvVars = (): string => {
  return process.env.DRAGONCHAIN_ID || '';
};

/**
 * @hidden
 * get the endpoint for a dragonchain from environment variables
 * @returns {string} Dragonchain enpdoint if found, empty string if not
 */
const getEndpointFromEnvVars = (): string => {
  return process.env.DRAGONCHAIN_ENDPOINT || '';
};

/**
 * @hidden
 * get the credentials for a dragonchain from environment variables
 * @returns {DragonchainCredentials} Dragonchain enpdoint if found, false if not
 */
const getCredsFromEnvVars = (): any => {
  const authKey = process.env.AUTH_KEY;
  const authKeyId = process.env.AUTH_KEY_ID;
  if (!authKey || !authKeyId) return false;
  return { authKey, authKeyId };
};

/**
 * @hidden
 * get the default dragonchain ID from the configuration file
 * @returns {Promise<string>} dragonchain ID if found in file, empty string if not
 */
const getIdFromFile = async (injected: any = { readFileAsync }): Promise<string> => {
  let config: any = {};
  try {
    config = ini.parse(await injected.readFileAsync(getConfigFilePath(), 'utf-8'));
  } catch (e) {
    logger.debug(`Error loading ID from config file ${e}`);
    return '';
  }
  return config.default && config.default.dragonchain_id ? config.default.dragonchain_id : '';
};

/**
 * @hidden
 * get the dragonchain endpoint from the configuration file
 * @returns {Promise<string>} dragonchain endpoint if found in file, empty string if not
 */
const getEndpointFromFile = async (dragonchainId: string, injected: any = { readFileAsync }): Promise<string> => {
  let config: any = {};
  try {
    config = ini.parse(await injected.readFileAsync(getConfigFilePath(), 'utf-8'));
  } catch (e) {
    logger.debug(`Error loading from config file ${e}`);
    return '';
  }
  return config[dragonchainId] && config[dragonchainId].endpoint ? config[dragonchainId].endpoint : '';
};

/**
 * @hidden
 * get the dragonchain credentials from the configuration file
 * @returns {Promise<DragonchainCredentials>} dragonchain credentials if found in file, false if not
 */
const getCredsFromFile = async (dragonchainId: string, injected: any = { readFileAsync }): Promise<any> => {
  let config: any = {};
  try {
    config = ini.parse(await injected.readFileAsync(getConfigFilePath(), 'utf-8'));
  } catch (e) {
    logger.debug(`Error loading credentials from config file ${e}`);
    return false;
  }
  if (!config[dragonchainId]) return false;
  const { auth_key, auth_key_id } = config[dragonchainId];
  if (!auth_key || !auth_key_id) return false;
  return {
    authKey: auth_key,
    authKeyId: auth_key_id,
  };
};

/**
 * @hidden
 * use a remote service to fetch the endpoint of a dragonchain by id
 * @param {string} dragonchainId dragonchainId to request endpoint for
 * @returns {Promise<string>} the endpoint of the dragonchain
 * @throws {FailureByDesign<NOT_FOUND>} if unable to contact remote service or not found
 */
const getEndpointFromRemote = async (dragonchainId: string, injected: any = { fetch }): Promise<string> => {
  try {
    const result = await injected.fetch(`https://matchmaking.api.dragonchain.com/registration/${dragonchainId}`, { timeout: 30000 });
    const json: any = result.json();
    const endpoint: string = json.url;
    if (!endpoint) throw new Error(`Bad response from remote service ${json}`); // Caught and re-thrown below
    return endpoint;
  } catch (e) {
    throw new FailureByDesign('NOT_FOUND', `Failure to retrieve dragonchain endpoint from remote service ${e}`);
  }
};

/**
 * @hidden
 * get credentials for a dragonchain from the standard location for a smart contract
 * @returns {Promise<DragonchainCredentials>} dragonchain credentials if found, false if not
 */
const getCredsAsSmartContract = async (injected: any = { readFileAsync }): Promise<any> => {
  let authKeyId = '';
  let authKey = '';
  const basePath = path.join('/', 'var', 'openfaas', 'secrets');
  try {
    authKeyId = await injected.readFileAsync(path.join(basePath, `sc-${process.env.SMART_CONTRACT_ID}-auth-key-id`), 'utf-8');
    authKey = await injected.readFileAsync(path.join(basePath, `sc-${process.env.SMART_CONTRACT_ID}-secret-key`), 'utf-8');
  } catch (e) {
    logger.debug(`Error loading credentials from SC location ${e}`);
    return false;
  }
  if (!authKeyId || !authKey) return false;
  return { authKey, authKeyId };
};

/**
 * Get the default configured dragonchainId from environment/config file
 * @returns {Promise<string>}
 * @throws {FailureByDesign<NOT_FOUND>}
 */
const getDragonchainId = async (injected: any = { getIdFromEnvVars, getIdFromFile }): Promise<string> => {
  logger.debug('Checking if dragonchain_id is in the environment');
  let dragonchainId = injected.getIdFromEnvVars();
  if (dragonchainId) return dragonchainId;
  logger.debug('Dragonchain ID not provided in environment, will search on disk');
  dragonchainId = await injected.getIdFromFile();
  if (dragonchainId) return dragonchainId;
  throw new FailureByDesign('NOT_FOUND', 'Configuration file is missing a default id');
};

/**
 * @hidden
 * Get the endpoint for a dragonchain. First checks environment, then configuration files, then a remote service
 * @param {string} dragonchainId dragonchainId to get endpoint for
 * @returns {Promise<string>} Endpoint of the dragonchain
 * @throws {FailureByDesign<NOT_FOUND>}
 */
const getDragonchainEndpoint = async (dragonchainId: string, injected: any = { getEndpointFromEnvVars, getEndpointFromFile, getEndpointFromRemote }): Promise<string> => {
  let endpoint = injected.getEndpointFromEnvVars();
  if (endpoint) return endpoint;
  logger.debug("Endpoint isn't in environment, trying to load from ini config file");
  endpoint = await injected.getEndpointFromFile(dragonchainId);
  if (endpoint) return endpoint;
  logger.debug("Endpoint isn't in config file, trying to load from remote service");
  return injected.getEndpointFromRemote(dragonchainId); // This will throw NOT_FOUND if necessary
};

/**
 * Get the credentials for a dragonchain. First checks environment, then configuration files, then a smart contract location
 * @param {string} dragonchainId dragonchainId to get credentials for
 * @returns {DragonchainCredentials} Credentials of the dragonchain
 * @throws {FailureByDesign<NOT_FOUND>}
 */
const getDragonchainCredentials = async (
  dragonchainId: string,
  injected: any = { getCredsFromEnvVars, getCredsFromFile, getCredsAsSmartContract }
): Promise<DragonchainCredentials> => {
  let credentials: DragonchainCredentials = injected.getCredsFromEnvVars();
  if (credentials) return credentials;
  logger.debug("Credentials aren't in environment, trying to load from ini config file");
  credentials = await injected.getCredsFromFile(dragonchainId);
  if (credentials) return credentials;
  logger.debug("Credentials aren't in config file, trying to load as a smart contract");
  credentials = await injected.getCredsAsSmartContract();
  if (credentials) return credentials;
  throw new FailureByDesign('NOT_FOUND', `Credentials for ${dragonchainId} could not be found`);
};

export {
  getDragonchainId,
  getDragonchainEndpoint,
  getDragonchainCredentials,
  getConfigFilePath,
  getIdFromEnvVars,
  getEndpointFromEnvVars,
  getCredsFromEnvVars,
  getIdFromFile,
  getEndpointFromFile,
  getCredsFromFile,
  getEndpointFromRemote,
  getCredsAsSmartContract,
};