Skip to content
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
56 changes: 40 additions & 16 deletions apps/events/src/routes/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ import {
createKey,
createSpace,
decryptKey,
decryptMessage,
deserialize,
encryptKey,
encryptMessage,
generateId,
serialize,
} from 'graph-framework';
import { useEffect, useState } from 'react';

Expand Down Expand Up @@ -59,7 +63,7 @@ type SpaceStorageEntry = {
events: SpaceEvent[];
state: SpaceState | undefined;
keys: { id: string; key: string }[];
updates: string[];
updates: Uint8Array[];
lastUpdateClock: number;
};

Expand Down Expand Up @@ -119,7 +123,7 @@ const App = ({
if (!websocketConnection) return;

const onMessage = async (event: MessageEvent) => {
const data = JSON.parse(event.data);
const data = deserialize(event.data);
const message = decodeResponseMessage(data);
if (message._tag === 'Right') {
const response = message.right;
Expand All @@ -141,7 +145,7 @@ const App = ({
// fetch all spaces (for debugging purposes)
for (const space of response.spaces) {
const message: RequestSubscribeToSpace = { type: 'subscribe-space', id: space.id };
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}
break;
}
Expand Down Expand Up @@ -176,10 +180,17 @@ const App = ({
updates.push(...space.updates);
}
if (response.updates) {
console.log('response.updates', response.updates, lastUpdateClock);
if (response.updates.firstUpdateClock === lastUpdateClock + 1) {
lastUpdateClock = response.updates.lastUpdateClock;
updates.push(...response.updates.updates);

const newUpdates = (response.updates ? response.updates.updates : []).map((encryptedUpdate) => {
return decryptMessage({
nonceAndCiphertext: encryptedUpdate,
secretKey: hexToBytes(keys[0].key),
});
});

updates.push(...newUpdates);
} else {
// TODO request missing updates from server
}
Expand Down Expand Up @@ -254,9 +265,16 @@ const App = ({
// TODO request missing updates from server
}

const newUpdates = (response.updates ? response.updates.updates : []).map((encryptedUpdate) => {
return decryptMessage({
nonceAndCiphertext: encryptedUpdate,
secretKey: hexToBytes(space.keys[0].key),
});
});

return {
...space,
updates: [...space.updates, ...response.updates.updates],
updates: [...space.updates, ...newUpdates],
lastUpdateClock,
};
}
Expand Down Expand Up @@ -308,7 +326,7 @@ const App = ({
authorPublicKey: encryptionPublicKey,
},
};
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
Create space
Expand All @@ -317,7 +335,7 @@ const App = ({
<Button
onClick={() => {
const message: RequestListSpaces = { type: 'list-spaces' };
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
List Spaces
Expand All @@ -326,7 +344,7 @@ const App = ({
<Button
onClick={() => {
const message: RequestListInvitations = { type: 'list-invitations' };
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
List Invitations
Expand Down Expand Up @@ -355,12 +373,12 @@ const App = ({
event: spaceEvent.value,
spaceId: invitation.spaceId,
};
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));

// temporary until we have define a strategy for accepting invitations response
setTimeout(() => {
const message2: RequestListInvitations = { type: 'list-invitations' };
websocketConnection?.send(JSON.stringify(message2));
websocketConnection?.send(serialize(message2));
}, 1000);
}}
/>
Expand All @@ -375,7 +393,7 @@ const App = ({
<Button
onClick={() => {
const message: RequestSubscribeToSpace = { type: 'subscribe-space', id: space.id };
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
Get data and subscribe to Space
Expand Down Expand Up @@ -430,7 +448,7 @@ const App = ({
spaceId: space.id,
keyBoxes,
};
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
Invite {invitee.accountId.substring(0, 4)}
Expand All @@ -445,18 +463,24 @@ const App = ({
setSpaces((currentSpaces) =>
currentSpaces.map((currentSpace) => {
if (space.id === currentSpace.id) {
return { ...currentSpace, updates: [...currentSpace.updates, 'a'] };
return { ...currentSpace, updates: [...currentSpace.updates, new Uint8Array([0])] };
}
return currentSpace;
}),
);

const nonceAndCiphertext = encryptMessage({
message: new Uint8Array([0]),
secretKey: hexToBytes(space.keys[0].key),
});

const message: RequestCreateUpdate = {
type: 'create-update',
ephemeralId,
update: 'a',
update: nonceAndCiphertext,
spaceId: space.id,
};
websocketConnection?.send(JSON.stringify(message));
websocketConnection?.send(serialize(message));
}}
>
Create an update
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/handlers/createUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { prisma } from '../prisma.js';

type Params = {
accountId: string;
update: string;
update: Uint8Array;
spaceId: string;
};

Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/handlers/getSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const getSpace = async ({ spaceId, accountId }: Params) => {
updates:
space.updates.length > 0
? {
updates: space.updates.map((update) => update.content.toString()),
updates: space.updates.map((update) => new Uint8Array(update.content)),
firstUpdateClock: space.updates[0].clock,
lastUpdateClock: space.updates[space.updates.length - 1].clock,
}
Expand Down
26 changes: 13 additions & 13 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
ResponseUpdatesNotification,
Updates,
} from 'graph-framework-messages';
import { RequestMessage } from 'graph-framework-messages';
import { RequestMessage, deserialize, serialize } from 'graph-framework-messages';
import type { SpaceEvent } from 'graph-framework-space-events';
import { applyEvent } from 'graph-framework-space-events';
import WebSocket, { WebSocketServer } from 'ws';
Expand Down Expand Up @@ -65,7 +65,7 @@ function broadcastSpaceEvents({
event,
};
if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) {
client.send(JSON.stringify(outgoingMessage));
client.send(serialize(outgoingMessage));
}
}
}
Expand All @@ -84,7 +84,7 @@ function broadcastUpdates({
spaceId,
};
if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) {
client.send(JSON.stringify(outgoingMessage));
client.send(serialize(outgoingMessage));
}
}
}
Expand All @@ -101,7 +101,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req

console.log('Connection established', accountId);
webSocket.on('message', async (message) => {
const rawData = JSON.parse(message.toString());
const rawData = deserialize(message.toString());
const result = decodeRequestMessage(rawData);
if (result._tag === 'Right') {
const data = result.right;
Expand All @@ -113,19 +113,19 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
type: 'space',
};
webSocket.subscribedSpaces.add(data.id);
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
break;
}
case 'list-spaces': {
const spaces = await listSpaces({ accountId });
const outgoingMessage: ResponseListSpaces = { type: 'list-spaces', spaces: spaces };
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
break;
}
case 'list-invitations': {
const invitations = await listInvitations({ accountId });
const outgoingMessage: ResponseListInvitations = { type: 'list-invitations', invitations: invitations };
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
break;
}
case 'create-space-event': {
Expand All @@ -137,7 +137,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
...spaceWithEvents,
type: 'space',
};
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
}
// TODO send back error
break;
Expand All @@ -155,7 +155,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
...spaceWithEvents,
type: 'space',
};
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
for (const client of webSocketServer.clients as Set<CustomWebSocket>) {
if (
client.readyState === WebSocket.OPEN &&
Expand All @@ -164,7 +164,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
const invitations = await listInvitations({ accountId: client.accountId });
const outgoingMessage: ResponseListInvitations = { type: 'list-invitations', invitations: invitations };
// for now sending the entire list of invitations to the client - we could send only a single one
client.send(JSON.stringify(outgoingMessage));
client.send(serialize(outgoingMessage));
}
}

Expand All @@ -178,7 +178,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
...spaceWithEvents,
type: 'space',
};
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));
broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket });
break;
}
Expand All @@ -190,12 +190,12 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
clock: update.clock,
spaceId: data.spaceId,
};
webSocket.send(JSON.stringify(outgoingMessage));
webSocket.send(serialize(outgoingMessage));

broadcastUpdates({
spaceId: data.spaceId,
updates: {
updates: [update.content.toString()],
updates: [new Uint8Array(update.content)],
firstUpdateClock: update.clock,
lastUpdateClock: update.clock,
},
Expand Down
50 changes: 50 additions & 0 deletions packages/graph-framework-messages/src/decrypt-message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest';
import { decryptMessage } from './decrypt-message.js';
import { encryptMessage } from './encrypt-message.js';
describe('decryptMessage', () => {
const testKey = new Uint8Array(32).fill(1);

it('should successfully decrypt a valid message', () => {
const nonceAndCiphertext = encryptMessage({
message: new TextEncoder().encode('Hello, World!'),
secretKey: testKey,
});

const result = decryptMessage({
nonceAndCiphertext,
secretKey: testKey,
});

expect(new TextDecoder().decode(result)).toBe('Hello, World!');
});

it('should fail to decrypt with an invalid nonce', () => {
const nonceAndCiphertext = encryptMessage({
message: new TextEncoder().encode('Hello, World!'),
secretKey: testKey,
});

expect(() => {
return decryptMessage({
nonceAndCiphertext: new Uint8Array([...new Uint8Array(24).fill(0), ...nonceAndCiphertext.subarray(24)]),
secretKey: testKey,
});
}).toThrow();
});

it('should fail to decrypt with the wrong key', () => {
const nonceAndCiphertext = encryptMessage({
message: new TextEncoder().encode('Hello, World!'),
secretKey: testKey,
});

const wrongKey = new Uint8Array(32).fill(0);

expect(() => {
return decryptMessage({
nonceAndCiphertext,
secretKey: wrongKey,
});
}).toThrow();
});
});
13 changes: 13 additions & 0 deletions packages/graph-framework-messages/src/decrypt-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { xchacha20poly1305 } from '@noble/ciphers/chacha';

interface Params {
nonceAndCiphertext: Uint8Array;
secretKey: Uint8Array;
}

export function decryptMessage({ nonceAndCiphertext, secretKey }: Params) {
const nonce = nonceAndCiphertext.subarray(0, 24);
const ciphertext = nonceAndCiphertext.subarray(24);
const cipher = xchacha20poly1305(secretKey, nonce);
return cipher.decrypt(ciphertext);
}
11 changes: 11 additions & 0 deletions packages/graph-framework-messages/src/encrypt-message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { encryptMessage } from './encrypt-message.js';
describe('encryptMessage', () => {
const tooShortKey = new Uint8Array(31).fill(1);

it('should fail to encrypt with a too short key', () => {
expect(() => {
encryptMessage({ message: new TextEncoder().encode('Hello, World!'), secretKey: tooShortKey });
}).toThrow();
});
});
14 changes: 14 additions & 0 deletions packages/graph-framework-messages/src/encrypt-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { randomBytes } from '@noble/ciphers/webcrypto';

type Params = {
message: Uint8Array;
secretKey: Uint8Array;
};

export function encryptMessage({ message, secretKey }: Params) {
const nonce = randomBytes(24);
const cipher = xchacha20poly1305(secretKey, nonce);
const ciphertext = cipher.encrypt(message);
return new Uint8Array([...nonce, ...ciphertext]);
}
3 changes: 3 additions & 0 deletions packages/graph-framework-messages/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './decrypt-message.js';
export * from './encrypt-message.js';
export * from './serialize.js';
export * from './types.js';
Loading
Loading