Skip to content

Commit 224d1c0

Browse files
feat(express): migrate unlocklightningwallet to typed routes
2 parents 8794733 + b9c7916 commit 224d1c0

File tree

6 files changed

+86
-34
lines changed

6 files changed

+86
-34
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,10 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
15601560
router.post('express.encrypt', [prepareBitGo(config), typedPromiseWrapper(handleEncrypt)]);
15611561
router.post('express.verifyaddress', [prepareBitGo(config), typedPromiseWrapper(handleVerifyAddress)]);
15621562
router.post('express.lightning.initWallet', [prepareBitGo(config), typedPromiseWrapper(handleInitLightningWallet)]);
1563+
router.post('express.lightning.unlockWallet', [
1564+
prepareBitGo(config),
1565+
typedPromiseWrapper(handleUnlockLightningWallet),
1566+
]);
15631567
router.post('express.calculateminerfeeinfo', [
15641568
prepareBitGo(config),
15651569
typedPromiseWrapper(handleCalculateMinerFeeInfo),
@@ -1783,11 +1787,5 @@ export function setupLightningSignerNodeRoutes(app: express.Application, config:
17831787
prepareBitGo(config),
17841788
promiseWrapper(handleCreateSignerMacaroon)
17851789
);
1786-
app.post(
1787-
'/api/v2/:coin/wallet/:id/unlockwallet',
1788-
parseBody,
1789-
prepareBitGo(config),
1790-
promiseWrapper(handleUnlockLightningWallet)
1791-
);
17921790
app.get('/api/v2/:coin/wallet/:id/state', prepareBitGo(config), promiseWrapper(handleGetLightningWalletState));
17931791
}

modules/express/src/lightning/lightningSignerRoutes.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {
1313
} from '@bitgo/abstract-lightning';
1414
import * as utxolib from '@bitgo/utxo-lib';
1515
import { Buffer } from 'buffer';
16+
import { ExpressApiRouteRequest } from '../typedRoutes/api';
1617

17-
import { CreateSignerMacaroonRequest, GetWalletStateResponse, UnlockLightningWalletRequest } from './codecs';
18+
import { CreateSignerMacaroonRequest, GetWalletStateResponse } from './codecs';
1819
import { LndSignerClient } from './lndSignerClient';
1920
import { ApiResponseError } from '../errors';
20-
import { ExpressApiRouteRequest } from '../typedRoutes/api';
2121

2222
type Decrypt = (params: { input: string; password: string }) => string;
2323

@@ -187,25 +187,13 @@ export async function handleGetLightningWalletState(req: express.Request): Promi
187187
/**
188188
* Handle the request to unlock a wallet in the signer.
189189
*/
190-
export async function handleUnlockLightningWallet(req: express.Request): Promise<{ message: string }> {
191-
const coinName = req.params.coin;
190+
export async function handleUnlockLightningWallet(
191+
req: ExpressApiRouteRequest<'express.lightning.unlockWallet', 'post'>
192+
): Promise<{ message: string }> {
193+
const { coin: coinName, id: walletId, passphrase } = req.decoded;
192194
if (!isLightningCoinName(coinName)) {
193195
throw new ApiResponseError(`Invalid coin to unlock lightning wallet: ${coinName}`, 400);
194196
}
195-
const walletId = req.params.id;
196-
if (typeof walletId !== 'string') {
197-
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
198-
}
199-
200-
const { passphrase } = decodeOrElse(
201-
UnlockLightningWalletRequest.name,
202-
UnlockLightningWalletRequest,
203-
req.body,
204-
(_) => {
205-
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
206-
throw new ApiResponseError('Invalid request body to unlock lightning wallet', 400);
207-
}
208-
);
209197

210198
const lndSignerClient = await LndSignerClient.create(walletId, req.config);
211199
// The passphrase at LND can only accommodate a base64 character set

modules/express/src/typedRoutes/api/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { PutPendingApproval } from './v1/pendingApproval';
1515
import { PostSignTransaction } from './v1/signTransaction';
1616
import { PostKeychainLocal } from './v2/keychainLocal';
1717
import { PostLightningInitWallet } from './v2/lightningInitWallet';
18+
import { PostUnlockLightningWallet } from './v2/unlockWallet';
1819
import { PostVerifyCoinAddress } from './v2/verifyAddress';
1920
import { PostDeriveLocalKeyChain } from './v1/deriveLocalKeyChain';
2021

@@ -55,6 +56,9 @@ export const ExpressApi = apiSpec({
5556
'express.lightning.initWallet': {
5657
post: PostLightningInitWallet,
5758
},
59+
'express.lightning.unlockWallet': {
60+
post: PostUnlockLightningWallet,
61+
},
5862
'express.verifycoinaddress': {
5963
post: PostVerifyCoinAddress,
6064
},
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Path parameters for unlocking a lightning wallet
7+
* @property {string} coin - A lightning coin name (e.g, lnbtc).
8+
* @property {string} id - The ID of the wallet.
9+
*/
10+
export const UnlockLightningWalletParams = {
11+
/** A lightning coin name (e.g, lnbtc, tlnbtc). */
12+
coin: t.string,
13+
/** The ID of the wallet. */
14+
id: t.string,
15+
} as const;
16+
17+
/**
18+
* Request body for unlocking a lightning wallet
19+
* @property {string} passphrase - Passphrase to unlock the lightning wallet.
20+
*/
21+
export const UnlockLightningWalletBody = {
22+
/** Passphrase to unlock the lightning wallet. */
23+
passphrase: t.string,
24+
} as const;
25+
26+
export const UnlockLightningWalletResponse200 = t.type({
27+
message: t.string,
28+
});
29+
30+
/**
31+
* Response for unlocking a lightning wallet.
32+
*/
33+
export const UnlockLightningWalletResponse = {
34+
/** Confirmation message. */
35+
200: UnlockLightningWalletResponse200,
36+
/** BitGo Express error payload. */
37+
400: BitgoExpressError,
38+
} as const;
39+
40+
/**
41+
* Lightning - Unlock node
42+
*
43+
* This is only used for self-custody lightning. Unlock the Lightning Network Daemon (LND) node with the given wallet password.
44+
*
45+
* @operationId express.lightning.unlockWallet
46+
*/
47+
export const PostUnlockLightningWallet = httpRoute({
48+
method: 'POST',
49+
path: '/api/v2/{coin}/wallet/{id}/unlockwallet',
50+
request: httpRequest({
51+
params: UnlockLightningWalletParams,
52+
body: UnlockLightningWalletBody,
53+
}),
54+
response: UnlockLightningWalletResponse,
55+
});

modules/express/test/unit/clientRoutes/lightning/lightningSignerRoutes.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import nock from 'nock';
55
import * as express from 'express';
66
import * as sinon from 'sinon';
77
import * as fs from 'fs';
8+
import { UnlockLightningWalletResponse } from '../../../../src/typedRoutes/api/v2/unlockWallet';
89

910
import { lightningSignerConfigs, apiData, signerApiData } from './lightningSignerFixture';
1011
import {
@@ -184,17 +185,14 @@ describe('Lightning signer routes', () => {
184185

185186
const req = {
186187
bitgo: bitgo,
187-
body: apiData.unlockWalletRequestBody,
188-
params: {
189-
coin: 'tlnbtc',
190-
id: 'fakeid',
191-
},
192-
config: {
193-
lightningSignerFileSystemPath: 'lightningSignerFileSystemPath',
194-
},
195-
} as unknown as express.Request;
188+
config: { lightningSignerFileSystemPath: 'lightningSignerFileSystemPath' },
189+
decoded: { coin: 'tlnbtc', id: 'fakeid', passphrase: apiData.unlockWalletRequestBody.passphrase },
190+
} as unknown as ExpressApiRouteRequest<'express.lightning.unlockWallet', 'post'>;
196191

197-
await handleUnlockLightningWallet(req);
192+
const res = await handleUnlockLightningWallet(req);
193+
decodeOrElse('UnlockLightningWalletResponse200', UnlockLightningWalletResponse[200], res, (_) => {
194+
throw new Error('Response did not match expected codec');
195+
});
198196

199197
unlockwalletNock.done();
200198
readFileStub.calledOnceWith('lightningSignerFileSystemPath').should.be.true();

modules/express/test/unit/typedRoutes/decode.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
LightningInitWalletBody,
1212
LightningInitWalletParams,
1313
} from '../../../src/typedRoutes/api/v2/lightningInitWallet';
14+
import { UnlockLightningWalletBody, UnlockLightningWalletParams } from '../../../src/typedRoutes/api/v2/unlockWallet';
1415

1516
export function assertDecode<T>(codec: t.Type<T, unknown>, input: unknown): T {
1617
const result = codec.decode(input);
@@ -174,4 +175,12 @@ describe('io-ts decode tests', function () {
174175
// valid with expressHost
175176
assertDecode(t.type(LightningInitWalletBody), { passphrase: 'p', expressHost: 'host.example' });
176177
});
178+
it('express.lightning.unlockWallet', function () {
179+
// params require coin and id
180+
assertDecode(t.type(UnlockLightningWalletParams), { coin: 'tlnbtc', id: 'wallet123' });
181+
// missing passphrase
182+
assert.throws(() => assertDecode(t.type(UnlockLightningWalletBody), {}));
183+
// valid body
184+
assertDecode(t.type(UnlockLightningWalletBody), { passphrase: 'secret' });
185+
});
177186
});

0 commit comments

Comments
 (0)