PHP WebShell

Текущая директория: /opt/BitGoJS/modules/bitgo/test/unit

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

//
// Tests for BitGo Object
//

import * as crypto from 'crypto';
import * as nock from 'nock';
import * as should from 'should';
import assert = require('assert');

import { common, generateGPGKeyPair } from '@bitgo/sdk-core';
import { bip32, ECPair } from '@bitgo/utxo-lib';
import * as _ from 'lodash';
import * as BitGoJS from '../../src/index';
const rp = require('request-promise');

import { TestBitGo } from '@bitgo/sdk-test';
import { BitGo } from '../../src/bitgo';

nock.disableNetConnect();

describe('BitGo Prototype Methods', function () {
  describe('Version', () => {
    it('version', function () {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.initializeTestVars();
      const version = bitgo.version();
      version.should.be.a.String();
    });
  });

  describe('validate', () => {
    it('should get', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.getValidate().should.equal(true);
    });

    it('should set', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.setValidate(false);
      bitgo.getValidate().should.equal(false);
      bitgo['_validate'].should.equal(false);
    });
  });

  describe('Environments', () => {
    it('production', () => {
      BitGoJS.setNetwork('testnet');
      TestBitGo.decorate(BitGo, { env: 'prod' });
      BitGoJS.getNetwork().should.equal('bitcoin');
    });

    it('staging', () => {
      BitGoJS.setNetwork('testnet');
      TestBitGo.decorate(BitGo, { env: 'staging' });
      BitGoJS.getNetwork().should.equal('testnet');
    });

    it('test', () => {
      BitGoJS.setNetwork('bitcoin');
      TestBitGo.decorate(BitGo, { env: 'test' });
      BitGoJS.getNetwork().should.equal('testnet');
    });

    it('dev', () => {
      TestBitGo.decorate(BitGo, { env: 'dev' });
      BitGoJS.getNetwork().should.equal('testnet');
    });

    it('custom network (prod)', () => {
      TestBitGo.decorate(BitGo, { customBitcoinNetwork: 'bitcoin', customRootURI: 'http://rooturi.example' });
      BitGoJS.getNetwork().should.equal('bitcoin');
    });

    it('custom network (testnet)', () => {
      TestBitGo.decorate(BitGo, { customBitcoinNetwork: 'testnet', customRootURI: 'http://rooturi.example' });
      BitGoJS.getNetwork().should.equal('testnet');
    });
  });

  describe('HMAC request verification', () => {
    it('throws if HMAC request verification is disabled for non-prod environments', function () {
      (() => TestBitGo.decorate(BitGo, { env: 'prod', hmacVerification: false })).should.throw(
        /Cannot disable request HMAC verification in environment/
      );
      (() => TestBitGo.decorate(BitGo, { env: 'test', hmacVerification: false })).should.not.throw(
        /Cannot disable request HMAC verification in environment/
      );
      (() => TestBitGo.decorate(BitGo, { env: 'adminProd', hmacVerification: false })).should.throw(
        /Cannot disable request HMAC verification in environment/
      );
      (() => TestBitGo.decorate(BitGo, { env: 'adminTest', hmacVerification: false })).should.not.throw(
        /Cannot disable request HMAC verification in environment/
      );
      (() =>
        TestBitGo.decorate(BitGo, {
          env: 'dev',
          customRootURI: 'http://rooturi.example',
          hmacVerification: false,
        })).should.not.throw(/Cannot disable request HMAC verification in environment/);
    });

    it('allows disabling of HMAC request verification only for dev environments', function () {
      (() => TestBitGo.decorate(BitGo, { env: 'dev', hmacVerification: false })).should.not.throw();
      (() => TestBitGo.decorate(BitGo, { env: 'latest', hmacVerification: false })).should.not.throw();
      (() => TestBitGo.decorate(BitGo, { env: 'adminDev', hmacVerification: false })).should.not.throw();
      (() => TestBitGo.decorate(BitGo, { env: 'adminLatest', hmacVerification: false })).should.not.throw();
      (() => TestBitGo.decorate(BitGo, { env: 'local', hmacVerification: false })).should.not.throw();
      (() => TestBitGo.decorate(BitGo, { env: 'localNonSecure', hmacVerification: false })).should.not.throw();
      (() =>
        TestBitGo.decorate(BitGo, {
          env: 'branch',
          customRootURI: 'http://rooturi.example',
          hmacVerification: false,
        })).should.not.throw();
    });
  });

  describe('Authenticate in Microservices', () => {
    let bitgo;
    const authenticateRequest = {
      username: 'test@bitgo.com',
      password: 'password',
      otp: '000000',
      extensible: false,
      extensionAddress: 'address',
      forceSMS: false,
    };

    it('goes to microservices', async function () {
      bitgo = TestBitGo.decorate(BitGo, { env: 'mock', microservicesUri: 'https://microservices.uri' } as any);
      const scope = nock(BitGoJS.Environments[bitgo.getEnv()].uri)
        .post('/api/auth/v1/session')
        .reply(200, {
          user: {
            username: 'test@bitgo.com',
          },
          access_token: 'token12356',
        });

      await bitgo.authenticate(authenticateRequest);
      scope.isDone().should.be.true();
    });

    it('goes to microservices even when microservicesUri is not specified', async function () {
      bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      const scope = nock(BitGoJS.Environments[bitgo.getEnv()].uri)
        .post('/api/auth/v1/session')
        .reply(200, {
          user: {
            username: 'test@bitgo.com',
          },
          access_token: 'token12356',
        });

      await bitgo.authenticate(authenticateRequest);
      scope.isDone().should.be.true();
    });
  });

  describe('Verify Address', () => {
    let bitgo;
    before(() => {
      bitgo = TestBitGo.decorate(BitGo);
    });

    it('errors', () => {
      (() => bitgo.verifyAddress()).should.throw();
      (() => bitgo.verifyAddress({})).should.throw();

      bitgo.verifyAddress({ address: 'xyzzy' }).should.be.false();
    });

    it('standard', () => {
      bitgo = TestBitGo.decorate(BitGo, { env: 'prod' });
      bitgo.verifyAddress({ address: '1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd' }).should.be.true();
      // wrong version byte:
      bitgo.verifyAddress({ address: '9Ef7HsuByGBogqkjoF5Yng7MYkq5UCdmZz' }).should.be.false();

      bitgo = TestBitGo.decorate(BitGo);
      bitgo.verifyAddress({ address: 'n4DNhSiEaodqaiF9tLYXTCh4kFbdUzxBHs' }).should.be.true();
    });

    it('p2sh', () => {
      bitgo = TestBitGo.decorate(BitGo, { env: 'prod' });
      bitgo.verifyAddress({ address: '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC' }).should.be.true();
      // wrong version byte:
      bitgo.verifyAddress({ address: 'HV8swrGkmeN7Xig4vENr93aQSrX4iHjg7D' }).should.be.false();
      bitgo = TestBitGo.decorate(BitGo);
      bitgo.verifyAddress({ address: '2NEeFWbfu4EA1rcKx48e82Mj8d6FKcWawZw' }).should.be.true();
    });
  });

  describe('Encrypt/Decrypt', () => {
    const password = 'mickey mouse';
    const secret = 'this is a secret';

    it('invalid password', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.initializeTestVars();
      const opaque = bitgo.encrypt({ password: password, input: secret });
      (() => bitgo.decrypt({ password: 'hack hack', input: opaque })).should.throw();
    });

    it('valid password', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.initializeTestVars();
      const opaque = bitgo.encrypt({ password: password, input: secret });
      bitgo.decrypt({ password: password, input: opaque }).should.equal(secret);
    });
  });

  describe('Password Generation', () => {
    it('generates a random password', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.initializeTestVars();
      const password = bitgo.generateRandomPassword();
      should.exist(password);
    });

    it('generates a random password with a numWords argument', () => {
      const bitgo = TestBitGo.decorate(BitGo);
      bitgo.initializeTestVars();
      for (let i = 0; i < 1000; i++) {
        const password = bitgo.generateRandomPassword(10);
        should.exist(password);
        // randomly generated password should be 55 characters roughly 92.5% of the time,
        // 54 characters roughly 7.5% of the time, 53 characters 0.001% of the time,
        // and fewer than 53 characters very, very rarely
        password.length.should.be.within(53, 55);
      }
    });
  });

  describe('Shamir Secret Sharing', () => {
    const bitgo = TestBitGo.decorate(BitGo);
    const seed = '8cc57dac9cdae42bf7848a2d12f2874d31eca1f9de8fe3f8fa13e7857b545d59';
    const xpub =
      'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN';
    const passwords = ['mickey', 'mouse', 'donald', 'duck'];

    it('should fail to split secret with wrong m', () => {
      (() =>
        bitgo.splitSecret({
          seed,
          passwords: ['abc'],
          m: 0,
        })).should.throw('m must be a positive integer greater than or equal to 2');
    });

    it('should fail to split secret with bad password count', () => {
      (() =>
        bitgo.splitSecret({
          seed,
          passwords: ['abc'],
          m: 2,
        })).should.throw('passwords array length cannot be less than m');
    });

    it('should split and fail to reconstitute secret with bad passwords', () => {
      const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
      const shards = _.at(splitSecret.seedShares, [0, 2]);
      const subsetPasswords = _.at(passwords, [0, 3]);
      (() =>
        bitgo.reconstituteSecret({
          shards,
          passwords: subsetPasswords,
          xpub,
        } as any)).should.throw(/ccm: tag doesn't match/);
    });

    it('should split and reconstitute secret', () => {
      const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
      const shards = _.at(splitSecret.seedShares, [0, 2]);
      const subsetPasswords = _.at(passwords, [0, 2]);
      const reconstitutedSeed = bitgo.reconstituteSecret({ shards, passwords: subsetPasswords });
      reconstitutedSeed.seed.should.equal(seed);
      reconstitutedSeed.xpub.should.equal(
        'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN'
      );
      reconstitutedSeed.xprv.should.equal(
        'xprv9s21ZrQH143K2Rnxdim5h3aws6o99QokXzP4o3CS6E5acSEtS9Zgfuw5ZWujhTHTWEAZDfmP3yxA1Ccn6myVkGEpRrT4xWgaEpoW7YiBAtC'
      );
    });

    it('should split and incorrectly verify secret', () => {
      const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
      const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
      isValid.should.equal(false);
    });

    it('should split and verify secret', () => {
      const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
      const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
      isValid.should.equal(true);
    });

    it('should split and verify secret with many parts', () => {
      const allPws = ['0', '1', '2', '3', '4', '5', '6', '7'];
      const splitSecret = bitgo.splitSecret({ seed, passwords: allPws, m: 3 });
      const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords: allPws, m: 3, xpub });
      isValid.should.equal(true);
    });
  });

  describe('ECDH sharing secret', () => {
    function getKey(seed: string) {
      return ECPair.fromPrivateKey(
        bip32.fromSeed(crypto.createHash('sha256').update(seed).digest()).privateKey as Buffer
      );
    }

    it('should calculate a new ECDH sharing secret correctly', () => {
      for (let i = 0; i < 256; i++) {
        const bitgo = TestBitGo.decorate(BitGo);
        const eckey1 = getKey(`${i}.a`);
        const eckey2 = getKey(`${i}.b`);
        const sharingKey1 = bitgo.getECDHSecret({ eckey: eckey1, otherPubKeyHex: eckey2.publicKey.toString('hex') });
        const sharingKey2 = bitgo.getECDHSecret({ eckey: eckey2, otherPubKeyHex: eckey1.publicKey.toString('hex') });
        sharingKey1.should.equal(sharingKey2);

        switch (i) {
          case 0:
            sharingKey1.should.eql('465ffe5745325998b83fb39631347148e24d4f21b3f3b54739c264d5c42db4b8');
            break;
          case 1:
            sharingKey1.should.eql('61ff44fc1af8061a433a314b7b8be8ae352c10f62aac5887047dbaa5643b818d');
            break;
        }
      }
    });
  });

  describe('change password', function () {
    let bitgo;
    let bgUrl;

    before(async function () {
      nock('https://bitgo.fakeurl')
        .post('/api/auth/v1/session')
        .reply(200, {
          access_token: 'access_token',
          user: { username: 'update_pw_tester@bitgo.com' },
        });

      bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      bitgo.initializeTestVars();
      bitgo.setValidate(false);

      await bitgo.authenticateChangePWTestUser(bitgo.testUserOTP());

      bgUrl = common.Environments[bitgo.getEnv()].uri;
    });

    const oldPassword = 'oldPassword';
    const newPassword = 'newPassword';
    const otherPassword = 'otherPassword';

    describe('should fail to change the password', function changePWFail() {
      it('wrong arguments', async function () {
        await bitgo.changePassword({ newPassword: '5678' }).should.be.rejectedWith('expected string oldPassword');
        await bitgo
          .changePassword({ oldPassword: 1234, newPassword: '5678' })
          .should.be.rejectedWith('expected string oldPassword');
        await bitgo.changePassword({ oldPassword: '1234' }).should.be.rejectedWith('expected string newPassword');
        await bitgo
          .changePassword({ oldPassword: '1234', newPassword: 5678 })
          .should.be.rejectedWith('expected string newPassword');
      });

      it('incorrect old password', async function () {
        nock(bgUrl).post('/api/v1/user/verifypassword').reply(200, { valid: false });
        await bitgo
          .changePassword({ oldPassword, newPassword })
          .should.be.rejectedWith('the provided oldPassword is incorrect');
      });
    });

    it('successful password change', async function () {
      nock(bgUrl).post('/api/v1/user/verifypassword').reply(200, { valid: true });

      nock(bgUrl)
        .post('/api/v1/user/encrypted')
        .reply(200, {
          version: 1,
          keychains: {
            xpub11: bitgo.encrypt({ input: 'xprv11', password: oldPassword }),
            xpub12: bitgo.encrypt({ input: 'xprv12', password: oldPassword }),
            xpub13: bitgo.encrypt({ input: 'xprv13', password: otherPassword }),
            xpub14: bitgo.encrypt({ input: 'xprv14', password: oldPassword }),
          },
        });

      nock(bgUrl)
        .get('/api/v2/tbtc/key')
        .query(true)
        .reply(200, {
          keys: [
            {
              pub: 'xpub21',
              encryptedPrv: bitgo.encrypt({ input: 'xprv21', password: oldPassword }),
            },
            {
              pub: 'xpub22',
              encryptedPrv: bitgo.encrypt({ input: 'xprv22', password: otherPassword }),
            },
          ],
        });

      nock(bgUrl).post('/api/v1/user/changepassword').reply(200, {});

      await bitgo.changePassword({ oldPassword, newPassword });
    });

    afterEach(function afterChangePassword() {
      nock.pendingMocks().should.be.empty();
    });
  });

  describe('HMAC Handling', () => {
    let bitgo;
    const token = 'v2x5b735fed2486593f8fea19113e5c717308f90a5fb00e740e46c7bfdcc078cfd0';

    before(() => {
      bitgo = TestBitGo.decorate(BitGo, { env: 'mock', accessToken: token });
    });

    it('should correctly calculate request headers', () => {
      const originalDateNow = Date.now;
      Date.now = () => 1521589882510;

      const fetchMeUrl = bitgo.url('/user/me');
      const requestHeaders = bitgo.calculateRequestHeaders({ url: fetchMeUrl, token });
      Date.now = originalDateNow;

      requestHeaders.timestamp.should.equal(1521589882510);
      requestHeaders.tokenHash.should.equal('a85af08e6723e41acd6a3fb9ef58422082e673df33c58e1db175bb740a2c934d');
      requestHeaders.hmac.should.equal('6de77d5a5446a3e5649456c11480706a71071b15639c3c787af65bdb02ecf1ec');
    });

    it('should correctly handle authentication response', () => {
      const responseJson = {
        encryptedToken:
          '{"iv":"EqxVaGTLY4naAYkuBaTz0w==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4S4dBYcgL4s=","ct":"FgBRJljb8iSYxnAjMi4Qotr7sTKbSmWnlfHZShMSi8YeeE3kiS8bpHNUwAPhY8tgouh3UsEwrJnY+54MvqFD7yd19pG1V4CVssr8"}',
        derivationPath: 'm/999999/104490948/173846667',
        encryptedECDHXprv:
          '{"iv":"QKHEF2GNcwOJwy6+pwANRA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"W2sVFvXDlOw=","ct":"8BTCqS25X37kLzmzQdGenhXH6znn9qEmkszAeS8kLnRdqKSiUiC7bTAVgg/Np5yrV7F7Jyiq+MTpVT76EoUT+PMJzArv0gUQKC2JPB3JuVKeAAVWBQmhWfkEwRfyv4hq4WMxwZtocwBqThvd2pJm9HE51GX4/Wo="}',
      };
      const parsedAuthenticationData = bitgo.handleTokenIssuance(responseJson, 'test@bitgo.com');
      parsedAuthenticationData.token.should.equal(token);
      parsedAuthenticationData.ecdhXprv.should.equal(
        'xprv9s21ZrQH143K3si1bKGp7KqgCQv39ttQ7aUwWzVdytgHd8HtDCHyEp14mxfhiT3qHTq4BaSrA7uUkG6AJTfPJBsRu63drvBqYuMZyTxepH7'
      );
    });

    it('should correctly verify a response hmac', async function () {
      const url = bitgo
        .coin('tltc')
        .url('/wallet/5941b202b42fcbc707170d5b597491d9/address/QNc4RFAcbvqmtrR1kR2wbGLCx6tEvojFYE?segwit=1');
      const requestHeaderData = bitgo.calculateRequestHeaders({ url, token });
      const requestHeaders = {
        'BitGo-Auth-Version': '2.0',
        'Content-Type': 'application/json',
        'Auth-Timestamp': requestHeaderData.timestamp,
        Authorization: 'Bearer ' + requestHeaderData.tokenHash,
        HMAC: requestHeaderData.hmac,
      };
      const responseBody =
        '{"id":"5a7ca8bcaf52c8e807c575fb692609ec","address":"QNc4RFAcbvqmtrR1kR2wbGLCx6tEvojFYE","chain":0,"index":2,"coin":"tltc","wallet":"5941b202b42fcbc707170d5b597491d9","coinSpecific":{"redeemScript":"522102835bcfd130f7a56f72c905b782d90b66e22f88ad3309cf72af5138a7d44be8b3210322c7f42a1eb212868eab78db7ba64846075d98c7f4c7aa25a02e57871039e0cd210265825be0d5bf957fb72abd7c23bf0836a78a15f951a073467cd5c99e03ce7ab753ae"},"balance":{"updated":"2018-02-28T23:48:07.341Z","numTx":1,"numUnspents":1,"totalReceived":20000000}}';

      nock('https://bitgo.fakeurl', { reqheaders: requestHeaders })
        .get('/api/v2/tltc/wallet/5941b202b42fcbc707170d5b597491d9/address/QNc4RFAcbvqmtrR1kR2wbGLCx6tEvojFYE?segwit=1')
        .reply(200, responseBody, {
          hmac: '30a5943043ab4b0503d807f0cca7dac3a670e8785331322567db5189432b87ec',
          timestamp: '1521590532925',
        });

      const responseData = (await rp({
        uri: url,
        method: 'GET',
        headers: requestHeaders,
        transform: (body, response) => {
          // verify the response headers
          const url = response.request.href;
          const hmac = response.headers.hmac;
          const timestamp = response.headers.timestamp;
          const statusCode = response.statusCode;
          const verificationParams = {
            url,
            hmac,
            timestamp,
            token,
            statusCode,
            text: body,
          };
          return bitgo.verifyResponse(verificationParams);
        },
      })) as any;
      responseData.signatureSubject.should.equal(
        '1521590532925|/api/v2/tltc/wallet/5941b202b42fcbc707170d5b597491d9/address/QNc4RFAcbvqmtrR1kR2wbGLCx6tEvojFYE?segwit=1|200|{"id":"5a7ca8bcaf52c8e807c575fb692609ec","address":"QNc4RFAcbvqmtrR1kR2wbGLCx6tEvojFYE","chain":0,"index":2,"coin":"tltc","wallet":"5941b202b42fcbc707170d5b597491d9","coinSpecific":{"redeemScript":"522102835bcfd130f7a56f72c905b782d90b66e22f88ad3309cf72af5138a7d44be8b3210322c7f42a1eb212868eab78db7ba64846075d98c7f4c7aa25a02e57871039e0cd210265825be0d5bf957fb72abd7c23bf0836a78a15f951a073467cd5c99e03ce7ab753ae"},"balance":{"updated":"2018-02-28T23:48:07.341Z","numTx":1,"numUnspents":1,"totalReceived":20000000}}'
      );
      responseData.expectedHmac.should.equal('30a5943043ab4b0503d807f0cca7dac3a670e8785331322567db5189432b87ec');
      responseData.isValid.should.equal(true);
    });

    it('should include request body as part of the hmac', async function () {
      const url = 'https://bitgo.fakeurl';
      const body = { test: 'test' };

      const fixedUnixTime = 1627374646;
      const originalDateNow = Date.now;
      Date.now = () => fixedUnixTime;

      try {
        nock(url)
          .post('/', body)
          .reply(201, undefined, {
            hmac: '677e0c9a65ca384415945cb19b40ae38eaadfbce3ccce8c5d7bf37c1973b2553',
            timestamp: String(fixedUnixTime),
          });

        const resp = (await bitgo.post(url).send(body)) as any;
        resp.req.headers['hmac'].should.equal('4425a4004ef2724add25b4dd019d21c66394653a049d82e37df3a2c356b5706d');
      } finally {
        Date.now = originalDateNow;
      }
    });

    it('should recognize trailing slash inconsistency', () => {
      const verificationParams = {
        url: 'https://google.com/api',
        hmac: '30a5943043ab4b0503d807f0cca7dac3a670e8785331322567db5189432b87ec',
        timestamp: '1521590532925',
        token: token,
        statusCode: 200,
        text: 'fakedata',
      };
      const verificationDetails = bitgo.verifyResponse(verificationParams);
      verificationDetails.signatureSubject.should.equal('1521590532925|/api|200|fakedata');
      verificationDetails.signatureSubject.should.not.equal('1521590532925|/api/|200|fakedata');
      verificationDetails.expectedHmac.should.equal('2064f2adb168ef8808f6a42f588d7d6bc14e98e8b41239c6bbb7349e52f2249a');
      verificationDetails.isValid.should.equal(false);
    });

    it('should auto-amend trailing slash', () => {
      const verificationParams = {
        url: 'https://google.com',
        hmac: '30a5943043ab4b0503d807f0cca7dac3a670e8785331322567db5189432b87ec',
        timestamp: '1521590532925',
        token: token,
        statusCode: 200,
        text: 'fakedata',
      };
      const verificationDetails = bitgo.verifyResponse(verificationParams);
      verificationDetails.signatureSubject.should.equal('1521590532925|/|200|fakedata');
      verificationDetails.expectedHmac.should.equal('51c6d024f261e166e8a323f8fa36a9bb8d4d02b076334c2a9ae0a49efc5724d4');
      verificationDetails.isValid.should.equal(false);
    });

    it('should throw if hmac validation is enabled, and no valid hmac headers are returned', async function () {
      const url = 'https://fakeurl.invalid';
      const scope = nock(url).get('/').reply(200);

      // test suite bitgo object has hmac verification enabled, so it should throw when the nock responds
      await bitgo.get(url).should.be.rejectedWith(/invalid response HMAC, possible man-in-the-middle-attack/);
      scope.done();
    });

    it('should not enforce hmac verification if hmac verification is disabled', async function () {
      const bg = TestBitGo.decorate(BitGo, { env: 'mock', hmacVerification: false, accessToken: token });
      const url = 'https://fakeurl.invalid';
      const scope = nock(url).get('/').reply(200, { ok: 1 });

      const res = (await bg.get(url)) as any;
      res.body.should.have.property('ok', 1);
      scope.done();
    });
  });

  describe('Token Definitions at Startup', function () {
    it('Should return a non-empty list of tokens before the server responds', async function () {
      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      bitgo.initializeTestVars();
      const constants = bitgo.getConstants();
      constants.should.have.propertyByPath('eth', 'tokens', 'length').greaterThan(0);
    });

    after(function tokenDefinitionsAfter() {
      nock.pendingMocks().should.be.empty();
    });
  });

  describe('superagent wrappers', function () {
    let bitgo;
    let bgUrl;
    before(function () {
      bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      bitgo.initializeTestVars();

      bgUrl = common.Environments[bitgo.getEnv()].uri;

      nock(bgUrl).patch('/').reply(200);
    });

    it('PATCH requests', async function () {
      const res = await bitgo.patch(bgUrl);
      res.status.should.equal(200);
    });

    after(function () {
      nock.pendingMocks().should.be.empty();
    });
  });

  describe('preprocessAuthenticationParams', () => {
    const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
    it('should fail if passed non-string username or password', function () {
      (() => bitgo.preprocessAuthenticationParams({ username: 123 } as any)).should.throw(/expected string username/);
      (() => bitgo.preprocessAuthenticationParams({ username: 'abc', password: {} } as any)).should.throw(
        /expected string password/
      );
    });
  });

  describe('authenticate', function () {
    afterEach(function ensureNoPendingMocks() {
      nock.pendingMocks().should.be.empty();
    });

    it('should get the ecdhKeychain if ensureEcdhKeychain is set and user already has ecdhKeychain', async function () {
      nock('https://bitgo.fakeurl')
        .post('/api/auth/v1/session')
        .reply(200, {
          access_token: 'access_token',
          user: { username: 'auth-test@bitgo.com' },
        });
      nock('https://bitgo.fakeurl')
        .get('/api/v1/user/settings')
        .reply(200, {
          settings: {
            ecdhKeychain: 'some-existing-xpub',
          },
        });

      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      const response = await bitgo.authenticate({
        username: 'auth-test@bitgo.com',
        password: 'password123',
        otp: '000000',
        ensureEcdhKeychain: true,
      });

      should.exist(response.user.ecdhKeychain);
      response.user.ecdhKeychain.should.equal('some-existing-xpub');
    });
    it('should create the ecdhKeychain if ensureEcdhKeychain is set and the user does not already have ecdhKeychain', async function () {
      nock('https://bitgo.fakeurl')
        .post('/api/auth/v1/session')
        .reply(200, {
          access_token: 'access_token',
          user: { username: 'auth-test@bitgo.com' },
        });
      /**
       * This is {} because want to make sure the user has no ecdhXpub set before we set it
       */
      nock('https://bitgo.fakeurl').get('/api/v1/user/settings').reply(200, {
        settings: {},
      });
      nock('https://bitgo.fakeurl').post('/api/v1/keychain').reply(200, {
        xpub: 'some-xpub',
      });
      nock('https://bitgo.fakeurl')
        .put('/api/v2/user/settings')
        .reply(200, {
          settings: {
            ecdhKeychain: 'some-xpub',
          },
        });

      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      const response = await bitgo.authenticate({
        username: 'auth-test@bitgo.com',
        password: 'password123',
        otp: '000000',
        ensureEcdhKeychain: true,
      });

      should.exist(response.user.ecdhKeychain);
      response.user.ecdhKeychain.should.equal('some-xpub');
    });
  });

  describe('passkey authentication', () => {
    afterEach(function ensureNoPendingMocks() {
      nock.cleanAll();
      nock.pendingMocks().should.be.empty();
    });

    it('should authenticate with a passkey', async () => {
      const userId = '123';
      const passkey = `{"id": "id", "response": {"authenticatorData": "123", "clientDataJSON": "123", "signature": "123", "userHandle": "${userId}"}}`;
      const keyPair = await generateGPGKeyPair('secp256k1');

      nock('https://bitgo.fakeurl')
        .persist()
        .get('/api/v1/client/constants')
        .reply(200, { ttl: 3600, constants: { passkeyBitGoGpgKey: keyPair.publicKey } });

      nock('https://bitgo.fakeurl')
        .post('/api/auth/v1/session')
        .reply(200, async (uri, requestBody) => {
          assert(typeof requestBody === 'object');
          should.exist(requestBody.userId);
          should.exist(requestBody.passkey);
          requestBody.userId.should.equal(userId);
          requestBody.passkey.should.equal(passkey);
          return {
            access_token: 'access_token',
            user: { username: 'auth-test@bitgo.com' },
          };
        });

      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      const response = await bitgo.authenticateWithPasskey(passkey);
      should.exist(response.access_token);
      response.access_token.should.equal('access_token');
    });

    it('should throw - invalid userHandle', async () => {
      const passkey = `{"id": "id", "response": {"authenticatorData": "123", "clientDataJSON": "123", "signature": "123", "userHandle": 123}}`;
      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      try {
        await bitgo.validatePasskeyResponse(passkey);
        assert.fail('Expected error not thrown');
      } catch (e) {
        assert.equal(e.message, 'userHandle is missing');
      }
    });
    it('should throw - invalid authenticatorData', async () => {
      const passkey = `{"id": "id", "response": { "clientDataJSON": "123", "signature": "123", "userHandle": "123"}}`;
      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      try {
        await bitgo.validatePasskeyResponse(passkey);
        assert.fail('Expected error not thrown');
      } catch (e) {
        assert.equal(e.message, 'authenticatorData is missing');
      }
    });
    it('should throw - invalid passkey json', async () => {
      const passkey = `{{"id": "id", "response": { "clientDataJSON": "123", "signature": "123", "userHandle": "123"}}`;
      const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
      try {
        await bitgo.validatePasskeyResponse(passkey);
        assert.fail('Expected error not thrown');
      } catch (e) {
        console.log(e);
        assert(e.message.includes('JSON'));
      }
    });
    it('should throw - missing access token', async () => {
      const passkey = `{"id": "id", "response": { "authenticatorData": "123", "clientDataJSON": "123", "signature": "123", "userHandle": "123"}}`;
      nock('https://bitgo.fakeurl')
        .post('/api/auth/v1/session')
        .reply(200, async () => {
          return {
            user: { username: 'auth-test@bitgo.com' },
          };
        });

      try {
        const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
        await bitgo.authenticateWithPasskey(passkey);
        assert.fail('Expected error not thrown');
      } catch (e) {
        assert.equal(e.message, 'Failed to login. Please contact support@bitgo.com');
      }
    });
  });
});

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


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