PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo-express/node_modules/bitgo/src

Просмотр файла: bitgo.js

//
// BitGo JavaScript SDK
//
// Copyright 2014, BitGo, Inc.  All Rights Reserved.
//

var superagent = require('superagent');
var bitcoin = require('./bitcoin');
var sanitizeHtml = require('sanitize-html');
var eol = require('eol');
var BaseCoin = require('./v2/baseCoin');
var Blockchain = require('./blockchain');
var EthBlockchain = require('./eth/ethBlockchain');
var Keychains = require('./keychains');
var TravelRule = require('./travelRule');
var Wallet = require('./wallet');
var EthWallet = require('./eth/ethWallet');
var Wallets = require('./wallets');
var EthWallets = require('./eth/ethWallets');
var Markets = require('./markets');
var PendingApprovals = require('./pendingapprovals');
var sjcl = require('./sjcl.min');
var common = require('./common');
var Util = require('./util');
var Q = require('q');
var pjson = require('../package.json');
var moment = require('moment');
var _ = require('lodash');
var url = require('url');
var querystring = require('querystring');
var crypto = require('crypto');

if (!process.browser) {
  require('superagent-proxy')(superagent);
}

// Patch superagent to return promises
var _end = superagent.Request.prototype.end;
superagent.Request.prototype.end = function(cb) {
  var self = this;
  if (typeof cb === 'function') return _end.call(self, cb);

  return new Q.Promise(function(resolve, reject) {
    var error;
    try {
      return _end.call(self, function(error, response) {
        if (error) {
          return reject(error);
        }
        return resolve(response);
      });
    } catch (_error) {
      error = _error;
      return reject(error);
    }
  });
};

// Handle HTTP errors appropriately, returning the result body, or a named
// field from the body, if the optionalField parameter is provided.
superagent.Request.prototype.result = function(optionalField) {
  return this.then(handleResponseResult(optionalField), handleResponseError);
};

var handleResponseResult = function(optionalField) {
  return function(res) {
    if (typeof(res.status) === 'number' && res.status >= 200 && res.status < 300) {
      return optionalField ? res.body[optionalField] : res.body;
    }
    throw errFromResponse(res);
  }
};

var errFromResponse = function(res) {
  var errString = createResponseErrorString(res);
  var err = new Error(errString);

  err.status = res.status;
  if (res.body) {
    err.result = res.body;
  }
  if (_.has(res.headers, 'x-auth-required') && (res.headers['x-auth-required'] === 'true')) {
    err.invalidToken = true;
  }
  if (res.body.needsOTP) {
    err.needsOTP = true;
  }
  return err;
};

var handleResponseError = function(e) {
  if (e.response) {
    throw errFromResponse(e.response);
  }
  throw e;
};

/**
 * There are many ways a request can fail, and may ways information on that failure can be
 * communicated to the client. This function tries to handle those cases and create a sane error string
 * @param res Response from an HTTP request
 * @returns {String}
 */
var createResponseErrorString = function(res) {
  var errString = res.statusCode.toString(); // at the very least we'll have the status code
  if (res.body.error) {
    // this is the case we hope for, where the server gives us a nice error from the JSON body
    errString = res.body.error;
  } else {
    // things get messy from here on, we try different parts of the response, salvaging what we can
    if (res.res && res.res.statusMessage) {
      errString = errString + '\n' + res.res.statusMessage;
    }
    if (res.text) {
      // if the response came back as text, we try to parse it as HTML and remove all tags, leaving us
      // just the bare text, which we then trim of excessive newlines and limit to a certain length
      try {
        var sanitizedText = sanitizeHtml(res.text, { allowedTags: [] });
        sanitizedText = sanitizedText.trim();
        sanitizedText = eol.lf(sanitizedText); // use '\n' for all newlines
        sanitizedText = _.replace(sanitizedText, /\n[ |\t]{1,}\n/g, '\n\n'); // remove the spaces/tabs between newlines
        sanitizedText = _.replace(sanitizedText, /[\n]{3,}/g, '\n\n'); // have at most 2 consecutive newlines
        sanitizedText = sanitizedText.substring(0, 5000); // prevent message from getting too large
        errString = errString + '\n' + sanitizedText; // add it to our existing errString (at this point the more info the better!)
      } catch (e) {
        // do nothing, the response's HTML was too wacky to be parsed cleanly
      }
    }
  }

  return errString;
};

