Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__tests__/api/master/musigRecovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('POST /api/:coin/wallet/recovery', () => {
enclavedExpressCert: 'dummy-cert',
tlsMode: TlsMode.DISABLED,
allowSelfSigned: true,
recoveryMode: true,
};

const app = expressApp(config);
Expand Down
124 changes: 124 additions & 0 deletions src/__tests__/api/master/nonRecovery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'should';
import * as request from 'supertest';
import nock from 'nock';
import { app as expressApp } from '../../../masterExpressApp';
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
import sinon from 'sinon';
import * as middleware from '../../../shared/middleware';
import * as masterMiddleware from '../../../api/master/middleware/middleware';
import { BitGoRequest } from '../../../types/request';
import { BitGoAPI } from '@bitgo-beta/sdk-api';
import { EnclavedExpressClient } from '../../../api/master/clients/enclavedExpressClient';

describe('Non Recovery Tests', () => {
let agent: request.SuperAgentTest;
let mockBitgo: BitGoAPI;
const enclavedExpressUrl = 'http://enclaved.invalid';
const accessToken = 'test-token';
const config: MasterExpressConfig = {
appMode: AppMode.MASTER_EXPRESS,
port: 0,
bind: 'localhost',
timeout: 60000,
env: 'test',
disableEnvCheck: true,
authVersion: 2,
enclavedExpressUrl: enclavedExpressUrl,
enclavedExpressCert: 'dummy-cert',
tlsMode: TlsMode.DISABLED,
httpLoggerFile: '',
allowSelfSigned: true,
recoveryMode: false,
};

beforeEach(() => {
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');

// Create mock BitGo instance with base functionality
mockBitgo = new BitGoAPI({ env: 'test' });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shoudn't be needed; you can just create a bitgo instance. See this for eg.

bitgo = new BitGoAPI({ env: 'test' });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// Setup middleware stubs before creating app
sinon.stub(middleware, 'prepareBitGo').callsFake(() => (req, res, next) => {
(req as BitGoRequest<MasterExpressConfig>).bitgo = mockBitgo;
(req as BitGoRequest<MasterExpressConfig>).config = config;
next();
});

// Create app after middleware is stubbed
const app = expressApp(config);
agent = request.agent(app);
});

afterEach(() => {
nock.cleanAll();
sinon.restore();
});

describe('Recovery', () => {
const coin = 'tbtc';

beforeEach(() => {
sinon.stub(masterMiddleware, 'validateMasterExpressConfig').callsFake((req, res, next) => {
(req as BitGoRequest<MasterExpressConfig>).params = { coin };
(req as BitGoRequest<MasterExpressConfig>).enclavedExpressClient =
new EnclavedExpressClient(config, coin);
next();
return undefined;
});
});

it('should fail to run mbe recovery if not in recovery mode', async () => {
const coin = 'tbtc';
const userPub = 'xpub_user';
const backupPub = 'xpub_backup';
const bitgoPub = 'xpub_bitgo';
const recoveryDestination = 'tb1qprdy6jwxrrr2qrwgd2tzl8z99hqp29jn6f3sguxulqm448myj6jsy2nwsu';
const response = await agent
.post(`/api/${coin}/wallet/recovery`)
.set('Authorization', `Bearer ${accessToken}`)
.send({
multiSigRecoveryParams: {
userPub,
backupPub,
bitgoPub,
walletContractAddress: '',
},
recoveryDestinationAddress: recoveryDestination,
coin,
apiKey: 'key',
coinSpecificParams: {
evmRecoveryOptions: {
gasPrice: 20000000000,
gasLimit: 500000,
},
},
});
response.status.should.equal(500);
response.body.should.have.property('error');
response.body.should.have.property('details');
response.body.details.should.containEql(
'Recovery operations are not enabled. The server must be in recovery mode to perform this action.',
);
});
});

describe('Recovery Consolidation', () => {
it('should fail to run mbe recovery consolidation if not in recovery mode', async () => {
const response = await agent
.post(`/api/trx/wallet/recoveryconsolidations`)
.set('Authorization', `Bearer ${accessToken}`)
.send({
multisigType: 'onchain',
userPub: 'user-xpub',
backupPub: 'backup-xpub',
bitgoPub: 'bitgo-xpub',
tokenContractAddress: 'tron-token',
startingScanIndex: 1,
endingScanIndex: 3,
});

response.status.should.equal(500);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('POST /api/:coin/wallet/recoveryconsolidations', () => {
enclavedExpressCert: 'test-cert',
tlsMode: TlsMode.DISABLED,
allowSelfSigned: true,
recoveryMode: true,
};
const app = expressApp(config);
agent = request.agent(app);
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/api/master/recoveryWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('Recovery Tests', () => {
enclavedExpressCert: 'dummy-cert',
tlsMode: TlsMode.DISABLED,
allowSelfSigned: true,
recoveryMode: true,
};

beforeEach(() => {
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ describe('Configuration', () => {
}
});

it('should read the recovery mode from the env', () => {
process.env.KMS_URL = 'http://localhost:3000';
process.env.TLS_KEY = mockTlsKey;
process.env.TLS_CERT = mockTlsCert;
process.env.RECOVERY_MODE = 'true';
const cfg = initConfig();
cfg.recoveryMode!.should.be.true();
});

it('should read TLS mode from environment variables', () => {
process.env.KMS_URL = 'http://localhost:3000';
process.env.TLS_KEY = mockTlsKey;
Expand Down
9 changes: 9 additions & 0 deletions src/api/master/handlerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BitGoAPI } from '@bitgo-beta/sdk-api';
import { CustomSigningFunction, RequestTracer } from '@bitgo-beta/sdk-core';
import { EnclavedExpressClient } from './clients/enclavedExpressClient';
import coinFactory from '../../shared/coinFactory';
import { MasterExpressConfig } from '../../shared/types';

/**
* Fetch wallet and signing keychain, with validation for source and pubkey.
Expand Down Expand Up @@ -73,3 +74,11 @@ export function makeCustomSigningFunction({
});
};
}

export function checkRecoveryMode(config: MasterExpressConfig) {
if (!config.recoveryMode) {
throw new Error(
'Recovery operations are not enabled. The server must be in recovery mode to perform this action.',
);
}
}
4 changes: 4 additions & 0 deletions src/api/master/handlers/recoveryConsolidationsWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type { Ada, Tada } from '@bitgo-beta/sdk-coin-ada';
import type { Dot, Tdot } from '@bitgo-beta/sdk-coin-dot';
import type { Tao, Ttao } from '@bitgo-beta/sdk-coin-tao';
import coinFactory from '../../../shared/coinFactory';
import { checkRecoveryMode } from '../handlerUtils';
import { MasterExpressConfig } from '../../../shared/types';

type RecoveryConsolidationParams =
| ConsolidationRecoveryOptions
Expand Down Expand Up @@ -77,6 +79,8 @@ export async function recoveryConsolidateWallets(
export async function handleRecoveryConsolidationsOnPrem(
req: MasterApiSpecRouteRequest<'v1.wallet.recoveryConsolidations', 'post'>,
) {
checkRecoveryMode(req.config as MasterExpressConfig);

const bitgo = req.bitgo;
const coin = req.decoded.coin;
const enclavedExpressClient = req.enclavedExpressClient;
Expand Down
5 changes: 4 additions & 1 deletion src/api/master/handlers/recoveryWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ import {
UtxoRecoveryOptions,
} from '../routers/masterApiSpec';
import { recoverEddsaWallets } from './recoverEddsaWallets';
import { EnvironmentName } from '../../../shared/types';
import { EnvironmentName, MasterExpressConfig } from '../../../shared/types';
import logger from '../../../logger';
import { CoinFamily } from '@bitgo-beta/statics';
import { ValidationError } from '../../../shared/errors';
import { checkRecoveryMode } from '../handlerUtils';

interface RecoveryParams {
userKey: string;
Expand Down Expand Up @@ -186,6 +187,8 @@ async function handleUtxoLikeRecovery(
export async function handleRecoveryWalletOnPrem(
req: MasterApiSpecRouteRequest<'v1.wallet.recovery', 'post'>,
) {
checkRecoveryMode(req.config as MasterExpressConfig);

const bitgo = req.bitgo;
const coin = req.decoded.coin;
const enclavedExpressClient = req.enclavedExpressClient;
Expand Down
4 changes: 4 additions & 0 deletions src/initConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ function enclavedEnvConfig(): Partial<EnclavedConfig> {
tlsMode: determineTlsMode(),
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
allowSelfSigned: readEnvVar('ALLOW_SELF_SIGNED') === 'true',
recoveryMode: readEnvVar('RECOVERY_MODE') === 'true',
};
}

Expand Down Expand Up @@ -130,6 +131,7 @@ function mergeEnclavedConfigs(...configs: Partial<EnclavedConfig>[]): EnclavedCo
tlsMode: get('tlsMode'),
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
allowSelfSigned: get('allowSelfSigned'),
recoveryMode: get('recoveryMode'),
};
}

Expand Down Expand Up @@ -241,6 +243,7 @@ function masterExpressEnvConfig(): Partial<MasterExpressConfig> {
tlsMode,
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
allowSelfSigned,
recoveryMode: readEnvVar('RECOVERY_MODE') === 'true',
};
}

Expand Down Expand Up @@ -278,6 +281,7 @@ function mergeMasterExpressConfigs(
tlsMode: get('tlsMode'),
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
allowSelfSigned: get('allowSelfSigned'),
recoveryMode: get('recoveryMode'),
};
}

Expand Down
1 change: 1 addition & 0 deletions src/shared/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface BaseConfig {
keepAliveTimeout?: number;
headersTimeout?: number;
httpLoggerFile: string;
recoveryMode?: boolean;
}

// Enclaved mode specific configuration
Expand Down