Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: streams secret option. #1146

Merged
merged 1 commit into from
Jun 9, 2023
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
6 changes: 6 additions & 0 deletions .changeset/nine-spiders-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'moralis': patch
'@moralisweb3/streams': patch
---

Added new configuration: `streamsSecret` to the `Moralis.start` method. This option allows you to specify a custom secret used by the `Moralis.Streams.verifySignature` function.
3 changes: 3 additions & 0 deletions demos/streams-webhook/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Your Moralis Api key, that can be found in the dashboard. Keep this secret!
MORALIS_API_KEY = ''
MORALIS_STREAMS_SECRET = ''
53 changes: 53 additions & 0 deletions demos/streams-webhook/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const Moralis = require('moralis').default;
const http = require('http');
const dotenv = require('dotenv');

dotenv.config();

const host = 'localhost';
const port = 6001;

Moralis.start({
apiKey: process.env.MORALIS_API_KEY,
streamsSecret: process.env.MORALIS_STREAMS_SECRET,
});

async function handler(req, res) {
const signature = req.headers['x-signature'];

const dataRaw = await readBody(req);
let isValid = null;
if (dataRaw) {
const body = JSON.parse(dataRaw);
try {
isValid = Moralis.Streams.verifySignature({ body, signature });
} catch (e) {
if (e.message.includes('[S0004] Signature is not valid')) {
isValid = false;
} else {
throw e;
}
}
}

console.log(`${req.method} ${req.url}`);
if (dataRaw) {
console.log(dataRaw);
}
console.log(req.headers);
console.log(`isValid = ${isValid}`);
res.end(`isValid = ${isValid}`);
}

function readBody(req) {
return new Promise((resolve) => {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => resolve(body));
});
}

const server = http.createServer(handler);
server.listen(port, host, () => {
console.log(`Listening on http://${host}:${port}/`);
});
17 changes: 17 additions & 0 deletions demos/streams-webhook/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "demo-streams-webhook",
"author": "Moralis",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"ngrok": "ngrok http 6001"
},
"dependencies": {
"dotenv": "^16.0.1",
"moralis": "^2.22.1"
},
"devDependencies": {
"ngrok": "^4.3.3"
}
}
2 changes: 1 addition & 1 deletion packages/apiUtils/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './ApiUtilsConfig';
export * from './MoralisConfig';
export * from './ApiUtilsConfigValues';
3 changes: 2 additions & 1 deletion packages/moralis/src/config/MoralisConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MoralisCoreConfigValues } from '@moralisweb3/common-core';
import { ApiUtilsConfigValues } from '@moralisweb3/api-utils';
import { StreamsConfigValues } from '@moralisweb3/streams';

export type MoralisConfigValues = MoralisCoreConfigValues | ApiUtilsConfigValues;
export type MoralisConfigValues = MoralisCoreConfigValues | ApiUtilsConfigValues | StreamsConfigValues;
5 changes: 3 additions & 2 deletions packages/streams/src/Streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
replayHistoryOperation,
getLogsOperation,
} from '@moralisweb3/common-streams-utils';
import { StreamsConfigSetup } from './config/StreamsApiConfigSetup';

const BASE_URL = 'https://api.moralis-streams.com';

Expand All @@ -43,7 +44,7 @@ export class Streams extends ApiModule {
}

public setup() {
// Nothing
StreamsConfigSetup.register(this.core.config);
}

public start() {
Expand Down Expand Up @@ -73,7 +74,7 @@ export class Streams extends ApiModule {
private readonly _readSettings = this.createFetcher(getSettingsOperation);
public readonly readSettings = () => this._readSettings({});

public readonly verifySignature = (options: VerifySignatureOptions) => makeVerifySignature(this.core)(options);
public readonly verifySignature = (options: VerifySignatureOptions) => makeVerifySignature(this.core.config)(options);

public readonly parsedLogs = <Event>(webhookData: IWebhook) => parseLog<Event>(webhookData);

Expand Down
8 changes: 8 additions & 0 deletions packages/streams/src/config/StreamsApiConfigSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Config } from '@moralisweb3/common-core';
import { StreamsConfig } from './StreamsConfig';

export class StreamsConfigSetup {
public static register(config: Config) {
config.registerKey(StreamsConfig.streamsSecret);
}
}
8 changes: 8 additions & 0 deletions packages/streams/src/config/StreamsConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ConfigKey } from '@moralisweb3/common-core';

