diff --git a/creator-node/src/apiSigning.js b/creator-node/src/apiSigning.ts similarity index 82% rename from creator-node/src/apiSigning.js rename to creator-node/src/apiSigning.ts index 60107cfc23e..531d5c4e617 100644 --- a/creator-node/src/apiSigning.js +++ b/creator-node/src/apiSigning.ts @@ -1,7 +1,10 @@ +import { getContentNodeInfoFromSpId } from './services/ContentNodeInfoManager' + const { libs } = require('@audius/sdk') const LibsUtils = libs.Utils const Web3 = require('web3') const web3 = new Web3() +const { logger: genericLogger } = require('./logging') /** * Max age of signature in milliseconds @@ -9,7 +12,7 @@ const web3 = new Web3() */ const MAX_SIGNATURE_AGE_MS = 300000 -const generateSignature = (data, privateKey) => { +export const generateSignature = (data: any, privateKey: string) => { // JSON stringify automatically removes white space given 1 param const toSignStr = JSON.stringify(sortKeys(data)) const toSignHash = web3.utils.keccak256(toSignStr) @@ -23,7 +26,10 @@ const generateSignature = (data, privateKey) => { * @param {object} data * @param {string} privateKey */ -const generateTimestampAndSignature = (data, privateKey) => { +export const generateTimestampAndSignature = ( + data: any, + privateKey: string +) => { const timestamp = new Date().toISOString() const toSignObj = { ...data, timestamp } const signature = generateSignature(toSignObj, privateKey) @@ -33,7 +39,7 @@ const generateTimestampAndSignature = (data, privateKey) => { // Keeps track of a cached listen signature // Two field object: { timestamp, signature } -let cachedListenSignature = null +let cachedListenSignature: { timestamp: string; signature: any } | null = null /** * Generates a signature for `data` if only the previous signature @@ -41,7 +47,7 @@ let cachedListenSignature = null * @param {string} privateKey * @returns {object} {signature, timestamp} signature data */ -const generateListenTimestampAndSignature = (privateKey) => { +export const generateListenTimestampAndSignature = (privateKey: any) => { if (cachedListenSignature) { const signatureTimestamp = cachedListenSignature.timestamp if (signatureHasExpired(signatureTimestamp)) { @@ -67,7 +73,7 @@ const generateListenTimestampAndSignature = (privateKey) => { * @param {*} signature signature generated with signed data */ // eslint-disable-next-line no-unused-vars -const recoverWallet = (data, signature) => { +export const recoverWallet = (data: any, signature: any) => { const structuredData = JSON.stringify(sortKeys(data)) const hashedData = web3.utils.keccak256(structuredData) const recoveredWallet = web3.eth.accounts.recover(hashedData, signature) @@ -79,13 +85,13 @@ const recoverWallet = (data, signature) => { * Returns boolean indicating if provided timestamp is older than maxTTL * @param {string | number} signatureTimestamp unix timestamp string when signature was generated */ -const signatureHasExpired = ( - signatureTimestamp, +export const signatureHasExpired = ( + signatureTimestamp: string | number, maxTTL = MAX_SIGNATURE_AGE_MS ) => { const signatureTimestampDate = new Date(signatureTimestamp) const currentTimestampDate = new Date() - const signatureAge = currentTimestampDate - signatureTimestampDate + const signatureAge = +currentTimestampDate - +signatureTimestampDate return signatureAge >= maxTTL } @@ -93,7 +99,7 @@ const signatureHasExpired = ( /** * Recursively sorts keys of object or object array */ -const sortKeys = (x) => { +export const sortKeys = (x: any): any => { if (typeof x !== 'object' || !x) { return x } @@ -105,7 +111,10 @@ const sortKeys = (x) => { .reduce((o, k) => ({ ...o, [k]: sortKeys(x[k]) }), {}) } -const generateTimestampAndSignatureForSPVerification = (spID, privateKey) => { +export const generateTimestampAndSignatureForSPVerification = ( + spID: number, + privateKey: string +) => { return generateTimestampAndSignature({ spID }, privateKey) } @@ -118,11 +127,14 @@ const generateTimestampAndSignatureForSPVerification = (spID, privateKey) => { * @param {string} data.reqTimestamp the timestamp from the request body * @param {string} data.reqSignature the signature from the request body */ -const verifyRequesterIsValidSP = async ({ - audiusLibs, +export const verifyRequesterIsValidSP = async ({ spID, reqTimestamp, reqSignature +}: { + spID: number | string + reqTimestamp: string + reqSignature: string }) => { if (!reqTimestamp || !reqSignature) { throw new Error( @@ -132,19 +144,14 @@ const verifyRequesterIsValidSP = async ({ spID = validateSPId(spID) - const spRecordFromSPFactory = - await audiusLibs.ethContracts.ServiceProviderFactoryClient.getServiceEndpointInfo( - 'content-node', - spID - ) - let { owner: ownerWalletFromSPFactory, delegateOwnerWallet: delegateOwnerWalletFromSPFactory, endpoint: nodeEndpointFromSPFactory - } = spRecordFromSPFactory + } = (await getContentNodeInfoFromSpId(spID, genericLogger)) || {} + delegateOwnerWalletFromSPFactory = - delegateOwnerWalletFromSPFactory.toLowerCase() + delegateOwnerWalletFromSPFactory?.toLowerCase() if (!ownerWalletFromSPFactory || !delegateOwnerWalletFromSPFactory) { throw new Error( @@ -192,18 +199,18 @@ const verifyRequesterIsValidSP = async ({ * @param {string} spID * @returns a parsed spID */ -function validateSPId(spID) { +export function validateSPId(spID: string | number): number { if (!spID) { throw new Error('Must provide all required query parameters: spID') } - spID = parseInt(spID) + const spIDNum = parseInt(spID + '', 10) - if (isNaN(spID) || spID < 0) { + if (isNaN(spIDNum) || spIDNum < 0) { throw new Error(`Provided spID is not a valid id. spID=${spID}`) } - return spID + return spIDNum } module.exports = { diff --git a/creator-node/src/components/replicaSet/URSMRegistrationComponentService.js b/creator-node/src/components/replicaSet/URSMRegistrationComponentService.js index 06536c9e4fc..17efe0bac32 100644 --- a/creator-node/src/components/replicaSet/URSMRegistrationComponentService.js +++ b/creator-node/src/components/replicaSet/URSMRegistrationComponentService.js @@ -53,7 +53,6 @@ const respondToURSMRequestForSignature = async ( nodeEndpointFromSPFactory, spID } = await verifyRequesterIsValidSP({ - audiusLibs, spID, reqTimestamp, reqSignature diff --git a/creator-node/src/middlewares.js b/creator-node/src/middlewares.js index f5798a7e230..ab5c4b8ea6a 100644 --- a/creator-node/src/middlewares.js +++ b/creator-node/src/middlewares.js @@ -465,7 +465,6 @@ async function ensureValidSPMiddleware(req, res, next) { } await verifyRequesterIsValidSP({ - audiusLibs: req.app.get('audiusLibs'), spID, reqTimestamp: timestamp, reqSignature: signature diff --git a/creator-node/src/reqLimiter.js b/creator-node/src/reqLimiter.js index b6ee18a3982..da05e65c3c0 100644 --- a/creator-node/src/reqLimiter.js +++ b/creator-node/src/reqLimiter.js @@ -4,7 +4,7 @@ const config = require('./config.js') const { logger } = require('./logging') const RedisStore = require('rate-limit-redis') const client = require('./redis.js') -const { verifyRequesterIsValidSP } = require('./apiSigning.js') +const { verifyRequesterIsValidSP } = require('./apiSigning') let endpointRateLimits = {} try { @@ -64,7 +64,6 @@ const userReqLimiter = rateLimit({ ) { try { await verifyRequesterIsValidSP({ - audiusLibs: libs, spID, reqTimestamp: timestamp, reqSignature: signature @@ -159,7 +158,6 @@ const batchCidsExistReqLimiter = rateLimit({ ) { try { await verifyRequesterIsValidSP({ - audiusLibs: libs, spID, reqTimestamp: timestamp, reqSignature: signature diff --git a/creator-node/test/apiSigning.test.js b/creator-node/test/apiSigning.test.js index 456e71551a2..4c7dd3baf22 100644 --- a/creator-node/test/apiSigning.test.js +++ b/creator-node/test/apiSigning.test.js @@ -1,19 +1,45 @@ const assert = require('assert') - -const { getLibsMock } = require('./lib/libsMock') - -const apiSigning = require('../src/apiSigning') +const proxyquire = require('proxyquire') describe('test apiSigning', function () { - let libsMock - beforeEach(async () => { - libsMock = getLibsMock() + const getContentNodeInfoFromSpId = async (spID, _genericLogger) => { + switch (spID) { + case 2: + return { + endpoint: 'http://mock-cn2.audius.co', + owner: '0xBdb47ebFF0eAe1A7647D029450C05666e22864Fb', + delegateOwnerWallet: '0xBdb47ebFF0eAe1A7647D029450C05666e22864Fb' + } + case 3: + return { + endpoint: 'http://mock-cn3.audius.co', + owner: '0x1Fffaa556B42f4506cdb01D7BbE6a9bDbb0E5f36', + delegateOwnerWallet: '0x1Fffaa556B42f4506cdb01D7BbE6a9bDbb0E5f36' + } + + case 1: + return { + endpoint: 'http://mock-cn1.audius.co', + owner: '0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25', + delegateOwnerWallet: '0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25' + } + default: + return { + owner: '0x0000000000000000000000000000000000000000', + endpoint: '', + delegateOwnerWallet: '0x0000000000000000000000000000000000000000' + } + } + } + const apiSigning = proxyquire('../src/apiSigning.ts', { + './services/ContentNodeInfoManager': { + getContentNodeInfoFromSpId + } }) it('given incomplete timestamp, signature, throw error', async function () { try { await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: 1, reqTimestamp: undefined, reqSignature: undefined @@ -31,7 +57,6 @@ describe('test apiSigning', function () { it('given bad spID, throw error', async function () { try { await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: undefined, reqTimestamp: '022-03-25T15:02:35.573Z', reqSignature: @@ -48,7 +73,6 @@ describe('test apiSigning', function () { try { await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: -1, reqTimestamp: '022-03-25T15:02:35.573Z', reqSignature: @@ -63,7 +87,6 @@ describe('test apiSigning', function () { it('if the wallets are zero addressed, throw error', async function () { try { await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: 100, reqTimestamp: '022-03-25T15:02:35.573Z', reqSignature: @@ -80,7 +103,6 @@ describe('test apiSigning', function () { it('if the api signing is mismatched, throw error', async function () { try { await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: 1, reqTimestamp: '022-03-25T15:02:35.573Z', reqSignature: @@ -101,7 +123,6 @@ describe('test apiSigning', function () { it('if inputs are valid, return proper return values', async function () { try { const resp = await apiSigning.verifyRequesterIsValidSP({ - audiusLibs: libsMock, spID: 1, reqTimestamp: '2022-03-25T15:22:54.866Z', reqSignature: diff --git a/creator-node/test/lib/app.js b/creator-node/test/lib/app.js index b2027e8faac..f61f1a0b52d 100644 --- a/creator-node/test/lib/app.js +++ b/creator-node/test/lib/app.js @@ -16,7 +16,8 @@ export async function getApp( libsClient, blacklistManager = BlacklistManager, setMockFn = null, - spId = 1 + spId = 1, + mockContentNodeInfoManager = false ) { // we need to clear the cache that commonjs require builds, otherwise it uses old values for imports etc // eg if you set a new env var, it doesn't propogate well unless you clear the cache for the config file as well @@ -46,6 +47,44 @@ export async function getApp( prometheusRegistry } + // Update import to make ensureValidSPMiddleware pass + if (mockContentNodeInfoManager) { + const getContentNodeInfoFromSpId = async (spID, _genericLogger) => { + switch (spID) { + case 2: + return { + endpoint: 'http://mock-cn2.audius.co', + owner: '0xBdb47ebFF0eAe1A7647D029450C05666e22864Fb', + delegateOwnerWallet: '0xBdb47ebFF0eAe1A7647D029450C05666e22864Fb' + } + case 3: + return { + endpoint: 'http://mock-cn3.audius.co', + owner: '0x1Fffaa556B42f4506cdb01D7BbE6a9bDbb0E5f36', + delegateOwnerWallet: '0x1Fffaa556B42f4506cdb01D7BbE6a9bDbb0E5f36' + } + + case 1: + return { + endpoint: 'http://mock-cn1.audius.co', + owner: '0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25', + delegateOwnerWallet: '0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25' + } + default: + return { + owner: '0x0000000000000000000000000000000000000000', + endpoint: '', + delegateOwnerWallet: '0x0000000000000000000000000000000000000000' + } + } + } + require.cache[ + require.resolve('../../src/services/ContentNodeInfoManager') + ] = { + exports: { getContentNodeInfoFromSpId } + } + } + // Update the import to be the mocked ServiceRegistry instance require.cache[require.resolve('../../src/serviceRegistry')] = { exports: { serviceRegistry: mockServiceRegistry } diff --git a/creator-node/test/transcodeAndSegment.test.js b/creator-node/test/transcodeAndSegment.test.js index a8f22445cde..35b5b9a2554 100644 --- a/creator-node/test/transcodeAndSegment.test.js +++ b/creator-node/test/transcodeAndSegment.test.js @@ -19,7 +19,8 @@ describe('test transcode_and_segment route', function () { /* libsMock */ getLibsMock(), BlacklistManager, /* setMockFn */ null, - 1 /* spId */ + 1 /* spId */, + true /* mockContentNodeInfoManager */ ) app = appInfo.app