Choose account
- Account: {accountId ? accountId : 'none'}
+ Account: {account?.accountId ? account.accountId : 'none'}
- {accountId && (
+ {account && (
)}
diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts
index 017f4350..ae101014 100755
--- a/apps/server/src/index.ts
+++ b/apps/server/src/index.ts
@@ -1,11 +1,11 @@
import cors from 'cors';
import 'dotenv/config';
import { parse } from 'node:url';
-import { Schema } from 'effect';
+import { Effect, Exit, Schema } from 'effect';
import express from 'express';
import type { ResponseListSpaces, ResponseSpace } from 'graph-framework-messages';
import { RequestMessage } from 'graph-framework-messages';
-import type { CreateSpaceEvent } from 'graph-framework-space-events';
+import { type CreateSpaceEvent, applyEvent } from 'graph-framework-space-events';
import type WebSocket from 'ws';
import { WebSocketServer } from 'ws';
import { createSpace } from './handlers/createSpace.js';
@@ -16,9 +16,9 @@ import { assertExhaustive } from './utils/assertExhaustive.js';
const decodeRequestMessage = Schema.decodeUnknownEither(RequestMessage);
-tmpInitAccount('abc');
-tmpInitAccount('cde');
-tmpInitAccount('def');
+tmpInitAccount('0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5');
+tmpInitAccount('03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462');
+tmpInitAccount('0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2');
const webSocketServer = new WebSocketServer({ noServer: true });
const PORT = process.env.PORT !== undefined ? Number.parseInt(process.env.PORT) : 3030;
@@ -70,14 +70,18 @@ webSocketServer.on('connection', async (webSocket: WebSocket, request: Request)
case 'event': {
switch (data.event.transaction.type) {
case 'create-space': {
- const space = await createSpace({ accountId, event: data.event as CreateSpaceEvent });
- const spaceWithEvents = await getSpace({ accountId, spaceId: space.id });
- const outgoingMessage: ResponseSpace = {
- type: 'space',
- id: space.id,
- events: spaceWithEvents.events.map((wrapper) => JSON.parse(wrapper.event)),
- };
- webSocket.send(JSON.stringify(outgoingMessage));
+ const applyEventResult = await Effect.runPromiseExit(applyEvent({ event: data.event }));
+ if (Exit.isSuccess(applyEventResult)) {
+ const space = await createSpace({ accountId, event: data.event as CreateSpaceEvent });
+ const spaceWithEvents = await getSpace({ accountId, spaceId: space.id });
+ const outgoingMessage: ResponseSpace = {
+ type: 'space',
+ id: space.id,
+ events: spaceWithEvents.events.map((wrapper) => JSON.parse(wrapper.event)),
+ };
+ webSocket.send(JSON.stringify(outgoingMessage));
+ }
+ // TODO send back error
break;
}
case 'delete-space': {
diff --git a/packages/graph-framework-space-events/package.json b/packages/graph-framework-space-events/package.json
index 98cd8035..ee713755 100644
--- a/packages/graph-framework-space-events/package.json
+++ b/packages/graph-framework-space-events/package.json
@@ -25,7 +25,8 @@
"effect": "^3.10.12"
},
"dependencies": {
- "uuid": "^11.0.2",
- "graph-framework-utils": "workspace:*"
+ "@noble/curves": "^1.6.0",
+ "graph-framework-utils": "workspace:*",
+ "uuid": "^11.0.2"
}
}
diff --git a/packages/graph-framework-space-events/src/apply-event.test.ts b/packages/graph-framework-space-events/src/apply-event.test.ts
new file mode 100644
index 00000000..6f484ea9
--- /dev/null
+++ b/packages/graph-framework-space-events/src/apply-event.test.ts
@@ -0,0 +1,36 @@
+import { secp256k1 } from '@noble/curves/secp256k1';
+import { Cause, Effect, Exit } from 'effect';
+import { canonicalize, stringToUint8Array } from 'graph-framework-utils';
+import { expect, it } from 'vitest';
+import { applyEvent } from './apply-event.js';
+import { createSpace } from './create-space.js';
+import { VerifySignatureError } from './types.js';
+
+it('should fail in case of an invalid signature', async () => {
+ const author = {
+ signaturePublicKey: '03594161eed61407084114a142d1ce05ef4c5a5279479fdd73a2b16944fbff003b',
+ signaturePrivateKey: '76b78f644c19d6133018a97a3bc2d5038be0af5a2858b9e640ff3e2f2db63a0b',
+ encryptionPublicKey: 'encryption',
+ };
+
+ const result = await Effect.runPromiseExit(
+ Effect.gen(function* () {
+ const spaceEvent = yield* createSpace({ author });
+
+ const emptyTransaction = stringToUint8Array(canonicalize({}));
+ const signature = secp256k1.sign(emptyTransaction, author.signaturePrivateKey, { prehash: true }).toCompactHex();
+
+ // @ts-expect-error
+ spaceEvent.author.signature = signature;
+ return yield* applyEvent({ event: spaceEvent });
+ }),
+ );
+
+ expect(Exit.isFailure(result)).toBe(true);
+ if (Exit.isFailure(result)) {
+ const cause = result.cause;
+ if (Cause.isFailType(cause)) {
+ expect(cause.error).toBeInstanceOf(VerifySignatureError);
+ }
+ }
+});
diff --git a/packages/graph-framework-space-events/src/apply-event.ts b/packages/graph-framework-space-events/src/apply-event.ts
index 6b8aad93..22f5896c 100644
--- a/packages/graph-framework-space-events/src/apply-event.ts
+++ b/packages/graph-framework-space-events/src/apply-event.ts
@@ -1,18 +1,35 @@
-import type { SpaceEvent, SpaceInvitation, SpaceMember, SpaceState } from './types.js';
+import { secp256k1 } from '@noble/curves/secp256k1';
+import { Effect, Schema } from 'effect';
+import type { ParseError } from 'effect/ParseResult';
+import { canonicalize, stringToUint8Array } from 'graph-framework-utils';
+import type { SpaceInvitation, SpaceMember, SpaceState } from './types.js';
+import { SpaceEvent, VerifySignatureError } from './types.js';
type Params = {
state?: SpaceState;
event: SpaceEvent;
};
-export const applyEvent = ({ state, event: rawEvent }: Params): SpaceState => {
- // TODO parse the event
- const event = rawEvent;
+const decodeSpaceEvent = Schema.decodeUnknownEither(SpaceEvent);
- // TODO verify the event
- // - verify the signature
- // - verify that this event is based on the previous one
- // - verify versioning
+export const applyEvent = ({
+ state,
+ event: rawEvent,
+}: Params): Effect.Effect