//
// Constructor for BitGo Object
// arguments:
//   @useProduction: flag to use the production bitcoin network rather than the
//                   testnet network.
//
var testNetWarningMessage = false;
var BitGo = function(params) {
  params = params || {};
  if (!common.validateParams(params, [], ['clientId', 'clientSecret', 'refreshToken', 'accessToken', 'userAgent', 'customRootURI', 'customBitcoinNetwork']) ||
    (params.useProduction && typeof(params.useProduction) != 'boolean')) {
    throw new Error('invalid argument');
  }

  if ((!params.clientId) !== (!params.clientSecret)) {
    throw new Error('invalid argument - must provide both client id and secret');
  }

  // By default, we operate on the test server.
  // Deprecate useProduction in the future
  if (params.useProduction) {
    if (params.env && params.env !== 'prod') {
      throw new Error("Cannot set test environment and use production");
    }
    params.env = 'prod';
  }

  if (params.env === 'production') {
    params.env = 'prod'; // make life easier
  }

  if (params.customRootURI ||
    params.customBitcoinNetwork ||
    params.customSigningAddress ||
    process.env.BITGO_CUSTOM_ROOT_URI ||
    process.env.BITGO_CUSTOM_BITCOIN_NETWORK) {
    params.env = 'custom';
    if (params.customRootURI) {
      common.Environments['custom'].uri = params.customRootURI;
    }
    if (params.customBitcoinNetwork) {
      common.Environments['custom'].network = params.customBitcoinNetwork;
    }
    if (params.customSigningAddress) {
      common.Environments['custom'].customSigningAddress = params.customSigningAddress;
    }
  }

  if (params.env) {
    if (common.Environments[params.env]) {
      this._baseUrl = common.Environments[params.env].uri;
    } else {
      throw new Error('invalid environment');
    }
  } else {
    params.env = process.env.BITGO_ENV || 'test';
    if (!testNetWarningMessage && params.env === 'test') {
      testNetWarningMessage = true;
      console.log('BitGo SDK env not set - defaulting to testnet at test.bitgo.com.');
    }
  }
  this.env = params.env;

  common.setNetwork(common.Environments[params.env].network);
  common.setEthNetwork(common.Environments[params.env].ethNetwork);
  common.setRmgNetwork(common.Environments[params.env].rmgNetwork);

  if (!this._baseUrl) {
    this._baseUrl = common.Environments[params.env].uri;
  }

  this._baseApiUrl = this._baseUrl + '/api/v1';
  this._user = null;
  this._keychains = null;
  this._wallets = null;
  this._clientId = params.clientId;
  this._clientSecret = params.clientSecret;
  this._token = params.accessToken || null;
  this._refreshToken = params.refreshToken || null;
  this._userAgent = params.userAgent || 'BitGoJS/' + this.version();
  this._promise = Q;

  // whether to perform extra client-side validation for some things, such as
  // address validation or signature validation. defaults to true, but can be
  // turned off by setting to false. can also be overridden individually in the
  // functions that use it.
  this._validate = params.validate === undefined ? true : params.validate;

  // Create superagent methods specific to this BitGo instance.
  this.request = {};
  var methods = ['get', 'post', 'put', 'del'];

  if (!params.proxy && process.env.BITGO_USE_PROXY) {
    params.proxy = process.env.BITGO_USE_PROXY;
  }

  if (process.browser && params.proxy) {
    throw new Error('cannot use https proxy params while in browser');
  }

  // This is a patching function which can apply our authorization
  // headers to any outbound request.
  var createPatch = function(method) {
    return function() {
      var req = superagent[method].apply(null, arguments);
      if (params.proxy) {
        req = req.proxy(params.proxy);
      }

      // Patch superagent to return promises
      req.prototypicalEnd = req.end;
      req.end = function() {
        // intercept a request before it's submitted to the server for v2 authentication (based on token)
        var bitgo = self;

        this.isV2Authenticated = true;
        // some of the older tokens appear to be only 40 characters long
        if ((bitgo._token && bitgo._token.length !== 67 && bitgo._token.indexOf('v2x') !== 0)
          || req.forceV1Auth) {
          // use the old method
          this.isV2Authenticated = false;

          this.set('Authorization', 'Bearer ' + bitgo._token);
          return this.prototypicalEnd.apply(this, arguments);
        }

        this.set('BitGo-Auth-Version', '2.0');
        if (bitgo._token) {

          // do a localized data serialization process
          var data = this._data;
          if (typeof data !== 'string') {
            function isJSON(mime) {
              return /[\/+]json\b/.test(mime);
            }

            var contentType = this.getHeader('Content-Type');
            // Parse out just the content type from the header (ignore the charset)
            if (contentType) {
              contentType = contentType.split(';')[0]
            }
            var serialize = superagent.serialize[contentType];
            if (!serialize && isJSON(contentType)) {
              serialize = superagent.serialize['application/json'];
            }
            if (serialize) {
              data = serialize(data);
            }
          }
          this._data = data;

          var urlDetails = url.parse(req.url);

          var queryString = null;
          if (req._query && req._query.length > 0) {
            // browser version
            queryString = req._query.join('&');
            req._query = [];
          } else if (req.qs) {
            // node version
            queryString = querystring.stringify(req.qs);
            req.qs = null;
          }

          if (queryString) {
            if (urlDetails.search) {
              urlDetails.search += '&' + queryString;
            } else {
              urlDetails.search = '?' + queryString;
            }
            req.url = urlDetails.format();
            urlDetails = url.parse(req.url);
          }

          var queryPath = (urlDetails.query && urlDetails.query.length > 0) ? urlDetails.path : urlDetails.pathname;
          var timestamp = Date.now();
          var signatureSubject = [timestamp, queryPath, data].join('|');

          this.set('Auth-Timestamp', timestamp);

          // calculate the SHA256 hash of the token
          var hashDigest = sjcl.hash.sha256.hash(bitgo._token);
          var hash = sjcl.codec.hex.fromBits(hashDigest);

          // we're not sending the actual token, but only its hash
          this.set('Authorization', 'Bearer ' + hash);

          // calculate the HMAC
          var hmacKey = sjcl.codec.utf8String.toBits(bitgo._token);
          var hmacDigest = (new sjcl.misc.hmac(hmacKey, sjcl.hash.sha256)).mac(signatureSubject);
          var hmac = sjcl.codec.hex.fromBits(hmacDigest);

          this.set('HMAC', hmac);
        }

        return this.prototypicalEnd.apply(this, arguments);
      };

      // verify that the response received from the server is signed correctly
      // right now, it is very permissive with the timestamp variance
      req.verifyResponse = function(response) {
        var bitgo = self;

        if (!req.isV2Authenticated || !bitgo._token) {
          return response;
        }

        var urlDetails = url.parse(req.url);

        // verify the HMAC and timestamp
        var timestamp = response.headers.timestamp;
        var queryPath = (urlDetails.query && urlDetails.query.length > 0) ? urlDetails.path : urlDetails.pathname;

        var signatureSubject = [timestamp, queryPath, response.statusCode, response.text].join('|');

        // calculate the HMAC
        var hmacKey = sjcl.codec.utf8String.toBits(bitgo._token);
        var hmacDigest = (new sjcl.misc.hmac(hmacKey, sjcl.hash.sha256)).mac(signatureSubject);
        var expectedHmac = sjcl.codec.hex.fromBits(hmacDigest);

        var receivedHmac = response.headers.hmac;
        if (expectedHmac !== receivedHmac) {
          var error = new Error('invalid response HMAC, possible man-in-the-middle-attack');
          error.status = 511;
          throw error;
        }
        return response;
      };

      var lastPromise = null;
      req.then = function() {

        if (!lastPromise) {
          var reference = req.end()
          .then(req.verifyResponse);
          lastPromise = reference.then.apply(reference, arguments);
        } else {
          lastPromise = lastPromise.then.apply(lastPromise, arguments);
        }

        return lastPromise;
      };

      if (!process.browser) {
        // If not in the browser, set the User-Agent. Browsers don't allow
        // setting of User-Agent, so we must disable this when run in the
        // browser (browserify sets process.browser).
        req.set('User-Agent', self._userAgent);
      }

      // Set the request timeout to just above 5 minutes by default
      req.timeout(process.env.BITGO_TIMEOUT * 1000 || 305 * 1000);
      return req;
    };
  };

  for (var index in methods) {
    var self = this;
    var method = methods[index];
    self[method] = createPatch(method);
  }

  // Kick off first load of constants
  this.fetchConstants();
};

