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: assert event.source equals popup #291

Merged
merged 4 commits into from
Oct 16, 2024
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
138 changes: 128 additions & 10 deletions src/relying-party.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -198,6 +200,7 @@ describe('Relying Party', () => {

const messageEventNotRpc = new MessageEvent('message', {
data: 'test',
source: window,
origin: mockParameters.url
});

Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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,
Expand Down Expand Up @@ -471,16 +477,19 @@ describe('Relying Party', () => {
describe('Request success', () => {
const requestId = crypto.randomUUID();

const messageEventSupportedStandards = new MessageEvent('message', {
const messagePayload = {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: requestId,
result: {
supportedStandards
}
}
});
};

const messageEventSupportedStandards = new MessageEvent('message', messagePayload);

it('should call the signer with postMessage', async () => {
const spy = vi.spyOn(relyingPartyHandlers, 'requestSupportedStandards');
Expand Down Expand Up @@ -513,6 +522,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.'
);
});
});
});

Expand Down Expand Up @@ -599,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(),
Expand All @@ -620,6 +647,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: hackerOrigin,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: crypto.randomUUID(),
Expand All @@ -645,6 +673,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: testId,
Expand Down Expand Up @@ -686,16 +715,19 @@ describe('Relying Party', () => {
describe('Request success', () => {
const requestId = crypto.randomUUID();

const messageEventScopes = new MessageEvent('message', {
const messagePayload = {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: requestId,
result: {
scopes
}
}
});
};

const messageEventScopes = new MessageEvent('message', messagePayload);

it('should call the signer with postMessage', async () => {
const spy = vi.spyOn(relyingPartyHandlers, 'permissions');
Expand Down Expand Up @@ -728,6 +760,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.'
);
});
});
});

Expand Down Expand Up @@ -795,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(),
Expand All @@ -816,6 +866,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: hackerOrigin,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: crypto.randomUUID(),
Expand All @@ -841,6 +892,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: testId,
Expand Down Expand Up @@ -882,16 +934,19 @@ describe('Relying Party', () => {
describe('Request success', () => {
const requestId = crypto.randomUUID();

const messageEventScopes = new MessageEvent('message', {
const messagePayload = {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: requestId,
result: {
scopes
}
}
});
};

const messageEventScopes = new MessageEvent('message', messagePayload);

it('should call the signer with postMessage and default scopes', async () => {
const spy = vi.spyOn(relyingPartyHandlers, 'requestPermissions');
Expand Down Expand Up @@ -953,6 +1008,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.'
);
});
});
});
});
Expand Down Expand Up @@ -1034,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(),
Expand All @@ -1055,6 +1128,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: hackerOrigin,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: crypto.randomUUID(),
Expand All @@ -1080,6 +1154,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: testId,
Expand Down Expand Up @@ -1121,16 +1196,19 @@ describe('Relying Party', () => {
describe('Request success', () => {
const requestId = crypto.randomUUID();

const messageEventSupportedStandards = new MessageEvent('message', {
const messagePayload = {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: requestId,
result: {
accounts: mockAccounts
}
}
});
};

const messageEventSupportedStandards = new MessageEvent('message', messagePayload);

it('should call the signer with postMessage', async () => {
const spy = vi.spyOn(relyingPartyHandlers, 'requestAccounts');
Expand Down Expand Up @@ -1163,6 +1241,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.'
);
});
});
});

Expand Down Expand Up @@ -1248,6 +1343,7 @@ describe('Relying Party', () => {

const messageEventScopes = new MessageEvent('message', {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: '123',
Expand All @@ -1267,6 +1363,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: hackerOrigin,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: '123',
Expand All @@ -1293,6 +1390,7 @@ describe('Relying Party', () => {

const messageEvent = new MessageEvent('message', {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: testId,
Expand Down Expand Up @@ -1334,14 +1432,17 @@ describe('Relying Party', () => {
describe('Request success', () => {
const requestId = crypto.randomUUID();

const messageEventScopes = new MessageEvent('message', {
const messagePayload = {
origin: mockParameters.url,
source: window,
data: {
jsonrpc: JSON_RPC_VERSION_2,
id: requestId,
result
}
});
};

const messageEventScopes = new MessageEvent('message', messagePayload);

let spyAssertCallResponse: MockInstance;

Expand Down Expand Up @@ -1398,6 +1499,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.'
);
});
});
});
});
Expand Down
9 changes: 8 additions & 1 deletion src/relying-party.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,21 @@ export class RelyingParty {
disconnect();
}, timeoutInMilliseconds);

const onMessage = ({origin, data}: RelyingPartyMessageEvent) => {
const onMessage = ({origin, data, source}: RelyingPartyMessageEvent) => {
const {success} = RpcResponseWithResultOrErrorSchema.safeParse(data);

if (!success) {
// We are only interested in JSON-RPC messages, so we are ignoring any other messages emitted at the window level, as the consumer might be using other events.
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(
Expand Down
Loading