From f505bd3ae61b20960d2068ad6e202fc265435642 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 15 Oct 2024 15:41:20 +0200 Subject: [PATCH 1/2] feat: assert event.source equals popup --- src/relying-party.spec.ts | 115 ++++++++++++++++++++++++++++++++++---- src/relying-party.ts | 9 ++- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/relying-party.spec.ts b/src/relying-party.spec.ts index cac83458..d25a1ee3 100644 --- a/src/relying-party.spec.ts +++ b/src/relying-party.spec.ts @@ -471,7 +471,7 @@ describe('Relying Party', () => { describe('Request success', () => { const requestId = crypto.randomUUID(); - const messageEventSupportedStandards = new MessageEvent('message', { + const messagePayload = { origin: mockParameters.url, data: { jsonrpc: JSON_RPC_VERSION_2, @@ -480,7 +480,9 @@ describe('Relying Party', () => { supportedStandards } } - }); + }; + + const messageEventSupportedStandards = new MessageEvent('message', messagePayload); it('should call the signer with postMessage', async () => { const spy = vi.spyOn(relyingPartyHandlers, 'requestSupportedStandards'); @@ -513,6 +515,23 @@ describe('Relying Party', () => { expect(result).toEqual(supportedStandards); }); + + it('should throw an error if the message source is not the opened popup window', async () => { + const mockHackerWindow = {} as Window; + + const messageEventWithDifferentSource = new MessageEvent('message', { + ...messagePayload, + source: mockHackerWindow + }); + + const promise = relyingParty.supportedStandards({options: {requestId}}); + + window.dispatchEvent(messageEventWithDifferentSource); + + await expect(async () => await promise).rejects.toThrow( + 'The response is not originating from the window that was opened.' + ); + }); }); }); @@ -686,7 +705,7 @@ describe('Relying Party', () => { describe('Request success', () => { const requestId = crypto.randomUUID(); - const messageEventScopes = new MessageEvent('message', { + const messagePayload = { origin: mockParameters.url, data: { jsonrpc: JSON_RPC_VERSION_2, @@ -695,7 +714,9 @@ describe('Relying Party', () => { scopes } } - }); + }; + + const messageEventScopes = new MessageEvent('message', messagePayload); it('should call the signer with postMessage', async () => { const spy = vi.spyOn(relyingPartyHandlers, 'permissions'); @@ -728,6 +749,23 @@ describe('Relying Party', () => { expect(result).toEqual(scopes); }); + + it('should throw an error if the message source is not the opened popup window', async () => { + const mockHackerWindow = {} as Window; + + const messageEventWithDifferentSource = new MessageEvent('message', { + ...messagePayload, + source: mockHackerWindow + }); + + const promise = relyingParty.permissions({options: {requestId}}); + + window.dispatchEvent(messageEventWithDifferentSource); + + await expect(async () => await promise).rejects.toThrow( + 'The response is not originating from the window that was opened.' + ); + }); }); }); @@ -882,7 +920,7 @@ describe('Relying Party', () => { describe('Request success', () => { const requestId = crypto.randomUUID(); - const messageEventScopes = new MessageEvent('message', { + const messagePayload = { origin: mockParameters.url, data: { jsonrpc: JSON_RPC_VERSION_2, @@ -891,7 +929,9 @@ describe('Relying Party', () => { scopes } } - }); + }; + + const messageEventScopes = new MessageEvent('message', messagePayload); it('should call the signer with postMessage and default scopes', async () => { const spy = vi.spyOn(relyingPartyHandlers, 'requestPermissions'); @@ -953,6 +993,23 @@ describe('Relying Party', () => { expect(result).toEqual(scopes); }); + + it('should throw an error if the message source is not the opened popup window', async () => { + const mockHackerWindow = {} as Window; + + const messageEventWithDifferentSource = new MessageEvent('message', { + ...messagePayload, + source: mockHackerWindow + }); + + const promise = relyingParty.requestPermissions({options: {requestId}}); + + window.dispatchEvent(messageEventWithDifferentSource); + + await expect(async () => await promise).rejects.toThrow( + 'The response is not originating from the window that was opened.' + ); + }); }); }); }); @@ -1121,7 +1178,7 @@ describe('Relying Party', () => { describe('Request success', () => { const requestId = crypto.randomUUID(); - const messageEventSupportedStandards = new MessageEvent('message', { + const messagePayload = { origin: mockParameters.url, data: { jsonrpc: JSON_RPC_VERSION_2, @@ -1130,7 +1187,9 @@ describe('Relying Party', () => { accounts: mockAccounts } } - }); + }; + + const messageEventSupportedStandards = new MessageEvent('message', messagePayload); it('should call the signer with postMessage', async () => { const spy = vi.spyOn(relyingPartyHandlers, 'requestAccounts'); @@ -1163,6 +1222,23 @@ describe('Relying Party', () => { expect(result).toEqual(mockAccounts); }); + + it('should throw an error if the message source is not the opened popup window', async () => { + const mockHackerWindow = {} as Window; + + const messageEventWithDifferentSource = new MessageEvent('message', { + ...messagePayload, + source: mockHackerWindow + }); + + const promise = relyingParty.accounts({options: {requestId}}); + + window.dispatchEvent(messageEventWithDifferentSource); + + await expect(async () => await promise).rejects.toThrow( + 'The response is not originating from the window that was opened.' + ); + }); }); }); @@ -1334,14 +1410,16 @@ describe('Relying Party', () => { describe('Request success', () => { const requestId = crypto.randomUUID(); - const messageEventScopes = new MessageEvent('message', { + const messagePayload = { origin: mockParameters.url, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId, result } - }); + }; + + const messageEventScopes = new MessageEvent('message', messagePayload); let spyAssertCallResponse: MockInstance; @@ -1398,6 +1476,23 @@ describe('Relying Party', () => { params: mockCallCanisterParams }); }); + + it('should throw an error if the message source is not the opened popup window', async () => { + const mockHackerWindow = {} as Window; + + const messageEventWithDifferentSource = new MessageEvent('message', { + ...messagePayload, + source: mockHackerWindow + }); + + const promise = relyingParty.call({options: {requestId}, params: mockCallCanisterParams}); + + window.dispatchEvent(messageEventWithDifferentSource); + + await expect(async () => await promise).rejects.toThrow( + 'The response is not originating from the window that was opened.' + ); + }); }); }); }); diff --git a/src/relying-party.ts b/src/relying-party.ts index c8b62729..dc5ca9b0 100644 --- a/src/relying-party.ts +++ b/src/relying-party.ts @@ -340,7 +340,7 @@ export class RelyingParty { disconnect(); }, timeoutInMilliseconds); - const onMessage = ({origin, data}: RelyingPartyMessageEvent) => { + const onMessage = ({origin, data, source}: RelyingPartyMessageEvent) => { const {success} = RpcResponseWithResultOrErrorSchema.safeParse(data); if (!success) { @@ -348,6 +348,13 @@ export class RelyingParty { return; } + if (source !== this.#popup) { + reject(new Error('The response is not originating from the window that was opened.')); + + disconnect(); + return; + } + if (notEmptyString(origin) && origin !== this.#origin) { reject( new Error( From ee5a82d2b2fea212daea4a97a70cf84e6b045d07 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 16 Oct 2024 06:19:14 +0200 Subject: [PATCH 2/2] test: add missing source --- src/relying-party.spec.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/relying-party.spec.ts b/src/relying-party.spec.ts index d25a1ee3..cf45c852 100644 --- a/src/relying-party.spec.ts +++ b/src/relying-party.spec.ts @@ -44,6 +44,7 @@ describe('Relying Party', () => { const messageEventReady = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -99,6 +100,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -198,6 +200,7 @@ describe('Relying Party', () => { const messageEventNotRpc = new MessageEvent('message', { data: 'test', + source: window, origin: mockParameters.url }); @@ -384,6 +387,7 @@ describe('Relying Party', () => { const messageEventSupportedStandards = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -405,6 +409,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -430,6 +435,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: testId, @@ -473,6 +479,7 @@ describe('Relying Party', () => { const messagePayload = { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId, @@ -618,6 +625,7 @@ describe('Relying Party', () => { const messageEventScopes = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -639,6 +647,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -664,6 +673,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: testId, @@ -707,6 +717,7 @@ describe('Relying Party', () => { const messagePayload = { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId, @@ -833,6 +844,7 @@ describe('Relying Party', () => { const messageEventScopes = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -854,6 +866,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -879,6 +892,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: testId, @@ -922,6 +936,7 @@ describe('Relying Party', () => { const messagePayload = { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId, @@ -1091,6 +1106,7 @@ describe('Relying Party', () => { const messageEventSupportedStandards = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -1112,6 +1128,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: crypto.randomUUID(), @@ -1137,6 +1154,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: testId, @@ -1180,6 +1198,7 @@ describe('Relying Party', () => { const messagePayload = { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId, @@ -1324,6 +1343,7 @@ describe('Relying Party', () => { const messageEventScopes = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: '123', @@ -1343,6 +1363,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: hackerOrigin, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: '123', @@ -1369,6 +1390,7 @@ describe('Relying Party', () => { const messageEvent = new MessageEvent('message', { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: testId, @@ -1412,6 +1434,7 @@ describe('Relying Party', () => { const messagePayload = { origin: mockParameters.url, + source: window, data: { jsonrpc: JSON_RPC_VERSION_2, id: requestId,