/**
 * Create a basecoin object
 * @param coinName
 */
BitGo.prototype.coin = function(coinName) {
  return new BaseCoin(this, coinName);
};

// Accessor object for Ethereum methods
BitGo.prototype.eth = function() {
  var self = this;

  var ethBlockchain = function() {
    if (!self._ethBlockchain) {
      self._ethBlockchain = new EthBlockchain(self);
    }
    return self._ethBlockchain;
  };

  var ethWallets = function() {
    if (!self._ethWallets) {
      self._ethWallets = new EthWallets(self);
    }
    return self._ethWallets;
  };

  var newEthWalletObject = function(walletParams) {
    return new EthWallet(self, walletParams);
  };

  var verifyEthAddress = function(params) {
    params = params || {};
    common.validateParams(params, ['address'], []);

    var address = params.address;
    return address.indexOf('0x') == 0 && address.length == 42;
  };

  var retrieveGasBalance = function(params, callback) {
    return self.get(self.url('/eth/user/gas'))
    .result()
    .nodeify(callback);
  };

  return {
    blockchain: ethBlockchain,
    wallets: ethWallets,
    newWalletObject: newEthWalletObject,
    verifyAddress: verifyEthAddress,
    weiToEtherString: Util.weiToEtherString,
    gasBalance: retrieveGasBalance
  };
};

BitGo.prototype.getValidate = function() {
  return this._validate;
};

BitGo.prototype.setValidate = function(validate) {
  if (typeof(validate) !== 'boolean') {
    throw new Error('invalid argument');
  }
  this._validate = validate;
};

// Return the current BitGo environment
BitGo.prototype.getEnv = function() {
  return this.env;
};

BitGo.prototype.clear = function() {
  this._user = this._token = this._refreshToken = undefined;
};

// Helper function to return a rejected promise or call callback with error
BitGo.prototype.reject = function(msg, callback) {
  return Q().thenReject(new Error(msg)).nodeify(callback);
};

//
// version
// Gets the version of the BitGoJS API
//
BitGo.prototype.version = function() {
  return pjson.version;
};

BitGo.prototype.toJSON = function() {
  return {
    user: this._user,
    token: this._token,
    extensionKey: this._extensionKey ? this._extensionKey.toWIF() : null
  };
};

BitGo.prototype.fromJSON = function(json) {
  this._user = json.user;
  this._token = json.token;
  if (json.extensionKey) {
    this._extensionKey = bitcoin.ECPair.fromWIF(json.extensionKey, bitcoin.getNetwork());
  }
};

