diff --git a/app/scripts/controllers/sign.test.ts b/app/scripts/controllers/sign.test.ts index dac749466..078d99291 100644 --- a/app/scripts/controllers/sign.test.ts +++ b/app/scripts/controllers/sign.test.ts @@ -52,6 +52,7 @@ const messageMock = { const coreMessageMock = { ...messageMock, messageParams: messageParamsMock, + securityProviderResponse: securityProviderResponseMock, }; const stateMessageMock = { diff --git a/app/scripts/controllers/sign.ts b/app/scripts/controllers/sign.ts index e042f831c..70c65596d 100644 --- a/app/scripts/controllers/sign.ts +++ b/app/scripts/controllers/sign.ts @@ -21,6 +21,7 @@ import { AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest, + SecurityProviderRequest, } from '@metamask/message-manager/dist/AbstractMessageManager'; import { BaseControllerV2, @@ -63,9 +64,10 @@ export type CoreMessage = AbstractMessage & { messageParams: AbstractMessageParams; }; -export type StateMessage = Required & { +export type StateMessage = Required< + Omit +> & { msgParams: Required; - securityProviderResponse: any; }; export type SignControllerState = { @@ -107,10 +109,7 @@ export type SignControllerOptions = { preferencesController: PreferencesController; getState: () => any; metricsEvent: (payload: any, options?: any) => void; - securityProviderRequest: ( - requestData: any, - methodName: string, - ) => Promise; + securityProviderRequest: SecurityProviderRequest; }; /** @@ -143,11 +142,6 @@ export default class SignController extends BaseControllerV2< private _metricsEvent: (payload: any, options?: any) => void; - private _securityProviderRequest: ( - requestData: any, - methodName: string, - ) => Promise; - /** * Construct a Sign controller. * @@ -178,12 +172,23 @@ export default class SignController extends BaseControllerV2< this._preferencesController = preferencesController; this._getState = getState; this._metricsEvent = metricsEvent; - this._securityProviderRequest = securityProviderRequest; this.hub = new EventEmitter(); - this._messageManager = new MessageManager(); - this._personalMessageManager = new PersonalMessageManager(); - this._typedMessageManager = new TypedMessageManager(); + this._messageManager = new MessageManager( + undefined, + undefined, + securityProviderRequest, + ); + this._personalMessageManager = new PersonalMessageManager( + undefined, + undefined, + securityProviderRequest, + ); + this._typedMessageManager = new TypedMessageManager( + undefined, + undefined, + securityProviderRequest, + ); this._messageManagers = [ this._messageManager, @@ -589,15 +594,7 @@ export default class SignController extends BaseControllerV2< origin: messageParams.origin as string, }, }; - - const messageId = coreMessage.id; - const existingMessage = this._getMessage(messageId); - - const securityProviderResponse = existingMessage - ? existingMessage.securityProviderResponse - : await this._securityProviderRequest(stateMessage, stateMessage.type); - - return { ...stateMessage, securityProviderResponse }; + return stateMessage; } private _normalizeMsgData(data: string) { diff --git a/app/scripts/lib/security-provider-helpers.js b/app/scripts/lib/security-provider-helpers.js deleted file mode 100644 index 986165cd9..000000000 --- a/app/scripts/lib/security-provider-helpers.js +++ /dev/null @@ -1,65 +0,0 @@ -import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; -import { MESSAGE_TYPE } from '../../../shared/constants/app'; - -const fetchWithTimeout = getFetchWithTimeout(); - -export async function securityProviderCheck( - requestData, - methodName, - chainId, - currentLocale, -) { - let dataToValidate; - - if (methodName === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) { - dataToValidate = { - host_name: requestData.msgParams.origin, - rpc_method_name: methodName, - chain_id: chainId, - data: requestData.msgParams.data, - currentLocale, - }; - } else if ( - methodName === MESSAGE_TYPE.ETH_SIGN || - methodName === MESSAGE_TYPE.PERSONAL_SIGN - ) { - dataToValidate = { - host_name: requestData.msgParams.origin, - rpc_method_name: methodName, - chain_id: chainId, - data: { - signer_address: requestData.msgParams.from, - msg_to_sign: requestData.msgParams.data, - }, - currentLocale, - }; - } else { - dataToValidate = { - host_name: requestData.origin, - rpc_method_name: methodName, - chain_id: chainId, - data: { - from_address: requestData?.txParams?.from, - to_address: requestData?.txParams?.to, - gas: requestData?.txParams?.gas, - gasPrice: requestData?.txParams?.gasPrice, - value: requestData?.txParams?.value, - data: requestData?.txParams?.data, - }, - currentLocale, - }; - } - - const response = await fetchWithTimeout( - 'https://proxy.metafi.codefi.network/opensea/security/v1/validate', - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(dataToValidate), - }, - ); - return await response.json(); -} diff --git a/app/scripts/lib/security-provider-helpers.test.ts b/app/scripts/lib/security-provider-helpers.test.ts new file mode 100644 index 000000000..6f3d13ef2 --- /dev/null +++ b/app/scripts/lib/security-provider-helpers.test.ts @@ -0,0 +1,117 @@ +import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { + RequestData, + securityProviderCheck, +} from './security-provider-helpers'; + +describe('securityProviderCheck', () => { + let fetchSpy: jest.SpyInstance; + + beforeEach(() => { + // Spy on the global fetch function + fetchSpy = jest.spyOn(global, 'fetch'); + fetchSpy.mockImplementation(async () => { + return new Response(JSON.stringify('result_mocked'), { status: 200 }); + }); + }); + + const paramsMock = { + origin: 'https://example.com', + data: 'some_data', + from: '0x', + }; + + // Utility function to handle different data properties based on methodName + const getExpectedData = (methodName: string, requestData: RequestData) => { + switch (methodName) { + case MESSAGE_TYPE.ETH_SIGN: + case MESSAGE_TYPE.PERSONAL_SIGN: + return { + signer_address: requestData.msgParams?.from, + msg_to_sign: requestData.msgParams?.data, + }; + case MESSAGE_TYPE.ETH_SIGN_TYPED_DATA: + return requestData.messageParams?.data; + default: + return { + from_address: requestData.txParams?.from, + to_address: requestData.txParams?.to, + gas: requestData.txParams?.gas, + gasPrice: requestData.txParams?.gasPrice, + value: requestData.txParams?.value, + data: requestData.txParams?.data, + }; + } + }; + + test.each([ + [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA], + [MESSAGE_TYPE.ETH_SIGN], + [MESSAGE_TYPE.PERSONAL_SIGN], + ['some_other_method'], + ])( + 'should call fetch with the correct parameters for %s', + async (methodName: string) => { + let requestData: RequestData; + + switch (methodName) { + case MESSAGE_TYPE.ETH_SIGN_TYPED_DATA: + requestData = { + origin: 'https://example.com', + messageParams: paramsMock, + }; + break; + case MESSAGE_TYPE.ETH_SIGN: + case MESSAGE_TYPE.PERSONAL_SIGN: + requestData = { + origin: 'https://example.com', + msgParams: paramsMock, + }; + break; + default: + requestData = { + origin: 'https://example.com', + txParams: { + from: '0x', + to: '0x', + gas: 'some_gas', + gasPrice: 'some_gasPrice', + value: 'some_value', + data: 'some_data', + }, + }; + } + + const result = await securityProviderCheck( + requestData, + methodName, + '1', + 'en', + ); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith( + 'https://proxy.metafi.codefi.network/opensea/security/v1/validate', + expect.objectContaining({ + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + host_name: + methodName === 'some_other_method' + ? requestData.origin + : requestData.msgParams?.origin || + requestData.messageParams?.origin, + rpc_method_name: methodName, + chain_id: '1', + data: getExpectedData(methodName, requestData), + currentLocale: 'en', + }), + }), + ); + expect(result).toEqual('result_mocked'); + }, + ); +}); diff --git a/app/scripts/lib/security-provider-helpers.ts b/app/scripts/lib/security-provider-helpers.ts new file mode 100644 index 000000000..4b4bca0d6 --- /dev/null +++ b/app/scripts/lib/security-provider-helpers.ts @@ -0,0 +1,92 @@ +import { Json } from '@metamask/utils'; +import { MessageParams } from '@metamask/message-manager'; +import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; +import { MESSAGE_TYPE } from '../../../shared/constants/app'; + +const fetchWithTimeout = getFetchWithTimeout(); + +export type TransactionRequestData = { + txParams: Record; + messageParams?: never; + msgParams?: never; +}; + +export type MessageRequestData = + | { + msgParams: MessageParams; + txParams?: never; + messageParams?: never; + } + | { + messageParams: MessageParams; + msgParams?: never; + txParams?: never; + } + | TransactionRequestData; + +export type RequestData = { + origin: string; +} & MessageRequestData; + +export async function securityProviderCheck( + requestData: RequestData, + methodName: string, + chainId: string, + currentLocale: string, +): Promise> { + let dataToValidate; + // Core message managers use messageParams but frontend uses msgParams with lots of references + const params = requestData.msgParams || requestData.messageParams; + + if (methodName === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) { + dataToValidate = { + host_name: params?.origin, + rpc_method_name: methodName, + chain_id: chainId, + data: params?.data, + currentLocale, + }; + } else if ( + methodName === MESSAGE_TYPE.ETH_SIGN || + methodName === MESSAGE_TYPE.PERSONAL_SIGN + ) { + dataToValidate = { + host_name: params?.origin, + rpc_method_name: methodName, + chain_id: chainId, + data: { + signer_address: params?.from, + msg_to_sign: params?.data, + }, + currentLocale, + }; + } else { + dataToValidate = { + host_name: requestData.origin, + rpc_method_name: methodName, + chain_id: chainId, + data: { + from_address: requestData.txParams?.from, + to_address: requestData.txParams?.to, + gas: requestData.txParams?.gas, + gasPrice: requestData.txParams?.gasPrice, + value: requestData.txParams?.value, + data: requestData.txParams?.data, + }, + currentLocale, + }; + } + + const response: Response = await fetchWithTimeout( + 'https://proxy.metafi.codefi.network/opensea/security/v1/validate', + { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dataToValidate), + }, + ); + return await response.json(); +}