From e5c885e8c59b0e711c31b9b3078a48da86d428e2 Mon Sep 17 00:00:00 2001 From: b4rtaz Date: Fri, 9 Jun 2023 14:14:10 +0200 Subject: [PATCH] feat: streams secret option. --- .changeset/nine-spiders-develop.md | 6 ++ demos/streams-webhook/.env.example | 3 + demos/streams-webhook/index.js | 53 ++++++++++++++ demos/streams-webhook/package.json | 17 +++++ ...ralisConfig.ts => ApiUtilsConfigValues.ts} | 0 packages/apiUtils/src/config/index.ts | 2 +- packages/moralis/src/config/MoralisConfig.ts | 3 +- packages/streams/src/Streams.ts | 5 +- .../src/config/StreamsApiConfigSetup.ts | 8 +++ packages/streams/src/config/StreamsConfig.ts | 8 +++ .../streams/src/config/StreamsConfigValues.ts | 3 + packages/streams/src/config/index.ts | 1 + packages/streams/src/index.ts | 1 + .../src/methods/verifySignature.test.ts | 70 +++++++++++++++++++ .../streams/src/methods/verifySignature.ts | 19 +++-- yarn.lock | 10 +++ 16 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 .changeset/nine-spiders-develop.md create mode 100644 demos/streams-webhook/.env.example create mode 100644 demos/streams-webhook/index.js create mode 100644 demos/streams-webhook/package.json rename packages/apiUtils/src/config/{MoralisConfig.ts => ApiUtilsConfigValues.ts} (100%) create mode 100644 packages/streams/src/config/StreamsApiConfigSetup.ts create mode 100644 packages/streams/src/config/StreamsConfig.ts create mode 100644 packages/streams/src/config/StreamsConfigValues.ts create mode 100644 packages/streams/src/config/index.ts create mode 100644 packages/streams/src/methods/verifySignature.test.ts diff --git a/.changeset/nine-spiders-develop.md b/.changeset/nine-spiders-develop.md new file mode 100644 index 0000000000..3e5854e6be --- /dev/null +++ b/.changeset/nine-spiders-develop.md @@ -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. diff --git a/demos/streams-webhook/.env.example b/demos/streams-webhook/.env.example new file mode 100644 index 0000000000..5659165d22 --- /dev/null +++ b/demos/streams-webhook/.env.example @@ -0,0 +1,3 @@ +# Your Moralis Api key, that can be found in the dashboard. Keep this secret! +MORALIS_API_KEY = '' +MORALIS_STREAMS_SECRET = '' diff --git a/demos/streams-webhook/index.js b/demos/streams-webhook/index.js new file mode 100644 index 0000000000..98f1340b49 --- /dev/null +++ b/demos/streams-webhook/index.js @@ -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}/`); +}); diff --git a/demos/streams-webhook/package.json b/demos/streams-webhook/package.json new file mode 100644 index 0000000000..c7e005d6b6 --- /dev/null +++ b/demos/streams-webhook/package.json @@ -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" + } +} diff --git a/packages/apiUtils/src/config/MoralisConfig.ts b/packages/apiUtils/src/config/ApiUtilsConfigValues.ts similarity index 100% rename from packages/apiUtils/src/config/MoralisConfig.ts rename to packages/apiUtils/src/config/ApiUtilsConfigValues.ts diff --git a/packages/apiUtils/src/config/index.ts b/packages/apiUtils/src/config/index.ts index 8c6253e7e4..2af8000fce 100644 --- a/packages/apiUtils/src/config/index.ts +++ b/packages/apiUtils/src/config/index.ts @@ -1,2 +1,2 @@ export * from './ApiUtilsConfig'; -export * from './MoralisConfig'; +export * from './ApiUtilsConfigValues'; diff --git a/packages/moralis/src/config/MoralisConfig.ts b/packages/moralis/src/config/MoralisConfig.ts index 8409120588..d13fbd0aa8 100644 --- a/packages/moralis/src/config/MoralisConfig.ts +++ b/packages/moralis/src/config/MoralisConfig.ts @@ -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; diff --git a/packages/streams/src/Streams.ts b/packages/streams/src/Streams.ts index 38c6d5f2a0..f81f49ee59 100644 --- a/packages/streams/src/Streams.ts +++ b/packages/streams/src/Streams.ts @@ -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'; @@ -43,7 +44,7 @@ export class Streams extends ApiModule { } public setup() { - // Nothing + StreamsConfigSetup.register(this.core.config); } public start() { @@ -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 = (webhookData: IWebhook) => parseLog(webhookData); diff --git a/packages/streams/src/config/StreamsApiConfigSetup.ts b/packages/streams/src/config/StreamsApiConfigSetup.ts new file mode 100644 index 0000000000..a3b4e03d4e --- /dev/null +++ b/packages/streams/src/config/StreamsApiConfigSetup.ts @@ -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); + } +} diff --git a/packages/streams/src/config/StreamsConfig.ts b/packages/streams/src/config/StreamsConfig.ts new file mode 100644 index 0000000000..bdf8a2ef27 --- /dev/null +++ b/packages/streams/src/config/StreamsConfig.ts @@ -0,0 +1,8 @@ +import { ConfigKey } from '@moralisweb3/common-core'; + +export const StreamsConfig = { + streamsSecret: { + name: 'streamsSecret', + defaultValue: null, + } as ConfigKey, +}; diff --git a/packages/streams/src/config/StreamsConfigValues.ts b/packages/streams/src/config/StreamsConfigValues.ts new file mode 100644 index 0000000000..809a657e15 --- /dev/null +++ b/packages/streams/src/config/StreamsConfigValues.ts @@ -0,0 +1,3 @@ +export interface StreamsConfigValues { + streamsSecret?: string; +} diff --git a/packages/streams/src/config/index.ts b/packages/streams/src/config/index.ts new file mode 100644 index 0000000000..a3bb02f917 --- /dev/null +++ b/packages/streams/src/config/index.ts @@ -0,0 +1 @@ +export * from './StreamsConfigValues'; diff --git a/packages/streams/src/index.ts b/packages/streams/src/index.ts index f4c358176e..93517606a6 100644 --- a/packages/streams/src/index.ts +++ b/packages/streams/src/index.ts @@ -6,5 +6,6 @@ export { Types }; export * from './Streams'; // Export SDK types +export * from './config'; export * from './methods/types'; export * from './mapping'; diff --git a/packages/streams/src/methods/verifySignature.test.ts b/packages/streams/src/methods/verifySignature.test.ts new file mode 100644 index 0000000000..f905463e0c --- /dev/null +++ b/packages/streams/src/methods/verifySignature.test.ts @@ -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'); + }); +}); diff --git a/packages/streams/src/methods/verifySignature.ts b/packages/streams/src/methods/verifySignature.ts index 681fc87bb8..7b24cc49b6 100644 --- a/packages/streams/src/methods/verifySignature.ts +++ b/packages/streams/src/methods/verifySignature.ts @@ -1,7 +1,8 @@ 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; @@ -9,20 +10,24 @@ export interface VerifySignatureOptions { } 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; diff --git a/yarn.lock b/yarn.lock index 3a6792108e..94534cba29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"