BitGo.prototype.user = function() {
  return this._user;
};

BitGo.prototype.verifyAddress = function(params) {
  params = params || {};
  common.validateParams(params, ['address'], []);

  var address;

  try {
    address = bitcoin.address.fromBase58Check(params.address);
  } catch (e) {
    return false;
  }

  var network = bitcoin.getNetwork();
  return address.version === network.pubKeyHash || address.version === network.scriptHash;
};

BitGo.prototype.verifyPassword = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['password'], []);

  if (!this._user || !this._user.username) {
    throw new Error('no current user');
  }
  var key = sjcl.codec.utf8String.toBits(this._user.username);
  var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
  var hmacPassword = sjcl.codec.hex.fromBits(hmac.encrypt(params.password));

  return this.post(this.url('/user/verifypassword'))
  .send({ password: hmacPassword })
  .result('valid')
  .nodeify(callback);
};

//
// encrypt
// Utility function to encrypt locally.
//
BitGo.prototype.encrypt = function(params) {
  params = params || {};
  common.validateParams(params, ['input', 'password'], []);

  // SJCL internally reuses salts for the same password, so we force a new random salt everytime
  // We use random.randomWords(2,0) because it's what SJCL uses for randomness by default
  var randomSalt = sjcl.random.randomWords(2, 0);
  var encryptOptions = { iter: 10000, ks: 256, salt: randomSalt };
  return sjcl.encrypt(params.password, params.input, encryptOptions);
};

//
// decrypt
// Utility function to decrypt locally.
//
BitGo.prototype.decrypt = function(params) {
  params = params || {};
  common.validateParams(params, ['input', 'password'], []);

  return sjcl.decrypt(params.password, params.input);
};

//
// ecdhSecret
// Construct an ECDH secret from a private key and other user's public key
//
BitGo.prototype.getECDHSecret = function(params) {
  params = params || {};
  common.validateParams(params, ['otherPubKeyHex'], []);

  if (typeof(params.eckey) !== 'object') {
    throw new Error('eckey object required');
  }

  var otherKeyPub = bitcoin.ECPair.fromPublicKeyBuffer(new Buffer(params.otherPubKeyHex, 'hex'));
  var secretPoint = otherKeyPub.Q.multiply(params.eckey.d);
  var secret = Util.bnToByteArrayUnsigned(secretPoint.affineX);
  return new Buffer(secret).toString('hex');
};

//
// user sharing keychain
// Gets the user's private keychain, used for receiving shares
BitGo.prototype.getECDHSharingKeychain = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], []);

  var self = this;

  return this.get(this.url('/user/settings'))
  .result()
  .then(function(result) {
    if (!result.settings.ecdhKeychain) {
      return self.reject('ecdh keychain not found for user', callback);
    }

    return self.keychains().get({ xpub: result.settings.ecdhKeychain });
  })
  .nodeify(callback);
};

/**
 * Get bitcoin market data
 */
BitGo.prototype.markets = function() {
  if (!this._markets) {
    this._markets = new Markets(this);
  }
  return this._markets;
};

//
// (Deprecated: Will be removed in the future) use bitgo.markets().latest()
// market
// Get the latest bitcoin prices.
//
BitGo.prototype.market = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/market/latest'))
  .result()
  .nodeify(callback);
};

//
// (Deprecated: Will be removed in the future) use bitgo.markets().yesterday()
// market data yesterday
// Get market data from yesterday
//
BitGo.prototype.yesterday = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/market/yesterday'))
  .result()
  .nodeify(callback);
};

/**
 * Synchronous method for activating an access token.
 * @param params
 *  - accessToken: the token to be used
 * @param callback
 */
BitGo.prototype.authenticateWithAccessToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['accessToken'], [], callback);

  this._token = params.accessToken;
};

/**
 *
 * @param responseBody Response body object
 * @param password Password for the symmetric decryption
 */
BitGo.prototype.handleTokenIssuance = function(responseBody, password) {
  // make sure the response body contains the necessary properties
  common.validateParams(responseBody, ['derivationPath'], ['encryptedECDHXprv']);

  var serverXpub = common.Environments[this.env].serverXpub;
  var ecdhXprv = this._ecdhXprv;
  if (!ecdhXprv) {
    if (!password || !responseBody.encryptedECDHXprv) {
      throw new Error('ecdhXprv property must be set or password and encrypted encryptedECDHXprv must be provided');
    }
    try {
      ecdhXprv = this.decrypt({ input: responseBody.encryptedECDHXprv, password: password });
    } catch (e) {
      e.errorCode = 'ecdh_xprv_decryption_failure';
      console.error('Failed to decrypt encryptedECDHXprv.');
      throw e;
    }
  }

  // construct HDNode objects for client's xprv and server's xpub
  var clientHDNode = bitcoin.HDNode.fromBase58(ecdhXprv);
  var serverHDNode = bitcoin.HDNode.fromBase58(serverXpub);

  // BIP32 derivation path is applied to both client and server master keys
  var derivationPath = responseBody.derivationPath;
  var clientDerivedNode = bitcoin.hdPath(clientHDNode).derive(derivationPath);
  var serverDerivedNode = bitcoin.hdPath(serverHDNode).derive(derivationPath);

  // calculating one-time ECDH key
  var secretPoint = serverDerivedNode.keyPair.__Q.multiply(clientDerivedNode.keyPair.d);
  var secret = secretPoint.getEncoded().toString('hex');

  // decrypt token with symmetric ECDH key
  var response = {};
  try {
    response.token = this.decrypt({ input: responseBody.encryptedToken, password: secret });
  } catch (e) {
    e.errorCode = 'token_decryption_failure';
    console.error('Failed to decrypt token.');
    throw e;
  }
  if (!this._ecdhXprv) {
    response.ecdhXprv = ecdhXprv;
  }
  return response;
};

