Skip to content

Commit feb35f3

Browse files
feat: added lavamoat (#572)
1 parent 725d782 commit feb35f3

21 files changed

+2079
-231
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ENV NODE_ENV=production
2626
# We will only use the node_modules folder from this step
2727

2828
COPY package.json package-lock.json ./
29+
COPY patches/ ./patches/
2930
RUN apk add --no-cache --virtual .gyp python3 make g++ &&\
3031
npm ci --only=production
3132

@@ -50,11 +51,12 @@ COPY --from=builder /usr/src/app/dist/ ./dist/
5051
COPY --from=builder /usr/src/app/dist-scripts/ ./dist-scripts/
5152
COPY --from=deps /usr/src/app/node_modules/ ./node_modules/
5253
COPY --from=deps /usr/src/app/package.json ./
54+
COPY lavamoat/ ./lavamoat/
5355
COPY Makefile ./
5456

5557
# Install the process supervisor
5658
RUN apk add --no-cache dumb-init make &&\
5759
rm -rf /tmp/* /var/cache/apk/*
5860

5961
EXPOSE 8000
60-
ENTRYPOINT ["dumb-init", "node", "dist/index.js"]
62+
ENTRYPOINT ["dumb-init", "./node_modules/.bin/lavamoat", "dist/index.js"]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) Hathor Labs and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { getWalletConfigFromSeed } from '../../src/helpers/wallet.helper';
9+
import { WalletStartError } from '../../src/errors';
10+
import * as loggerModule from '../../src/logger';
11+
12+
const STUB_SEED = 'upon tennis increase embark dismiss diamond monitor face magnet jungle scout salute rural master shoulder cry juice jeans radar present close meat antenna mind';
13+
14+
describe('getWalletConfigFromSeed', () => {
15+
it('should return a wallet config for a valid seed', () => {
16+
const config = getWalletConfigFromSeed({ seed: STUB_SEED });
17+
expect(config).toHaveProperty('seed');
18+
expect(config).toHaveProperty('password');
19+
expect(config).toHaveProperty('pinCode');
20+
});
21+
22+
it('should throw WalletStartError for an invalid seed', () => {
23+
expect(() => getWalletConfigFromSeed({ seed: 'invalid seed words' })).toThrow(WalletStartError);
24+
});
25+
26+
it('should throw WalletStartError when passphrase is provided but not allowed', () => {
27+
const mockError = jest.fn();
28+
jest.spyOn(loggerModule, 'buildAppLogger').mockReturnValue({ error: mockError });
29+
30+
expect(() => getWalletConfigFromSeed({
31+
seed: STUB_SEED,
32+
passphrase: 'my-passphrase',
33+
allowPassphrase: false,
34+
})).toThrow(WalletStartError);
35+
36+
expect(mockError).toHaveBeenCalledWith(
37+
expect.stringContaining('passphrase is not allowed')
38+
);
39+
});
40+
41+
it('should set passphrase when allowed', () => {
42+
const config = getWalletConfigFromSeed({
43+
seed: STUB_SEED,
44+
passphrase: 'my-passphrase',
45+
allowPassphrase: true,
46+
});
47+
expect(config.passphrase).toBe('my-passphrase');
48+
});
49+
});

__tests__/plugins/child.test.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { bigIntUtils } from '@hathor/wallet-lib';
2-
import { handleMessage } from '../../src/plugins/child';
2+
import { handleMessage, loadPlugins } from '../../src/plugins/child';
33
import { notificationBus, EVENTBUS_EVENT_NAME } from '../../src/services/notification.service';
4+
import * as loggerModule from '../../src/logger';
45

56
jest.mock('../../src/services/notification.service', () => ({
67
notificationBus: {
@@ -9,6 +10,34 @@ jest.mock('../../src/services/notification.service', () => ({
910
EVENTBUS_EVENT_NAME: 'eventbus_event',
1011
}));
1112

13+
describe('loadPlugins', () => {
14+
it('should warn and skip unknown plugins', async () => {
15+
const mockWarn = jest.fn();
16+
jest.spyOn(loggerModule, 'buildAppLogger').mockReturnValue({ warn: mockWarn });
17+
18+
const plugins = await loadPlugins(['nonexistent_plugin'], {});
19+
20+
expect(mockWarn).toHaveBeenCalledWith('Unable to find plugin nonexistent_plugin, skipping.');
21+
expect(plugins).toEqual([]);
22+
});
23+
24+
it('should load known hathor plugins', async () => {
25+
const plugins = await loadPlugins(['debug'], {});
26+
27+
expect(plugins).toHaveLength(1);
28+
expect(plugins[0]).toHaveProperty('eventHandler');
29+
});
30+
31+
it('should load custom plugins from config', async () => {
32+
const plugins = await loadPlugins(['my_plugin'], {
33+
my_plugin: { name: 'debug', file: 'hathor_debug.js' },
34+
});
35+
36+
expect(plugins).toHaveLength(1);
37+
expect(plugins[0]).toHaveProperty('eventHandler');
38+
});
39+
});
40+
1241
describe('handleMessage', () => {
1342
afterEach(() => {
1443
jest.clearAllMocks();

__tests__/plugins/debug_plugin.test.js

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import { bigIntUtils } from '@hathor/wallet-lib';
98
import { eventHandler, getSettings } from '../../src/plugins/hathor_debug';
9+
import * as logger from '../../src/logger';
1010

1111
test('settings', () => {
1212
const oldArgs = process.argv;
@@ -27,10 +27,11 @@ test('settings', () => {
2727

2828
test('event handler', () => {
2929
const oldArgs = process.argv;
30-
const logSpy = jest.spyOn(console, 'log');
31-
const smallMsg = { type: 'small', walletId: 'default', foo: 'bar', bigInt: BigInt(Number.MAX_SAFE_INTEGER) + 1n };
30+
const mockLoggerInfo = jest.fn();
31+
const buildAppLoggerSpy = jest.spyOn(logger, 'buildAppLogger').mockReturnValue({ info: mockLoggerInfo });
32+
const smallMsg = { type: 'small', walletId: 'default', foo: 'bar' };
3233
const bigMsg = { type: 'big', walletId: 'default' };
33-
const bigCompleteMsg = { ...bigMsg, message: '', bigInt: BigInt(Number.MAX_SAFE_INTEGER) + 1n };
34+
const bigCompleteMsg = { ...bigMsg, message: '' };
3435
for (let i = 0; i < 200; i++) {
3536
// 200 * 'aaaaa'(length of 5) -> lenght of 1000
3637
bigCompleteMsg.message += 'aaaaa';
@@ -46,30 +47,30 @@ test('event handler', () => {
4647
'--plugin_debug_long', 'off',
4748
];
4849
getSettings(); // set debugLong value
49-
logSpy.mockReset();
50+
mockLoggerInfo.mockReset();
5051
// small message: always log
5152
eventHandler(smallMsg);
52-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(smallMsg)));
53-
logSpy.mockReset();
53+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(smallMsg)));
54+
mockLoggerInfo.mockReset();
5455
// big message: should not log
5556
eventHandler(bigCompleteMsg);
56-
expect(logSpy).not.toHaveBeenCalled();
57+
expect(mockLoggerInfo).not.toHaveBeenCalled();
5758

5859
// debugLong: all
5960
process.argv = [
6061
'node', 'a_script_file.js', // not used but a value is required
6162
'--plugin_debug_long', 'all',
6263
];
6364
getSettings(); // set debugLong value
64-
logSpy.mockReset();
65+
mockLoggerInfo.mockReset();
6566
// small message: always log
6667
eventHandler(smallMsg);
67-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(smallMsg)));
68-
logSpy.mockReset();
68+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(smallMsg)));
69+
mockLoggerInfo.mockReset();
6970
// big message: should log the entire message
7071
eventHandler(bigCompleteMsg);
71-
expect(logSpy).toHaveBeenCalledWith(
72-
toDebugMessage(bigIntUtils.JSONBigInt.stringify(bigCompleteMsg))
72+
expect(mockLoggerInfo).toHaveBeenCalledWith(
73+
toDebugMessage(JSON.stringify(bigCompleteMsg))
7374
);
7475

7576
// debugLong: unexpected value
@@ -78,30 +79,30 @@ test('event handler', () => {
7879
'--plugin_debug_long', 'any-unexpected-value',
7980
];
8081
getSettings(); // set debugLong value
81-
logSpy.mockReset();
82+
mockLoggerInfo.mockReset();
8283
// small message: always log
8384
eventHandler(smallMsg);
84-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(smallMsg)));
85-
logSpy.mockReset();
85+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(smallMsg)));
86+
mockLoggerInfo.mockReset();
8687
// big message: should log partially
8788
eventHandler(bigCompleteMsg);
88-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(bigMsg)));
89+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(bigMsg)));
8990

9091
// debugLong: default (should be the same as unexpected)
9192
process.argv = [
9293
'node', 'a_script_file.js', // not used but a value is required
9394
];
9495
getSettings(); // set debugLong value
95-
logSpy.mockReset();
96+
mockLoggerInfo.mockReset();
9697
// small message: always log
9798
eventHandler(smallMsg);
98-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(smallMsg)));
99-
logSpy.mockReset();
99+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(smallMsg)));
100+
mockLoggerInfo.mockReset();
100101
// big message: should log partially
101102
eventHandler(bigCompleteMsg);
102-
expect(logSpy).toHaveBeenCalledWith(toDebugMessage(bigIntUtils.JSONBigInt.stringify(bigMsg)));
103+
expect(mockLoggerInfo).toHaveBeenCalledWith(toDebugMessage(JSON.stringify(bigMsg)));
103104

104105
// Restore original argv state
105106
process.argv = oldArgs;
106-
logSpy.mockRestore();
107+
buildAppLoggerSpy.mockRestore();
107108
});

__tests__/plugins/sqs_plugin.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,22 @@ test('event handler', () => {
5656
MessageBody: bigIntUtils.JSONBigInt.stringify(data),
5757
}, expect.anything());
5858
});
59+
60+
test('event handler logs error on SQS failure', () => {
61+
const mockError = jest.fn();
62+
// eslint-disable-next-line global-require
63+
const loggerModule = require('../../src/logger');
64+
jest.spyOn(loggerModule, 'buildAppLogger').mockReturnValue({ error: mockError });
65+
66+
const sqsMock = {
67+
sendMessage: jest.fn((params, cb) => cb(new Error('SQS send failed'))),
68+
};
69+
const mockedSettings = { queueUrl: 'test-queue' };
70+
const evHandler = eventHandlerFactory(sqsMock, mockedSettings);
71+
72+
evHandler({ test: 'event' });
73+
74+
expect(mockError).toHaveBeenCalledWith(
75+
expect.stringContaining('plugin[sqs] error sending to sqs:')
76+
);
77+
});

__tests__/plugins/ws_plugin.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@
77

88
import { bigIntUtils } from '@hathor/wallet-lib';
99
import { getSockets, eventHandler, connectionHandler, getSettings } from '../../src/plugins/hathor_websocket';
10+
import * as loggerModule from '../../src/logger';
11+
12+
beforeEach(() => {
13+
jest.spyOn(loggerModule, 'buildAppLogger').mockReturnValue({
14+
info: jest.fn(),
15+
warn: jest.fn(),
16+
error: jest.fn(),
17+
debug: jest.fn(),
18+
});
19+
});
20+
21+
afterEach(() => {
22+
jest.restoreAllMocks();
23+
});
1024

1125
test('settings', () => {
1226
const oldArgs = process.argv;

lavamoat/node/policy-override.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"resources": {
3+
"depd": {
4+
"globals": {
5+
"Error.prepareStackTrace": "write",
6+
"Error.captureStackTrace": true,
7+
"Error.stackTraceLimit": "write"
8+
}
9+
},
10+
11+
"axios": {
12+
"globals": {
13+
"document": false,
14+
"importScripts": false,
15+
"location.href": false,
16+
"navigator": false,
17+
"WorkerGlobalScope": false,
18+
"XMLHttpRequest": false
19+
}
20+
},
21+
22+
"morgan>debug": {
23+
"globals": {
24+
"chrome": false,
25+
"document": false,
26+
"localStorage": false,
27+
"navigator": false
28+
}
29+
},
30+
31+
"winston": {
32+
"builtin": {
33+
"http": false,
34+
"https": false
35+
},
36+
"globals": {
37+
"process.exit": false
38+
}
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)