Skip to content

Commit 57eaca1

Browse files
Hide recovery APIs for mbe
1 parent 525671d commit 57eaca1

File tree

10 files changed

+186
-1
lines changed

10 files changed

+186
-1
lines changed

src/__tests__/api/master/musigRecovery.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('POST /api/:coin/wallet/recovery', () => {
3232
tlsMode: TlsMode.DISABLED,
3333
mtlsRequestCert: false,
3434
allowSelfSigned: true,
35+
recoveryMode: true
3536
};
3637

3738
const app = expressApp(config);
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import 'should';
2+
import * as request from 'supertest';
3+
import nock from 'nock';
4+
import { app as expressApp } from '../../../masterExpressApp';
5+
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
6+
import sinon from 'sinon';
7+
import * as middleware from '../../../shared/middleware';
8+
import * as masterMiddleware from '../../../api/master/middleware/middleware';
9+
import { BitGoRequest } from '../../../types/request';
10+
import { BitGoAPI } from '@bitgo-beta/sdk-api';
11+
import { EnclavedExpressClient } from '../../../api/master/clients/enclavedExpressClient';
12+
import { CoinFamily } from '@bitgo-beta/statics';
13+
import { Trx } from '@bitgo-beta/sdk-coin-trx';
14+
15+
describe('Non Recovery Tests', () => {
16+
let agent: request.SuperAgentTest;
17+
let mockBitgo: BitGoAPI;
18+
let coinStub: sinon.SinonStub;
19+
const enclavedExpressUrl = 'http://enclaved.invalid';
20+
const accessToken = 'test-token';
21+
const config: MasterExpressConfig = {
22+
appMode: AppMode.MASTER_EXPRESS,
23+
port: 0,
24+
bind: 'localhost',
25+
timeout: 60000,
26+
logFile: '',
27+
env: 'test',
28+
disableEnvCheck: true,
29+
authVersion: 2,
30+
enclavedExpressUrl: enclavedExpressUrl,
31+
enclavedExpressCert: 'dummy-cert',
32+
tlsMode: TlsMode.DISABLED,
33+
mtlsRequestCert: false,
34+
allowSelfSigned: true,
35+
recoveryMode: false,
36+
};
37+
38+
beforeEach(() => {
39+
nock.disableNetConnect();
40+
nock.enableNetConnect('127.0.0.1');
41+
42+
// Create mock BitGo instance with base functionality
43+
mockBitgo = {
44+
coin: sinon.stub(),
45+
_coinFactory: {},
46+
_useAms: false,
47+
initCoinFactory: sinon.stub(),
48+
registerToken: sinon.stub(),
49+
getValidate: sinon.stub(),
50+
validateAddress: sinon.stub(),
51+
verifyAddress: sinon.stub(),
52+
verifyPassword: sinon.stub(),
53+
encrypt: sinon.stub(),
54+
decrypt: sinon.stub(),
55+
lock: sinon.stub(),
56+
unlock: sinon.stub(),
57+
getSharingKey: sinon.stub(),
58+
ping: sinon.stub(),
59+
authenticate: sinon.stub(),
60+
authenticateWithAccessToken: sinon.stub(),
61+
logout: sinon.stub(),
62+
me: sinon.stub(),
63+
session: sinon.stub(),
64+
getUser: sinon.stub(),
65+
users: sinon.stub(),
66+
getWallet: sinon.stub(),
67+
getWallets: sinon.stub(),
68+
addWallet: sinon.stub(),
69+
removeWallet: sinon.stub(),
70+
getAsUser: sinon.stub(),
71+
register: sinon.stub(),
72+
} as unknown as BitGoAPI;
73+
74+
coinStub = mockBitgo.coin as sinon.SinonStub;
75+
76+
// Setup middleware stubs before creating app
77+
sinon.stub(middleware, 'prepareBitGo').callsFake(() => (req, res, next) => {
78+
(req as BitGoRequest<MasterExpressConfig>).bitgo = mockBitgo;
79+
(req as BitGoRequest<MasterExpressConfig>).config = config;
80+
next();
81+
});
82+
83+
// Create app after middleware is stubbed
84+
const app = expressApp(config);
85+
agent = request.agent(app);
86+
});
87+
88+
afterEach(() => {
89+
nock.cleanAll();
90+
sinon.restore();
91+
});
92+
93+
describe('Recovery', () => {
94+
const coin = 'tbtc';
95+
96+
beforeEach(() => {
97+
sinon.stub(masterMiddleware, 'validateMasterExpressConfig').callsFake((req, res, next) => {
98+
(req as BitGoRequest<MasterExpressConfig>).params = { coin };
99+
(req as BitGoRequest<MasterExpressConfig>).enclavedExpressClient =
100+
new EnclavedExpressClient(config, coin);
101+
next();
102+
return undefined;
103+
});
104+
});
105+
106+
it('should fail to run recovery if not in recovery mode', async () => {
107+
const coin = 'tbtc';
108+
const userPub = 'xpub_user';
109+
const backupPub = 'xpub_backup';
110+
const bitgoPub = 'xpub_bitgo';
111+
const recoveryDestination = 'tb1qprdy6jwxrrr2qrwgd2tzl8z99hqp29jn6f3sguxulqm448myj6jsy2nwsu';
112+
const response = await agent
113+
.post(`/api/${coin}/wallet/recovery`)
114+
.set('Authorization', `Bearer ${accessToken}`)
115+
.send({
116+
multiSigRecoveryParams: {
117+
userPub,
118+
backupPub,
119+
bitgoPub,
120+
walletContractAddress: '',
121+
},
122+
recoveryDestinationAddress: recoveryDestination,
123+
coin,
124+
apiKey: 'key',
125+
coinSpecificParams: {
126+
evmRecoveryOptions: {
127+
gasPrice: 20000000000,
128+
gasLimit: 500000,
129+
},
130+
},
131+
});
132+
response.status.should.equal(500);
133+
response.body.should.have.property('error');
134+
response.body.should.have.property('details');
135+
response.body.details.should.containEql('Recovery operations are not enabled. The server must be in recovery mode to perform this action.');
136+
});
137+
});
138+
139+
describe('Recovery Consolidation', () => {
140+
it('should fail to run recovery consolidation if not in recovery mode', async () => {
141+
const response = await agent
142+
.post(`/api/trx/wallet/recoveryconsolidations`)
143+
.set('Authorization', `Bearer ${accessToken}`)
144+
.send({
145+
multisigType: 'onchain',
146+
userPub: 'user-xpub',
147+
backupPub: 'backup-xpub',
148+
bitgoPub: 'bitgo-xpub',
149+
tokenContractAddress: 'tron-token',
150+
startingScanIndex: 1,
151+
endingScanIndex: 3,
152+
});
153+
154+
response.status.should.equal(500);
155+
});
156+
});
157+
});

src/__tests__/api/master/recoveryConsolidationsWallet.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('POST /api/:coin/wallet/recoveryconsolidations', () => {
3131
tlsMode: TlsMode.DISABLED,
3232
mtlsRequestCert: false,
3333
allowSelfSigned: true,
34+
recoveryMode: true
3435
};
3536
const app = expressApp(config);
3637
agent = request.agent(app);

src/__tests__/api/master/recoveryWallet.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('Recovery Tests', () => {
3131
tlsMode: TlsMode.DISABLED,
3232
mtlsRequestCert: false,
3333
allowSelfSigned: true,
34+
recoveryMode: true,
3435
};
3536

3637
beforeEach(() => {

src/__tests__/config.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ describe('Configuration', () => {
3838
process.env.TLS_CERT = mockTlsCert;
3939
});
4040

41+
it('should read the recovery mode from the env', () => {
42+
process.env.RECOVERY_MODE = 'true';
43+
const cfg = initConfig();
44+
cfg.recoveryMode!.should.be.true();
45+
});
46+
4147
it('should use default configuration when no environment variables are set', () => {
4248
const cfg = initConfig();
4349
isEnclavedConfig(cfg).should.be.true();

src/api/master/handlerUtils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BitGoAPI } from '@bitgo-beta/sdk-api';
22
import { CustomSigningFunction, RequestTracer } from '@bitgo-beta/sdk-core';
33
import { EnclavedExpressClient } from './clients/enclavedExpressClient';
44
import coinFactory from '../../shared/coinFactory';
5+
import { MasterExpressConfig } from '../../shared/types';
56

67
/**
78
* Fetch wallet and signing keychain, with validation for source and pubkey.
@@ -73,3 +74,9 @@ export function makeCustomSigningFunction({
7374
});
7475
};
7576
}
77+
78+
export function checkRecoveryMode(config: MasterExpressConfig) {
79+
if (!config.recoveryMode) {
80+
throw new Error('Recovery operations are not enabled. The server must be in recovery mode to perform this action.');
81+
}
82+
}

src/api/master/handlers/recoveryConsolidationsWallet.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import type { Ada, Tada } from '@bitgo-beta/sdk-coin-ada';
2020
import type { Dot, Tdot } from '@bitgo-beta/sdk-coin-dot';
2121
import type { Tao, Ttao } from '@bitgo-beta/sdk-coin-tao';
2222
import coinFactory from '../../../shared/coinFactory';
23+
import { checkRecoveryMode } from '../handlerUtils';
24+
import { MasterExpressConfig } from '../../../shared/types';
2325

2426
type RecoveryConsolidationParams =
2527
| ConsolidationRecoveryOptions
@@ -77,6 +79,8 @@ export async function recoveryConsolidateWallets(
7779
export async function handleRecoveryConsolidationsOnPrem(
7880
req: MasterApiSpecRouteRequest<'v1.wallet.recoveryConsolidations', 'post'>,
7981
) {
82+
checkRecoveryMode(req.config as MasterExpressConfig);
83+
8084
const bitgo = req.bitgo;
8185
const coin = req.decoded.coin;
8286
const enclavedExpressClient = req.enclavedExpressClient;

src/api/master/handlers/recoveryWallet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import {
2828
UtxoRecoveryOptions,
2929
} from '../routers/masterApiSpec';
3030
import { recoverEddsaWallets } from './recoverEddsaWallets';
31-
import { EnvironmentName } from '../../../shared/types';
31+
import { EnvironmentName, MasterExpressConfig } from '../../../shared/types';
3232
import logger from '../../../logger';
3333
import { CoinFamily } from '@bitgo-beta/statics';
3434
import { ValidationError } from '../../../shared/errors';
35+
import { checkRecoveryMode } from '../handlerUtils';
3536

3637
interface RecoveryParams {
3738
userKey: string;
@@ -186,6 +187,8 @@ async function handleUtxoLikeRecovery(
186187
export async function handleRecoveryWalletOnPrem(
187188
req: MasterApiSpecRouteRequest<'v1.wallet.recovery', 'post'>,
188189
) {
190+
checkRecoveryMode(req.config as MasterExpressConfig);
191+
189192
const bitgo = req.bitgo;
190193
const coin = req.decoded.coin;
191194
const enclavedExpressClient = req.enclavedExpressClient;

src/initConfig.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ function enclavedEnvConfig(): Partial<EnclavedConfig> {
104104
mtlsRequestCert: readEnvVar('MTLS_REQUEST_CERT')?.toLowerCase() !== 'false',
105105
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
106106
allowSelfSigned: readEnvVar('ALLOW_SELF_SIGNED') === 'true',
107+
recoveryMode: readEnvVar('RECOVERY_MODE') === 'true',
107108
};
108109
}
109110

@@ -135,6 +136,7 @@ function mergeEnclavedConfigs(...configs: Partial<EnclavedConfig>[]): EnclavedCo
135136
mtlsRequestCert: get('mtlsRequestCert'),
136137
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
137138
allowSelfSigned: get('allowSelfSigned'),
139+
recoveryMode: get('recoveryMode'),
138140
};
139141
}
140142

@@ -251,6 +253,7 @@ function masterExpressEnvConfig(): Partial<MasterExpressConfig> {
251253
mtlsRequestCert,
252254
mtlsAllowedClientFingerprints: readEnvVar('MTLS_ALLOWED_CLIENT_FINGERPRINTS')?.split(','),
253255
allowSelfSigned,
256+
recoveryMode: readEnvVar('RECOVERY_MODE') === 'true',
254257
};
255258
}
256259

@@ -290,6 +293,7 @@ function mergeMasterExpressConfigs(
290293
mtlsRequestCert: get('mtlsRequestCert'),
291294
mtlsAllowedClientFingerprints: get('mtlsAllowedClientFingerprints'),
292295
allowSelfSigned: get('allowSelfSigned'),
296+
recoveryMode: get('recoveryMode'),
293297
};
294298
}
295299

src/shared/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface BaseConfig {
2121
timeout: number;
2222
keepAliveTimeout?: number;
2323
headersTimeout?: number;
24+
recoveryMode?: boolean;
2425
}
2526

2627
// Enclaved mode specific configuration

0 commit comments

Comments
 (0)