UNPKG

bitgo

Version:
656 lines • 114 kB
"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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