bitgo
Version:
BitGo JavaScript SDK
656 lines • 114 kB
JavaScript
"use strict";
//
// Tests for BitGo Object
//
Object.defineProperty(exports, "__esModule", { value: true });
const crypto = require("crypto");
const nock = require("nock");
const should = require("should");
const assert = require("assert");
const sdk_core_1 = require("@bitgo/sdk-core");
const utxo_lib_1 = require("@bitgo/utxo-lib");
const _ = require("lodash");
const BitGoJS = require("../../src/index");
const axios_1 = require("axios");
const sdk_test_1 = require("@bitgo/sdk-test");
const bitgo_1 = require("../../src/bitgo");
nock.disableNetConnect();
describe('BitGo Prototype Methods', function () {
describe('Version', () => {
it('version', function () {
const bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
bitgo.initializeTestVars();
const version = bitgo.version();
version.should.be.a.String();
});
});
describe('validate', () => {
it('should get', () => {
const bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
bitgo.getValidate().should.equal(true);
});
it('should set', () => {
const bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
bitgo.setValidate(false);
bitgo.getValidate().should.equal(false);
bitgo['_validate'].should.equal(false);
});
});
describe('Environments', () => {
it('production', () => {
BitGoJS.setNetwork('testnet');
sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'prod' });
BitGoJS.getNetwork().should.equal('bitcoin');
});
it('staging', () => {
BitGoJS.setNetwork('testnet');
sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'staging' });
BitGoJS.getNetwork().should.equal('testnet');
});
it('test', () => {
BitGoJS.setNetwork('bitcoin');
sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'test' });
BitGoJS.getNetwork().should.equal('testnet');
});
it('dev', () => {
sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'dev' });
BitGoJS.getNetwork().should.equal('testnet');
});
it('custom network (prod)', () => {
sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { customBitcoinNetwork: 'bitcoin', customRootURI: 'http://rooturi.example' });
BitGoJS.getNetwork().should.equal('bitcoin');
});
it('custom network (testnet)', () => {
sdk_test_1.TestBitGo.decorate(bitgo_1.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 () {
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'prod', hmacVerification: false })).should.throw(/Cannot disable request HMAC verification in environment/);
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'test', hmacVerification: false })).should.not.throw(/Cannot disable request HMAC verification in environment/);
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'adminProd', hmacVerification: false })).should.throw(/Cannot disable request HMAC verification in environment/);
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'adminTest', hmacVerification: false })).should.not.throw(/Cannot disable request HMAC verification in environment/);
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.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 () {
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'dev', hmacVerification: false })).should.not.throw();
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'adminDev', hmacVerification: false })).should.not.throw();
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'adminStaging', hmacVerification: false })).should.not.throw();
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'local', hmacVerification: false })).should.not.throw();
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'localNonSecure', hmacVerification: false })).should.not.throw();
(() => sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'mock', microservicesUri: 'https://microservices.uri' });
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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
});
it('errors', () => {
(() => bitgo.verifyAddress()).should.throw();
(() => bitgo.verifyAddress({})).should.throw();
bitgo.verifyAddress({ address: 'xyzzy' }).should.be.false();
});
it('standard', () => {
bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'prod' });
bitgo.verifyAddress({ address: '1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd' }).should.be.true();
// wrong version byte:
bitgo.verifyAddress({ address: '9Ef7HsuByGBogqkjoF5Yng7MYkq5UCdmZz' }).should.be.false();
bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
bitgo.verifyAddress({ address: 'n4DNhSiEaodqaiF9tLYXTCh4kFbdUzxBHs' }).should.be.true();
});
it('p2sh', () => {
bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'prod' });
bitgo.verifyAddress({ address: '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC' }).should.be.true();
// wrong version byte:
bitgo.verifyAddress({ address: 'HV8swrGkmeN7Xig4vENr93aQSrX4iHjg7D' }).should.be.false();
bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo);
bitgo.initializeTestVars();
const password = bitgo.generateRandomPassword();
should.exist(password);
});
it('generates a random password with a numWords argument', () => {
const bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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,
})).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 });
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) {
return utxo_lib_1.ECPair.fromPrivateKey(utxo_lib_1.bip32.fromSeed(crypto.createHash('sha256').update(seed).digest()).privateKey);
}
it('should calculate a new ECDH sharing secret correctly', () => {
for (let i = 0; i < 256; i++) {
const bitgo = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'mock' });
bitgo.initializeTestVars();
bitgo.setValidate(false);
await bitgo.authenticateChangePWTestUser(bitgo.testUserOTP());
bgUrl = sdk_core_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 response = await axios_1.default.get(url, {
headers: requestHeaders,
transformResponse: [],
});
const finalUrl = response.request.responseURL || url;
const hmac = response.headers.hmac;
const timestamp = response.headers.timestamp;
const statusCode = response.status;
const verificationParams = {
url: finalUrl,
hmac,
timestamp,
token,
statusCode,
text: response.data,
};
const responseData = bitgo.verifyResponse(verificationParams);
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));
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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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));
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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'mock' });
bitgo.initializeTestVars();
bgUrl = sdk_core_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'mock' });
it('should fail if passed non-string username or password', function () {
(() => bitgo.preprocessAuthenticationParams({ username: 123 })).should.throw(/expected string username/);
(() => bitgo.preprocessAuthenticationParams({ username: 'abc', password: {} })).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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 (0, sdk_core_1.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.ok(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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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 = sdk_test_1.TestBitGo.decorate(bitgo_1.BitGo, { env: 'mock' });
try {
await bitgo.validatePasskeyResponse(passkey);
assert.fail('Expected error not thrown');
}
catch (e) {
console.log(e);
assert.ok(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 = sdk_test_1.TestBitGo.decorate(bitgo_1.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');
}
});
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYml0Z28uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90ZXN0L3VuaXQvYml0Z28udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLEVBQUU7QUFDRix5QkFBeUI7QUFDekIsRUFBRTs7QUFFRixpQ0FBaUM7QUFDakMsNkJBQThCO0FBQzlCLGlDQUFrQztBQUNsQyxpQ0FBa0M7QUFFbEMsOENBQTZEO0FBQzdELDhDQUFnRDtBQUNoRCw0QkFBNEI7QUFDNUIsMkNBQTJDO0FBQzNDLGlDQUEwQjtBQUUxQiw4Q0FBNEM7QUFDNUMsMkNBQXdDO0FBRXhDLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO0FBRXpCLFFBQVEsQ0FBQyx5QkFBeUIsRUFBRTtJQUNsQyxRQUFRLENBQUMsU0FBUyxFQUFFLEdBQUcsRUFBRTtRQUN2QixFQUFFLENBQUMsU0FBUyxFQUFFO1lBQ1osTUFBTSxLQUFLLEdBQUcsb0JBQVMsQ0FBQyxRQUFRLENBQUMsYUFBSyxDQUFDLENBQUM7WUFDeEMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDM0IsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2hDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUMvQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDO0lBRUgsUUFBUSxDQUFDLFVBQVUsRUFBRSxHQUFHLEVBQUU7UUFDeEIsRUFBRSxDQUFDLFlBQVksRUFBRSxHQUFHLEVBQUU7WUFDcEIsTUFBTSxLQUFLLEdBQUcsb0JBQVMsQ0FBQyxRQUFRLENBQUMsYUFBSyxDQUFDLENBQUM7WUFDeEMsS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekMsQ0FBQyxDQUFDLENBQUM7UUFFSCxFQUFFLENBQUMsWUFBWSxFQUFFLEdBQUcsRUFBRTtZ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