//
// authenticate
// Login to the bitgo system.
// Params:
// - forceV1Auth (boolean)
// Returns:
//   {
//     token: <user's token>,
//     user: <user object
//   }
BitGo.prototype.authenticate = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['username', 'password'], ['otp'], callback);

  var username = params.username;
  var password = params.password;
  var otp = params.otp;
  var trust = params.trust;
  var forceV1Auth = !!params.forceV1Auth;

  // Calculate the password HMAC so we don't send clear-text passwords
  var key = sjcl.codec.utf8String.toBits(username);
  var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
  var hmacPassword = sjcl.codec.hex.fromBits(hmac.encrypt(password));

  var authParams = {
    email: username,
    password: hmacPassword,
    forceSMS: !!params.forceSMS
  };

  if (otp) {
    authParams.otp = otp;
    if (trust) {
      authParams.trust = 1;
    }
  }

  if (params.extensible) {
    this._extensionKey = bitcoin.makeRandomKey();
    authParams.extensible = true;
    authParams.extensionAddress = this._extensionKey.getAddress();
  }

  var self = this;
  if (this._token) {
    return this.reject('already logged in', callback);
  }

  var request = this.post(this.url('/user/login'));
  if (forceV1Auth) {
    request.forceV1Auth = true;
    // tell the server that the client was forced to downgrade the authentication protocol
    authParams.forceV1Auth = true;
  }
  return request.send(authParams)
  .then(function(response) {
    // extract body and user information
    var body = response.body;
    self._user = body.user;

    if (body.access_token) {
      self._token = body.access_token;
      // if the downgrade was forced, adding a warning message might be prudent
    } else {
      // check the presence of an encrypted ECDH xprv
      // if not present, legacy account
      var encryptedXprv = body.encryptedECDHXprv;
      if (!encryptedXprv) {
        throw new Error('Keychain needs encryptedXprv property');
      }

      var responseDetails = self.handleTokenIssuance(response.body, password);
      self._token = responseDetails.token;
      self._ecdhXprv = responseDetails.ecdhXprv;

      // verify the response's authenticity
      request.verifyResponse(response);

      // add the remaining component for easier access
      response.body.access_token = self._token;
    }

    return response;
  })
  .then(handleResponseResult(), handleResponseError)
  .nodeify(callback);
};

/**
 *
 * @param params
 * - operatingSystem: one of ios, android
 * - pushToken: hex-formatted token for the respective native push notification service
 * @param callback
 * @returns {*}
 */
BitGo.prototype.registerPushToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['pushToken', 'operatingSystem'], [], callback);

  if (!this._token) {
    // this device has to be registered to an extensible session
    return this.reject('not logged in', callback);
  }

  var postParams = _.pick(params, ['pushToken', 'operatingSystem']);

  return this.post(this.url('/devices'))
  .send(postParams)
  .result()
  .nodeify(callback);
};

/**
 *
 * @param params
 * - pushVerificationToken: the token received via push notification to confirm the device's mobility
 * @param callback
 */
BitGo.prototype.verifyPushToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['pushVerificationToken'], [], callback);

  if (!this._token) {
    // this device has to be registered to an extensible session
    return this.reject('not logged in', callback);
  }

  var postParams = _.pick(params, 'pushVerificationToken');

  return this.post(this.url('/devices/verify'))
  .send(postParams)
  .result()
  .nodeify(callback);
};

//
// authenticateWithAuthCode
// Login to the bitgo system using an authcode generated via Oauth
// Returns:
//   {
//     authCode: <authentication code sent from the BitGo OAuth redirect>
//   }
BitGo.prototype.authenticateWithAuthCode = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['authCode'], [], callback);

  if (!this._clientId || !this._clientSecret) {
    throw new Error('Need client id and secret set first to use this');
  }

  var authCode = params.authCode;

  var self = this;
  if (this._token) {
    return this.reject('already logged in', callback);
  }

  var token_result;

  var request = this.post(this._baseUrl + '/oauth/token');
  request.forceV1Auth = true; // OAuth currently only supports v1 authentication
  return request
  .send({
    grant_type: 'authorization_code',
    code: authCode,
    client_id: self._clientId,
    client_secret: self._clientSecret
  })
  .result()
  .then(function(body) {
    token_result = body;
    self._token = body.access_token;
    self._refreshToken = body.refresh_token;
    return self.me();
  })
  .then(function(user) {
    self._user = user;
    return token_result;
  })
  .nodeify(callback);
};

