PHP WebShell

Текущая директория: /opt/BitGoJS/modules/express/src

Просмотр файла: expressApp.ts

/**
 * @prettier
 */
import * as express from 'express';
import * as path from 'path';
import * as _ from 'lodash';
import * as debugLib from 'debug';
import * as https from 'https';
import * as http from 'http';
import * as morgan from 'morgan';
import * as fs from 'fs';
import type { Request as StaticRequest } from 'express-serve-static-core';
import * as timeout from 'connect-timeout';

import { Config, config } from './config';

const debug = debugLib('bitgo:express');

import { SSL_OP_NO_TLSv1 } from 'constants';
import {
  IpcError,
  NodeEnvironmentError,
  TlsConfigurationError,
  ExternalSignerConfigError,
  LightningSignerConfigError,
} from './errors';

import { Environments } from 'bitgo';
import * as clientRoutes from './clientRoutes';

/**
 * Set up the logging middleware provided by morgan
 *
 * @param app
 * @param config
 */
function setupLogging(app: express.Application, config: Config): void {
  // Set up morgan for logging, with optional logging into a file
  let middleware;
  if (config.logFile) {
    // create a write stream (in append mode)
    const accessLogPath = path.resolve(config.logFile);
    const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' });
    /* eslint-disable-next-line no-console */
    console.log('Log location: ' + accessLogPath);
    // setup the logger
    middleware = morgan('combined', { stream: accessLogStream });
  } else {
    middleware = morgan('combined');
  }

  app.use(middleware);
  morgan.token('remote-user', function (req: StaticRequest) {
    return req.isProxy ? 'proxy' : 'local_express';
  });
}

/**
 * If we're running in a custom env, set the appropriate environment URI and network properties
 *
 * @param config
 */
function configureEnvironment(config: Config): void {
  const { customRootUri, customBitcoinNetwork } = config;
  if (customRootUri) {
    Environments['custom'].uri = customRootUri;
  }

  if (customBitcoinNetwork) {
    Environments['custom'].network = customBitcoinNetwork;
  }
}

/**
 * Create an HTTP server configured for accepting HTTPS connections
 *
 * @param config application configuration
 * @param app
 * @return {Server}
 */
async function createHttpsServer(app: express.Application, config: Config): Promise<https.Server> {
  const { keyPath, crtPath, sslKey, sslCert } = config;
  let key: string;
  let cert: string;
  if (sslKey && sslCert) {
    key = sslKey;
    cert = sslCert;
  } else if (keyPath && crtPath) {
    const privateKeyPromise = fs.promises.readFile(keyPath, 'utf8');
    const certificatePromise = fs.promises.readFile(crtPath, 'utf8');

    [key, cert] = await Promise.all([privateKeyPromise, certificatePromise]);
  } else {
    throw new Error('Failed to get ssl key and certificate');
  }

  return https.createServer({ secureOptions: SSL_OP_NO_TLSv1, key, cert }, app);
}

/**
 * Create an HTTP server configured for accepting plain old HTTP connections
 *
 * @param app
 * @return {Server}
 */
function createHttpServer(app: express.Application): http.Server {
  return http.createServer(app);
}

/**
 * Create a startup function which will be run upon server initialization
 *
 * @param config
 * @param baseUri
 * @return {Function}
 */
export function startup(config: Config, baseUri: string): () => void {
  return function () {
    const { env, ipc, customRootUri, customBitcoinNetwork, signerMode, lightningSignerFileSystemPath } = config;
    /* eslint-disable no-console */
    console.log('BitGo-Express running');
    console.log(`Environment: ${env}`);
    if (ipc) {
      console.log(`IPC path: ${ipc}`);
    } else {
      console.log(`Base URI: ${baseUri}`);
    }
    if (customRootUri) {
      console.log(`Custom root URI: ${customRootUri}`);
    }
    if (customBitcoinNetwork) {
      console.log(`Custom bitcoin network: ${customBitcoinNetwork}`);
    }
    if (signerMode) {
      console.log(`External signer mode: ${signerMode}`);
    }
    if (lightningSignerFileSystemPath) {
      console.log(`Lightning signer file system path: ${lightningSignerFileSystemPath}`);
    }
    /* eslint-enable no-console */
  };
}

/**
 * helper function to determine whether we should run the server over TLS or not
 */
function isTLS(config: Config): config is Config & { keyPath: string; crtPath: string } {
  const { keyPath, crtPath, sslKey, sslCert } = config;
  return Boolean((keyPath && crtPath) || (sslKey && sslCert));
}

/**
 * Create either a HTTP or HTTPS server
 */
export async function createServer(config: Config, app: express.Application): Promise<https.Server | http.Server> {
  const server = isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);

  // Set keepAliveTimeout and headersTimeout if specified in config
  if (config.keepAliveTimeout !== undefined) {
    server.keepAliveTimeout = config.keepAliveTimeout;
  }
  if (config.headersTimeout !== undefined) {
    server.headersTimeout = config.headersTimeout;
  }
  return server;
}

/**
 * Create the base URI where the BitGoExpress server will be available once started
 * @return {string}
 */