export const StreamsConfig = {
streamsSecret: {
name: 'streamsSecret',
defaultValue: null,
} as ConfigKey<string | null>,
};
3 changes: 3 additions & 0 deletions packages/streams/src/config/StreamsConfigValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface StreamsConfigValues {
streamsSecret?: string;
}
1 change: 1 addition & 0 deletions packages/streams/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './StreamsConfigValues';
1 change: 1 addition & 0 deletions packages/streams/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export { Types };
export * from './Streams';

// Export SDK types
export * from './config';
export * from './methods/types';
export * from './mapping';
70 changes: 70 additions & 0 deletions packages/streams/src/methods/verifySignature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Config } from '@moralisweb3/common-core';
import { IWebhook } from '@moralisweb3/streams-typings';
import { makeVerifySignature } from './verifySignature';
import { ApiUtilsConfig } from '@moralisweb3/api-utils';
import { StreamsConfig } from '../config/StreamsConfig';

describe('verifySignature', () => {
const body: IWebhook = {
abi: [],
block: { number: '', hash: '', timestamp: '' },
txs: [],
txsInternal: [],
logs: [],
chainId: '',
confirmed: true,
retries: 0,
tag: '',
streamId: '',
erc20Approvals: [],
erc20Transfers: [],
nftTokenApprovals: [],
nftApprovals: { ERC721: [], ERC1155: [] },
nftTransfers: [],
nativeBalances: [],
};
const apiKeyOrSecret1 = 'k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9k9';
const apiKeyOrSecret2 = 'x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3x3';
const signature = '0x3c4b2e6b07e386f97198eb2dc639f7901f00a5d3ec1b8c8effe4838920d71829'; // for apiKeyOrSecret1
let config: Config;

beforeEach(() => {
config = new Config();
config.registerKey(ApiUtilsConfig.apiKey);
config.registerKey(StreamsConfig.streamsSecret);
});

it('verifies correctly if api key is present', () => {
config.set('apiKey', apiKeyOrSecret1);

expect(
makeVerifySignature(config)({
body,
signature: signature,
}),
).toBe(true);
});

it('verifies correctly if streams secret is present', () => {
config.set('apiKey', apiKeyOrSecret2);
config.set('streamsSecret', apiKeyOrSecret1);

expect(
makeVerifySignature(config)({
body,
signature: signature,
}),
).toBe(true);
});

it('throw error if signature is not valid', () => {
config.set('apiKey', apiKeyOrSecret1);

expect(() =>
makeVerifySignature(config)({
body,
signature: 'wrong_signature',
}),
).toThrowError('[S0004] Signature is not valid');
});
});
19 changes: 12 additions & 7 deletions packages/streams/src/methods/verifySignature.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { ApiUtilsConfig } from '@moralisweb3/api-utils';
import { Core, MoralisStreamError, StreamErrorCode } from '@moralisweb3/common-core';
import { Config, MoralisStreamError, StreamErrorCode } from '@moralisweb3/common-core';
import { IWebhook } from '@moralisweb3/streams-typings';
import { sha3 } from '../utils/sha3';
import { StreamsConfig } from '../config/StreamsConfig';

export interface VerifySignatureOptions {
body: IWebhook;
signature: string;
}

export const makeVerifySignature =
(core: Core) =>
(config: Config) =>
({ body, signature }: VerifySignatureOptions): boolean => {
const apiKey = core.config.get(ApiUtilsConfig.apiKey);
if (!apiKey) {
let secret = config.get(StreamsConfig.streamsSecret);
if (!secret) {
secret = config.get(ApiUtilsConfig.apiKey);
}
if (!secret) {
throw new MoralisStreamError({
code: StreamErrorCode.GENERIC_STREAM_ERROR,
message: 'unable to verify signature without an api key',
message: 'Unable to verify signature without an api key or streams secret',
});
}
const generatedSignature = sha3(JSON.stringify(body) + apiKey);

const generatedSignature = sha3(JSON.stringify(body) + secret);
if (signature !== generatedSignature) {
throw new MoralisStreamError({
code: StreamErrorCode.INVALID_SIGNATURE,
message: 'signature is not valid',
message: 'Signature is not valid',
});
}
return true;
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16086,6 +16086,16 @@ __metadata:
languageName: unknown
linkType: soft

"demo-streams-webhook@workspace:demos/streams-webhook":
version: 0.0.0-use.local
resolution: "demo-streams-webhook@workspace:demos/streams-webhook"
dependencies:
dotenv: ^16.0.1
moralis: ^2.22.1
ngrok: ^4.3.3
languageName: unknown
linkType: soft

"demo-supabase-auth@workspace:demos/supabase-auth":
version: 0.0.0-use.local
resolution: "demo-supabase-auth@workspace:demos/supabase-auth"
Expand Down