//
// refreshToken
// Use refresh token to get new access token.
// If the refresh token is null/defined, then we use the stored token from auth
// Returns:
//   {
//     refreshToken: <optional refresh code sent from a previous authcode>
//   }
BitGo.prototype.refreshToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], ['refreshToken'], callback);

  var refreshToken = params.refreshToken || this._refreshToken;

  if (!refreshToken) {
    throw new Error('Must provide refresh token or have authenticated with Oauth before');
  }

  if (!this._clientId || !this._clientSecret) {
    throw new Error('Need client id and secret set first to use this');
  }

  var self = this;
  return this.post(this._baseUrl + '/oauth/token')
  .send({
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
    client_id: self._clientId,
    client_secret: self._clientSecret
  })
  .result()
  .then(function(body) {
    self._token = body.access_token;
    self._refreshToken = body.refresh_token;
    return body;
  })
  .nodeify(callback);
};

//
// listAccessTokens
// Get information on all of the BitGo access tokens on the user
// Returns:
// {
//    id: <id of the token>
//    label: <the user-provided label for this token>
//    user: <id of the user on the token>
//    enterprise <id of the enterprise this token is valid for>
//    client: <the auth client that this token belongs to>
//    scope: <list of allowed OAuth scope values>
//    created: <date the token was created>
//    expires: <date the token will expire>
//    origin: <the origin for which this token is valid>
//    isExtensible: <flag indicating if the token can be extended>
//    extensionAddress: <address whose private key's signature is necessary for extensions>
//    unlock: <info for actions that require an unlock before firing>
// }
//
BitGo.prototype.listAccessTokens = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/user/accesstoken'))
  .send()
  .result('accessTokens')
  .nodeify(callback);
};

//
// addAccessToken
// Add a BitGo API Access Token to the current user account
// Params:
// {
//    otp: (required) <valid otp code>
//    label: (required) <label for the token>
//    duration: <length of time in seconds the token will be valid for>
//    ipRestrict: <array of IP address strings to whitelist>
//    txValueLimit: <number of outgoing satoshis allowed on this token>
//    scope: (required) <authorization scope of the requested token>
// }
// Returns:
// {
//    id: <id of the token>
//    token: <access token hex string to be used for BitGo API request verification>
//    label: <user-provided label for this token>
//    user: <id of the user on the token>
//    enterprise <id of the enterprise this token is valid for>
//    client: <the auth client that this token belongs to>
//    scope: <list of allowed OAuth scope values>
//    created: <date the token was created>
//    expires: <date the token will expire>
//    origin: <the origin for which this token is valid>
//    isExtensible: <flag indicating if the token can be extended>
//    extensionAddress: <address whose private key's signature is necessary for extensions>
//    unlock: <info for actions that require an unlock before firing>
// }
//
BitGo.prototype.addAccessToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['label'], ['otp'], callback);

  // check non-string params
  if (params.duration) {
    if (typeof(params.duration) !== 'number' || params.duration < 0) {
      throw new Error('duration must be a non-negative number');
    }
  }
  if (params.ipRestrict) {
    if (!_.isArray(params.ipRestrict)) {
      throw new Error('ipRestrict must be an array');
    }
    _.forEach(params.ipRestrict, function(ipAddr) {
      if (!_.isString(ipAddr)) {
        throw new Error('ipRestrict must be an array of IP address strings');
      }
    });
  }
  if (params.txValueLimit) {
    if (typeof(params.txValueLimit) !== 'number') {
      throw new Error('txValueLimit must be a number');
    }
    if (params.txValueLimit < 0) {
      throw new Error('txValueLimit must be a non-negative number')
    }
  }
  if(params.scope && params.scope.length > 0) {
    if(!_.isArray(params.scope)) {
      throw new Error('scope must be an array');
    } 
  } else {
      throw new Error('must specify scope for token')
  }

  var bitgo = this;

  var request = this.post(this.url('/user/accesstoken'));
  if (!bitgo._ecdhXprv) {
    // without a private key, the user cannot decrypt the new access token the server will send
    request.forceV1Auth = true;
  }

  return request.send(params)
  .then(function(response) {
    if (request.forceV1Auth) {
      response.body.warning = 'A protocol downgrade has occurred because this is a legacy account.';
      return response;
    }

    // verify the authenticity of the server's response before proceeding any further
    request.verifyResponse(response);

    var responseDetails = bitgo.handleTokenIssuance(response.body);
    response.body.token = responseDetails.token;

    return response;
  })
  .then(handleResponseResult(), handleResponseError)
  .nodeify(callback);
};