export function createBaseUri(config: Config): string {
  const { bind, port } = config;
  const tls = isTLS(config);
  const isStandardPort = (port === 80 && !tls) || (port === 443 && tls);
  return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`;
}

/**
 * Check the that the json file exists
 * @param path
 */
function checkJsonFilePath(path: string) {
  try {
    const jsonFile = fs.readFileSync(path, { encoding: 'utf8' });
    JSON.parse(jsonFile);
  } catch (e) {
    throw new Error(`Failed to parse ${path} - ${e.message}`);
  }
}

/**
 * Check environment and other preconditions to ensure bitgo-express can start safely
 * @param config
 */
function checkPreconditions(config: Config) {
  const {
    env,
    disableEnvCheck,
    bind,
    ipc,
    disableSSL,
    keyPath,
    crtPath,
    sslKey,
    sslCert,
    customRootUri,
    customBitcoinNetwork,
    externalSignerUrl,
    signerMode,
    signerFileSystemPath,
    lightningSignerFileSystemPath,
  } = config;

  // warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
  if (env === 'prod' && process.env.NODE_ENV !== 'production') {
    if (!disableEnvCheck) {
      throw new NodeEnvironmentError(
        'NODE_ENV should be set to production when running against prod environment. Use --disableenvcheck if you really want to run in a non-production node configuration.'
      );
    } else {
      console.warn(
        `warning: unsafe NODE_ENV '${process.env.NODE_ENV}'. NODE_ENV must be set to 'production' when running against BitGo production environment.`
      );
    }
  }

  const needsTLS = !ipc && env === 'prod' && bind !== 'localhost' && !disableSSL;

  // make sure keyPath and crtPath are set when running over TLS
  if (needsTLS && !(keyPath && crtPath) && !(sslKey && sslCert)) {
    throw new TlsConfigurationError('Must enable TLS when running against prod and listening on external interfaces!');
  }

  if (Boolean(keyPath) !== Boolean(crtPath) || Boolean(sslKey) !== Boolean(sslCert)) {
    throw new TlsConfigurationError('Must provide both keypath and crtpath when running in TLS mode!');
  }

  if ((customRootUri || customBitcoinNetwork) && env !== 'custom') {
    console.warn(`customRootUri or customBitcoinNetwork is set, but env is '${env}'. Setting env to 'custom'.`);
    config.env = 'custom';
  }

  if (externalSignerUrl !== undefined && (signerMode !== undefined || signerFileSystemPath !== undefined)) {
    throw new ExternalSignerConfigError(
      'signerMode or signerFileSystemPath is set, but externalSignerUrl is also set.'
    );
  }

  if ((signerMode !== undefined || signerFileSystemPath !== undefined) && !(signerMode && signerFileSystemPath)) {
    throw new ExternalSignerConfigError(
      'signerMode and signerFileSystemPath must both be set in order to run in external signing mode.'
    );
  }

  if (signerMode !== undefined && lightningSignerFileSystemPath !== undefined) {
    throw new LightningSignerConfigError(
      'signerMode and lightningSignerFileSystemPath cannot be set at the same time.'
    );
  }

  if (signerFileSystemPath !== undefined) {
    checkJsonFilePath(signerFileSystemPath);
  }

  if (lightningSignerFileSystemPath !== undefined) {
    checkJsonFilePath(lightningSignerFileSystemPath);
  }
}

export function setupRoutes(app: express.Application, config: Config): void {
  if (config.signerMode) {
    clientRoutes.setupSigningRoutes(app, config);
  } else {
    if (config.lightningSignerFileSystemPath) {
      clientRoutes.setupLightningSignerNodeRoutes(app, config);
    }
    clientRoutes.setupAPIRoutes(app, config);
  }
}

export function app(cfg: Config): express.Application {
  debug('app is initializing');

  const app = express();

  setupLogging(app, cfg);
  debug('logging setup');

  const { debugNamespace } = cfg;

  // enable specified debug namespaces
  if (_.isArray(debugNamespace)) {
    for (const ns of debugNamespace) {
      if (ns && !debugLib.enabled(ns)) {
        debugLib.enable(ns);
      }
    }
  }

  checkPreconditions(cfg);
  debug('preconditions satisfied');

  // Be more robust about accepting URLs with double slashes
  app.use(function replaceUrlSlashes(req, res, next) {
    req.url = req.url.replace(/\/\//g, '/');
    next();
  });

  app.use(timeout(cfg.timeout));

  // Decorate the client routes
  setupRoutes(app, cfg);

  configureEnvironment(cfg);

  return app;
}

/**
 * Prepare to listen on an IPC (unix domain) socket instead of a normal TCP port.
 * @param ipcSocketFilePath path to file where IPC socket should be created
 */
export async function prepareIpc(ipcSocketFilePath: string) {
  if (process.platform === 'win32') {
    throw new IpcError(`IPC option is not supported on platform ${process.platform}`);
  }

  try {
    const stat = fs.statSync(ipcSocketFilePath);
    if (!stat.isSocket()) {
      throw new IpcError('IPC socket is not actually a socket');
    }
    // ipc socket does exist and is indeed a socket. However, the socket cannot already exist prior
    // to being bound since it will be created by express internally when binding. If there's a stale
    // socket from the last run, clean it up before attempting to bind to it again. Arguably, it would
    // be better to do this before exiting, but that gets a bit more complicated when all exit paths
    // need to clean up the socket file correctly.
    fs.unlinkSync(ipcSocketFilePath);
  } catch (e) {
    if (e.code !== 'ENOENT') {
      throw e;
    }
  }
}

export async function init(): Promise<void> {
  const cfg = config();
  const expressApp = app(cfg);

  const server = await createServer(cfg, expressApp);

  const { port, bind, ipc } = cfg;
  const baseUri = createBaseUri(cfg);

  if (ipc) {
    await prepareIpc(ipc);
    server.listen(ipc, startup(cfg, baseUri));
  } else {
    server.listen(port, bind, startup(cfg, baseUri));
  }
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!