Skip to content

Commit 1d5fd38

Browse files
committed
feat(express): migrate lightningSignerMacaroon to typed routes
TICKET: WP-5445
1 parent 826afcd commit 1d5fd38

File tree

6 files changed

+80
-26
lines changed

6 files changed

+80
-26
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,11 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
15961596

15971597
router.put('express.v1.pendingapprovals', [prepareBitGo(config), typedPromiseWrapper(handleApproveTransaction)]);
15981598

1599+
router.post('express.lightning.signerMacaroon', [
1600+
prepareBitGo(config),
1601+
typedPromiseWrapper(handleCreateSignerMacaroon),
1602+
]);
1603+
15991604
app.put(
16001605
'/api/v1/pendingapprovals/:id/constructTx',
16011606
parseBody,
@@ -1796,12 +1801,6 @@ export function setupLightningSignerNodeRoutes(app: express.Application, config:
17961801
prepareBitGo(config),
17971802
promiseWrapper(handleInitLightningWallet)
17981803
);
1799-
app.post(
1800-
'/api/v2/:coin/wallet/:id/signermacaroon',
1801-
parseBody,
1802-
prepareBitGo(config),
1803-
promiseWrapper(handleCreateSignerMacaroon)
1804-
);
18051804
app.post(
18061805
'/api/v2/:coin/wallet/:id/unlockwallet',
18071806
parseBody,

modules/express/src/lightning/lightningSignerRoutes.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,10 @@ import {
1414
import * as utxolib from '@bitgo/utxo-lib';
1515
import { Buffer } from 'buffer';
1616

17-
import {
18-
CreateSignerMacaroonRequest,
19-
GetWalletStateResponse,
20-
InitLightningWalletRequest,
21-
UnlockLightningWalletRequest,
22-
} from './codecs';
17+
import { GetWalletStateResponse, InitLightningWalletRequest, UnlockLightningWalletRequest } from './codecs';
2318
import { LndSignerClient } from './lndSignerClient';
2419
import { ApiResponseError } from '../errors';
20+
import type { ExpressApiRouteRequest } from '../typedRoutes/api';
2521

2622
type Decrypt = (params: { input: string; password: string }) => string;
2723

@@ -123,28 +119,20 @@ export async function handleInitLightningWallet(req: express.Request): Promise<u
123119
/**
124120
* Handle the request to create a signer macaroon from remote signer LND for a wallet.
125121
*/
126-
export async function handleCreateSignerMacaroon(req: express.Request): Promise<unknown> {
122+
export async function handleCreateSignerMacaroon(
123+
req: ExpressApiRouteRequest<'express.lightning.signerMacaroon', 'post'>
124+
): Promise<unknown> {
127125
const bitgo = req.bitgo;
128-
const coinName = req.params.coin;
126+
const { walletId, passphrase, addIpCaveatToMacaroon } = req.decoded;
127+
const coinName = req.decoded.coin;
129128
if (!isLightningCoinName(coinName)) {
130129
throw new ApiResponseError(`Invalid coin to create signer macaroon: ${coinName}. Must be a lightning coin.`, 400);
131130
}
132131
const coin = bitgo.coin(coinName);
133-
const walletId = req.params.id;
134132
if (typeof walletId !== 'string') {
135133
throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400);
136134
}
137135

138-
const { passphrase, addIpCaveatToMacaroon } = decodeOrElse(
139-
CreateSignerMacaroonRequest.name,
140-
CreateSignerMacaroonRequest,
141-
req.body,
142-
(_) => {
143-
// DON'T throw errors from decodeOrElse. It could leak sensitive information.
144-
throw new ApiResponseError('Invalid request body to create signer macaroon', 400);
145-
}
146-
);
147-
148136
const wallet = await coin.wallets().get({ id: walletId, includeBalance: false });
149137
if (wallet.subType() !== 'lightningSelfCustody') {
150138
throw new ApiResponseError(`not a self custodial lighting wallet ${walletId}`, 400);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PostAcceptShare } from './v1/acceptShare';
1212
import { PostSimpleCreate } from './v1/simpleCreate';
1313
import { PutPendingApproval } from './v1/pendingApproval';
1414
import { PostSignTransaction } from './v1/signTransaction';
15+
import { PostSignerMacaroon } from './v2/signerMacaroon';
1516

1617
export const ExpressApi = apiSpec({
1718
'express.ping': {
@@ -44,6 +45,9 @@ export const ExpressApi = apiSpec({
4445
'express.v1.wallet.signTransaction': {
4546
post: PostSignTransaction,
4647
},
48+
'express.lightning.signerMacaroon': {
49+
post: PostSignerMacaroon,
50+
},
4751
});
4852

4953
export type ExpressApi = typeof ExpressApi;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Path parameters for creating a signer macaroon
7+
* @property {string} coin - A lightning coin name (e.g, lnbtc).
8+
* @property {string} walletId - The ID of the wallet.
9+
*/
10+
export const SignerMacaroonParams = {
11+
coin: t.string,
12+
walletId: t.string,
13+
};
14+
15+
/**
16+
* Request body for creating a signer macaroon
17+
* @property {string} passphrase - Passphrase to decrypt the admin macaroon of the signer node.
18+
* @property {boolean} addIpCaveatToMacaroon - If true, adds an IP caveat to the generated signer macaroon.
19+
*/
20+
export const SignerMacaroonBody = {
21+
passphrase: t.string,
22+
addIpCaveatToMacaroon: optional(t.boolean),
23+
};
24+
25+
/**
26+
* Lightning - Create signer macaroon
27+
*
28+
* This is only used for self-custody lightning. Create the signer macaroon for the watch-only Lightning Network Daemon (LND) node. This macaroon derives from the signer node admin macaroon and is used by the watch-only node to request signatures from the signer node for operational tasks. Returns the updated wallet with the encrypted signer macaroon in the `coinSpecific` response field.
29+
*
30+
* @operationId express.lightning.signerMacaroon
31+
*/
32+
export const PostSignerMacaroon = httpRoute({
33+
method: 'POST',
34+
path: '/api/v2/{coin}/wallet/{walletId}/signermacaroon',
35+
request: httpRequest({
36+
params: SignerMacaroonParams,
37+
body: SignerMacaroonBody,
38+
}),
39+
response: {
40+
200: t.UnknownRecord,
41+
400: BitgoExpressError,
42+
},
43+
});

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,18 @@ describe('Lightning signer routes', () => {
118118
params: {
119119
coin: 'tlnbtc',
120120
id: 'fakeid',
121+
walletId: 'fakeid',
122+
},
123+
decoded: {
124+
coin: 'tlnbtc',
125+
walletId: apiData.wallet.id,
126+
passphrase: apiData.signerMacaroonRequestBody.passphrase,
127+
addIpCaveatToMacaroon,
121128
},
122129
config: {
123130
lightningSignerFileSystemPath: 'lightningSignerFileSystemPath',
124131
},
125-
} as unknown as express.Request;
132+
} as any;
126133

127134
try {
128135
await handleCreateSignerMacaroon(req);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EncryptRequestBody } from '../../../src/typedRoutes/api/common/encrypt'
55
import { LoginRequest } from '../../../src/typedRoutes/api/common/login';
66
import { VerifyAddressBody } from '../../../src/typedRoutes/api/common/verifyAddress';
77
import { SimpleCreateRequestBody } from '../../../src/typedRoutes/api/v1/simpleCreate';
8+
import { SignerMacaroonBody, SignerMacaroonParams } from '../../../src/typedRoutes/api/v2/signerMacaroon';
89

910
export function assertDecode<T>(codec: t.Type<T, unknown>, input: unknown): T {
1011
const result = codec.decode(input);
@@ -100,4 +101,16 @@ describe('io-ts decode tests', function () {
100101
passphrase: 'pass',
101102
});
102103
});
104+
it('express.lightning.signerMacaroon body valid', function () {
105+
assertDecode(t.type(SignerMacaroonBody), { passphrase: 'pw', addIpCaveatToMacaroon: true });
106+
});
107+
it('express.lightning.signerMacaroon body valid (missing addIpCaveatToMacaroon)', function () {
108+
assertDecode(t.type(SignerMacaroonBody), { passphrase: 'pw' });
109+
});
110+
it('express.lightning.signerMacaroon params valid', function () {
111+
assertDecode(t.type(SignerMacaroonParams), { coin: 'lnbtc', walletId: 'wid123' });
112+
});
113+
it('express.lightning.signerMacaroon params invalid', function () {
114+
assert.throws(() => assertDecode(t.type(SignerMacaroonParams), { coin: 'lnbtc' }));
115+
});
103116
});

0 commit comments

Comments
 (0)