//
// removeAccessToken
// Sets the expire time of an access token matching either the id or label to the current date, effectively deleting it
// Params:
// {
//    id: <id of the access token to be deleted>
//    label: <label of the access token to be deleted>
// }
// Returns:
// {
//    id: <id of the token>
//    label: <user-provided label for this token>
//    user: <id of the user on the token>
//    enterprise <id of the enterprise this token is valid for>
//    client: <the auth client that this token belongs to>
//    scope: <list of allowed OAuth scope values>
//    created: <date the token was created>
//    expires: <date the token will expire>
//    origin: <the origin for which this token is valid>
//    isExtensible: <flag indicating if the token can be extended>
//    extensionAddress: <address whose private key's signature is necessary for extensions>
//    unlock: <info for actions that require an unlock before firing>
// }
//
BitGo.prototype.removeAccessToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], ['id', 'label'], callback);
  var exactlyOne = !!params.id ^ !!params.label;
  if (!exactlyOne) {
    throw new Error('must provide exactly one of id or label')
  }

  var self = this;

  return Q().then(function() {
    if (params.id) {
      return params.id;
    }

    // we have to get the id of the token by using the label before we can delete it
    return self.listAccessTokens()
    .then(function(tokens) {
      if (!tokens) {
        throw new Error('token with this label does not exist');
      }

      var matchingTokens = _.filter(tokens, { label: params.label });
      if (matchingTokens.length > 1) {
        throw new Error('ambiguous call: multiple tokens matching this label');
      }
      if (matchingTokens.length === 0) {
        throw new Error('token with this label does not exist');
      }
      return matchingTokens[0].id;
    });
  })
  .then(function(tokenId) {
    return self.del(self.url('/user/accesstoken/' + tokenId))
    .send()
    .result();
  })
  .nodeify(callback);
};

//
// logout
// Logout of BitGo
//
BitGo.prototype.logout = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  var self = this;
  return this.get(this.url('/user/logout'))
  .result()
  .then(function() {
    self.clear();
  })
  .nodeify(callback);
};

//
// getUser
// Get a user by ID (name/email only)
//
BitGo.prototype.getUser = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['id'], [], callback);

  return this.get(this.url('/user/' + params.id))
  .result('user')
  .nodeify(callback);
};

//
// me
// Get the current logged in user
//
BitGo.prototype.me = function(params, callback) {
  return this.getUser({ id: 'me' }, callback);
};

/**
 * Unlock the session by providing OTP
 * @param {string} otp Required OTP code for the account.
 * @param {number} duration Desired duration of the unlock in seconds (default=600, max=3600).
 */
BitGo.prototype.unlock = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], ['otp'], callback);

  return this.post(this.url('/user/unlock'))
  .send(params)
  .result()
  .nodeify(callback);
};

//
// lock
// Lock the session
//
BitGo.prototype.lock = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.post(this.url('/user/lock'))
  .result()
  .nodeify(callback);
};

//
// me
// Get the current session
//
BitGo.prototype.session = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/user/session'))
  .result('session')
  .nodeify(callback);
};

/**
 * Trigger a push/sms for the OTP code
 * @param {boolean} forceSMS If set to true, will use SMS to send the OTP to the user even if they have other 2FA method set up.
 */
BitGo.prototype.sendOTP = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.post(this.url('/user/sendotp'))
  .send(params)
  .result()
  .nodeify(callback);
};

/**
 * Extend token, provided the current token is extendable
 * @param params
 * - duration: duration in seconds by which to extend the token, starting at the current time
 * @param callback
 */
BitGo.prototype.extendToken = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  var timestamp = Date.now();
  var duration = params.duration;
  var message = timestamp + '|' + this._token + '|' + duration;
  var signature = bitcoin.message.sign(this._extensionKey, message, bitcoin.networks.bitcoin).toString('hex');

  return this.post(this.url('/user/extendtoken'))
  .send(params)
  .set('timestamp', timestamp)
  .set('signature', signature)
  .result()
  .nodeify(callback);
};

//
// getSharingKey
// Get a key for sharing a wallet with a user
//
BitGo.prototype.getSharingKey = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['email'], [], callback);

  return this.post(this.url('/user/sharingkey'))
  .send(params)
  .result()
  .nodeify(callback);
};

//
// ping
// Test connectivity to the server
//
BitGo.prototype.ping = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/ping'))
  .result()
  .nodeify(callback);
};

//
// Blockchain
// Get the blockchain object.
//
BitGo.prototype.blockchain = function() {
  if (!this._blockchain) {
    this._blockchain = new Blockchain(this);
  }
  return this._blockchain;
};

//
// keychains
// Get the user's keychains object.
//
BitGo.prototype.keychains = function() {
  if (!this._keychains) {
    this._keychains = new Keychains(this);
  }
  return this._keychains;
};

//
// wallets
// Get the user's wallets object.
//
BitGo.prototype.wallets = function() {
  if (!this._wallets) {
    this._wallets = new Wallets(this);
  }
  return this._wallets;
};

//
// travel rule
// Get the travel rule object
//
BitGo.prototype.travelRule = function() {
  if (!this._travel) {
    this._travelRule = new TravelRule(this);
  }
  return this._travelRule;
};

//
// pendingApprovals
// Get pending approvals that can be approved/ or rejected
//
BitGo.prototype.pendingApprovals = function() {
  if (!this._pendingApprovals) {
    this._pendingApprovals = new PendingApprovals(this);
  }
  return this._pendingApprovals;
};

//
// newWalletObject
// A factory method to create a new Wallet object, initialized with the wallet params
// Can be used to reconstitute a wallet from cached data
//
BitGo.prototype.newWalletObject = function(walletParams) {
  return new Wallet(this, walletParams);
};

BitGo.prototype.url = function(path) {
  return this._baseApiUrl + path;
};

//
// labels
// Get all the address labels on all of the user's wallets
//
BitGo.prototype.labels = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  return this.get(this.url('/labels'))
  .result('labels')
  .nodeify(callback);
};

/** 
* Estimates approximate fee per kb needed for a tx to get into a block
* @param {number} numBlocks target blocks for the transaction to be confirmed
* @param {number} maxFee maximum fee willing to be paid (for safety)
* @param {array[string]} inputs list of unconfirmed txIds from which this transaction uses inputs
* @param {number} txSize estimated transaction size in bytes, optional parameter used for CPFP estimation.
* @param {boolean} cpfpAware flag indicating fee should take into account CPFP
* @returns 
*/
BitGo.prototype.estimateFee = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  var queryParams = { version: 12 };
  if (params.numBlocks) {
    if (typeof(params.numBlocks) !== 'number') {
      throw new Error('invalid argument');
    }
    queryParams.numBlocks = params.numBlocks;
  }
  if (params.maxFee) {
    if (typeof(params.maxFee) !== 'number') {
      throw new Error('invalid argument');
    }
    queryParams.maxFee = params.maxFee;
  }
  if (params.inputs) {
    if (!Array.isArray(params.inputs)) {
      throw new Error('invalid argument');
    }
    queryParams.inputs = params.inputs;
  }
  if (params.txSize) {
    if (typeof(params.txSize) !== 'number') {
      throw new Error('invalid argument');
    }
    queryParams.txSize = params.txSize;
  }
  if (params.cpfpAware) {
    if (typeof(params.cpfpAware) !== 'boolean') {
      throw new Error('invalid argument');
    }
    queryParams.cpfpAware = params.cpfpAware;
  }

  return this.get(this.url('/tx/fee'))
  .query(queryParams)
  .result()
  .nodeify(callback);
};

//
// instantGuarantee
// Get BitGo's guarantee using an instant id
//
BitGo.prototype.instantGuarantee = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['id'], [], callback);

  var self = this;
  return this.get(this.url('/instant/' + params.id))
  .result()
  .then(function(body) {
    if (!body.guarantee) {
      throw new Error('no guarantee found in response body');
    }
    if (!body.signature) {
      throw new Error('no signature found in guarantee response body');
    }
    var signingAddress = common.Environments[self.env].signingAddress;
    if (!bitcoin.message.verify(signingAddress, new Buffer(body.signature, 'hex'), body.guarantee, bitcoin.getNetwork())) {
      throw new Error('incorrect signature');
    }
    return body;
  })
  .nodeify(callback);
};

//
// getBitGoFeeAddress
// Get a target address for payment of a BitGo fee
//
BitGo.prototype.getBitGoFeeAddress = function(params, callback) {
  params = params || {};
  common.validateParams(params, [], [], callback);

  var self = this;
  return this.post(this.url('/billing/address'))
  .send({})
  .result()
  .nodeify(callback);
};

/**
 * Gets an address object (including the wallet id) for a given address.
 * @param {string} address The address to look up.
 */
BitGo.prototype.getWalletAddress = function(params, callback) {
  params = params || {};
  common.validateParams(params, ['address'], [], callback);

  var self = this;
  return this.get(this.url('/walletaddress/' + params.address))
  .result()
  .nodeify(callback);
};

//
// fetchConstants
// Receives a TTL and refetches as necessary
//
BitGo.prototype.fetchConstants = function(params, callback) {
  var env = this.env;
  if (!BitGo._constants) {
    BitGo._constants = {};
  }
  if (!BitGo._constantsExpire) {
    BitGo._constantsExpire = {};
  }

  if (BitGo._constants[env] && BitGo._constantsExpire[env] && new Date() < BitGo._constantsExpire[env]) {
    return Q().then(function() {
      return BitGo._constants[env];
    })
    .nodeify(callback);
  }

  return this.get(this.url('/client/constants'))
  .result()
  .then(function(result) {
    BitGo._constants[env] = result.constants;
    BitGo._constantsExpire[env] = moment.utc().add(result.ttl, 'second').toDate();
    return BitGo._constants[env];
  })
  .nodeify(callback);
};

//
// getConstants
// Get a set of constants from the server to use as defaults
//
BitGo.prototype.getConstants = function(params) {
  params = params || {};

  // TODO: once server starts returning eth address keychains, remove bitgoEthAddress
  var defaultConstants = {
    maxFee: 0.1e8,
    maxFeeRate: 1000000,
    minFeeRate: 5000,
    fallbackFeeRate: 50000,
    minOutputSize: 2730,
    bitgoEthAddress: '0x0f47ea803926926f299b7f1afc8460888d850f47'
  };

  this.fetchConstants(params);

  // use defaultConstants as the backup for keys that are not set in this._constants
  return _.merge({}, defaultConstants, BitGo._constants[this.env]);
};

module.exports = BitGo;

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


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